diff options
| author | ha-dai <contact@haradai.net> | 2017-11-27 03:41:47 +0900 |
|---|---|---|
| committer | ha-dai <contact@haradai.net> | 2017-11-27 03:41:47 +0900 |
| commit | 6c75bc6d5188cbbf80fe1086fa0e8828f4edb873 (patch) | |
| tree | 3ffedcc3a06e53269e92d2990cf0b3bb247ac04a /src/web/app/common | |
| parent | Merge branch 'master' of https://github.com/syuilo/misskey (diff) | |
| parent | Update dependencies :rocket: (diff) | |
| download | misskey-6c75bc6d5188cbbf80fe1086fa0e8828f4edb873.tar.gz misskey-6c75bc6d5188cbbf80fe1086fa0e8828f4edb873.tar.bz2 misskey-6c75bc6d5188cbbf80fe1086fa0e8828f4edb873.zip | |
Merge branch 'master' of github.com:syuilo/misskey
Diffstat (limited to 'src/web/app/common')
52 files changed, 882 insertions, 272 deletions
diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts new file mode 100644 index 0000000000..6ee42ea8a7 --- /dev/null +++ b/src/web/app/common/mios.ts @@ -0,0 +1,351 @@ +import { EventEmitter } from 'eventemitter3'; +import * as riot from 'riot'; +import signout from './scripts/signout'; +import Progress from './scripts/loading'; +import HomeStreamManager from './scripts/streaming/home-stream-manager'; +import api from './scripts/api'; + +//#region environment variables +declare const _VERSION_: string; +declare const _LANG_: string; +declare const _API_URL_: string; +declare const _SW_PUBLICKEY_: string; +//#endregion + +/** + * Misskey Operating System + */ +export default class MiOS extends EventEmitter { + /** + * Misskeyの /meta で取得できるメタ情報 + */ + private meta: { + data: { [x: string]: any }; + chachedAt: Date; + }; + + private isMetaFetching = false; + + /** + * A signing user + */ + public i: { [x: string]: any }; + + /** + * Whether signed in + */ + public get isSignedin() { + return this.i != null; + } + + /** + * Whether is debug mode + */ + public get debug() { + return localStorage.getItem('debug') == 'true'; + } + + /** + * A connection manager of home stream + */ + public stream: HomeStreamManager; + + /** + * A registration of service worker + */ + private swRegistration: ServiceWorkerRegistration = null; + + /** + * Whether should register ServiceWorker + */ + private shouldRegisterSw: boolean; + + /** + * MiOSインスタンスを作成します + * @param shouldRegisterSw ServiceWorkerを登録するかどうか + */ + constructor(shouldRegisterSw = false) { + super(); + + this.shouldRegisterSw = shouldRegisterSw; + + //#region BIND + this.log = this.log.bind(this); + this.logInfo = this.logInfo.bind(this); + this.logWarn = this.logWarn.bind(this); + this.logError = this.logError.bind(this); + this.init = this.init.bind(this); + this.api = this.api.bind(this); + this.getMeta = this.getMeta.bind(this); + this.registerSw = this.registerSw.bind(this); + //#endregion + } + + public log(...args) { + if (!this.debug) return; + console.log.apply(null, args); + } + + public logInfo(...args) { + if (!this.debug) return; + console.info.apply(null, args); + } + + public logWarn(...args) { + if (!this.debug) return; + console.warn.apply(null, args); + } + + public logError(...args) { + if (!this.debug) return; + console.error.apply(null, args); + } + + /** + * Initialize MiOS (boot) + * @param callback A function that call when initialized + */ + public async init(callback) { + // ユーザーをフェッチしてコールバックする + const fetchme = (token, cb) => { + let me = null; + + // Return when not signed in + if (token == null) { + return done(); + } + + // Fetch user + fetch(`${_API_URL_}/i`, { + method: 'POST', + body: JSON.stringify({ + i: token + }) + }) + // When success + .then(res => { + // When failed to authenticate user + if (res.status !== 200) { + return signout(); + } + + // Parse response + res.json().then(i => { + me = i; + me.token = token; + done(); + }); + }) + // When failure + .catch(() => { + // Render the error screen + document.body.innerHTML = '<mk-error />'; + riot.mount('*'); + + Progress.done(); + }); + + function done() { + if (cb) cb(me); + } + }; + + // フェッチが完了したとき + const fetched = me => { + if (me) { + riot.observable(me); + + // この me オブジェクトを更新するメソッド + me.update = data => { + if (data) Object.assign(me, data); + me.trigger('updated'); + }; + + // ローカルストレージにキャッシュ + localStorage.setItem('me', JSON.stringify(me)); + + // 自分の情報が更新されたとき + me.on('updated', () => { + // キャッシュ更新 + localStorage.setItem('me', JSON.stringify(me)); + }); + } + + this.i = me; + + // Init home stream manager + this.stream = this.isSignedin + ? new HomeStreamManager(this.i) + : null; + + // Finish init + callback(); + + //#region Post + + // Init service worker + if (this.shouldRegisterSw) this.registerSw(); + + //#endregion + }; + + // Get cached account data + const cachedMe = JSON.parse(localStorage.getItem('me')); + + // キャッシュがあったとき + if (cachedMe) { + // とりあえずキャッシュされたデータでお茶を濁して(?)おいて、 + fetched(cachedMe); + + // 後から新鮮なデータをフェッチ + fetchme(cachedMe.token, freshData => { + Object.assign(cachedMe, freshData); + cachedMe.trigger('updated'); + cachedMe.trigger('refreshed'); + }); + } else { + // Get token from cookie + const i = (document.cookie.match(/i=(!\w+)/) || [null, null])[1]; + + fetchme(i, fetched); + } + } + + /** + * Register service worker + */ + private registerSw() { + // Check whether service worker and push manager supported + const isSwSupported = + ('serviceWorker' in navigator) && ('PushManager' in window); + + // Reject when browser not service worker supported + if (!isSwSupported) return; + + // Reject when not signed in to Misskey + if (!this.isSignedin) return; + + // When service worker activated + navigator.serviceWorker.ready.then(registration => { + this.log('[sw] ready: ', registration); + + this.swRegistration = registration; + + // Options of pushManager.subscribe + // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters + const opts = { + // A boolean indicating that the returned push subscription + // will only be used for messages whose effect is made visible to the user. + userVisibleOnly: true, + + // A public key your push server will use to send + // messages to client apps via a push server. + applicationServerKey: urlBase64ToUint8Array(_SW_PUBLICKEY_) + }; + + // Subscribe push notification + this.swRegistration.pushManager.subscribe(opts).then(subscription => { + this.log('[sw] Subscribe OK:', subscription); + + function encode(buffer: ArrayBuffer) { + return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); + } + + // Register + this.api('sw/register', { + endpoint: subscription.endpoint, + auth: encode(subscription.getKey('auth')), + publickey: encode(subscription.getKey('p256dh')) + }); + }) + // When subscribe failed + .catch(async (err: Error) => { + this.logError('[sw] Subscribe Error:', err); + + // 通知が許可されていなかったとき + if (err.name == 'NotAllowedError') { + this.logError('[sw] Subscribe failed due to notification not allowed'); + return; + } + + // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが + // 既に存在していることが原因でエラーになった可能性があるので、 + // そのサブスクリプションを解除しておく + const subscription = await this.swRegistration.pushManager.getSubscription(); + if (subscription) subscription.unsubscribe(); + }); + }); + + // The path of service worker script + const sw = `/sw.${_VERSION_}.${_LANG_}.js`; + + // Register service worker + navigator.serviceWorker.register(sw).then(registration => { + // 登録成功 + this.logInfo('[sw] Registration successful with scope: ', registration.scope); + }).catch(err => { + // 登録失敗 :( + this.logError('[sw] Registration failed: ', err); + }); + } + + /** + * Misskey APIにリクエストします + * @param endpoint エンドポイント名 + * @param data パラメータ + */ + public api(endpoint: string, data?: { [x: string]: any }) { + return api(this.i, endpoint, data); + } + + /** + * Misskeyのメタ情報を取得します + * @param force キャッシュを無視するか否か + */ + public getMeta(force = false) { + return new Promise<{ [x: string]: any }>(async (res, rej) => { + if (this.isMetaFetching) { + this.once('_meta_fetched_', () => { + res(this.meta.data); + }); + return; + } + + const expire = 1000 * 60; // 1min + + // forceが有効, meta情報を保持していない or 期限切れ + if (force || this.meta == null || Date.now() - this.meta.chachedAt.getTime() > expire) { + this.isMetaFetching = true; + const meta = await this.api('meta'); + this.meta = { + data: meta, + chachedAt: new Date() + }; + this.isMetaFetching = false; + this.emit('_meta_fetched_'); + res(meta); + } else { + res(this.meta.data); + } + }); + } +} + +/** + * Convert the URL safe base64 string to a Uint8Array + * @param base64String base64 string + */ +function urlBase64ToUint8Array(base64String: string): Uint8Array { + const padding = '='.repeat((4 - base64String.length % 4) % 4); + const base64 = (base64String + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + return outputArray; +} diff --git a/src/web/app/common/mixins.ts b/src/web/app/common/mixins.ts new file mode 100644 index 0000000000..e9c3625937 --- /dev/null +++ b/src/web/app/common/mixins.ts @@ -0,0 +1,40 @@ +import * as riot from 'riot'; + +import MiOS from './mios'; +import ServerStreamManager from './scripts/streaming/server-stream-manager'; +import RequestsStreamManager from './scripts/streaming/requests-stream-manager'; +import MessagingIndexStreamManager from './scripts/streaming/messaging-index-stream-manager'; +import DriveStreamManager from './scripts/streaming/drive-stream-manager'; + +export default (mios: MiOS) => { + (riot as any).mixin('os', { + mios: mios + }); + + (riot as any).mixin('i', { + init: function() { + this.I = mios.i; + this.SIGNIN = mios.isSignedin; + + if (this.SIGNIN) { + this.on('mount', () => { + mios.i.on('updated', this.update); + }); + this.on('unmount', () => { + mios.i.off('updated', this.update); + }); + } + }, + me: mios.i + }); + + (riot as any).mixin('api', { + api: mios.api + }); + + (riot as any).mixin('stream', { stream: mios.stream }); + (riot as any).mixin('drive-stream', { driveStream: new DriveStreamManager(mios.i) }); + (riot as any).mixin('server-stream', { serverStream: new ServerStreamManager() }); + (riot as any).mixin('requests-stream', { requestsStream: new RequestsStreamManager() }); + (riot as any).mixin('messaging-index-stream', { messagingIndexStream: new MessagingIndexStreamManager(mios.i) }); +}; diff --git a/src/web/app/common/mixins/api.js b/src/web/app/common/mixins/api.js deleted file mode 100644 index 42d96db559..0000000000 --- a/src/web/app/common/mixins/api.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as riot from 'riot'; -import api from '../scripts/api'; - -export default me => { - riot.mixin('api', { - api: api.bind(null, me ? me.token : null) - }); -}; diff --git a/src/web/app/common/mixins/i.js b/src/web/app/common/mixins/i.js deleted file mode 100644 index 5225147766..0000000000 --- a/src/web/app/common/mixins/i.js +++ /dev/null @@ -1,20 +0,0 @@ -import * as riot from 'riot'; - -export default me => { - riot.mixin('i', { - init: function() { - this.I = me; - this.SIGNIN = me != null; - - if (this.SIGNIN) { - this.on('mount', () => { - me.on('updated', this.update); - }); - this.on('unmount', () => { - me.off('updated', this.update); - }); - } - }, - me: me - }); -}; diff --git a/src/web/app/common/mixins/index.js b/src/web/app/common/mixins/index.js deleted file mode 100644 index 9718ee949b..0000000000 --- a/src/web/app/common/mixins/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import activateMe from './i'; -import activateApi from './api'; -import activateStream from './stream'; - -export default (me, stream) => { - activateMe(me); - activateApi(me); - activateStream(stream); -}; diff --git a/src/web/app/common/mixins/stream.js b/src/web/app/common/mixins/stream.js deleted file mode 100644 index 4706042b04..0000000000 --- a/src/web/app/common/mixins/stream.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as riot from 'riot'; - -export default stream => { - riot.mixin('stream', { stream }); -}; diff --git a/src/web/app/common/scripts/api.js b/src/web/app/common/scripts/api.ts index 4855f736c7..e62447b0a0 100644 --- a/src/web/app/common/scripts/api.js +++ b/src/web/app/common/scripts/api.ts @@ -2,7 +2,7 @@ * API Request */ -import CONFIG from './config'; +declare const _API_URL_: string; let spinner = null; let pending = 0; @@ -14,7 +14,7 @@ let pending = 0; * @param {any} [data={}] Data * @return {Promise<any>} Response */ -export default (i, endpoint, data = {}) => { +export default (i, endpoint, data = {}): Promise<{ [x: string]: any }> => { if (++pending === 1) { spinner = document.createElement('div'); spinner.setAttribute('id', 'wait'); @@ -22,11 +22,11 @@ export default (i, endpoint, data = {}) => { } // Append the credential - if (i != null) data.i = typeof i === 'object' ? i.token : i; + if (i != null) (data as any).i = typeof i === 'object' ? i.token : i; return new Promise((resolve, reject) => { // Send request - fetch(endpoint.indexOf('://') > -1 ? endpoint : `${CONFIG.apiUrl}/${endpoint}`, { + fetch(endpoint.indexOf('://') > -1 ? endpoint : `${_API_URL_}/${endpoint}`, { method: 'POST', body: JSON.stringify(data), credentials: endpoint === 'signin' ? 'include' : 'omit' diff --git a/src/web/app/common/scripts/bytes-to-size.js b/src/web/app/common/scripts/bytes-to-size.ts index af0268dbd0..1d2b1e7ce3 100644 --- a/src/web/app/common/scripts/bytes-to-size.js +++ b/src/web/app/common/scripts/bytes-to-size.ts @@ -1,6 +1,6 @@ export default (bytes, digits = 0) => { - var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == 0) return '0Byte'; - var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); + const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i]; }; diff --git a/src/web/app/common/scripts/check-for-update.js b/src/web/app/common/scripts/check-for-update.js deleted file mode 100644 index 7cb7839d29..0000000000 --- a/src/web/app/common/scripts/check-for-update.js +++ /dev/null @@ -1,14 +0,0 @@ -import CONFIG from './config'; - -export default function() { - fetch(CONFIG.apiUrl + '/meta', { - method: 'POST' - }).then(res => { - res.json().then(meta => { - if (meta.version != VERSION) { - localStorage.setItem('should-refresh', 'true'); - alert('%i18n:common.update-available%'.replace('{newer}', meta.version).replace('{current}', VERSION)); - } - }); - }); -}; diff --git a/src/web/app/common/scripts/check-for-update.ts b/src/web/app/common/scripts/check-for-update.ts new file mode 100644 index 0000000000..c447a517fa --- /dev/null +++ b/src/web/app/common/scripts/check-for-update.ts @@ -0,0 +1,12 @@ +import MiOS from '../mios'; + +declare const _VERSION_: string; + +export default async function(mios: MiOS) { + const meta = await mios.getMeta(); + + if (meta.version != _VERSION_) { + localStorage.setItem('should-refresh', 'true'); + alert('%i18n:common.update-available%'.replace('{newer}', meta.version).replace('{current}', _VERSION_)); + } +} diff --git a/src/web/app/common/scripts/compose-notification.ts b/src/web/app/common/scripts/compose-notification.ts new file mode 100644 index 0000000000..d0e0c2098d --- /dev/null +++ b/src/web/app/common/scripts/compose-notification.ts @@ -0,0 +1,60 @@ +import getPostSummary from '../../../../common/get-post-summary'; +import getReactionEmoji from '../../../../common/get-reaction-emoji'; + +type Notification = { + title: string; + body: string; + icon: string; + onclick?: any; +}; + +// TODO: i18n + +export default function(type, data): Notification { + switch (type) { + case 'drive_file_created': + return { + title: 'ファイルがアップロードされました', + body: data.name, + icon: data.url + '?thumbnail&size=64' + }; + + case 'mention': + return { + title: `${data.user.name}さんから:`, + body: getPostSummary(data), + icon: data.user.avatar_url + '?thumbnail&size=64' + }; + + case 'reply': + return { + title: `${data.user.name}さんから返信:`, + body: getPostSummary(data), + icon: data.user.avatar_url + '?thumbnail&size=64' + }; + + case 'quote': + return { + title: `${data.user.name}さんが引用:`, + body: getPostSummary(data), + icon: data.user.avatar_url + '?thumbnail&size=64' + }; + + case 'reaction': + return { + title: `${data.user.name}: ${getReactionEmoji(data.reaction)}:`, + body: getPostSummary(data.post), + icon: data.user.avatar_url + '?thumbnail&size=64' + }; + + case 'unread_messaging_message': + return { + title: `${data.user.name}さんからメッセージ:`, + body: data.text, // TODO: getMessagingMessageSummary(data), + icon: data.user.avatar_url + '?thumbnail&size=64' + }; + + default: + return null; + } +} diff --git a/src/web/app/common/scripts/config.js b/src/web/app/common/scripts/config.js deleted file mode 100644 index c5015622f0..0000000000 --- a/src/web/app/common/scripts/config.js +++ /dev/null @@ -1,25 +0,0 @@ -const Url = new URL(location.href); - -const isRoot = Url.host.split('.')[0] == 'misskey'; - -const host = isRoot ? Url.host : Url.host.substring(Url.host.indexOf('.') + 1, Url.host.length); -const scheme = Url.protocol; -const url = `${scheme}//${host}`; -const apiUrl = `${scheme}//api.${host}`; -const chUrl = `${scheme}//ch.${host}`; -const devUrl = `${scheme}//dev.${host}`; -const aboutUrl = `${scheme}//about.${host}`; -const statsUrl = `${scheme}//stats.${host}`; -const statusUrl = `${scheme}//status.${host}`; - -export default { - host, - scheme, - url, - apiUrl, - chUrl, - devUrl, - aboutUrl, - statsUrl, - statusUrl -}; diff --git a/src/web/app/common/scripts/contains.js b/src/web/app/common/scripts/contains.ts index a5071b3f25..a5071b3f25 100644 --- a/src/web/app/common/scripts/contains.js +++ b/src/web/app/common/scripts/contains.ts diff --git a/src/web/app/common/scripts/copy-to-clipboard.js b/src/web/app/common/scripts/copy-to-clipboard.ts index 3d2741f8d7..3d2741f8d7 100644 --- a/src/web/app/common/scripts/copy-to-clipboard.js +++ b/src/web/app/common/scripts/copy-to-clipboard.ts diff --git a/src/web/app/common/scripts/date-stringify.js b/src/web/app/common/scripts/date-stringify.ts index e51de8833d..e51de8833d 100644 --- a/src/web/app/common/scripts/date-stringify.js +++ b/src/web/app/common/scripts/date-stringify.ts diff --git a/src/web/app/common/scripts/gcd.js b/src/web/app/common/scripts/gcd.ts index 9a19f9da66..9a19f9da66 100644 --- a/src/web/app/common/scripts/gcd.js +++ b/src/web/app/common/scripts/gcd.ts diff --git a/src/web/app/common/scripts/generate-default-userdata.js b/src/web/app/common/scripts/generate-default-userdata.js deleted file mode 100644 index 1200563e1a..0000000000 --- a/src/web/app/common/scripts/generate-default-userdata.js +++ /dev/null @@ -1,45 +0,0 @@ -import uuid from './uuid'; - -const home = { - left: [ - 'profile', - 'calendar', - 'rss-reader', - 'photo-stream', - 'version' - ], - right: [ - 'broadcast', - 'notifications', - 'user-recommendation', - 'donation', - 'nav', - 'tips' - ] -}; - -export default () => { - const homeData = []; - - home.left.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'left' - }); - }); - - home.right.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'right' - }); - }); - - const data = { - home: JSON.stringify(homeData) - }; - - return data; -}; diff --git a/src/web/app/common/scripts/get-kao.js b/src/web/app/common/scripts/get-kao.ts index 0b77ee285a..2168c5be88 100644 --- a/src/web/app/common/scripts/get-kao.js +++ b/src/web/app/common/scripts/get-kao.ts @@ -1,5 +1,5 @@ export default () => [ '(=^・・^=)', 'v(‘ω’)v', - '🐡( '-' 🐡 )フグパンチ!!!!' + '🐡( \'-\' 🐡 )フグパンチ!!!!' ][Math.floor(Math.random() * 3)]; diff --git a/src/web/app/common/scripts/get-median.ts b/src/web/app/common/scripts/get-median.ts new file mode 100644 index 0000000000..91a415d5b2 --- /dev/null +++ b/src/web/app/common/scripts/get-median.ts @@ -0,0 +1,11 @@ +/** + * 中央値を求めます + * @param samples サンプル + */ +export default function(samples) { + if (!samples.length) return 0; + const numbers = samples.slice(0).sort((a, b) => a - b); + const middle = Math.floor(numbers.length / 2); + const isEven = numbers.length % 2 === 0; + return isEven ? (numbers[middle] + numbers[middle - 1]) / 2 : numbers[middle]; +} diff --git a/src/web/app/common/scripts/is-promise.js b/src/web/app/common/scripts/is-promise.ts index 3b4cd70b49..3b4cd70b49 100644 --- a/src/web/app/common/scripts/is-promise.js +++ b/src/web/app/common/scripts/is-promise.ts diff --git a/src/web/app/common/scripts/loading.js b/src/web/app/common/scripts/loading.ts index c48e626648..c48e626648 100644 --- a/src/web/app/common/scripts/loading.js +++ b/src/web/app/common/scripts/loading.ts diff --git a/src/web/app/common/scripts/signout.js b/src/web/app/common/scripts/signout.js deleted file mode 100644 index 6c95cfbc9c..0000000000 --- a/src/web/app/common/scripts/signout.js +++ /dev/null @@ -1,7 +0,0 @@ -import CONFIG from './config'; - -export default () => { - localStorage.removeItem('me'); - document.cookie = `i=; domain=.${CONFIG.host}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; - location.href = '/'; -}; diff --git a/src/web/app/common/scripts/signout.ts b/src/web/app/common/scripts/signout.ts new file mode 100644 index 0000000000..2923196549 --- /dev/null +++ b/src/web/app/common/scripts/signout.ts @@ -0,0 +1,7 @@ +declare const _HOST_: string; + +export default () => { + localStorage.removeItem('me'); + document.cookie = `i=; domain=.${_HOST_}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; + location.href = '/'; +}; diff --git a/src/web/app/common/scripts/channel-stream.js b/src/web/app/common/scripts/streaming/channel-stream.ts index 17944dbe45..434b108b9e 100644 --- a/src/web/app/common/scripts/channel-stream.js +++ b/src/web/app/common/scripts/streaming/channel-stream.ts @@ -1,16 +1,12 @@ -'use strict'; - import Stream from './stream'; /** * Channel stream connection */ -class Connection extends Stream { +export default class Connection extends Stream { constructor(channelId) { super('channel', { channel: channelId }); } } - -export default Connection; diff --git a/src/web/app/common/scripts/streaming/drive-stream-manager.ts b/src/web/app/common/scripts/streaming/drive-stream-manager.ts new file mode 100644 index 0000000000..8acdd7cbba --- /dev/null +++ b/src/web/app/common/scripts/streaming/drive-stream-manager.ts @@ -0,0 +1,20 @@ +import StreamManager from './stream-manager'; +import Connection from './drive-stream'; + +export default class DriveStreamManager extends StreamManager<Connection> { + private me; + + constructor(me) { + super(); + + this.me = me; + } + + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(this.me); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/streaming/drive-stream.ts b/src/web/app/common/scripts/streaming/drive-stream.ts new file mode 100644 index 0000000000..0da3f12554 --- /dev/null +++ b/src/web/app/common/scripts/streaming/drive-stream.ts @@ -0,0 +1,12 @@ +import Stream from './stream'; + +/** + * Drive stream connection + */ +export default class Connection extends Stream { + constructor(me) { + super('drive', { + i: me.token + }); + } +} diff --git a/src/web/app/common/scripts/streaming/home-stream-manager.ts b/src/web/app/common/scripts/streaming/home-stream-manager.ts new file mode 100644 index 0000000000..ad1dc870eb --- /dev/null +++ b/src/web/app/common/scripts/streaming/home-stream-manager.ts @@ -0,0 +1,20 @@ +import StreamManager from './stream-manager'; +import Connection from './home-stream'; + +export default class HomeStreamManager extends StreamManager<Connection> { + private me; + + constructor(me) { + super(); + + this.me = me; + } + + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(this.me); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/home-stream.js b/src/web/app/common/scripts/streaming/home-stream.ts index de9ceb3b51..11ad754ef0 100644 --- a/src/web/app/common/scripts/home-stream.js +++ b/src/web/app/common/scripts/streaming/home-stream.ts @@ -1,12 +1,10 @@ -'use strict'; - import Stream from './stream'; -import signout from './signout'; +import signout from '../signout'; /** * Home stream connection */ -class Connection extends Stream { +export default class Connection extends Stream { constructor(me) { super('', { i: me.token @@ -17,13 +15,14 @@ class Connection extends Stream { this.send({ type: 'alive' }); }, 1000 * 60); + // 自分の情報が更新されたとき this.on('i_updated', me.update); + // トークンが再生成されたとき + // このままではAPIが利用できないので強制的にサインアウトさせる this.on('my_token_regenerated', () => { alert('%i18n:common.my-token-regenerated%'); signout(); }); } } - -export default Connection; diff --git a/src/web/app/common/scripts/streaming/messaging-index-stream-manager.ts b/src/web/app/common/scripts/streaming/messaging-index-stream-manager.ts new file mode 100644 index 0000000000..0f08b01481 --- /dev/null +++ b/src/web/app/common/scripts/streaming/messaging-index-stream-manager.ts @@ -0,0 +1,20 @@ +import StreamManager from './stream-manager'; +import Connection from './messaging-index-stream'; + +export default class MessagingIndexStreamManager extends StreamManager<Connection> { + private me; + + constructor(me) { + super(); + + this.me = me; + } + + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(this.me); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/streaming/messaging-index-stream.ts b/src/web/app/common/scripts/streaming/messaging-index-stream.ts new file mode 100644 index 0000000000..8015c840b4 --- /dev/null +++ b/src/web/app/common/scripts/streaming/messaging-index-stream.ts @@ -0,0 +1,12 @@ +import Stream from './stream'; + +/** + * Messaging index stream connection + */ +export default class Connection extends Stream { + constructor(me) { + super('messaging-index', { + i: me.token + }); + } +} diff --git a/src/web/app/common/scripts/messaging-stream.js b/src/web/app/common/scripts/streaming/messaging-stream.ts index 261525d5f6..68dfc5ec09 100644 --- a/src/web/app/common/scripts/messaging-stream.js +++ b/src/web/app/common/scripts/streaming/messaging-stream.ts @@ -1,23 +1,19 @@ -'use strict'; - import Stream from './stream'; /** * Messaging stream connection */ -class Connection extends Stream { +export default class Connection extends Stream { constructor(me, otherparty) { super('messaging', { i: me.token, otherparty }); - this.on('_connected_', () => { + (this as any).on('_connected_', () => { this.send({ i: me.token }); }); } } - -export default Connection; diff --git a/src/web/app/common/scripts/streaming/requests-stream-manager.ts b/src/web/app/common/scripts/streaming/requests-stream-manager.ts new file mode 100644 index 0000000000..44db913e78 --- /dev/null +++ b/src/web/app/common/scripts/streaming/requests-stream-manager.ts @@ -0,0 +1,12 @@ +import StreamManager from './stream-manager'; +import Connection from './requests-stream'; + +export default class RequestsStreamManager extends StreamManager<Connection> { + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/streaming/requests-stream.ts b/src/web/app/common/scripts/streaming/requests-stream.ts new file mode 100644 index 0000000000..22ecea6c07 --- /dev/null +++ b/src/web/app/common/scripts/streaming/requests-stream.ts @@ -0,0 +1,10 @@ +import Stream from './stream'; + +/** + * Requests stream connection + */ +export default class Connection extends Stream { + constructor() { + super('requests'); + } +} diff --git a/src/web/app/common/scripts/streaming/server-stream-manager.ts b/src/web/app/common/scripts/streaming/server-stream-manager.ts new file mode 100644 index 0000000000..a170daebb9 --- /dev/null +++ b/src/web/app/common/scripts/streaming/server-stream-manager.ts @@ -0,0 +1,12 @@ +import StreamManager from './stream-manager'; +import Connection from './server-stream'; + +export default class ServerStreamManager extends StreamManager<Connection> { + public getConnection() { + if (this.connection == null) { + this.connection = new Connection(); + } + + return this.connection; + } +} diff --git a/src/web/app/common/scripts/server-stream.js b/src/web/app/common/scripts/streaming/server-stream.ts index a1c466b35d..b9e0684465 100644 --- a/src/web/app/common/scripts/server-stream.js +++ b/src/web/app/common/scripts/streaming/server-stream.ts @@ -1,14 +1,10 @@ -'use strict'; - import Stream from './stream'; /** * Server stream connection */ -class Connection extends Stream { +export default class Connection extends Stream { constructor() { super('server'); } } - -export default Connection; diff --git a/src/web/app/common/scripts/streaming/stream-manager.ts b/src/web/app/common/scripts/streaming/stream-manager.ts new file mode 100644 index 0000000000..5bb0dc701c --- /dev/null +++ b/src/web/app/common/scripts/streaming/stream-manager.ts @@ -0,0 +1,89 @@ +import { EventEmitter } from 'eventemitter3'; +import * as uuid from 'uuid'; +import Connection from './stream'; + +/** + * ストリーム接続を管理するクラス + * 複数の場所から同じストリームを利用する際、接続をまとめたりする + */ +export default abstract class StreamManager<T extends Connection> extends EventEmitter { + private _connection: T = null; + + private disposeTimerId: any; + + /** + * コネクションを必要としているユーザー + */ + private users = []; + + protected set connection(connection: T) { + this._connection = connection; + + if (this._connection == null) { + this.emit('disconnected'); + } else { + this.emit('connected', this._connection); + } + } + + protected get connection() { + return this._connection; + } + + /** + * コネクションを持っているか否か + */ + public get hasConnection() { + return this._connection != null; + } + + /** + * コネクションを要求します + */ + public abstract getConnection(): T; + + /** + * 現在接続しているコネクションを取得します + */ + public borrow() { + return this._connection; + } + + /** + * コネクションを要求するためのユーザーIDを発行します + */ + public use() { + // タイマー解除 + if (this.disposeTimerId) { + clearTimeout(this.disposeTimerId); + this.disposeTimerId = null; + } + + // ユーザーID生成 + const userId = uuid(); + + this.users.push(userId); + + return userId; + } + + /** + * コネクションを利用し終わってもう必要ないことを通知します + * @param userId use で発行したユーザーID + */ + public dispose(userId) { + this.users = this.users.filter(id => id != userId); + + // 誰もコネクションの利用者がいなくなったら + if (this.users.length == 0) { + // また直ぐに再利用される可能性があるので、一定時間待ち、 + // 新たな利用者が現れなければコネクションを切断する + this.disposeTimerId = setTimeout(() => { + this.disposeTimerId = null; + + this.connection.close(); + this.connection = null; + }, 3000); + } + } +} diff --git a/src/web/app/common/scripts/stream.js b/src/web/app/common/scripts/streaming/stream.ts index 981118b5de..770d77510f 100644 --- a/src/web/app/common/scripts/stream.js +++ b/src/web/app/common/scripts/streaming/stream.ts @@ -1,35 +1,38 @@ -'use strict'; +declare const _API_URL_: string; -const ReconnectingWebSocket = require('reconnecting-websocket'); -import * as riot from 'riot'; -import CONFIG from './config'; +import { EventEmitter } from 'eventemitter3'; +import * as ReconnectingWebsocket from 'reconnecting-websocket'; /** * Misskey stream connection */ -class Connection { - constructor(endpoint, params) { - // BIND ----------------------------------- +export default class Connection extends EventEmitter { + private state: string; + private buffer: any[]; + private socket: ReconnectingWebsocket; + + constructor(endpoint, params?) { + super(); + + //#region BIND this.onOpen = this.onOpen.bind(this); this.onClose = this.onClose.bind(this); this.onMessage = this.onMessage.bind(this); this.send = this.send.bind(this); this.close = this.close.bind(this); - // ---------------------------------------- - - riot.observable(this); + //#endregion this.state = 'initializing'; this.buffer = []; - const host = CONFIG.apiUrl.replace('http', 'ws'); + const host = _API_URL_.replace('http', 'ws'); const query = params ? Object.keys(params) .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k])) .join('&') : null; - this.socket = new ReconnectingWebSocket(`${host}/${endpoint}${query ? '?' + query : ''}`); + this.socket = new ReconnectingWebsocket(`${host}/${endpoint}${query ? '?' + query : ''}`); this.socket.addEventListener('open', this.onOpen); this.socket.addEventListener('close', this.onClose); this.socket.addEventListener('message', this.onMessage); @@ -37,11 +40,10 @@ class Connection { /** * Callback of when open connection - * @private */ - onOpen() { + private onOpen() { this.state = 'connected'; - this.trigger('_connected_'); + this.emit('_connected_'); // バッファーを処理 const _buffer = [].concat(this.buffer); // Shallow copy @@ -53,48 +55,42 @@ class Connection { /** * Callback of when close connection - * @private */ - onClose() { + private onClose() { this.state = 'reconnecting'; - this.trigger('_closed_'); + this.emit('_closed_'); } /** * Callback of when received a message from connection - * @private */ - onMessage(message) { + private onMessage(message) { try { const msg = JSON.parse(message.data); - if (msg.type) this.trigger(msg.type, msg.body); - } catch(e) { + if (msg.type) this.emit(msg.type, msg.body); + } catch (e) { // noop } } /** * Send a message to connection - * @public */ - send(message) { + public send(message) { // まだ接続が確立されていなかったらバッファリングして次に接続した時に送信する if (this.state != 'connected') { this.buffer.push(message); return; - }; + } this.socket.send(JSON.stringify(message)); } /** * Close this connection - * @public */ - close() { + public close() { this.socket.removeEventListener('open', this.onOpen); this.socket.removeEventListener('message', this.onMessage); } } - -export default Connection; diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.ts index 0a9b8022df..e0ea47df26 100644 --- a/src/web/app/common/scripts/text-compiler.js +++ b/src/web/app/common/scripts/text-compiler.ts @@ -1,6 +1,7 @@ +declare const _URL_: string; + import * as riot from 'riot'; -const pictograph = require('pictograph'); -import CONFIG from './config'; +import * as pictograph from 'pictograph'; const escape = text => text @@ -12,7 +13,7 @@ export default (tokens, shouldBreak) => { shouldBreak = true; } - const me = riot.mixin('i').me; + const me = (riot as any).mixin('i').me; let text = tokens.map(token => { switch (token.type) { @@ -26,7 +27,7 @@ export default (tokens, shouldBreak) => { case 'link': return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`; case 'mention': - return `<a href="${CONFIG.url + '/' + escape(token.username)}" target="_blank" data-user-preview="${token.content}" ${me && me.username == token.username ? 'data-is-me' : ''}>${token.content}</a>`; + return `<a href="${_URL_ + '/' + escape(token.username)}" target="_blank" data-user-preview="${token.content}" ${me && me.username == token.username ? 'data-is-me' : ''}>${token.content}</a>`; case 'hashtag': // TODO return `<a>${escape(token.content)}</a>`; case 'code': diff --git a/src/web/app/common/scripts/uuid.js b/src/web/app/common/scripts/uuid.js deleted file mode 100644 index ff016e18ad..0000000000 --- a/src/web/app/common/scripts/uuid.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Generate a UUID - */ -export default () => { - let uuid = ''; - - for (let i = 0; i < 32; i++) { - const random = Math.random() * 16 | 0; - - if (i == 8 || i == 12 || i == 16 || i == 20) { - uuid += '-'; - } - - uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16); - } - - return uuid; -}; diff --git a/src/web/app/common/tags/error.tag b/src/web/app/common/tags/error.tag index 62f4563e5c..51c2a6c13c 100644 --- a/src/web/app/common/tags/error.tag +++ b/src/web/app/common/tags/error.tag @@ -170,8 +170,6 @@ </style> <script> - import CONFIG from '../../common/scripts/config'; - this.on('mount', () => { this.update({ network: navigator.onLine @@ -193,7 +191,7 @@ }); // Check misskey server is available - fetch(`${CONFIG.apiUrl}/meta`).then(() => { + fetch(`${_API_URL_}/meta`).then(() => { this.update({ end: true, server: true diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.ts index 35a9f4586e..2f4e1181d4 100644 --- a/src/web/app/common/tags/index.js +++ b/src/web/app/common/tags/index.ts @@ -28,3 +28,4 @@ require('./reaction-picker.tag'); require('./reactions-viewer.tag'); require('./reaction-icon.tag'); require('./post-menu.tag'); +require('./nav-links.tag'); diff --git a/src/web/app/common/tags/introduction.tag b/src/web/app/common/tags/introduction.tag index fa1b1e247a..3256688d10 100644 --- a/src/web/app/common/tags/introduction.tag +++ b/src/web/app/common/tags/introduction.tag @@ -3,7 +3,7 @@ <h1>Misskeyとは?</h1> <p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo/misskey" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p> <p>無料で誰でも利用でき、広告も掲載していません。</p> - <p><a href={ CONFIG.aboutUrl } target="_blank">もっと知りたい方はこちら</a></p> + <p><a href={ _ABOUT_URL_ } target="_blank">もっと知りたい方はこちら</a></p> </article> <style> :scope diff --git a/src/web/app/common/tags/messaging/index.tag b/src/web/app/common/tags/messaging/index.tag index 731c9da2c7..aebcec9d8d 100644 --- a/src/web/app/common/tags/messaging/index.tag +++ b/src/web/app/common/tags/messaging/index.tag @@ -1,5 +1,5 @@ -<mk-messaging> - <div class="search"> +<mk-messaging data-compact={ opts.compact }> + <div class="search" if={ !opts.compact }> <div class="form"> <label for="search-input"><i class="fa fa-search"></i></label> <input ref="search" type="search" oninput={ search } onkeydown={ onSearchKeydown } placeholder="%i18n:common.tags.mk-messaging.search-user%"/> @@ -31,11 +31,37 @@ </a> </virtual> </div> - <p class="no-history" if={ history.length == 0 }>%i18n:common.tags.mk-messaging.no-history%</p> + <p class="no-history" if={ !fetching && history.length == 0 }>%i18n:common.tags.mk-messaging.no-history%</p> + <p class="fetching" if={ fetching }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:common.loading%<mk-ellipsis/></p> <style> :scope display block + &[data-compact] + font-size 0.8em + + > .history + > a + &:last-child + border-bottom none + + &:not([data-is-me]):not([data-is-read]) + > div + background-image none + border-left solid 4px #3aa2dc + + > div + padding 16px + + > header + > mk-time + font-size 1em + + > .avatar + width 42px + height 42px + margin 0 12px 0 0 + > .search display block position -webkit-sticky @@ -75,7 +101,7 @@ > input margin 0 - padding 0 12px 0 38px + padding 0 0 0 38px width 100% font-size 1em line-height 38px @@ -272,6 +298,15 @@ color #999 font-weight 500 + > .fetching + margin 0 + padding 16px + text-align center + color #aaa + + > i + margin-right 4px + // TODO: element base media query @media (max-width 400px) > .search @@ -299,22 +334,61 @@ this.mixin('i'); this.mixin('api'); + this.mixin('messaging-index-stream'); + this.connection = this.messagingIndexStream.getConnection(); + this.connectionId = this.messagingIndexStream.use(); + this.searchResult = []; + this.history = []; + this.fetching = true; + + this.registerMessage = message => { + message.is_me = message.user_id == this.I.id; + message._click = () => { + this.trigger('navigate-user', message.is_me ? message.recipient : message.user); + }; + }; this.on('mount', () => { + this.connection.on('message', this.onMessage); + this.connection.on('read', this.onRead); + this.api('messaging/history').then(history => { - this.isLoading = false; + this.fetching = false; history.forEach(message => { - message.is_me = message.user_id == this.I.id - message._click = () => { - this.trigger('navigate-user', message.is_me ? message.recipient : message.user); - }; + this.registerMessage(message); }); this.history = history; this.update(); }); }); + this.on('unmount', () => { + this.connection.off('message', this.onMessage); + this.connection.off('read', this.onRead); + this.messagingIndexStream.dispose(this.connectionId); + }); + + this.onMessage = message => { + this.history = this.history.filter(m => !( + (m.recipient_id == message.recipient_id && m.user_id == message.user_id) || + (m.recipient_id == message.user_id && m.user_id == message.recipient_id))); + + this.registerMessage(message); + + this.history.unshift(message); + this.update(); + }; + + this.onRead = ids => { + ids.forEach(id => { + const found = this.history.find(m => m.id == id); + if (found) found.is_read = true; + }); + + this.update(); + }; + this.search = () => { const q = this.refs.search.value; if (q == '') { diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag index d6db9070e2..ea1ea2310b 100644 --- a/src/web/app/common/tags/messaging/message.tag +++ b/src/web/app/common/tags/messaging/message.tag @@ -219,7 +219,7 @@ this.refs.text.innerHTML = compile(tokens); - this.refs.text.children.forEach(e => { + Array.from(this.refs.text.children).forEach(e => { if (e.tagName == 'MK-URL') riot.mount(e); }); diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag index b1082e26be..a930327841 100644 --- a/src/web/app/common/tags/messaging/room.tag +++ b/src/web/app/common/tags/messaging/room.tag @@ -162,7 +162,7 @@ </style> <script> - import MessagingStreamConnection from '../../scripts/messaging-stream'; + import MessagingStreamConnection from '../../scripts/streaming/messaging-stream'; this.mixin('i'); this.mixin('api'); diff --git a/src/web/app/common/tags/nav-links.tag b/src/web/app/common/tags/nav-links.tag new file mode 100644 index 0000000000..6043f128fa --- /dev/null +++ b/src/web/app/common/tags/nav-links.tag @@ -0,0 +1,7 @@ +<mk-nav-links> + <a href={ _ABOUT_URL_ }>%i18n:common.tags.mk-nav-links.about%</a><i>・</i><a href={ _STATS_URL_ }>%i18n:common.tags.mk-nav-links.stats%</a><i>・</i><a href={ _STATUS_URL_ }>%i18n:common.tags.mk-nav-links.status%</a><i>・</i><a href="http://zawazawa.jp/misskey/">%i18n:common.tags.mk-nav-links.wiki%</a><i>・</i><a href="https://github.com/syuilo/misskey/blob/master/DONORS.md">%i18n:common.tags.mk-nav-links.donors%</a><i>・</i><a href="https://github.com/syuilo/misskey">%i18n:common.tags.mk-nav-links.repository%</a><i>・</i><a href={ _DEV_URL_ }>%i18n:common.tags.mk-nav-links.develop%</a><i>・</i><a href="https://twitter.com/misskey_xyz" target="_blank">Follow us on <i class="fa fa-twitter"></i></a> + <style> + :scope + display inline + </style> +</mk-nav-links> diff --git a/src/web/app/common/tags/raw.tag b/src/web/app/common/tags/raw.tag index e1285694e4..adc6de5a3b 100644 --- a/src/web/app/common/tags/raw.tag +++ b/src/web/app/common/tags/raw.tag @@ -5,5 +5,9 @@ </style> <script> this.root.innerHTML = this.opts.content; + + this.on('updated', () => { + this.root.innerHTML = this.opts.content; + }); </script> </mk-raw> diff --git a/src/web/app/common/tags/signin-history.tag b/src/web/app/common/tags/signin-history.tag index 9c96746249..b9bd859851 100644 --- a/src/web/app/common/tags/signin-history.tag +++ b/src/web/app/common/tags/signin-history.tag @@ -50,7 +50,10 @@ <script> this.mixin('i'); this.mixin('api'); + this.mixin('stream'); + this.connection = this.stream.getConnection(); + this.connectionId = this.stream.use(); this.history = []; this.fetching = true; @@ -63,11 +66,12 @@ }); }); - this.stream.on('signin', this.onSignin); + this.connection.on('signin', this.onSignin); }); this.on('unmount', () => { - this.stream.off('signin', this.onSignin); + this.connection.off('signin', this.onSignin); + this.stream.dispose(this.connectionId); }); this.onSignin = signin => { diff --git a/src/web/app/common/tags/signup.tag b/src/web/app/common/tags/signup.tag index 17de0347f5..6fec46ff31 100644 --- a/src/web/app/common/tags/signup.tag +++ b/src/web/app/common/tags/signup.tag @@ -3,7 +3,7 @@ <label class="username"> <p class="caption"><i class="fa fa-at"></i>%i18n:common.tags.mk-signup.username%</p> <input ref="username" type="text" pattern="^[a-zA-Z0-9-]{3,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required="required" onkeyup={ onChangeUsername }/> - <p class="profile-page-url-preview" if={ refs.username.value != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange' }>{ CONFIG.url + '/' + refs.username.value }</p> + <p class="profile-page-url-preview" if={ refs.username.value != '' && username-state != 'invalidFormat' && username-state != 'minRange' && username-state != 'maxRange' }>{ _URL_ + '/' + refs.username.value }</p> <p class="info" if={ usernameState == 'wait' } style="color:#999"><i class="fa fa-fw fa-spinner fa-pulse"></i>%i18n:common.tags.mk-signup.checking%</p> <p class="info" if={ usernameState == 'ok' } style="color:#3CB7B5"><i class="fa fa-fw fa-check"></i>%i18n:common.tags.mk-signup.available%</p> <p class="info" if={ usernameState == 'unavailable' } style="color:#FF1161"><i class="fa fa-fw fa-exclamation-triangle"></i>%i18n:common.tags.mk-signup.unavailable%</p> @@ -30,7 +30,7 @@ </label> <label class="recaptcha"> <p class="caption"><i class="fa fa-toggle-on" if={ recaptchaed }></i><i class="fa fa-toggle-off" if={ !recaptchaed }></i>%i18n:common.tags.mk-signup.recaptcha%</p> - <div if={ recaptcha } class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" data-sitekey={ recaptcha.siteKey }></div> + <div if={ recaptcha } class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" data-sitekey={ recaptcha.site_key }></div> </label> <label class="agree-tou"> <input name="agree-tou" type="checkbox" autocomplete="off" required="required"/> @@ -193,20 +193,16 @@ }; this.on('mount', () => { - fetch('/config.json').then(res => { - res.json().then(conf => { - this.update({ - recaptcha: { - siteKey: conf.recaptcha.siteKey - } - }); - - const head = document.getElementsByTagName('head')[0]; - const script = document.createElement('script'); - script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); - head.appendChild(script); - }); + this.update({ + recaptcha: { + site_key: _RECAPTCHA_SITEKEY_ + } }); + + const head = document.getElementsByTagName('head')[0]; + const script = document.createElement('script'); + script.setAttribute('src', 'https://www.google.com/recaptcha/api.js'); + head.appendChild(script); }); this.onChangeUsername = () => { diff --git a/src/web/app/common/tags/stream-indicator.tag b/src/web/app/common/tags/stream-indicator.tag index ea1c437035..0d74985c88 100644 --- a/src/web/app/common/tags/stream-indicator.tag +++ b/src/web/app/common/tags/stream-indicator.tag @@ -1,13 +1,13 @@ <mk-stream-indicator> - <p if={ stream.state == 'initializing' }> + <p if={ connection.state == 'initializing' }> <i class="fa fa-spinner fa-spin"></i> <span>%i18n:common.tags.mk-stream-indicator.connecting%<mk-ellipsis/></span> </p> - <p if={ stream.state == 'reconnecting' }> + <p if={ connection.state == 'reconnecting' }> <i class="fa fa-spinner fa-spin"></i> <span>%i18n:common.tags.mk-stream-indicator.reconnecting%<mk-ellipsis/></span> </p> - <p if={ stream.state == 'connected' }> + <p if={ connection.state == 'connected' }> <i class="fa fa-check"></i> <span>%i18n:common.tags.mk-stream-indicator.connected%</span> </p> @@ -38,34 +38,41 @@ import anime from 'animejs'; this.mixin('i'); + this.mixin('stream'); + this.connection = this.stream.getConnection(); + this.connectionId = this.stream.use(); this.on('before-mount', () => { - if (this.stream.state == 'connected') { + if (this.connection.state == 'connected') { this.root.style.opacity = 0; } - }); - this.stream.on('_connected_', () => { - this.update(); - setTimeout(() => { + this.connection.on('_connected_', () => { + this.update(); + setTimeout(() => { + anime({ + targets: this.root, + opacity: 0, + easing: 'linear', + duration: 200 + }); + }, 1000); + }); + + this.connection.on('_closed_', () => { + this.update(); anime({ targets: this.root, - opacity: 0, + opacity: 1, easing: 'linear', - duration: 200 + duration: 100 }); - }, 1000); + }); }); - this.stream.on('_closed_', () => { - this.update(); - anime({ - targets: this.root, - opacity: 1, - easing: 'linear', - duration: 100 - }); + this.on('unmount', () => { + this.stream.dispose(this.connectionId); }); </script> </mk-stream-indicator> diff --git a/src/web/app/common/tags/twitter-setting.tag b/src/web/app/common/tags/twitter-setting.tag index 470426700c..3b70505ba2 100644 --- a/src/web/app/common/tags/twitter-setting.tag +++ b/src/web/app/common/tags/twitter-setting.tag @@ -1,10 +1,10 @@ <mk-twitter-setting> - <p>%i18n:common.tags.mk-twitter-setting.description%<a href={ CONFIG.aboutUrl + '/link-to-twitter' } target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p> + <p>%i18n:common.tags.mk-twitter-setting.description%<a href={ _ABOUT_URL_ + '/link-to-twitter' } target="_blank">%i18n:common.tags.mk-twitter-setting.detail%</a></p> <p class="account" if={ I.twitter } title={ 'Twitter ID: ' + I.twitter.user_id }>%i18n:common.tags.mk-twitter-setting.connected-to%: <a href={ 'https://twitter.com/' + I.twitter.screen_name } target="_blank">@{ I.twitter.screen_name }</a></p> <p> - <a href={ CONFIG.apiUrl + '/connect/twitter' } target="_blank" onclick={ connect }>{ I.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }</a> + <a href={ _API_URL_ + '/connect/twitter' } target="_blank" onclick={ connect }>{ I.twitter ? '%i18n:common.tags.mk-twitter-setting.reconnect%' : '%i18n:common.tags.mk-twitter-setting.connect%' }</a> <span if={ I.twitter }> or </span> - <a href={ CONFIG.apiUrl + '/disconnect/twitter' } target="_blank" if={ I.twitter } onclick={ disconnect }>%i18n:common.tags.mk-twitter-setting.disconnect%</a> + <a href={ _API_URL_ + '/disconnect/twitter' } target="_blank" if={ I.twitter } onclick={ disconnect }>%i18n:common.tags.mk-twitter-setting.disconnect%</a> </p> <p class="id" if={ I.twitter }>Twitter ID: { I.twitter.user_id }</p> <style> @@ -25,8 +25,6 @@ color #8899a6 </style> <script> - import CONFIG from '../scripts/config'; - this.mixin('i'); this.form = null; @@ -47,7 +45,7 @@ this.connect = e => { e.preventDefault(); - this.form = window.open(CONFIG.apiUrl + '/connect/twitter', + this.form = window.open(_API_URL_ + '/connect/twitter', 'twitter_connect_window', 'height=570,width=520'); return false; @@ -55,7 +53,7 @@ this.disconnect = e => { e.preventDefault(); - window.open(CONFIG.apiUrl + '/disconnect/twitter', + window.open(_API_URL_ + '/disconnect/twitter', 'twitter_disconnect_window', 'height=570,width=520'); return false; diff --git a/src/web/app/common/tags/uploader.tag b/src/web/app/common/tags/uploader.tag index da97957a2c..1453391696 100644 --- a/src/web/app/common/tags/uploader.tag +++ b/src/web/app/common/tags/uploader.tag @@ -172,7 +172,7 @@ if (folder) data.append('folder_id', folder); const xhr = new XMLHttpRequest(); - xhr.open('POST', this.CONFIG.apiUrl + '/drive/files/create', true); + xhr.open('POST', _API_URL_ + '/drive/files/create', true); xhr.onload = e => { const driveFile = JSON.parse(e.target.response); |