整理した

This commit is contained in:
syuilo 2018-03-29 20:32:18 +09:00
parent 8a279a4656
commit cf33e483f7
552 changed files with 360 additions and 1311 deletions

View file

@ -0,0 +1,3 @@
h1 About Misskey
p Misskey is a mini blog SNS.

View file

@ -0,0 +1,3 @@
h1 Misskeyについて
p MisskeyはミニブログSNSです。

103
src/client/docs/api.ja.pug Normal file
View file

@ -0,0 +1,103 @@
h1 Misskey API
p MisskeyはWeb APIを公開しており、様々な操作をプログラム上から行うことができます。
p APIを自分のアカウントから利用する場合(自分のアカウントのみ操作したい場合)と、アプリケーションから利用する場合(不特定のアカウントを操作したい場合)とで利用手順が異なりますので、それぞれのケースについて説明します。
section
h2 自分の所有するアカウントからAPIにアクセスする場合
p 「設定 > API」で、APIにアクセスするのに必要なAPIキーを取得してください。
p APIにアクセスする際には、リクエストにAPIキーを「i」というパラメータ名で含めます。
div.ui.info.warn: p %fa:exclamation-triangle%アカウントを不正利用される可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。
p APIの詳しい使用法は「Misskey APIの利用」セクションをご覧ください。
section
h2 アプリケーションからAPIにアクセスする場合
p
| 直接ユーザーのAPIキーをアプリケーションが扱うのは危険なので、
| アプリケーションからAPIを利用する際には、アプリケーションとアプリケーションを利用するユーザーが結び付けられた専用のトークン(アクセストークン)をMisskeyに発行してもらい、
| そのトークンをリクエストのパラメータに含める必要があります。
div.ui.info: p %fa:info-circle%アクセストークンは、ユーザーが自分のアカウントにあなたのアプリケーションがアクセスすることを許可した場合のみ発行されます
p それでは、アクセストークンを取得するまでの流れを説明します。
section
h3 1.アプリケーションを登録する
p まず、あなたのアプリケーションやWebサービス(以後、あなたのアプリと呼びます)をMisskeyに登録します。
p
a(href=common.config.dev_url, target="_blank") デベロッパーセンター
| にアクセスし、「アプリ > アプリ作成」に進みます。
| フォームに必要事項を記入し、アプリを作成してください。フォームの記入欄の説明は以下の通りです:
table
thead
tr
th 名前
th 説明
tbody
tr
td アプリケーション名
td あなたのアプリの名称。
tr
td アプリの概要
td あなたのアプリの簡単な説明や紹介。
tr
td コールバックURL
td ユーザーが後述する認証フォームで認証を終えた際にリダイレクトするURLを設定できます。あなたのアプリがWebサービスである場合に有用です。
tr
td 権限
td あなたのアプリが要求する権限。ここで要求した機能だけがAPIからアクセスできます。
p 登録が済むとあなたのアプリのシークレットキーが入手できます。このシークレットキーは後で使用します。
div.ui.info.warn: p %fa:exclamation-triangle%アプリに成りすまされる可能性があるため、極力このシークレットキーは公開しないようにしてください。
section
h3 2.ユーザーに認証させる
p あなたのアプリを使ってもらうには、ユーザーにアカウントへのアクセスの許可をもらう必要があります。
p
| 認証セッションを開始するには、#{common.config.api_url}/auth/session/generate へパラメータに appSecret としてシークレットキーを含めたリクエストを送信します。
| リクエスト形式はJSONで、メソッドはPOSTです。
| レスポンスとして認証セッションのトークンや認証フォームのURLが取得できるので、認証フォームのURLをブラウザで表示し、ユーザーにフォームを提示してください。
p
| あなたのアプリがコールバックURLを設定している場合、
| ユーザーがあなたのアプリの連携を許可すると設定しているコールバックURLに token という名前でセッションのトークンが含まれたクエリを付けてリダイレクトします。
p
| あなたのアプリがコールバックURLを設定していない場合、ユーザーがあなたのアプリの連携を許可したことを(何らかの方法で(たとえばボタンを押させるなど))確認出来るようにしてください。
section
h3 3.ユーザーのアクセストークンを取得する
p ユーザーが連携を許可したら、#{common.config.api_url}/auth/session/userkey へ次のパラメータを含むリクエストを送信します:
table
thead
tr
th 名前
th 型
th 説明
tbody
tr
td appSecret
td string
td あなたのアプリのシークレットキー
tr
td token
td string
td セッションのトークン
p 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
p アクセストークンが取得できたら、「ユーザーのアクセストークン+あなたのアプリのシークレットキーをsha256したもの」を「i」というパラメータでリクエストに含めると、APIにアクセスすることができます。
p 「i」パラメータの生成方法を擬似コードで表すと次のようになります:
pre: code
| const i = sha256(accessToken + secretKey);
p APIの詳しい使用法は「Misskey APIの利用」セクションをご覧ください。
section
h2 Misskey APIの利用
p APIはすべてリクエストのパラメータ・レスポンスともにJSON形式です。また、すべてのエンドポイントはPOSTメソッドのみ受け付けます。
p APIリファレンスもご確認ください。
section
h3 レートリミット
p Misskey APIにはレートリミットがあり、短時間のうちに多数のリクエストを送信すると、一定時間APIを利用することができなくなることがあります。

View file

@ -0,0 +1,53 @@
endpoint: "posts/create"
desc:
ja: "投稿します。"
en: "Compose new post."
params:
- name: "text"
type: "string"
optional: true
desc:
ja: "投稿の本文"
en: "The text of your post"
- name: "mediaIds"
type: "id(DriveFile)[]"
optional: true
desc:
ja: "添付するメディア(1~4つ)"
en: "Media you want to attach (1~4)"
- name: "replyId"
type: "id(Post)"
optional: true
desc:
ja: "返信する投稿"
en: "The post you want to reply"
- name: "repostId"
type: "id(Post)"
optional: true
desc:
ja: "引用する投稿"
en: "The post you want to quote"
- name: "poll"
type: "object"
optional: true
desc:
ja: "投票"
en: "The poll"
defName: "poll"
def:
- name: "choices"
type: "string[]"
optional: false
desc:
ja: "投票の選択肢"
en: "Choices of a poll"
res:
- name: "createdPost"
type: "entity(Post)"
optional: false
desc:
ja: "作成した投稿"
en: "A post that created"

View file

@ -0,0 +1,32 @@
endpoint: "posts/timeline"
desc:
ja: "タイムラインを取得します。"
en: "Get your timeline."
params:
- name: "limit"
type: "number"
optional: true
desc:
ja: "取得する最大の数"
- name: "sinceId"
type: "id(Post)"
optional: true
desc:
ja: "指定すると、この投稿を基点としてより新しい投稿を取得します"
- name: "untilId"
type: "id(Post)"
optional: true
desc:
ja: "指定すると、この投稿を基点としてより古い投稿を取得します"
- name: "sinceDate"
type: "number"
optional: true
desc:
ja: "指定した時間を基点としてより新しい投稿を取得します。数値は、1970 年 1 月 1 日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。"
- name: "untilDate"
type: "number"
optional: true
desc:
ja: "指定した時間を基点としてより古い投稿を取得します。数値は、1970 年 1 月 1 日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。"

View file

@ -0,0 +1,21 @@
@import "../style"
#url
padding 8px 12px 8px 8px
font-family Consolas, 'Courier New', Courier, Monaco, monospace
color #fff
background #222e40
border-radius 4px
> .method
display inline-block
margin 0 8px 0 0
padding 0 6px
color #f4fcff
background #17afc7
border-radius 4px
user-select none
pointer-events none
> .host
opacity 0.7

View file

@ -0,0 +1,32 @@
extends ../../layout.pug
include ../mixins
block meta
link(rel="stylesheet" href="/assets/api/endpoints/style.css")
block main
h1= endpoint
p#url
span.method POST
span.host
= url.host
| /
span.path= url.path
p#desc= desc[lang] || desc['ja']
section
h2 %i18n:docs.api.endpoints.params%
+propTable(params)
if paramDefs
each paramDef in paramDefs
section(id= paramDef.name)
h3= paramDef.name
+propTable(paramDef.params)
if res
section
h2 %i18n:docs.api.endpoints.res%
+propTable(res)

View file

@ -0,0 +1,73 @@
name: "DriveFile"
desc:
ja: "ドライブのファイル。"
en: "A file of Drive."
props:
- name: "id"
type: "id"
optional: false
desc:
ja: "ファイルID"
en: "The ID of this file"
- name: "createdAt"
type: "date"
optional: false
desc:
ja: "アップロード日時"
en: "The upload date of this file"
- name: "userId"
type: "id(User)"
optional: false
desc:
ja: "所有者ID"
en: "The ID of the owner of this file"
- name: "user"
type: "entity(User)"
optional: true
desc:
ja: "所有者"
en: "The owner of this file"
- name: "name"
type: "string"
optional: false
desc:
ja: "ファイル名"
en: "The name of this file"
- name: "md5"
type: "string"
optional: false
desc:
ja: "ファイルのMD5ハッシュ値"
en: "The md5 hash value of this file"
- name: "type"
type: "string"
optional: false
desc:
ja: "ファイルの種類"
en: "The type of this file"
- name: "datasize"
type: "number"
optional: false
desc:
ja: "ファイルサイズ(bytes)"
en: "The size of this file (bytes)"
- name: "url"
type: "string"
optional: false
desc:
ja: "ファイルのURL"
en: "The URL of this file"
- name: "folderId"
type: "id(DriveFolder)"
optional: true
desc:
ja: "フォルダID"
en: "The ID of the folder of this file"
- name: "folder"
type: "entity(DriveFolder)"
optional: true
desc:
ja: "フォルダ"
en: "The folder of this file"

View file

@ -0,0 +1,168 @@
name: "Post"
desc:
ja: "投稿。"
en: "A post."
props:
- name: "id"
type: "id"
optional: false
desc:
ja: "投稿ID"
en: "The ID of this post"
- name: "createdAt"
type: "date"
optional: false
desc:
ja: "投稿日時"
en: "The posted date of this post"
- name: "viaMobile"
type: "boolean"
optional: true
desc:
ja: "モバイル端末から投稿したか否か(自己申告であることに留意)"
en: "Whether this post sent via a mobile device"
- name: "text"
type: "string"
optional: true
desc:
ja: "投稿の本文"
en: "The text of this post"
- name: "mediaIds"
type: "id(DriveFile)[]"
optional: true
desc:
ja: "添付されているメディアのID"
en: "The IDs of the attached media"
- name: "media"
type: "entity(DriveFile)[]"
optional: true
desc:
ja: "添付されているメディア"
en: "The attached media"
- name: "userId"
type: "id(User)"
optional: false
desc:
ja: "投稿者ID"
en: "The ID of author of this post"
- name: "user"
type: "entity(User)"
optional: true
desc:
ja: "投稿者"
en: "The author of this post"
- name: "myReaction"
type: "string"
optional: true
desc:
ja: "この投稿に対する自分の<a href='/docs/api/reactions'>リアクション</a>"
en: "The your <a href='/docs/api/reactions'>reaction</a> of this post"
- name: "reactionCounts"
type: "object"
optional: false
desc:
ja: "<a href='/docs/api/reactions'>リアクション</a>をキーとし、この投稿に対するそのリアクションの数を値としたオブジェクト"
- name: "replyId"
type: "id(Post)"
optional: true
desc:
ja: "返信した投稿のID"
en: "The ID of the replyed post"
- name: "reply"
type: "entity(Post)"
optional: true
desc:
ja: "返信した投稿"
en: "The replyed post"
- name: "repostId"
type: "id(Post)"
optional: true
desc:
ja: "引用した投稿のID"
en: "The ID of the quoted post"
- name: "repost"
type: "entity(Post)"
optional: true
desc:
ja: "引用した投稿"
en: "The quoted post"
- name: "poll"
type: "object"
optional: true
desc:
ja: "投票"
en: "The poll"
defName: "poll"
def:
- name: "choices"
type: "object[]"
optional: false
desc:
ja: "投票の選択肢"
en: "The choices of this poll"
defName: "choice"
def:
- name: "id"
type: "number"
optional: false
desc:
ja: "選択肢ID"
en: "The ID of this choice"
- name: "isVoted"
type: "boolean"
optional: true
desc:
ja: "自分がこの選択肢に投票したかどうか"
en: "Whether you voted to this choice"
- name: "text"
type: "string"
optional: false
desc:
ja: "選択肢本文"
en: "The text of this choice"
- name: "votes"
type: "number"
optional: false
desc:
ja: "この選択肢に投票された数"
en: "The number voted for this choice"
- name: "geo"
type: "object"
optional: true
desc:
ja: "位置情報"
en: "Geo location"
defName: "geo"
def:
- name: "coordinates"
type: "number[]"
optional: false
desc:
ja: "座標。最初に経度:-180〜180で表す。最後に緯度-90〜90で表す。"
- name: "altitude"
type: "number"
optional: false
desc:
ja: "高度。メートル単位で表す。"
- name: "accuracy"
type: "number"
optional: false
desc:
ja: "緯度、経度の精度。メートル単位で表す。"
- name: "altitudeAccuracy"
type: "number"
optional: false
desc:
ja: "高度の精度。メートル単位で表す。"
- name: "heading"
type: "number"
optional: false
desc:
ja: "方角。0〜360の角度で表す。0が北、90が東、180が南、270が西。"
- name: "speed"
type: "number"
optional: false
desc:
ja: "速度。メートル / 秒数で表す。"

View file

@ -0,0 +1 @@
@import "../style"

View file

@ -0,0 +1,173 @@
name: "User"
desc:
ja: "ユーザー。"
en: "A user."
props:
- name: "id"
type: "id"
optional: false
desc:
ja: "ユーザーID"
en: "The ID of this user"
- name: "createdAt"
type: "date"
optional: false
desc:
ja: "アカウント作成日時"
en: "The registered date of this user"
- name: "username"
type: "string"
optional: false
desc:
ja: "ユーザー名"
en: "The username of this user"
- name: "description"
type: "string"
optional: false
desc:
ja: "アカウントの説明(自己紹介)"
en: "The description of this user"
- name: "avatarId"
type: "id(DriveFile)"
optional: true
desc:
ja: "アバターのID"
en: "The ID of the avatar of this user"
- name: "avatarUrl"
type: "string"
optional: false
desc:
ja: "アバターのURL"
en: "The URL of the avatar of this user"
- name: "bannerId"
type: "id(DriveFile)"
optional: true
desc:
ja: "バナーのID"
en: "The ID of the banner of this user"
- name: "bannerUrl"
type: "string"
optional: false
desc:
ja: "バナーのURL"
en: "The URL of the banner of this user"
- name: "followersCount"
type: "number"
optional: false
desc:
ja: "フォロワーの数"
en: "The number of the followers for this user"
- name: "followingCount"
type: "number"
optional: false
desc:
ja: "フォローしているユーザーの数"
en: "The number of the following users for this user"
- name: "isFollowing"
type: "boolean"
optional: true
desc:
ja: "自分がこのユーザーをフォローしているか"
- name: "isFollowed"
type: "boolean"
optional: true
desc:
ja: "自分がこのユーザーにフォローされているか"
- name: "isMuted"
type: "boolean"
optional: true
desc:
ja: "自分がこのユーザーをミュートしているか"
en: "Whether you muted this user"
- name: "postsCount"
type: "number"
optional: false
desc:
ja: "投稿の数"
en: "The number of the posts of this user"
- name: "pinnedPost"
type: "entity(Post)"
optional: true
desc:
ja: "ピン留めされた投稿"
en: "The pinned post of this user"
- name: "pinnedPostId"
type: "id(Post)"
optional: true
desc:
ja: "ピン留めされた投稿のID"
en: "The ID of the pinned post of this user"
- name: "driveCapacity"
type: "number"
optional: false
desc:
ja: "ドライブの容量(bytes)"
en: "The capacity of drive of this user (bytes)"
- name: "host"
type: "string | null"
optional: false
desc:
ja: "ホスト (例: example.com:3000)"
en: "Host (e.g. example.com:3000)"
- name: "account"
type: "object"
optional: false
desc:
ja: "このサーバーにおけるアカウント"
en: "The account of this user on this server"
defName: "account"
def:
- name: "lastUsedAt"
type: "date"
optional: false
desc:
ja: "最終利用日時"
en: "The last used date of this user"
- name: "isBot"
type: "boolean"
optional: true
desc:
ja: "botか否か(自己申告であることに留意)"
en: "Whether is bot or not"
- name: "twitter"
type: "object"
optional: true
desc:
ja: "連携されているTwitterアカウント情報"
en: "The info of the connected twitter account of this user"
defName: "twitter"
def:
- name: "userId"
type: "string"
optional: false
desc:
ja: "ユーザーID"
en: "The user ID"
- name: "screenName"
type: "string"
optional: false
desc:
ja: "ユーザー名"
en: "The screen name of this user"
- name: "profile"
type: "object"
optional: false
desc:
ja: "プロフィール"
en: "The profile of this user"
defName: "profile"
def:
- name: "location"
type: "string"
optional: true
desc:
ja: "場所"
en: "The location of this user"
- name: "birthday"
type: "string"
optional: true
desc:
ja: "誕生日 (YYYY-MM-DD)"
en: "The birthday of this user (YYYY-MM-DD)"

View file

@ -0,0 +1,20 @@
extends ../../layout.pug
include ../mixins
block meta
link(rel="stylesheet" href="/assets/api/entities/style.css")
block main
h1= name
p#desc= desc[lang] || desc['ja']
section
h2 %i18n:docs.api.entities.properties%
+propTable(props)
if propDefs
each propDef in propDefs
section(id= propDef.name)
h3= propDef.name
+propTable(propDef.params)

View file

@ -0,0 +1,188 @@
/**
* Gulp tasks
*/
import * as fs from 'fs';
import * as path from 'path';
import * as glob from 'glob';
import * as gulp from 'gulp';
import * as pug from 'pug';
import * as yaml from 'js-yaml';
import * as mkdirp from 'mkdirp';
import locales from '../../../../locales';
import I18nReplacer from '../../../build/i18n';
import fa from '../../../build/fa';
import config from './../../../conf';
import generateVars from '../vars';
const langs = Object.keys(locales);
const kebab = string => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
const parseParam = param => {
const id = param.type.match(/^id\((.+?)\)|^id/);
const entity = param.type.match(/^entity\((.+?)\)/);
const isObject = /^object/.test(param.type);
const isDate = /^date/.test(param.type);
const isArray = /\[\]$/.test(param.type);
if (id) {
param.kind = 'id';
param.type = 'string';
param.entity = id[1];
if (isArray) {
param.type += '[]';
}
}
if (entity) {
param.kind = 'entity';
param.type = 'object';
param.entity = entity[1];
if (isArray) {
param.type += '[]';
}
}
if (isObject) {
param.kind = 'object';
}
if (isDate) {
param.kind = 'date';
param.type = 'string';
if (isArray) {
param.type += '[]';
}
}
return param;
};
const sortParams = params => {
params.sort((a, b) => {
if (a.name < b.name)
return -1;
if (a.name > b.name)
return 1;
return 0;
});
return params;
};
const extractDefs = params => {
let defs = [];
params.forEach(param => {
if (param.def) {
defs.push({
name: param.defName,
params: sortParams(param.def.map(p => parseParam(p)))
});
const childDefs = extractDefs(param.def);
defs = defs.concat(childDefs);
}
});
return sortParams(defs);
};
gulp.task('doc:api', [
'doc:api:endpoints',
'doc:api:entities'
]);
gulp.task('doc:api:endpoints', async () => {
const commonVars = await generateVars();
glob('./src/client/docs/api/endpoints/**/*.yaml', (globErr, files) => {
if (globErr) {
console.error(globErr);
return;
}
//console.log(files);
files.forEach(file => {
const ep = yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
const vars = {
endpoint: ep.endpoint,
url: {
host: config.api_url,
path: ep.endpoint
},
desc: ep.desc,
params: sortParams(ep.params.map(p => parseParam(p))),
paramDefs: extractDefs(ep.params),
res: ep.res ? sortParams(ep.res.map(p => parseParam(p))) : null,
resDefs: ep.res ? extractDefs(ep.res) : null,
};
langs.forEach(lang => {
pug.renderFile('./src/client/docs/api/endpoints/view.pug', Object.assign({}, vars, {
lang,
title: ep.endpoint,
src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/api/endpoints/${ep.endpoint}.yaml`,
kebab,
common: commonVars
}), (renderErr, html) => {
if (renderErr) {
console.error(renderErr);
return;
}
const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html);
const htmlPath = `./built/client/docs/${lang}/api/endpoints/${ep.endpoint}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => {
if (mkdirErr) {
console.error(mkdirErr);
return;
}
fs.writeFileSync(htmlPath, html, 'utf-8');
});
});
});
});
});
});
gulp.task('doc:api:entities', async () => {
const commonVars = await generateVars();
glob('./src/client/docs/api/entities/**/*.yaml', (globErr, files) => {
if (globErr) {
console.error(globErr);
return;
}
files.forEach(file => {
const entity = yaml.safeLoad(fs.readFileSync(file, 'utf-8'));
const vars = {
name: entity.name,
desc: entity.desc,
props: sortParams(entity.props.map(p => parseParam(p))),
propDefs: extractDefs(entity.props),
};
langs.forEach(lang => {
pug.renderFile('./src/client/docs/api/entities/view.pug', Object.assign({}, vars, {
lang,
title: entity.name,
src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/api/entities/${kebab(entity.name)}.yaml`,
kebab,
common: commonVars
}), (renderErr, html) => {
if (renderErr) {
console.error(renderErr);
return;
}
const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html);
const htmlPath = `./built/client/docs/${lang}/api/entities/${kebab(entity.name)}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => {
if (mkdirErr) {
console.error(mkdirErr);
return;
}
fs.writeFileSync(htmlPath, html, 'utf-8');
});
});
});
});
});
});

View file

@ -0,0 +1,37 @@
mixin propTable(props)
table.props
thead: tr
th %i18n:docs.api.props.name%
th %i18n:docs.api.props.type%
th %i18n:docs.api.props.optional%
th %i18n:docs.api.props.description%
tbody
each prop in props
tr
td.name= prop.name
td.type
i= prop.type
if prop.kind == 'id'
if prop.entity
| (
a(href=`/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| ID)
else
| (ID)
else if prop.kind == 'entity'
| (
a(href=`/${lang}/api/entities/${kebab(prop.entity)}`)= prop.entity
| )
else if prop.kind == 'object'
if prop.def
| (
a(href=`#${prop.defName}`)= prop.defName
| )
else if prop.kind == 'date'
| (Date)
td.optional
if prop.optional
| %i18n:docs.api.props.yes%
else
| %i18n:docs.api.props.no%
td.desc!= prop.desc[lang] || prop.desc['ja']

View file

@ -0,0 +1,11 @@
@import "../style"
table.props
.name
font-weight bold
.name
.type
.optional
font-family Consolas, 'Courier New', Courier, Monaco, monospace

View file

@ -0,0 +1,77 @@
/**
* Gulp tasks
*/
import * as fs from 'fs';
import * as path from 'path';
import * as glob from 'glob';
import * as gulp from 'gulp';
import * as pug from 'pug';
import * as mkdirp from 'mkdirp';
import stylus = require('gulp-stylus');
import cssnano = require('gulp-cssnano');
import I18nReplacer from '../../build/i18n';
import fa from '../../build/fa';
import generateVars from './vars';
require('./api/gulpfile.ts');
gulp.task('doc', [
'doc:docs',
'doc:api',
'doc:styles'
]);
gulp.task('doc:docs', async () => {
const commonVars = await generateVars();
glob('./src/client/docs/**/*.*.pug', (globErr, files) => {
if (globErr) {
console.error(globErr);
return;
}
files.forEach(file => {
const [, name, lang] = file.match(/docs\/(.+?)\.(.+?)\.pug$/);
const vars = {
common: commonVars,
lang: lang,
title: fs.readFileSync(file, 'utf-8').match(/^h1 (.+?)\r?\n/)[1],
src: `https://github.com/syuilo/misskey/tree/master/src/client/docs/${name}.${lang}.pug`,
};
pug.renderFile(file, vars, (renderErr, content) => {
if (renderErr) {
console.error(renderErr);
return;
}
pug.renderFile('./src/client/docs/layout.pug', Object.assign({}, vars, {
content
}), (renderErr2, html) => {
if (renderErr2) {
console.error(renderErr2);
return;
}
const i18n = new I18nReplacer(lang);
html = html.replace(i18n.pattern, i18n.replacement);
html = fa(html);
const htmlPath = `./built/client/docs/${lang}/${name}.html`;
mkdirp(path.dirname(htmlPath), (mkdirErr) => {
if (mkdirErr) {
console.error(mkdirErr);
return;
}
fs.writeFileSync(htmlPath, html, 'utf-8');
});
});
});
});
});
});
gulp.task('doc:styles', () =>
gulp.src('./src/client/docs/**/*.styl')
.pipe(stylus())
.pipe((cssnano as any)())
.pipe(gulp.dest('./built/client/docs/assets/'))
);

View file

@ -0,0 +1,3 @@
h1 Misskey Docs
p Welcome to docs of Misskey.

View file

@ -0,0 +1,3 @@
h1 Misskey ドキュメント
p Misskeyのドキュメントへようこそ

View file

@ -0,0 +1,41 @@
doctype html
html(lang= lang)
head
meta(charset="UTF-8")
meta(name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no")
title
| #{title} | Misskey Docs
link(rel="stylesheet" href="/assets/style.css")
block meta
//- FontAwesome style
style #{common.facss}
body
nav
ul
each doc in common.docs
li: a(href=`/${lang}/${doc.name}`)= doc.title[lang] || doc.title['ja']
section
h2 API
ul
li Entities
ul
each entity in common.entities
li: a(href=`/${lang}/api/entities/${common.kebab(entity)}`)= entity
li Endpoints
ul
each endpoint in common.endpoints
li: a(href=`/${lang}/api/endpoints/${common.kebab(endpoint)}`)= endpoint
main
article
block main
if content
| !{content}
footer
p
| %i18n:docs.edit-this-page-on-github%
a(href=src target="_blank") %i18n:docs.edit-this-page-on-github-link%
small= common.copyright

View file

@ -0,0 +1,17 @@
h1 License
div!= common.license
details
summary Libraries
section
h2 Libraries
each dependency, name in common.dependencies
details
summary= name
section
h3= name
pre= dependency.licenseText

View file

@ -0,0 +1,17 @@
h1 ライセンス
div!= common.license
details
summary サードパーティ
section
h2 サードパーティ
each dependency, name in common.dependencies
details
summary= name
section
h3= name
pre= dependency.licenseText

View file

@ -0,0 +1,13 @@
h1 ミュート
p ユーザーページから、そのユーザーをミュートすることができます。
p ユーザーをミュートすると、そのユーザーに関する次のコンテンツがMisskeyに表示されなくなります:
ul
li タイムラインや投稿の検索結果内の、そのユーザーの投稿(およびそれらの投稿に対する返信やRepost)
li そのユーザーからの通知
li メッセージ履歴一覧内の、そのユーザーとのメッセージ履歴
p ミュートを行ったことは相手に通知されず、ミュートされていることを知ることもできません。
p 設定>ミュート から、自分がミュートしているユーザー一覧を確認することができます。

View file

@ -0,0 +1,120 @@
h1 検索
p 投稿を検索することができます。
p
| キーワードを半角スペースで区切ると、and検索になります。
| 例えば、「git コミット」と検索すると、「gitで編集したファイルの特定の行だけコミットする方法がわからない」などがマッチします。
section
h2 キーワードの除外
p キーワードの前に「-」(ハイフン)をプリフィクスすると、そのキーワードを含まない投稿に限定します。
p 例えば、「gitというキーワードを含むが、コミットというキーワードは含まない投稿」を検索したい場合、クエリは以下のようになります:
code git -コミット
section
h2 完全一致
p テキストを「"""」で囲むと、そのテキストと完全に一致する投稿を検索します。
p 例えば、「"""にゃーん"""」と検索すると、「にゃーん」という投稿のみがヒットし、「にゃーん…」という投稿はヒットしません。
section
h2 タグ
p キーワードの前に「#」(シャープ)をプリフィクスすると、そのキーワードと一致するタグを持つ投稿に限定します。
section
h2 オプション
p
| オプションを使用して、より高度な検索を行えます。
| オプションを指定するには、「オプション名:値」という形式でクエリに含めます。
p 利用可能なオプション一覧です:
table
thead
tr
th 名前
th 説明
tbody
tr
td user
td
| 指定されたユーザー名のユーザーの投稿に限定します。
| 「,」(カンマ)で区切って、複数ユーザーを指定することもできます。
br
| 例えば、
code user:himawari,sakurako
| と検索すると「@himawariまたは@sakurakoの投稿」だけに限定します。
| (つまりユーザーのホワイトリストです)
tr
td exclude_user
td
| 指定されたユーザー名のユーザーの投稿を除外します。
| 「,」(カンマ)で区切って、複数ユーザーを指定することもできます。
br
| 例えば、
code exclude_user:akari,chinatsu
| と検索すると「@akariまたは@chinatsu以外の投稿」に限定します。
| (つまりユーザーのブラックリストです)
tr
td follow
td
| true ... フォローしているユーザーに限定。
br
| false ... フォローしていないユーザーに限定。
br
| null ... 特に限定しない(デフォルト)
tr
td mute
td
| mute_all ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostを除外する(デフォルト)
br
| mute_related ... ミュートしているユーザーの投稿に対する返信やRepostだけ除外する
br
| mute_direct ... ミュートしているユーザーの投稿だけ除外する
br
| disabled ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostも含める
br
| direct_only ... ミュートしているユーザーの投稿だけに限定
br
| related_only ... ミュートしているユーザーの投稿に対する返信やRepostだけに限定
br
| all_only ... ミュートしているユーザーの投稿とその投稿に対する返信やRepostに限定
tr
td reply
td
| true ... 返信に限定。
br
| false ... 返信でない投稿に限定。
br
| null ... 特に限定しない(デフォルト)
tr
td repost
td
| true ... Repostに限定。
br
| false ... Repostでない投稿に限定。
br
| null ... 特に限定しない(デフォルト)
tr
td media
td
| true ... メディアが添付されている投稿に限定。
br
| false ... メディアが添付されていない投稿に限定。
br
| null ... 特に限定しない(デフォルト)
tr
td poll
td
| true ... 投票が添付されている投稿に限定。
br
| false ... 投票が添付されていない投稿に限定。
br
| null ... 特に限定しない(デフォルト)
tr
td until
td 上限の日時。(YYYY-MM-DD)
tr
td since
td 下限の日時。(YYYY-MM-DD)
p 例えば、「@syuiloの2017年11月1日から2017年12月31日までの『Misskey』というテキストを含む返信ではない投稿」を検索したい場合、クエリは以下のようになります:
code user:syuilo since:2017-11-01 until:2017-12-31 reply:false Misskey

21
src/client/docs/server.ts Normal file
View file

@ -0,0 +1,21 @@
/**
* Docs Server
*/
import * as express from 'express';
/**
* Init app
*/
const app = express();
app.disable('x-powered-by');
app.use('/assets', express.static(`${__dirname}/assets`));
/**
* Routing
*/
app.get(/^\/([a-z_\-\/]+?)$/, (req, res) =>
res.sendFile(`${__dirname}/${req.params[0]}.html`));
module.exports = app;

120
src/client/docs/style.styl Normal file
View file

@ -0,0 +1,120 @@
@import "../style"
@import "./ui"
body
margin 0
color #34495e
word-break break-word
main
margin 0 0 0 256px
padding 64px
width 100%
max-width 768px
section
margin 32px 0
h1
margin 0 0 24px 0
padding 16px 0
font-size 1.5em
border-bottom solid 2px #eee
h2
margin 0 0 24px 0
padding 0 0 16px 0
font-size 1.4em
border-bottom solid 1px #eee
h3
margin 0
padding 0
font-size 1.25em
h4
margin 0
p
margin 1em 0
line-height 1.6em
footer
margin 32px 0 0 0
border-top solid 2px #eee
> small
margin 16px 0 0 0
color #aaa
nav
display block
position fixed
z-index 10000
top 0
left 0
width 256px
height 100%
overflow auto
padding 32px
background #fff
border-right solid 2px #eee
@media (max-width 1025px)
main
margin 0
max-width 100%
nav
position relative
width 100%
max-height 128px
background #f9f9f9
border-right none
@media (max-width 768px)
main
padding 32px
@media (max-width 512px)
main
padding 16px
table
display block
width 100%
max-width 100%
overflow auto
border-spacing 0
border-collapse collapse
thead
font-weight bold
border-bottom solid 2px #eee
tr
th
text-align left
tbody
tr
&:nth-child(odd)
background #fbfbfb
th, td
padding 8px 16px
min-width 128px
code
display inline-block
padding 8px 10px
font-family Consolas, 'Courier New', Courier, Monaco, monospace
color #295c92
background #f2f2f2
border-radius 4px
pre
overflow auto
> code
display block

View file

@ -0,0 +1,3 @@
h1 利用規約
p 公序良俗に反する行為はおやめください。

19
src/client/docs/ui.styl Normal file
View file

@ -0,0 +1,19 @@
.ui.info
display block
margin 1em 0
padding 0 1em
font-size 90%
color rgba(#000, 0.87)
background #f8f8f9
border-radius 4px
overflow hidden
> p
opacity 0.8
> [data-fa]:first-child
margin-right 0.25em
&.warn
color #573a08
background #FFFAF3

64
src/client/docs/vars.ts Normal file
View file

@ -0,0 +1,64 @@
import * as fs from 'fs';
import * as util from 'util';
import * as glob from 'glob';
import * as yaml from 'js-yaml';
import * as licenseChecker from 'license-checker';
import * as tmp from 'tmp';
import { fa } from '../../build/fa';
import config from '../../conf';
import { licenseHtml } from '../../build/license';
const constants = require('../../const.json');
export default async function(): Promise<{ [key: string]: any }> {
const vars = {} as { [key: string]: any };
const endpoints = glob.sync('./src/client/docs/api/endpoints/**/*.yaml');
vars['endpoints'] = endpoints.map(ep => {
const _ep = yaml.safeLoad(fs.readFileSync(ep, 'utf-8'));
return _ep.endpoint;
});
const entities = glob.sync('./src/client/docs/api/entities/**/*.yaml');
vars['entities'] = entities.map(x => {
const _x = yaml.safeLoad(fs.readFileSync(x, 'utf-8'));
return _x.name;
});
const docs = glob.sync('./src/client/docs/**/*.*.pug');
vars['docs'] = {};
docs.forEach(x => {
const [, name, lang] = x.match(/docs\/(.+?)\.(.+?)\.pug$/);
if (vars['docs'][name] == null) {
vars['docs'][name] = {
name,
title: {}
};
}
vars['docs'][name]['title'][lang] = fs.readFileSync(x, 'utf-8').match(/^h1 (.+?)\r?\n/)[1];
});
vars['kebab'] = string => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
vars['config'] = config;
vars['copyright'] = constants.copyright;
vars['facss'] = fa.dom.css();
vars['license'] = licenseHtml;
const tmpObj = tmp.fileSync();
fs.writeFileSync(tmpObj.name, JSON.stringify({
licenseText: ''
}), 'utf-8');
const dependencies = await util.promisify(licenseChecker.init).bind(licenseChecker)({
start: __dirname + '/../../../',
customPath: tmpObj.name
});
tmpObj.removeCallback();
vars['dependencies'] = dependencies;
return vars;
}