From a1e57841e71e442b1bf52e445e6c541f16ec066d Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 21 Feb 2018 02:53:34 +0900 Subject: [PATCH] wip --- .eslintrc | 3 +- src/web/app/common/mios.ts | 34 +++++++ src/web/app/common/scripts/fuck-ad-block.ts | 21 ++++ src/web/app/desktop/api/notify.ts | 10 ++ src/web/app/desktop/api/update-avatar.ts | 95 +++++++++++++++++++ src/web/app/desktop/api/update-banner.ts | 95 +++++++++++++++++++ src/web/app/desktop/script.ts | 24 +++-- src/web/app/desktop/scripts/dialog.ts | 16 ---- src/web/app/desktop/scripts/fuck-ad-block.ts | 20 ---- src/web/app/desktop/scripts/input-dialog.ts | 12 --- .../scripts/not-implemented-exception.ts | 8 -- src/web/app/desktop/scripts/notify.ts | 8 -- .../app/desktop/scripts/scroll-follower.ts | 61 ------------ src/web/app/desktop/scripts/update-avatar.ts | 88 ----------------- src/web/app/desktop/scripts/update-banner.ts | 88 ----------------- .../app/desktop/views/components/dialog.vue | 2 +- .../desktop/views/components/post-form.vue | 5 +- .../desktop/views/components/repost-form.vue | 5 +- .../views/components/settings.profile.vue | 3 +- .../views/components/ui-notification.vue | 32 ++++--- .../desktop/views/pages/user/user.header.vue | 3 +- src/web/app/init.ts | 43 ++------- 22 files changed, 304 insertions(+), 372 deletions(-) create mode 100644 src/web/app/common/scripts/fuck-ad-block.ts create mode 100644 src/web/app/desktop/api/notify.ts create mode 100644 src/web/app/desktop/api/update-avatar.ts create mode 100644 src/web/app/desktop/api/update-banner.ts delete mode 100644 src/web/app/desktop/scripts/dialog.ts delete mode 100644 src/web/app/desktop/scripts/fuck-ad-block.ts delete mode 100644 src/web/app/desktop/scripts/input-dialog.ts delete mode 100644 src/web/app/desktop/scripts/not-implemented-exception.ts delete mode 100644 src/web/app/desktop/scripts/notify.ts delete mode 100644 src/web/app/desktop/scripts/scroll-follower.ts delete mode 100644 src/web/app/desktop/scripts/update-avatar.ts delete mode 100644 src/web/app/desktop/scripts/update-banner.ts diff --git a/.eslintrc b/.eslintrc index 6caf8f532a..679d4f12db 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ "vue/html-self-closing": false, "vue/no-unused-vars": false, "no-console": 0, - "no-unused-vars": 0 + "no-unused-vars": 0, + "no-empty": 0 } } diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts index a98df1bc03..c4208aa913 100644 --- a/src/web/app/common/mios.ts +++ b/src/web/app/common/mios.ts @@ -16,6 +16,38 @@ declare const _API_URL_: string; declare const _SW_PUBLICKEY_: string; //#endregion +export type API = { + chooseDriveFile: (opts: { + title?: string; + currentFolder?: any; + multiple?: boolean; + }) => Promise; + + chooseDriveFolder: (opts: { + title?: string; + currentFolder?: any; + }) => Promise; + + dialog: (opts: { + title: string; + text: string; + actions: Array<{ + text: string; + id?: string; + }>; + }) => Promise; + + input: (opts: { + title: string; + placeholder?: string; + default?: string; + }) => Promise; + + post: () => void; + + notify: (message: string) => void; +}; + /** * Misskey Operating System */ @@ -49,6 +81,8 @@ export default class MiOS extends EventEmitter { return localStorage.getItem('debug') == 'true'; } + public apis: API; + /** * A connection manager of home stream */ diff --git a/src/web/app/common/scripts/fuck-ad-block.ts b/src/web/app/common/scripts/fuck-ad-block.ts new file mode 100644 index 0000000000..9bcf7deeff --- /dev/null +++ b/src/web/app/common/scripts/fuck-ad-block.ts @@ -0,0 +1,21 @@ +require('fuckadblock'); + +declare const fuckAdBlock: any; + +export default (os) => { + function adBlockDetected() { + os.apis.dialog({ + title: '%fa:exclamation-triangle%広告ブロッカーを無効にしてください', + text: 'Misskeyは広告を掲載していませんが、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。', + actins: [{ + text: 'OK' + }] + }); + } + + if (fuckAdBlock === undefined) { + adBlockDetected(); + } else { + fuckAdBlock.onDetected(adBlockDetected); + } +}; diff --git a/src/web/app/desktop/api/notify.ts b/src/web/app/desktop/api/notify.ts new file mode 100644 index 0000000000..1f89f40ce6 --- /dev/null +++ b/src/web/app/desktop/api/notify.ts @@ -0,0 +1,10 @@ +import Notification from '../views/components/ui-notification.vue'; + +export default function(message) { + const vm = new Notification({ + propsData: { + message + } + }).$mount(); + document.body.appendChild(vm.$el); +} diff --git a/src/web/app/desktop/api/update-avatar.ts b/src/web/app/desktop/api/update-avatar.ts new file mode 100644 index 0000000000..eff0728348 --- /dev/null +++ b/src/web/app/desktop/api/update-avatar.ts @@ -0,0 +1,95 @@ +import OS from '../../common/mios'; +import { apiUrl } from '../../config'; +import CropWindow from '../views/components/crop-window.vue'; +import ProgressDialog from '../views/components/progress-dialog.vue'; + +export default (os: OS) => (cb, file = null) => { + const fileSelected = file => { + + const w = new CropWindow({ + propsData: { + file: file, + title: 'アバターとして表示する部分を選択', + aspectRatio: 1 / 1 + } + }).$mount(); + + w.$once('cropped', blob => { + const data = new FormData(); + data.append('i', os.i.token); + data.append('file', blob, file.name + '.cropped.png'); + + os.api('drive/folders/find', { + name: 'アイコン' + }).then(iconFolder => { + if (iconFolder.length === 0) { + os.api('drive/folders/create', { + name: 'アイコン' + }).then(iconFolder => { + upload(data, iconFolder); + }); + } else { + upload(data, iconFolder[0]); + } + }); + }); + + w.$once('skipped', () => { + set(file); + }); + + document.body.appendChild(w.$el); + }; + + const upload = (data, folder) => { + const dialog = new ProgressDialog({ + propsData: { + title: '新しいアバターをアップロードしています' + } + }).$mount(); + document.body.appendChild(dialog.$el); + + if (folder) data.append('folder_id', folder.id); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', apiUrl + '/drive/files/create', true); + xhr.onload = e => { + const file = JSON.parse((e.target as any).response); + (dialog as any).close(); + set(file); + }; + + xhr.upload.onprogress = e => { + if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total); + }; + + xhr.send(data); + }; + + const set = file => { + os.api('i/update', { + avatar_id: file.id + }).then(i => { + os.apis.dialog({ + title: '%fa:info-circle%アバターを更新しました', + text: '新しいアバターが反映されるまで時間がかかる場合があります。', + actions: [{ + text: 'わかった' + }] + }); + + if (cb) cb(i); + }); + }; + + if (file) { + fileSelected(file); + } else { + os.apis.chooseDriveFile({ + multiple: false, + title: '%fa:image%アバターにする画像を選択' + }).then(file => { + fileSelected(file); + }); + } +}; diff --git a/src/web/app/desktop/api/update-banner.ts b/src/web/app/desktop/api/update-banner.ts new file mode 100644 index 0000000000..5751616581 --- /dev/null +++ b/src/web/app/desktop/api/update-banner.ts @@ -0,0 +1,95 @@ +import OS from '../../common/mios'; +import { apiUrl } from '../../config'; +import CropWindow from '../views/components/crop-window.vue'; +import ProgressDialog from '../views/components/progress-dialog.vue'; + +export default (os: OS) => (cb, file = null) => { + const fileSelected = file => { + + const w = new CropWindow({ + propsData: { + file: file, + title: 'バナーとして表示する部分を選択', + aspectRatio: 16 / 9 + } + }).$mount(); + + w.$once('cropped', blob => { + const data = new FormData(); + data.append('i', os.i.token); + data.append('file', blob, file.name + '.cropped.png'); + + os.api('drive/folders/find', { + name: 'バナー' + }).then(bannerFolder => { + if (bannerFolder.length === 0) { + os.api('drive/folders/create', { + name: 'バナー' + }).then(iconFolder => { + upload(data, iconFolder); + }); + } else { + upload(data, bannerFolder[0]); + } + }); + }); + + w.$once('skipped', () => { + set(file); + }); + + document.body.appendChild(w.$el); + }; + + const upload = (data, folder) => { + const dialog = new ProgressDialog({ + propsData: { + title: '新しいバナーをアップロードしています' + } + }).$mount(); + document.body.appendChild(dialog.$el); + + if (folder) data.append('folder_id', folder.id); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', apiUrl + '/drive/files/create', true); + xhr.onload = e => { + const file = JSON.parse((e.target as any).response); + (dialog as any).close(); + set(file); + }; + + xhr.upload.onprogress = e => { + if (e.lengthComputable) (dialog as any).updateProgress(e.loaded, e.total); + }; + + xhr.send(data); + }; + + const set = file => { + os.api('i/update', { + avatar_id: file.id + }).then(i => { + os.apis.dialog({ + title: '%fa:info-circle%バナーを更新しました', + text: '新しいバナーが反映されるまで時間がかかる場合があります。', + actions: [{ + text: 'わかった' + }] + }); + + if (cb) cb(i); + }); + }; + + if (file) { + fileSelected(file); + } else { + os.apis.chooseDriveFile({ + multiple: false, + title: '%fa:image%バナーにする画像を選択' + }).then(file => { + fileSelected(file); + }); + } +}; diff --git a/src/web/app/desktop/script.ts b/src/web/app/desktop/script.ts index 2477f62f49..b647f4031d 100644 --- a/src/web/app/desktop/script.ts +++ b/src/web/app/desktop/script.ts @@ -6,7 +6,7 @@ import './style.styl'; import init from '../init'; -import fuckAdBlock from './scripts/fuck-ad-block'; +import fuckAdBlock from '../common/scripts/fuck-ad-block'; import HomeStreamManager from '../common/scripts/streaming/home-stream-manager'; import composeNotification from '../common/scripts/compose-notification'; @@ -15,6 +15,9 @@ import chooseDriveFile from './api/choose-drive-file'; import dialog from './api/dialog'; import input from './api/input'; import post from './api/post'; +import notify from './api/notify'; +import updateAvatar from './api/update-avatar'; +import updateBanner from './api/update-banner'; import MkIndex from './views/pages/index.vue'; import MkUser from './views/pages/user/user.vue'; @@ -25,24 +28,27 @@ import MkDrive from './views/pages/drive.vue'; * init */ init(async (launch) => { - /** - * Fuck AD Block - */ - fuckAdBlock(); - // Register directives require('./views/directives'); // Register components require('./views/components'); - const app = launch({ + const [app, os] = launch(os => ({ chooseDriveFolder, chooseDriveFile, dialog, input, - post - }); + post, + notify, + updateAvatar: updateAvatar(os), + updateBanner: updateBanner(os) + })); + + /** + * Fuck AD Block + */ + fuckAdBlock(os); /** * Init Notification diff --git a/src/web/app/desktop/scripts/dialog.ts b/src/web/app/desktop/scripts/dialog.ts deleted file mode 100644 index 816ba4b5f5..0000000000 --- a/src/web/app/desktop/scripts/dialog.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as riot from 'riot'; - -export default (title, text, buttons, canThrough?, onThrough?) => { - const dialog = document.body.appendChild(document.createElement('mk-dialog')); - const controller = riot.observable(); - (riot as any).mount(dialog, { - controller: controller, - title: title, - text: text, - buttons: buttons, - canThrough: canThrough, - onThrough: onThrough - }); - controller.trigger('open'); - return controller; -}; diff --git a/src/web/app/desktop/scripts/fuck-ad-block.ts b/src/web/app/desktop/scripts/fuck-ad-block.ts deleted file mode 100644 index ddeb600b6e..0000000000 --- a/src/web/app/desktop/scripts/fuck-ad-block.ts +++ /dev/null @@ -1,20 +0,0 @@ -require('fuckadblock'); -import dialog from './dialog'; - -declare const fuckAdBlock: any; - -export default () => { - if (fuckAdBlock === undefined) { - adBlockDetected(); - } else { - fuckAdBlock.onDetected(adBlockDetected); - } -}; - -function adBlockDetected() { - dialog('%fa:exclamation-triangle%広告ブロッカーを無効にしてください', - 'Misskeyは広告を掲載していませんが、広告をブロックする機能が有効だと一部の機能が利用できなかったり、不具合が発生する場合があります。', - [{ - text: 'OK' - }]); -} diff --git a/src/web/app/desktop/scripts/input-dialog.ts b/src/web/app/desktop/scripts/input-dialog.ts deleted file mode 100644 index b06d011c6b..0000000000 --- a/src/web/app/desktop/scripts/input-dialog.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as riot from 'riot'; - -export default (title, placeholder, defaultValue, onOk, onCancel) => { - const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); - return (riot as any).mount(dialog, { - title: title, - placeholder: placeholder, - 'default': defaultValue, - onOk: onOk, - onCancel: onCancel - }); -}; diff --git a/src/web/app/desktop/scripts/not-implemented-exception.ts b/src/web/app/desktop/scripts/not-implemented-exception.ts deleted file mode 100644 index b4660fa62f..0000000000 --- a/src/web/app/desktop/scripts/not-implemented-exception.ts +++ /dev/null @@ -1,8 +0,0 @@ -import dialog from './dialog'; - -export default () => { - dialog('%fa:exclamation-triangle%Not implemented yet', - '要求された操作は実装されていません。
Misskeyの開発に参加する', [{ - text: 'OK' - }]); -}; diff --git a/src/web/app/desktop/scripts/notify.ts b/src/web/app/desktop/scripts/notify.ts deleted file mode 100644 index 2e6cbdeed8..0000000000 --- a/src/web/app/desktop/scripts/notify.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as riot from 'riot'; - -export default message => { - const notification = document.body.appendChild(document.createElement('mk-ui-notification')); - (riot as any).mount(notification, { - message: message - }); -}; diff --git a/src/web/app/desktop/scripts/scroll-follower.ts b/src/web/app/desktop/scripts/scroll-follower.ts deleted file mode 100644 index 05072958ce..0000000000 --- a/src/web/app/desktop/scripts/scroll-follower.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * 要素をスクロールに追従させる - */ -export default class ScrollFollower { - private follower: Element; - private containerTop: number; - private topPadding: number; - - constructor(follower: Element, topPadding: number) { - //#region - this.follow = this.follow.bind(this); - //#endregion - - this.follower = follower; - this.containerTop = follower.getBoundingClientRect().top; - this.topPadding = topPadding; - - window.addEventListener('scroll', this.follow); - window.addEventListener('resize', this.follow); - } - - /** - * 追従解除 - */ - public dispose() { - window.removeEventListener('scroll', this.follow); - window.removeEventListener('resize', this.follow); - } - - private follow() { - const windowBottom = window.scrollY + window.innerHeight; - const windowTop = window.scrollY + this.topPadding; - - const rect = this.follower.getBoundingClientRect(); - const followerBottom = (rect.top + window.scrollY) + rect.height; - const screenHeight = window.innerHeight - this.topPadding; - - // スクロールの上部(+余白)がフォロワーコンテナの上部よりも上方にある - if (window.scrollY + this.topPadding < this.containerTop) { - // フォロワーをコンテナの最上部に合わせる - (this.follower.parentNode as any).style.marginTop = '0px'; - return; - } - - // スクロールの下部がフォロワーの下部よりも下方にある かつ 表示領域の縦幅がフォロワーの縦幅よりも狭い - if (windowBottom > followerBottom && rect.height > screenHeight) { - // フォロワーの下部をスクロール下部に合わせる - const top = (windowBottom - rect.height) - this.containerTop; - (this.follower.parentNode as any).style.marginTop = `${top}px`; - return; - } - - // スクロールの上部(+余白)がフォロワーの上部よりも上方にある または 表示領域の縦幅がフォロワーの縦幅よりも広い - if (windowTop < rect.top + window.scrollY || rect.height < screenHeight) { - // フォロワーの上部をスクロール上部(+余白)に合わせる - const top = windowTop - this.containerTop; - (this.follower.parentNode as any).style.marginTop = `${top}px`; - return; - } - } -} diff --git a/src/web/app/desktop/scripts/update-avatar.ts b/src/web/app/desktop/scripts/update-avatar.ts deleted file mode 100644 index fea5db80bb..0000000000 --- a/src/web/app/desktop/scripts/update-avatar.ts +++ /dev/null @@ -1,88 +0,0 @@ -declare const _API_URL_: string; - -import * as riot from 'riot'; -import dialog from './dialog'; -import api from '../../common/scripts/api'; - -export default (I, cb, file = null) => { - const fileSelected = file => { - const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { - file: file, - title: 'アバターとして表示する部分を選択', - aspectRatio: 1 / 1 - })[0]; - - cropper.on('cropped', blob => { - const data = new FormData(); - data.append('i', I.token); - data.append('file', blob, file.name + '.cropped.png'); - - api(I, 'drive/folders/find', { - name: 'アイコン' - }).then(iconFolder => { - if (iconFolder.length === 0) { - api(I, 'drive/folders/create', { - name: 'アイコン' - }).then(iconFolder => { - upload(data, iconFolder); - }); - } else { - upload(data, iconFolder[0]); - } - }); - }); - - cropper.on('skipped', () => { - set(file); - }); - }; - - const upload = (data, folder) => { - const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { - title: '新しいアバターをアップロードしています' - })[0]; - - if (folder) data.append('folder_id', folder.id); - - const xhr = new XMLHttpRequest(); - xhr.open('POST', _API_URL_ + '/drive/files/create', true); - xhr.onload = e => { - const file = JSON.parse((e.target as any).response); - progress.close(); - set(file); - }; - - xhr.upload.onprogress = e => { - if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); - }; - - xhr.send(data); - }; - - const set = file => { - api(I, 'i/update', { - avatar_id: file.id - }).then(i => { - dialog('%fa:info-circle%アバターを更新しました', - '新しいアバターが反映されるまで時間がかかる場合があります。', - [{ - text: 'わかった' - }]); - - if (cb) cb(i); - }); - }; - - if (file) { - fileSelected(file); - } else { - const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { - multiple: false, - title: '%fa:image%アバターにする画像を選択' - })[0]; - - browser.one('selected', file => { - fileSelected(file); - }); - } -}; diff --git a/src/web/app/desktop/scripts/update-banner.ts b/src/web/app/desktop/scripts/update-banner.ts deleted file mode 100644 index 325775622d..0000000000 --- a/src/web/app/desktop/scripts/update-banner.ts +++ /dev/null @@ -1,88 +0,0 @@ -declare const _API_URL_: string; - -import * as riot from 'riot'; -import dialog from './dialog'; -import api from '../../common/scripts/api'; - -export default (I, cb, file = null) => { - const fileSelected = file => { - const cropper = (riot as any).mount(document.body.appendChild(document.createElement('mk-crop-window')), { - file: file, - title: 'バナーとして表示する部分を選択', - aspectRatio: 16 / 9 - })[0]; - - cropper.on('cropped', blob => { - const data = new FormData(); - data.append('i', I.token); - data.append('file', blob, file.name + '.cropped.png'); - - api(I, 'drive/folders/find', { - name: 'バナー' - }).then(iconFolder => { - if (iconFolder.length === 0) { - api(I, 'drive/folders/create', { - name: 'バナー' - }).then(iconFolder => { - upload(data, iconFolder); - }); - } else { - upload(data, iconFolder[0]); - } - }); - }); - - cropper.on('skipped', () => { - set(file); - }); - }; - - const upload = (data, folder) => { - const progress = (riot as any).mount(document.body.appendChild(document.createElement('mk-progress-dialog')), { - title: '新しいバナーをアップロードしています' - })[0]; - - if (folder) data.append('folder_id', folder.id); - - const xhr = new XMLHttpRequest(); - xhr.open('POST', _API_URL_ + '/drive/files/create', true); - xhr.onload = e => { - const file = JSON.parse((e.target as any).response); - progress.close(); - set(file); - }; - - xhr.upload.onprogress = e => { - if (e.lengthComputable) progress.updateProgress(e.loaded, e.total); - }; - - xhr.send(data); - }; - - const set = file => { - api(I, 'i/update', { - banner_id: file.id - }).then(i => { - dialog('%fa:info-circle%バナーを更新しました', - '新しいバナーが反映されるまで時間がかかる場合があります。', - [{ - text: 'わかりました。' - }]); - - if (cb) cb(i); - }); - }; - - if (file) { - fileSelected(file); - } else { - const browser = (riot as any).mount(document.body.appendChild(document.createElement('mk-select-file-from-drive-window')), { - multiple: false, - title: '%fa:image%バナーにする画像を選択' - })[0]; - - browser.one('selected', file => { - fileSelected(file); - }); - } -}; diff --git a/src/web/app/desktop/views/components/dialog.vue b/src/web/app/desktop/views/components/dialog.vue index e92050dbad..f089b19a4e 100644 --- a/src/web/app/desktop/views/components/dialog.vue +++ b/src/web/app/desktop/views/components/dialog.vue @@ -5,7 +5,7 @@
- +
diff --git a/src/web/app/desktop/views/components/post-form.vue b/src/web/app/desktop/views/components/post-form.vue index f117f8cc5e..c362d500ea 100644 --- a/src/web/app/desktop/views/components/post-form.vue +++ b/src/web/app/desktop/views/components/post-form.vue @@ -40,7 +40,6 @@ import Vue from 'vue'; import * as Sortable from 'sortablejs'; import Autocomplete from '../../scripts/autocomplete'; import getKao from '../../../common/scripts/get-kao'; -import notify from '../../scripts/notify'; export default Vue.extend({ props: ['reply', 'repost'], @@ -200,13 +199,13 @@ export default Vue.extend({ this.clear(); this.deleteDraft(); this.$emit('posted'); - notify(this.repost + (this as any).apis.notify(this.repost ? '%i18n:desktop.tags.mk-post-form.reposted%' : this.reply ? '%i18n:desktop.tags.mk-post-form.replied%' : '%i18n:desktop.tags.mk-post-form.posted%'); }).catch(err => { - notify(this.repost + (this as any).apis.notify(this.repost ? '%i18n:desktop.tags.mk-post-form.repost-failed%' : this.reply ? '%i18n:desktop.tags.mk-post-form.reply-failed%' diff --git a/src/web/app/desktop/views/components/repost-form.vue b/src/web/app/desktop/views/components/repost-form.vue index d4a6186c4d..5bf7eaaf03 100644 --- a/src/web/app/desktop/views/components/repost-form.vue +++ b/src/web/app/desktop/views/components/repost-form.vue @@ -16,7 +16,6 @@ diff --git a/src/web/app/desktop/views/pages/user/user.header.vue b/src/web/app/desktop/views/pages/user/user.header.vue index 81174f6570..67d110f2f3 100644 --- a/src/web/app/desktop/views/pages/user/user.header.vue +++ b/src/web/app/desktop/views/pages/user/user.header.vue @@ -22,7 +22,6 @@