Merge branch 'master' of github.com:syuilo/misskey into swagger
This commit is contained in:
commit
0420fee5d2
|
@ -1,6 +1,9 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "7.3.0"
|
- "7.3.0"
|
||||||
|
services:
|
||||||
|
- mongodb
|
||||||
|
- redis-server
|
||||||
before_script:
|
before_script:
|
||||||
- "mkdir -p ./.config && cp ./.ci-files/config.yml ./.config"
|
- "mkdir -p ./.config && cp ./.ci-files/config.yml ./.config"
|
||||||
env:
|
env:
|
||||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,2 +1,11 @@
|
||||||
# 1
|
# Server
|
||||||
|
## 1
|
||||||
|
First version
|
||||||
|
|
||||||
|
# API
|
||||||
|
## 2
|
||||||
|
* パラメータ: _userkey --> i
|
||||||
|
* トークンは、アクセストークン + アプリのシークレットキーをsha512したものに
|
||||||
|
|
||||||
|
## 1
|
||||||
First version
|
First version
|
||||||
|
|
|
@ -70,5 +70,7 @@ block content
|
||||||
| 次に、<code>#{api_url}/auth/session/userkey</code>へ<code>app_secret</code>としてApp Secretを、<code>token</code>としてセッションのトークンをパラメータとして付与したリクエストを送信してください。
|
| 次に、<code>#{api_url}/auth/session/userkey</code>へ<code>app_secret</code>としてApp Secretを、<code>token</code>としてセッションのトークンをパラメータとして付与したリクエストを送信してください。
|
||||||
br
|
br
|
||||||
| 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
|
| 上手くいけば、認証したユーザーのアクセストークンがレスポンスとして取得できます。おめでとうございます!
|
||||||
|
p
|
||||||
|
| 以降アクセストークンは、<strong>ユーザーのアクセストークン+アプリのシークレットキーをsha512したもの</strong>として扱います。
|
||||||
|
|
||||||
p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>_userkey</code>(「自分のアクセストークンを取得したい場合」の方法で取得したアクセストークンの場合は<code>i</code>)としてパラメータに含めるだけです。
|
p アクセストークンを取得できたら、あとは簡単です。REST APIなら、リクエストにアクセストークンを<code>i</code>としてパラメータに含めるだけです。
|
||||||
|
|
|
@ -149,6 +149,7 @@ const aliasifyConfig = {
|
||||||
'chart.js': './node_modules/chart.js/src/chart.js',
|
'chart.js': './node_modules/chart.js/src/chart.js',
|
||||||
'textarea-caret-position': './node_modules/textarea-caret/index.js',
|
'textarea-caret-position': './node_modules/textarea-caret/index.js',
|
||||||
'misskey-text': './src/common/text/index.js',
|
'misskey-text': './src/common/text/index.js',
|
||||||
|
'nyaize': './node_modules/nyaize/built/index.js',
|
||||||
'strength.js': './node_modules/syuilo-password-strength/strength.js',
|
'strength.js': './node_modules/syuilo-password-strength/strength.js',
|
||||||
'cropper': './node_modules/cropperjs/dist/cropper.js',
|
'cropper': './node_modules/cropperjs/dist/cropper.js',
|
||||||
'Sortable': './node_modules/sortablejs/Sortable.js',
|
'Sortable': './node_modules/sortablejs/Sortable.js',
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"@types/browserify": "12.0.30",
|
"@types/browserify": "12.0.30",
|
||||||
"@types/chalk": "0.4.31",
|
"@types/chalk": "0.4.31",
|
||||||
"@types/compression": "0.0.33",
|
"@types/compression": "0.0.33",
|
||||||
"@types/cors": "0.0.33",
|
"@types/cors": "2.8.0",
|
||||||
"@types/elasticsearch": "5.0.9",
|
"@types/elasticsearch": "5.0.9",
|
||||||
"@types/escape-html": "0.0.19",
|
"@types/escape-html": "0.0.19",
|
||||||
"@types/event-stream": "3.3.30",
|
"@types/event-stream": "3.3.30",
|
||||||
|
@ -63,13 +63,14 @@
|
||||||
"babel-preset-stage-3": "6.17.0",
|
"babel-preset-stage-3": "6.17.0",
|
||||||
"bcrypt": "1.0.2",
|
"bcrypt": "1.0.2",
|
||||||
"body-parser": "1.15.2",
|
"body-parser": "1.15.2",
|
||||||
"browserify": "13.1.1",
|
"browserify": "13.3.0",
|
||||||
"browserify-livescript": "0.2.3",
|
"browserify-livescript": "0.2.3",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
"chart.js": "2.4.0",
|
"chart.js": "2.4.0",
|
||||||
"compression": "1.6.2",
|
"compression": "1.6.2",
|
||||||
"cors": "2.8.1",
|
"cors": "2.8.1",
|
||||||
"cropperjs": "1.0.0-beta",
|
"cropperjs": "1.0.0-beta",
|
||||||
|
"crypto": "0.0.3",
|
||||||
"deepcopy": "0.6.3",
|
"deepcopy": "0.6.3",
|
||||||
"del": "2.2.2",
|
"del": "2.2.2",
|
||||||
"elasticsearch": "12.1.3",
|
"elasticsearch": "12.1.3",
|
||||||
|
@ -99,10 +100,11 @@
|
||||||
"livescript": "1.5.0",
|
"livescript": "1.5.0",
|
||||||
"mime-types": "2.1.13",
|
"mime-types": "2.1.13",
|
||||||
"mocha": "3.2.0",
|
"mocha": "3.2.0",
|
||||||
"mongodb": "2.2.16",
|
"mongodb": "2.2.19",
|
||||||
"ms": "0.7.2",
|
"ms": "0.7.2",
|
||||||
"multer": "1.2.1",
|
"multer": "1.2.1",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
"nyaize": "0.0.2",
|
||||||
"page": "1.7.1",
|
"page": "1.7.1",
|
||||||
"prominence": "0.2.0",
|
"prominence": "0.2.0",
|
||||||
"pug": "2.0.0-beta6",
|
"pug": "2.0.0-beta6",
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import App from './models/app';
|
import App from './models/app';
|
||||||
import User from './models/user';
|
import User from './models/user';
|
||||||
import Userkey from './models/userkey';
|
import AccessToken from './models/access-token';
|
||||||
|
import isNativeToken from './common/is-native-token';
|
||||||
|
|
||||||
export interface IAuthContext {
|
export interface IAuthContext {
|
||||||
/**
|
/**
|
||||||
|
@ -20,10 +21,14 @@ export interface IAuthContext {
|
||||||
isSecure: boolean;
|
isSecure: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (req: express.Request) =>
|
export default (req: express.Request) => new Promise<IAuthContext>(async (resolve, reject) => {
|
||||||
new Promise<IAuthContext>(async (resolve, reject) => {
|
|
||||||
const token = req.body['i'];
|
const token = req.body['i'];
|
||||||
if (token) {
|
|
||||||
|
if (token == null) {
|
||||||
|
return resolve({ app: null, user: null, isSecure: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNativeToken(token)) {
|
||||||
const user = await User
|
const user = await User
|
||||||
.findOne({ token: token });
|
.findOne({ token: token });
|
||||||
|
|
||||||
|
@ -36,26 +41,21 @@ export default (req: express.Request) =>
|
||||||
user: user,
|
user: user,
|
||||||
isSecure: true
|
isSecure: true
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
const accessToken = await AccessToken.findOne({
|
||||||
const userkey = req.headers['userkey'] || req.body['_userkey'];
|
hash: token
|
||||||
if (userkey) {
|
|
||||||
const userkeyDoc = await Userkey.findOne({
|
|
||||||
key: userkey
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (userkeyDoc === null) {
|
if (accessToken === null) {
|
||||||
return reject('invalid userkey');
|
return reject('invalid signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await App
|
const app = await App
|
||||||
.findOne({ _id: userkeyDoc.app_id });
|
.findOne({ _id: accessToken.app_id });
|
||||||
|
|
||||||
const user = await User
|
const user = await User
|
||||||
.findOne({ _id: userkeyDoc.user_id });
|
.findOne({ _id: accessToken.user_id });
|
||||||
|
|
||||||
return resolve({ app: app, user: user, isSecure: false });
|
return resolve({ app: app, user: user, isSecure: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve({ app: null, user: null, isSecure: false });
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export default (token: string) => token[0] == '!';
|
|
@ -4,8 +4,10 @@
|
||||||
* Module dependencies
|
* Module dependencies
|
||||||
*/
|
*/
|
||||||
import rndstr from 'rndstr';
|
import rndstr from 'rndstr';
|
||||||
|
const crypto = require('crypto');
|
||||||
|
import App from '../../models/app';
|
||||||
import AuthSess from '../../models/auth-session';
|
import AuthSess from '../../models/auth-session';
|
||||||
import Userkey from '../../models/userkey';
|
import AccessToken from '../../models/access-token';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
|
@ -41,35 +43,46 @@ module.exports = (params, user) =>
|
||||||
new Promise(async (res, rej) =>
|
new Promise(async (res, rej) =>
|
||||||
{
|
{
|
||||||
// Get 'token' parameter
|
// Get 'token' parameter
|
||||||
const token = params.token;
|
const sesstoken = params.token;
|
||||||
if (token == null) {
|
if (sesstoken == null) {
|
||||||
return rej('token is required');
|
return rej('token is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch token
|
// Fetch token
|
||||||
const session = await AuthSess
|
const session = await AuthSess
|
||||||
.findOne({ token: token });
|
.findOne({ token: sesstoken });
|
||||||
|
|
||||||
if (session === null) {
|
if (session === null) {
|
||||||
return rej('session not found');
|
return rej('session not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate userkey
|
// Generate access token
|
||||||
const key = rndstr('a-zA-Z0-9', 32);
|
const token = rndstr('a-zA-Z0-9', 32);
|
||||||
|
|
||||||
// Fetch exist userkey
|
// Fetch exist access token
|
||||||
const exist = await Userkey.findOne({
|
const exist = await AccessToken.findOne({
|
||||||
app_id: session.app_id,
|
app_id: session.app_id,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exist === null) {
|
if (exist === null) {
|
||||||
// Insert userkey doc
|
// Lookup app
|
||||||
await Userkey.insert({
|
const app = await App.findOne({
|
||||||
|
app_id: session.app_id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate Hash
|
||||||
|
const sha512 = crypto.createHash('sha512');
|
||||||
|
sha512.update(token + app.secret);
|
||||||
|
const hash = sha512.digest('hex');
|
||||||
|
|
||||||
|
// Insert access token doc
|
||||||
|
await AccessToken.insert({
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
app_id: session.app_id,
|
app_id: session.app_id,
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
key: key
|
token: token,
|
||||||
|
hash: hash
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import App from '../../../models/app';
|
import App from '../../../models/app';
|
||||||
import AuthSess from '../../../models/auth-session';
|
import AuthSess from '../../../models/auth-session';
|
||||||
import Userkey from '../../../models/userkey';
|
import AccessToken from '../../../models/access-token';
|
||||||
import serialize from '../../../serializers/user';
|
import serialize from '../../../serializers/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,8 +89,8 @@ module.exports = (params) =>
|
||||||
return rej('this session is not allowed yet');
|
return rej('this session is not allowed yet');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup userkey
|
// Lookup access token
|
||||||
const userkey = await Userkey.findOne({
|
const accessToken = await AccessToken.findOne({
|
||||||
app_id: app._id,
|
app_id: app._id,
|
||||||
user_id: session.user_id
|
user_id: session.user_id
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ module.exports = (params) =>
|
||||||
|
|
||||||
// Response
|
// Response
|
||||||
res({
|
res({
|
||||||
userkey: userkey.key,
|
access_token: accessToken.token,
|
||||||
user: await serialize(session.user_id, null, {
|
user: await serialize(session.user_id, null, {
|
||||||
detail: true
|
detail: true
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,9 +19,19 @@ module.exports = (params, me) =>
|
||||||
new Promise(async (res, rej) =>
|
new Promise(async (res, rej) =>
|
||||||
{
|
{
|
||||||
// Get 'user_id' parameter
|
// Get 'user_id' parameter
|
||||||
const userId = params.user_id;
|
let userId = params.user_id;
|
||||||
if (userId === undefined || userId === null) {
|
if (userId === undefined || userId === null || userId === '') {
|
||||||
return rej('user_id is required');
|
userId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 'username' parameter
|
||||||
|
let username = params.username;
|
||||||
|
if (username === undefined || username === null || username === '') {
|
||||||
|
username = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId === null && username === null) {
|
||||||
|
return rej('user_id or username is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 'with_replies' parameter
|
// Get 'with_replies' parameter
|
||||||
|
@ -62,9 +72,9 @@ module.exports = (params, me) =>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lookup user
|
// Lookup user
|
||||||
const user = await User.findOne({
|
const user = userId !== null
|
||||||
_id: new mongo.ObjectID(userId)
|
? await User.findOne({ _id: new mongo.ObjectID(userId) })
|
||||||
});
|
: await User.findOne({ username_lower: username.toLowerCase() });
|
||||||
|
|
||||||
if (user === null) {
|
if (user === null) {
|
||||||
return rej('user not found');
|
return rej('user not found');
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const collection = global.db.collection('access_tokens');
|
||||||
|
|
||||||
|
collection.createIndex('token');
|
||||||
|
collection.createIndex('hash');
|
||||||
|
|
||||||
|
export default collection;
|
|
@ -1,5 +0,0 @@
|
||||||
const collection = global.db.collection('userkeys');
|
|
||||||
|
|
||||||
collection.createIndex('key');
|
|
||||||
|
|
||||||
export default collection;
|
|
|
@ -48,7 +48,7 @@ export default async (req: express.Request, res: express.Response) => {
|
||||||
const hash = bcrypt.hashSync(password, salt);
|
const hash = bcrypt.hashSync(password, salt);
|
||||||
|
|
||||||
// Generate secret
|
// Generate secret
|
||||||
const secret = rndstr('a-zA-Z0-9', 32);
|
const secret = '!' + rndstr('a-zA-Z0-9', 32);
|
||||||
|
|
||||||
// Create account
|
// Create account
|
||||||
const inserted = await User.insert({
|
const inserted = await User.insert({
|
||||||
|
|
|
@ -7,7 +7,7 @@ import * as mongo from 'mongodb';
|
||||||
import deepcopy = require('deepcopy');
|
import deepcopy = require('deepcopy');
|
||||||
import App from '../models/app';
|
import App from '../models/app';
|
||||||
import User from '../models/user';
|
import User from '../models/user';
|
||||||
import Userkey from '../models/userkey';
|
import AccessToken from '../models/access-token';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize an app
|
* Serialize an app
|
||||||
|
@ -71,7 +71,7 @@ export default (
|
||||||
|
|
||||||
if (me) {
|
if (me) {
|
||||||
// 既に連携しているか
|
// 既に連携しているか
|
||||||
const exist = await Userkey.count({
|
const exist = await AccessToken.count({
|
||||||
app_id: _app.id,
|
app_id: _app.id,
|
||||||
user_id: me,
|
user_id: me,
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -2,6 +2,8 @@ import * as http from 'http';
|
||||||
import * as websocket from 'websocket';
|
import * as websocket from 'websocket';
|
||||||
import * as redis from 'redis';
|
import * as redis from 'redis';
|
||||||
import User from './models/user';
|
import User from './models/user';
|
||||||
|
import AccessToken from './models/access-token';
|
||||||
|
import isNativeToken from './common/is-native-token';
|
||||||
|
|
||||||
import homeStream from './stream/home';
|
import homeStream from './stream/home';
|
||||||
import messagingStream from './stream/messaging';
|
import messagingStream from './stream/messaging';
|
||||||
|
@ -17,7 +19,13 @@ module.exports = (server: http.Server) => {
|
||||||
ws.on('request', async (request) => {
|
ws.on('request', async (request) => {
|
||||||
const connection = request.accept();
|
const connection = request.accept();
|
||||||
|
|
||||||
const user = await authenticate(connection);
|
const user = await authenticate(connection, request.resourceURL.query.i);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
connection.send('authentication-failed');
|
||||||
|
connection.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Connect to Redis
|
// Connect to Redis
|
||||||
const subscriber = redis.createClient(
|
const subscriber = redis.createClient(
|
||||||
|
@ -41,29 +49,36 @@ module.exports = (server: http.Server) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function authenticate(connection: websocket.connection): Promise<any> {
|
function authenticate(connection: websocket.connection, token: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
// Listen first message
|
if (isNativeToken(token)) {
|
||||||
connection.once('message', async (data) => {
|
|
||||||
const msg = JSON.parse(data.utf8Data);
|
|
||||||
|
|
||||||
// Fetch user
|
// Fetch user
|
||||||
// SELECT _id
|
// SELECT _id
|
||||||
const user = await User
|
const user = await User
|
||||||
.findOne({
|
.findOne({
|
||||||
token: msg.i
|
token: token
|
||||||
}, {
|
}, {
|
||||||
_id: true
|
_id: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user === null) {
|
resolve(user);
|
||||||
connection.close();
|
} else {
|
||||||
return;
|
const accessToken = await AccessToken.findOne({
|
||||||
|
hash: token
|
||||||
|
});
|
||||||
|
|
||||||
|
if (accessToken == null) {
|
||||||
|
return reject('invalid signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.send('authenticated');
|
// Fetch user
|
||||||
|
// SELECT _id
|
||||||
|
const user = await User
|
||||||
|
.findOne({ _id: accessToken.user_id }, {
|
||||||
|
_id: true
|
||||||
|
});
|
||||||
|
|
||||||
resolve(user);
|
resolve(user);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ try {
|
||||||
checkForUpdate();
|
checkForUpdate();
|
||||||
|
|
||||||
// Get token from cookie
|
// Get token from cookie
|
||||||
const i = (document.cookie.match(/i=(\w+)/) || [null, null])[1];
|
const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1];
|
||||||
|
|
||||||
// ユーザーをフェッチしてコールバックする
|
// ユーザーをフェッチしてコールバックする
|
||||||
module.exports = callback => {
|
module.exports = callback => {
|
||||||
|
|
|
@ -3,7 +3,7 @@ module.exports = (date) ->
|
||||||
|
|
||||||
text =
|
text =
|
||||||
date.get-full-year! + \年 +
|
date.get-full-year! + \年 +
|
||||||
date.get-month! + \月 +
|
date.get-month! + 1 + \月 +
|
||||||
date.get-date! + \日 +
|
date.get-date! + \日 +
|
||||||
' ' +
|
' ' +
|
||||||
date.get-hours! + \時 +
|
date.get-hours! + \時 +
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Connection
|
||||||
@event = riot.observable!
|
@event = riot.observable!
|
||||||
@me = me
|
@me = me
|
||||||
host = CONFIG.api.url.replace \http \ws
|
host = CONFIG.api.url.replace \http \ws
|
||||||
@socket = new ReconnectingWebSocket "#{host}/messaging?otherparty=#{otherparty}"
|
@socket = new ReconnectingWebSocket "#{host}/messaging?i=#{me.token}&otherparty=#{otherparty}"
|
||||||
|
|
||||||
@socket.add-event-listener \open @on-open
|
@socket.add-event-listener \open @on-open
|
||||||
@socket.add-event-listener \message @on-message
|
@socket.add-event-listener \message @on-message
|
||||||
|
|
|
@ -9,13 +9,12 @@ module.exports = (me) ~>
|
||||||
state-ev = riot.observable!
|
state-ev = riot.observable!
|
||||||
event = riot.observable!
|
event = riot.observable!
|
||||||
|
|
||||||
socket = new ReconnectingWebSocket CONFIG.api.url.replace \http \ws
|
host = CONFIG.api.url.replace \http \ws
|
||||||
|
socket = new ReconnectingWebSocket "#{host}?i=#{me.token}"
|
||||||
|
|
||||||
socket.onopen = ~>
|
socket.onopen = ~>
|
||||||
state := \connected
|
state := \connected
|
||||||
state-ev.trigger \connected
|
state-ev.trigger \connected
|
||||||
socket.send JSON.stringify do
|
|
||||||
i: me.token
|
|
||||||
|
|
||||||
socket.onclose = ~>
|
socket.onclose = ~>
|
||||||
state := \reconnecting
|
state := \reconnecting
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const riot = require('riot');
|
const riot = require('riot');
|
||||||
|
const nyaize = require('nyaize').default;
|
||||||
|
|
||||||
module.exports = function(tokens, shouldBreak, escape) {
|
module.exports = function(tokens, shouldBreak, escape) {
|
||||||
if (shouldBreak == null) {
|
if (shouldBreak == null) {
|
||||||
|
@ -34,10 +35,7 @@ module.exports = function(tokens, shouldBreak, escape) {
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
if (me && me.data && me.data.nya) {
|
if (me && me.data && me.data.nya) {
|
||||||
text = text.replace(/な/g, 'にゃ')
|
text = nyaize(text);
|
||||||
.replace(/ニャ/g, 'にゃ')
|
|
||||||
.replace(/にゃでにゃで/g, 'なでなで')
|
|
||||||
.replace(/ニャデニャデ/g, 'ナデナデ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
|
|
|
@ -11,7 +11,7 @@ script.
|
||||||
|
|
||||||
@absolute =
|
@absolute =
|
||||||
@time.get-full-year! + \年 +
|
@time.get-full-year! + \年 +
|
||||||
@time.get-month! + \月 +
|
@time.get-month! + 1 + \月 +
|
||||||
@time.get-date! + \日 +
|
@time.get-date! + \日 +
|
||||||
' ' +
|
' ' +
|
||||||
@time.get-hours! + \時 +
|
@time.get-hours! + \時 +
|
||||||
|
|
|
@ -32,11 +32,6 @@ boot(me => {
|
||||||
// Register mixins
|
// Register mixins
|
||||||
mixins(me);
|
mixins(me);
|
||||||
|
|
||||||
// Debug
|
|
||||||
if (me != null && me.data.debug) {
|
|
||||||
riot.mount(document.body.appendChild(document.createElement('mk-log-window')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start routing
|
// Start routing
|
||||||
route(me);
|
route(me);
|
||||||
});
|
});
|
||||||
|
|
|
@ -99,5 +99,3 @@ require './tags/user-followers-window.tag'
|
||||||
require './tags/list-user.tag'
|
require './tags/list-user.tag'
|
||||||
require './tags/ui-notification.tag'
|
require './tags/ui-notification.tag'
|
||||||
require './tags/signin-history.tag'
|
require './tags/signin-history.tag'
|
||||||
require './tags/log.tag'
|
|
||||||
require './tags/log-window.tag'
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
mk-log-window
|
|
||||||
mk-window@window(width={ '600px' }, height={ '400px' })
|
|
||||||
<yield to="header">
|
|
||||||
i.fa.fa-terminal
|
|
||||||
| Log
|
|
||||||
</yield>
|
|
||||||
<yield to="content">
|
|
||||||
mk-log
|
|
||||||
</yield>
|
|
||||||
|
|
||||||
style.
|
|
||||||
> mk-window
|
|
||||||
[data-yield='header']
|
|
||||||
> i
|
|
||||||
margin-right 4px
|
|
||||||
|
|
||||||
script.
|
|
||||||
@on \mount ~>
|
|
||||||
@refs.window.on \closed ~>
|
|
||||||
@unmount!
|
|
|
@ -1,62 +0,0 @@
|
||||||
mk-log
|
|
||||||
header
|
|
||||||
button.follow(class={ following: following }, onclick={ follow }) Follow
|
|
||||||
div.logs@logs
|
|
||||||
code(each={ logs })
|
|
||||||
span.date { date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds() }
|
|
||||||
span.message { message }
|
|
||||||
|
|
||||||
style.
|
|
||||||
display block
|
|
||||||
height 100%
|
|
||||||
color #fff
|
|
||||||
background #000
|
|
||||||
|
|
||||||
> header
|
|
||||||
height 32px
|
|
||||||
background #343a42
|
|
||||||
|
|
||||||
> button
|
|
||||||
line-height 32px
|
|
||||||
|
|
||||||
> .follow
|
|
||||||
position absolute
|
|
||||||
top 0
|
|
||||||
right 0
|
|
||||||
|
|
||||||
&.following
|
|
||||||
color #ff0
|
|
||||||
|
|
||||||
> .logs
|
|
||||||
height calc(100% - 32px)
|
|
||||||
overflow auto
|
|
||||||
|
|
||||||
> code
|
|
||||||
display block
|
|
||||||
padding 4px 8px
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
background rgba(#fff, 0.15)
|
|
||||||
|
|
||||||
> .date
|
|
||||||
margin-right 8px
|
|
||||||
opacity 0.5
|
|
||||||
|
|
||||||
script.
|
|
||||||
@mixin \log
|
|
||||||
|
|
||||||
@following = true
|
|
||||||
|
|
||||||
@on \mount ~>
|
|
||||||
@log-event.on \log @on-log
|
|
||||||
|
|
||||||
@on \unmount ~>
|
|
||||||
@log-event.off \log @on-log
|
|
||||||
|
|
||||||
@follow = ~>
|
|
||||||
@following = true
|
|
||||||
|
|
||||||
@on-log = ~>
|
|
||||||
@update!
|
|
||||||
if @following
|
|
||||||
@refs.logs.scroll-top = @refs.logs.scroll-height
|
|
|
@ -3,11 +3,10 @@ mk-user-preview
|
||||||
img.avatar(src={ user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
img.avatar(src={ user.avatar_url + '?thumbnail&size=64' }, alt='avatar')
|
||||||
div.main
|
div.main
|
||||||
header
|
header
|
||||||
div.left
|
a.name(href={ CONFIG.url + '/' + user.username })
|
||||||
a.name(href={ CONFIG.url + '/' + user.username })
|
| { user.name }
|
||||||
| { user.name }
|
span.username
|
||||||
span.username
|
| @{ user.username }
|
||||||
| @{ user.username }
|
|
||||||
div.body
|
div.body
|
||||||
div.bio { user.bio }
|
div.bio { user.bio }
|
||||||
|
|
||||||
|
@ -57,36 +56,26 @@ style.
|
||||||
width calc(100% - 74px)
|
width calc(100% - 74px)
|
||||||
|
|
||||||
> header
|
> header
|
||||||
white-space nowrap
|
|
||||||
|
|
||||||
@media (min-width 500px)
|
@media (min-width 500px)
|
||||||
margin-bottom 2px
|
margin-bottom 2px
|
||||||
|
|
||||||
&:after
|
> .name
|
||||||
content ""
|
display inline
|
||||||
display block
|
margin 0
|
||||||
clear both
|
padding 0
|
||||||
|
color #777
|
||||||
|
font-size 1em
|
||||||
|
font-weight 700
|
||||||
|
text-align left
|
||||||
|
text-decoration none
|
||||||
|
|
||||||
> .left
|
&:hover
|
||||||
float left
|
text-decoration underline
|
||||||
|
|
||||||
> .name
|
> .username
|
||||||
display inline
|
text-align left
|
||||||
margin 0
|
margin 0 0 0 8px
|
||||||
padding 0
|
color #ccc
|
||||||
color #777
|
|
||||||
font-size 1em
|
|
||||||
font-weight 700
|
|
||||||
text-align left
|
|
||||||
text-decoration none
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
text-decoration underline
|
|
||||||
|
|
||||||
> .username
|
|
||||||
text-align left
|
|
||||||
margin 0 0 0 8px
|
|
||||||
color #ccc
|
|
||||||
|
|
||||||
> .body
|
> .body
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue