diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/api/endpoints/i/update_home.ts | 9 | ||||
| -rw-r--r-- | src/api/endpoints/i/update_mobile_home.ts | 9 | ||||
| -rw-r--r-- | src/web/app/app.vue | 2 | ||||
| -rw-r--r-- | src/web/app/auth/script.ts | 23 | ||||
| -rw-r--r-- | src/web/app/common/define-widget.ts | 20 | ||||
| -rw-r--r-- | src/web/app/common/mios.ts | 15 | ||||
| -rw-r--r-- | src/web/app/common/views/components/autocomplete.vue | 234 | ||||
| -rw-r--r-- | src/web/app/common/views/components/index.ts | 30 | ||||
| -rw-r--r-- | src/web/app/common/views/components/messaging-room.form.vue | 8 | ||||
| -rw-r--r-- | src/web/app/common/views/components/messaging-room.vue | 13 | ||||
| -rw-r--r-- | src/web/app/common/views/components/post-html.ts | 6 | ||||
| -rw-r--r-- | src/web/app/common/views/directives/autocomplete.ts (renamed from src/web/app/desktop/views/directives/autocomplete.ts) | 91 | ||||
| -rw-r--r-- | src/web/app/common/views/directives/focus.ts | 5 | ||||
| -rw-r--r-- | src/web/app/common/views/directives/index.ts | 4 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/access-log.vue (renamed from src/web/app/common/views/components/widgets/access-log.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/broadcast.vue (renamed from src/web/app/common/views/components/widgets/broadcast.vue) | 4 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/calendar.vue (renamed from src/web/app/common/views/components/widgets/calendar.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/donation.vue (renamed from src/web/app/common/views/components/widgets/donation.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/index.ts | 25 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/nav.vue (renamed from src/web/app/common/views/components/widgets/nav.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/photo-stream.vue (renamed from src/web/app/common/views/components/widgets/photo-stream.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/rss.vue (renamed from src/web/app/common/views/components/widgets/rss.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.cpu-memory.vue (renamed from src/web/app/common/views/components/widgets/server.cpu-memory.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.cpu.vue (renamed from src/web/app/common/views/components/widgets/server.cpu.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.disk.vue (renamed from src/web/app/common/views/components/widgets/server.disk.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.info.vue (renamed from src/web/app/common/views/components/widgets/server.info.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.memory.vue (renamed from src/web/app/common/views/components/widgets/server.memory.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.pie.vue (renamed from src/web/app/common/views/components/widgets/server.pie.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.uptimes.vue (renamed from src/web/app/common/views/components/widgets/server.uptimes.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/server.vue (renamed from src/web/app/common/views/components/widgets/server.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/slideshow.vue (renamed from src/web/app/common/views/components/widgets/slideshow.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/tips.vue (renamed from src/web/app/common/views/components/widgets/tips.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/common/views/widgets/version.vue (renamed from src/web/app/common/views/components/widgets/version.vue) | 4 | ||||
| -rw-r--r-- | src/web/app/desktop/script.ts | 1 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/autocomplete.vue | 190 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/home.vue | 32 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/index.ts | 24 | ||||
| -rw-r--r-- | src/web/app/desktop/views/components/posts.post.vue | 49 | ||||
| -rw-r--r-- | src/web/app/desktop/views/directives/index.ts | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/activity.vue (renamed from src/web/app/desktop/views/components/widgets/activity.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/channel.channel.form.vue (renamed from src/web/app/desktop/views/components/widgets/channel.channel.form.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/channel.channel.post.vue (renamed from src/web/app/desktop/views/components/widgets/channel.channel.post.vue) | 0 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/channel.channel.vue (renamed from src/web/app/desktop/views/components/widgets/channel.channel.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/channel.vue (renamed from src/web/app/desktop/views/components/widgets/channel.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/index.ts | 23 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/messaging.vue (renamed from src/web/app/desktop/views/components/widgets/messaging.vue) | 4 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/notifications.vue (renamed from src/web/app/desktop/views/components/widgets/notifications.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/polls.vue (renamed from src/web/app/desktop/views/components/widgets/polls.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/post-form.vue (renamed from src/web/app/desktop/views/components/widgets/post-form.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/profile.vue (renamed from src/web/app/common/views/components/widgets/profile.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/timemachine.vue (renamed from src/web/app/desktop/views/components/widgets/timemachine.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/trends.vue (renamed from src/web/app/desktop/views/components/widgets/trends.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/desktop/views/widgets/users.vue (renamed from src/web/app/desktop/views/components/widgets/users.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/init.ts | 35 | ||||
| -rw-r--r-- | src/web/app/mobile/script.ts | 1 | ||||
| -rw-r--r-- | src/web/app/mobile/views/components/index.ts | 8 | ||||
| -rw-r--r-- | src/web/app/mobile/views/components/posts.post.vue | 28 | ||||
| -rw-r--r-- | src/web/app/mobile/views/pages/home.vue | 19 | ||||
| -rw-r--r-- | src/web/app/mobile/views/widgets/activity.vue (renamed from src/web/app/mobile/views/components/widgets/activity.vue) | 2 | ||||
| -rw-r--r-- | src/web/app/mobile/views/widgets/index.ts | 7 | ||||
| -rw-r--r-- | src/web/app/mobile/views/widgets/profile.vue | 62 | ||||
| -rw-r--r-- | src/web/app/stats/script.ts | 23 | ||||
| -rw-r--r-- | src/web/app/status/script.ts | 23 |
63 files changed, 596 insertions, 477 deletions
diff --git a/src/api/endpoints/i/update_home.ts b/src/api/endpoints/i/update_home.ts index 5dfb7d7915..394686cbd1 100644 --- a/src/api/endpoints/i/update_home.ts +++ b/src/api/endpoints/i/update_home.ts @@ -3,6 +3,7 @@ */ import $ from 'cafy'; import User from '../../models/user'; +import event from '../../event'; module.exports = async (params, user) => new Promise(async (res, rej) => { // Get 'home' parameter @@ -30,6 +31,10 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { }); res(); + + event(user._id, 'home_updated', { + home + }); } else { if (id == null && data == null) return rej('you need to set id and data params if home param unset'); @@ -47,5 +52,9 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { }); res(); + + event(user._id, 'home_updated', { + id, data + }); } }); diff --git a/src/api/endpoints/i/update_mobile_home.ts b/src/api/endpoints/i/update_mobile_home.ts index a87d89cad7..70181431a2 100644 --- a/src/api/endpoints/i/update_mobile_home.ts +++ b/src/api/endpoints/i/update_mobile_home.ts @@ -3,6 +3,7 @@ */ import $ from 'cafy'; import User from '../../models/user'; +import event from '../../event'; module.exports = async (params, user) => new Promise(async (res, rej) => { // Get 'home' parameter @@ -29,6 +30,10 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { }); res(); + + event(user._id, 'mobile_home_updated', { + home + }); } else { if (id == null && data == null) return rej('you need to set id and data params if home param unset'); @@ -46,5 +51,9 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { }); res(); + + event(user._id, 'mobile_home_updated', { + id, data + }); } }); diff --git a/src/web/app/app.vue b/src/web/app/app.vue index 321e003930..7a46e7dea0 100644 --- a/src/web/app/app.vue +++ b/src/web/app/app.vue @@ -1,3 +1,3 @@ <template> - <router-view id="app"></router-view> +<router-view id="app"></router-view> </template> diff --git a/src/web/app/auth/script.ts b/src/web/app/auth/script.ts deleted file mode 100644 index dd598d1ed6..0000000000 --- a/src/web/app/auth/script.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Authorize Form - */ - -// Style -import './style.styl'; - -import * as riot from 'riot'; -require('./tags'); -import init from '../init'; - -document.title = 'Misskey | アプリの連携'; - -/** - * init - */ -init(() => { - mount(document.createElement('mk-index')); -}); - -function mount(content) { - riot.mount(document.getElementById('app').appendChild(content)); -} diff --git a/src/web/app/common/define-widget.ts b/src/web/app/common/define-widget.ts index 826f9cc636..97925cf443 100644 --- a/src/web/app/common/define-widget.ts +++ b/src/web/app/common/define-widget.ts @@ -21,7 +21,9 @@ export default function<T extends object>(data: { }, data() { return { - props: data.props ? data.props() : {} as T + props: data.props ? data.props() : {} as T, + bakedOldProps: null, + preventSave: false }; }, created() { @@ -33,7 +35,18 @@ export default function<T extends object>(data: { }); } + this.bakeProps(); + this.$watch('props', newProps => { + if (this.preventSave) { + this.preventSave = false; + this.bakeProps(); + return; + } + if (this.bakedOldProps == JSON.stringify(newProps)) return; + + this.bakeProps(); + if (this.isMobile) { (this as any).api('i/update_mobile_home', { id: this.id, @@ -52,6 +65,11 @@ export default function<T extends object>(data: { }, { deep: true }); + }, + methods: { + bakeProps() { + this.bakedOldProps = JSON.stringify(this.props); + } } }); } diff --git a/src/web/app/common/mios.ts b/src/web/app/common/mios.ts index e20f4bfe4e..6c95e5b9b5 100644 --- a/src/web/app/common/mios.ts +++ b/src/web/app/common/mios.ts @@ -1,5 +1,7 @@ import Vue from 'vue'; import { EventEmitter } from 'eventemitter3'; + +import { apiUrl, swPublickey, version, lang } from '../config'; import api from './scripts/api'; import signout from './scripts/signout'; import Progress from './scripts/loading'; @@ -11,13 +13,6 @@ import MessagingIndexStreamManager from './scripts/streaming/messaging-index-str import Err from '../common/views/components/connect-failed.vue'; -//#region environment variables -declare const _VERSION_: string; -declare const _LANG_: string; -declare const _API_URL_: string; -declare const _SW_PUBLICKEY_: string; -//#endregion - export type API = { chooseDriveFile: (opts: { title?: string; @@ -204,7 +199,7 @@ export default class MiOS extends EventEmitter { } // Fetch user - fetch(`${_API_URL_}/i`, { + fetch(`${apiUrl}/i`, { method: 'POST', body: JSON.stringify({ i: token @@ -311,7 +306,7 @@ export default class MiOS extends EventEmitter { // A public key your push server will use to send // messages to client apps via a push server. - applicationServerKey: urlBase64ToUint8Array(_SW_PUBLICKEY_) + applicationServerKey: urlBase64ToUint8Array(swPublickey) }; // Subscribe push notification @@ -348,7 +343,7 @@ export default class MiOS extends EventEmitter { }); // The path of service worker script - const sw = `/sw.${_VERSION_}.${_LANG_}.js`; + const sw = `/sw.${version}.${lang}.js`; // Register service worker navigator.serviceWorker.register(sw).then(registration => { diff --git a/src/web/app/common/views/components/autocomplete.vue b/src/web/app/common/views/components/autocomplete.vue new file mode 100644 index 0000000000..b068ecc4fa --- /dev/null +++ b/src/web/app/common/views/components/autocomplete.vue @@ -0,0 +1,234 @@ +<template> +<div class="mk-autocomplete" @contextmenu.prevent="() => {}"> + <ol class="users" ref="suggests" v-if="users.length > 0"> + <li v-for="user in users" @click="complete(type, user)" @keydown="onKeydown" tabindex="-1"> + <img class="avatar" :src="`${user.avatar_url}?thumbnail&size=32`" alt=""/> + <span class="name">{{ user.name }}</span> + <span class="username">@{{ user.username }}</span> + </li> + </ol> + <ol class="emojis" ref="suggests" v-if="emojis.length > 0"> + <li v-for="emoji in emojis" @click="complete(type, pictograph.dic[emoji])" @keydown="onKeydown" tabindex="-1"> + <span class="emoji">{{ pictograph.dic[emoji] }}</span> + <span class="name" v-html="emoji.replace(q, `<b>${q}</b>`)"></span> + </li> + </ol> +</div> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import * as pictograph from 'pictograph'; +import contains from '../../../common/scripts/contains'; + +export default Vue.extend({ + props: ['type', 'q', 'textarea', 'complete', 'close', 'x', 'y'], + data() { + return { + fetching: true, + users: [], + emojis: [], + select: -1, + pictograph + } + }, + computed: { + items(): HTMLCollection { + return (this.$refs.suggests as Element).children; + } + }, + updated() { + //#region 位置調整 + const margin = 32; + + if (this.x + this.$el.offsetWidth > window.innerWidth - margin) { + this.$el.style.left = (this.x - this.$el.offsetWidth) + 'px'; + this.$el.style.marginLeft = '-16px'; + } else { + this.$el.style.left = this.x + 'px'; + this.$el.style.marginLeft = '0'; + } + + if (this.y + this.$el.offsetHeight > window.innerHeight - margin) { + this.$el.style.top = (this.y - this.$el.offsetHeight) + 'px'; + this.$el.style.marginTop = '0'; + } else { + this.$el.style.top = this.y + 'px'; + this.$el.style.marginTop = 'calc(1em + 8px)'; + } + //#endregion + }, + mounted() { + this.textarea.addEventListener('keydown', this.onKeydown); + + Array.from(document.querySelectorAll('body *')).forEach(el => { + el.addEventListener('mousedown', this.onMousedown); + }); + + if (this.type == 'user') { + (this as any).api('users/search_by_username', { + query: this.q, + limit: 30 + }).then(users => { + this.users = users; + this.fetching = false; + }); + } else if (this.type == 'emoji') { + const emojis = Object.keys(pictograph.dic).sort((a, b) => a.length - b.length); + const matched = emojis.filter(e => e.indexOf(this.q) > -1); + this.emojis = matched.filter((x, i) => i <= 30); + } + }, + beforeDestroy() { + this.textarea.removeEventListener('keydown', this.onKeydown); + + Array.from(document.querySelectorAll('body *')).forEach(el => { + el.removeEventListener('mousedown', this.onMousedown); + }); + }, + methods: { + onMousedown(e) { + if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); + }, + + onKeydown(e) { + const cancel = () => { + e.preventDefault(); + e.stopPropagation(); + }; + + switch (e.which) { + case 10: // [ENTER] + case 13: // [ENTER] + if (this.select !== -1) { + cancel(); + (this.items[this.select] as any).click(); + } else { + this.close(); + } + break; + + case 27: // [ESC] + cancel(); + this.close(); + break; + + case 38: // [↑] + if (this.select !== -1) { + cancel(); + this.selectPrev(); + } else { + this.close(); + } + break; + + case 9: // [TAB] + case 40: // [↓] + cancel(); + this.selectNext(); + break; + + default: + this.close(); + } + }, + + selectNext() { + if (++this.select >= this.items.length) this.select = 0; + this.applySelect(); + }, + + selectPrev() { + if (--this.select < 0) this.select = this.items.length - 1; + this.applySelect(); + }, + + applySelect() { + Array.from(this.items).forEach(el => { + el.removeAttribute('data-selected'); + }); + + this.items[this.select].setAttribute('data-selected', 'true'); + (this.items[this.select] as any).focus(); + } + } +}); +</script> + +<style lang="stylus" scoped> +.mk-autocomplete + position fixed + z-index 65535 + margin-top calc(1em + 8px) + overflow hidden + background #fff + border solid 1px rgba(0, 0, 0, 0.1) + border-radius 4px + + > ol + display block + margin 0 + padding 4px 0 + max-height 190px + max-width 500px + overflow auto + list-style none + + > li + display block + padding 4px 12px + white-space nowrap + overflow hidden + font-size 0.9em + color rgba(0, 0, 0, 0.8) + cursor default + + &, * + user-select none + + &:hover + &[data-selected='true'] + background $theme-color + + &, * + color #fff !important + + &:active + background darken($theme-color, 10%) + + &, * + color #fff !important + + > .users > li + + .avatar + vertical-align middle + min-width 28px + min-height 28px + max-width 28px + max-height 28px + margin 0 8px 0 0 + border-radius 100% + + .name + margin 0 8px 0 0 + /*font-weight bold*/ + font-weight normal + color rgba(0, 0, 0, 0.8) + + .username + font-weight normal + color rgba(0, 0, 0, 0.3) + + > .emojis > li + + .emoji + display inline-block + margin 0 4px 0 0 + width 24px + + .name + font-weight normal + color rgba(0, 0, 0, 0.8) + +</style> diff --git a/src/web/app/common/views/components/index.ts b/src/web/app/common/views/components/index.ts index e66a323266..ab0f1767d4 100644 --- a/src/web/app/common/views/components/index.ts +++ b/src/web/app/common/views/components/index.ts @@ -21,21 +21,6 @@ import urlPreview from './url-preview.vue'; import twitterSetting from './twitter-setting.vue'; import fileTypeIcon from './file-type-icon.vue'; -//#region widgets -import wAccessLog from './widgets/access-log.vue'; -import wVersion from './widgets/version.vue'; -import wRss from './widgets/rss.vue'; -import wProfile from './widgets/profile.vue'; -import wServer from './widgets/server.vue'; -import wBroadcast from './widgets/broadcast.vue'; -import wCalendar from './widgets/calendar.vue'; -import wPhotoStream from './widgets/photo-stream.vue'; -import wSlideshow from './widgets/slideshow.vue'; -import wTips from './widgets/tips.vue'; -import wDonation from './widgets/donation.vue'; -import wNav from './widgets/nav.vue'; -//#endregion - Vue.component('mk-signin', signin); Vue.component('mk-signup', signup); Vue.component('mk-forkit', forkit); @@ -56,18 +41,3 @@ Vue.component('mk-messaging-room', messagingRoom); Vue.component('mk-url-preview', urlPreview); Vue.component('mk-twitter-setting', twitterSetting); Vue.component('mk-file-type-icon', fileTypeIcon); - -//#region widgets -Vue.component('mkw-nav', wNav); -Vue.component('mkw-calendar', wCalendar); -Vue.component('mkw-photo-stream', wPhotoStream); -Vue.component('mkw-slideshow', wSlideshow); -Vue.component('mkw-tips', wTips); -Vue.component('mkw-donation', wDonation); -Vue.component('mkw-broadcast', wBroadcast); -Vue.component('mkw-profile', wProfile); -Vue.component('mkw-server', wServer); -Vue.component('mkw-rss', wRss); -Vue.component('mkw-version', wVersion); -Vue.component('mkw-access-log', wAccessLog); -//#endregion diff --git a/src/web/app/common/views/components/messaging-room.form.vue b/src/web/app/common/views/components/messaging-room.form.vue index b89365a5d8..adc3372bed 100644 --- a/src/web/app/common/views/components/messaging-room.form.vue +++ b/src/web/app/common/views/components/messaging-room.form.vue @@ -1,6 +1,6 @@ <template> <div class="mk-messaging-form"> - <textarea v-model="text" @keypress="onKeypress" @paste="onPaste" placeholder="%i18n:common.input-message-here%"></textarea> + <textarea v-model="text" ref="textarea" @keypress="onKeypress" @paste="onPaste" placeholder="%i18n:common.input-message-here%" v-autocomplete></textarea> <div class="file" v-if="file">{{ file.name }}</div> <mk-uploader ref="uploader"/> <button class="send" @click="send" :disabled="sending" title="%i18n:common.send%"> @@ -18,6 +18,8 @@ <script lang="ts"> import Vue from 'vue'; +import * as autosize from 'autosize'; + export default Vue.extend({ props: ['user'], data() { @@ -27,6 +29,9 @@ export default Vue.extend({ sending: false }; }, + mounted() { + autosize(this.$refs.textarea); + }, methods: { onPaste(e) { const data = e.clipboardData; @@ -93,6 +98,7 @@ export default Vue.extend({ height 64px margin 0 padding 8px + resize none font-size 1em color #000 outline none diff --git a/src/web/app/common/views/components/messaging-room.vue b/src/web/app/common/views/components/messaging-room.vue index 7af6b3fae3..310b56f6fc 100644 --- a/src/web/app/common/views/components/messaging-room.vue +++ b/src/web/app/common/views/components/messaging-room.vue @@ -16,7 +16,6 @@ </div> <footer> <div ref="notifications" class="notifications"></div> - <div class="grippie" title="%i18n:common.tags.mk-messaging-room.resize-form%"></div> <x-form :user="user"/> </footer> </div> @@ -316,16 +315,4 @@ export default Vue.extend({ line-height 32px font-size 16px - > .grippie - height 10px - margin-top -10px - background transparent - cursor ns-resize - - &:hover - //background rgba(0, 0, 0, 0.1) - - &:active - //background rgba(0, 0, 0, 0.2) - </style> diff --git a/src/web/app/common/views/components/post-html.ts b/src/web/app/common/views/components/post-html.ts index 37954cd7e7..adb025589d 100644 --- a/src/web/app/common/views/components/post-html.ts +++ b/src/web/app/common/views/components/post-html.ts @@ -81,7 +81,11 @@ export default Vue.component('mk-post-html', { case 'code': return createElement('pre', [ - createElement('code', token.html) + createElement('code', { + domProps: { + innerHTML: token.html + } + }) ]); case 'inline-code': diff --git a/src/web/app/desktop/views/directives/autocomplete.ts b/src/web/app/common/views/directives/autocomplete.ts index 53fa5a4df2..4d6ba41df1 100644 --- a/src/web/app/desktop/views/directives/autocomplete.ts +++ b/src/web/app/common/views/directives/autocomplete.ts @@ -25,11 +25,11 @@ class Autocomplete { * 対象のテキストエリアを与えてインスタンスを初期化します。 */ constructor(textarea) { - // BIND --------------------------------- - this.onInput = this.onInput.bind(this); + //#region BIND + this.onInput = this.onInput.bind(this); this.complete = this.complete.bind(this); - this.close = this.close.bind(this); - // -------------------------------------- + this.close = this.close.bind(this); + //#endregion this.suggestion = null; this.textarea = textarea; @@ -60,14 +60,19 @@ class Autocomplete { const text = this.textarea.value.substr(0, caret); const mentionIndex = text.lastIndexOf('@'); + const emojiIndex = text.lastIndexOf(':'); - if (mentionIndex == -1) return; + if (mentionIndex != -1 && mentionIndex > emojiIndex) { + const username = text.substr(mentionIndex + 1); + if (!username.match(/^[a-zA-Z0-9-]+$/)) return; + this.open('user', username); + } - const username = text.substr(mentionIndex + 1); - - if (!username.match(/^[a-zA-Z0-9-]+$/)) return; - - this.open('user', username); + if (emojiIndex != -1 && emojiIndex > mentionIndex) { + const emoji = text.substr(emojiIndex + 1); + if (!emoji.match(/^[\+\-a-z_]+$/)) return; + this.open('emoji', emoji); + } } /** @@ -77,6 +82,15 @@ class Autocomplete { // 既に開いているサジェストは閉じる this.close(); + //#region サジェストを表示すべき位置を計算 + const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); + + const rect = this.textarea.getBoundingClientRect(); + + const x = rect.left + caretPosition.left - this.textarea.scrollLeft; + const y = rect.top + caretPosition.top - this.textarea.scrollTop; + //#endregion + // サジェスト要素作成 this.suggestion = new MkAutocomplete({ propsData: { @@ -84,22 +98,12 @@ class Autocomplete { complete: this.complete, close: this.close, type: type, - q: q + q: q, + x, + y } }).$mount(); - // ~ サジェストを表示すべき位置を計算 ~ - - const caretPosition = getCaretCoordinates(this.textarea, this.textarea.selectionStart); - - const rect = this.textarea.getBoundingClientRect(); - - const x = rect.left + window.pageXOffset + caretPosition.left; - const y = rect.top + window.pageYOffset + caretPosition.top; - - this.suggestion.$el.style.left = x + 'px'; - this.suggestion.$el.style.top = y + 'px'; - // 要素追加 document.body.appendChild(this.suggestion.$el); } @@ -119,24 +123,39 @@ class Autocomplete { /** * オートコンプリートする */ - private complete(user) { + private complete(type, value) { this.close(); - const value = user.username; - const caret = this.textarea.selectionStart; - const source = this.textarea.value; - const before = source.substr(0, caret); - const trimmedBefore = before.substring(0, before.lastIndexOf('@')); - const after = source.substr(caret); + if (type == 'user') { + const source = this.textarea.value; - // 結果を挿入する - this.textarea.value = trimmedBefore + '@' + value + ' ' + after; + const before = source.substr(0, caret); + const trimmedBefore = before.substring(0, before.lastIndexOf('@')); + const after = source.substr(caret); - // キャレットを戻す - this.textarea.focus(); - const pos = caret + value.length; - this.textarea.setSelectionRange(pos, pos); + // 挿入 + this.textarea.value = trimmedBefore + '@' + value.username + ' ' + after; + + // キャレットを戻す + this.textarea.focus(); + const pos = caret + value.username.length; + this.textarea.setSelectionRange(pos, pos); + } else if (type == 'emoji') { + const source = this.textarea.value; + + const before = source.substr(0, caret); + const trimmedBefore = before.substring(0, before.lastIndexOf(':')); + const after = source.substr(caret); + + // 挿入 + this.textarea.value = trimmedBefore + value + after; + + // キャレットを戻す + this.textarea.focus(); + const pos = caret + value.length; + this.textarea.setSelectionRange(pos, pos); + } } } diff --git a/src/web/app/common/views/directives/focus.ts b/src/web/app/common/views/directives/focus.ts deleted file mode 100644 index b4fbcb6a87..0000000000 --- a/src/web/app/common/views/directives/focus.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - inserted(el) { - el.focus(); - } -}; diff --git a/src/web/app/common/views/directives/index.ts b/src/web/app/common/views/directives/index.ts index 358866f500..268f07a950 100644 --- a/src/web/app/common/views/directives/index.ts +++ b/src/web/app/common/views/directives/index.ts @@ -1,5 +1,5 @@ import Vue from 'vue'; -import focus from './focus'; +import autocomplete from './autocomplete'; -Vue.directive('focus', focus); +Vue.directive('autocomplete', autocomplete); diff --git a/src/web/app/common/views/components/widgets/access-log.vue b/src/web/app/common/views/widgets/access-log.vue index c810c2d157..f7bb17d833 100644 --- a/src/web/app/common/views/components/widgets/access-log.vue +++ b/src/web/app/common/views/widgets/access-log.vue @@ -15,7 +15,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; import * as seedrandom from 'seedrandom'; export default define({ diff --git a/src/web/app/common/views/components/widgets/broadcast.vue b/src/web/app/common/views/widgets/broadcast.vue index 0bb59caf43..bf41a5fc67 100644 --- a/src/web/app/common/views/components/widgets/broadcast.vue +++ b/src/web/app/common/views/widgets/broadcast.vue @@ -24,8 +24,8 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; -import { lang } from '../../../../config'; +import define from '../../../common/define-widget'; +import { lang } from '../../../config'; export default define({ name: 'broadcast', diff --git a/src/web/app/common/views/components/widgets/calendar.vue b/src/web/app/common/views/widgets/calendar.vue index bfcbd7f68d..2bcdb07f9e 100644 --- a/src/web/app/common/views/components/widgets/calendar.vue +++ b/src/web/app/common/views/widgets/calendar.vue @@ -36,7 +36,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'calendar', props: () => ({ diff --git a/src/web/app/common/views/components/widgets/donation.vue b/src/web/app/common/views/widgets/donation.vue index 08aab8ecd1..e218df06e1 100644 --- a/src/web/app/common/views/components/widgets/donation.vue +++ b/src/web/app/common/views/widgets/donation.vue @@ -12,7 +12,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'donation' }); diff --git a/src/web/app/common/views/widgets/index.ts b/src/web/app/common/views/widgets/index.ts new file mode 100644 index 0000000000..e41030e85a --- /dev/null +++ b/src/web/app/common/views/widgets/index.ts @@ -0,0 +1,25 @@ +import Vue from 'vue'; + +import wAccessLog from './access-log.vue'; +import wVersion from './version.vue'; +import wRss from './rss.vue'; +import wServer from './server.vue'; +import wBroadcast from './broadcast.vue'; +import wCalendar from './calendar.vue'; +import wPhotoStream from './photo-stream.vue'; +import wSlideshow from './slideshow.vue'; +import wTips from './tips.vue'; +import wDonation from './donation.vue'; +import wNav from './nav.vue'; + +Vue.component('mkw-nav', wNav); +Vue.component('mkw-calendar', wCalendar); +Vue.component('mkw-photo-stream', wPhotoStream); +Vue.component('mkw-slideshow', wSlideshow); +Vue.component('mkw-tips', wTips); +Vue.component('mkw-donation', wDonation); +Vue.component('mkw-broadcast', wBroadcast); +Vue.component('mkw-server', wServer); +Vue.component('mkw-rss', wRss); +Vue.component('mkw-version', wVersion); +Vue.component('mkw-access-log', wAccessLog); diff --git a/src/web/app/common/views/components/widgets/nav.vue b/src/web/app/common/views/widgets/nav.vue index ce88e587a8..7bd5a7832f 100644 --- a/src/web/app/common/views/components/widgets/nav.vue +++ b/src/web/app/common/views/widgets/nav.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'nav' }); diff --git a/src/web/app/common/views/components/widgets/photo-stream.vue b/src/web/app/common/views/widgets/photo-stream.vue index dcaa6624dd..78864cc8bf 100644 --- a/src/web/app/common/views/components/widgets/photo-stream.vue +++ b/src/web/app/common/views/widgets/photo-stream.vue @@ -13,7 +13,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'photo-stream', props: () => ({ diff --git a/src/web/app/common/views/components/widgets/rss.vue b/src/web/app/common/views/widgets/rss.vue index 186d495d00..4d74b2f7a4 100644 --- a/src/web/app/common/views/components/widgets/rss.vue +++ b/src/web/app/common/views/widgets/rss.vue @@ -13,7 +13,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'rss', props: () => ({ diff --git a/src/web/app/common/views/components/widgets/server.cpu-memory.vue b/src/web/app/common/views/widgets/server.cpu-memory.vue index d75a142568..d75a142568 100644 --- a/src/web/app/common/views/components/widgets/server.cpu-memory.vue +++ b/src/web/app/common/views/widgets/server.cpu-memory.vue diff --git a/src/web/app/common/views/components/widgets/server.cpu.vue b/src/web/app/common/views/widgets/server.cpu.vue index 596c856da8..596c856da8 100644 --- a/src/web/app/common/views/components/widgets/server.cpu.vue +++ b/src/web/app/common/views/widgets/server.cpu.vue diff --git a/src/web/app/common/views/components/widgets/server.disk.vue b/src/web/app/common/views/widgets/server.disk.vue index 2af1982a96..2af1982a96 100644 --- a/src/web/app/common/views/components/widgets/server.disk.vue +++ b/src/web/app/common/views/widgets/server.disk.vue diff --git a/src/web/app/common/views/components/widgets/server.info.vue b/src/web/app/common/views/widgets/server.info.vue index bed6a1b743..bed6a1b743 100644 --- a/src/web/app/common/views/components/widgets/server.info.vue +++ b/src/web/app/common/views/widgets/server.info.vue diff --git a/src/web/app/common/views/components/widgets/server.memory.vue b/src/web/app/common/views/widgets/server.memory.vue index 834a62671d..834a62671d 100644 --- a/src/web/app/common/views/components/widgets/server.memory.vue +++ b/src/web/app/common/views/widgets/server.memory.vue diff --git a/src/web/app/common/views/components/widgets/server.pie.vue b/src/web/app/common/views/widgets/server.pie.vue index ce2cff1d00..ce2cff1d00 100644 --- a/src/web/app/common/views/components/widgets/server.pie.vue +++ b/src/web/app/common/views/widgets/server.pie.vue diff --git a/src/web/app/common/views/components/widgets/server.uptimes.vue b/src/web/app/common/views/widgets/server.uptimes.vue index 06713d83ce..06713d83ce 100644 --- a/src/web/app/common/views/components/widgets/server.uptimes.vue +++ b/src/web/app/common/views/widgets/server.uptimes.vue diff --git a/src/web/app/common/views/components/widgets/server.vue b/src/web/app/common/views/widgets/server.vue index 4ebc5767d6..3d5248998f 100644 --- a/src/web/app/common/views/components/widgets/server.vue +++ b/src/web/app/common/views/widgets/server.vue @@ -18,7 +18,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; import XCpuMemory from './server.cpu-memory.vue'; import XCpu from './server.cpu.vue'; import XMemory from './server.memory.vue'; diff --git a/src/web/app/common/views/components/widgets/slideshow.vue b/src/web/app/common/views/widgets/slideshow.vue index c2f4eb70d3..56eb654c2b 100644 --- a/src/web/app/common/views/components/widgets/slideshow.vue +++ b/src/web/app/common/views/widgets/slideshow.vue @@ -12,7 +12,7 @@ <script lang="ts"> import * as anime from 'animejs'; -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'slideshow', props: () => ({ diff --git a/src/web/app/common/views/components/widgets/tips.vue b/src/web/app/common/views/widgets/tips.vue index 2991fbc3b9..bdecc068e1 100644 --- a/src/web/app/common/views/components/widgets/tips.vue +++ b/src/web/app/common/views/widgets/tips.vue @@ -6,7 +6,7 @@ <script lang="ts"> import * as anime from 'animejs'; -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; const tips = [ '<kbd>t</kbd>でタイムラインにフォーカスできます', diff --git a/src/web/app/common/views/components/widgets/version.vue b/src/web/app/common/views/widgets/version.vue index ad2b27bc40..5072d9b74a 100644 --- a/src/web/app/common/views/components/widgets/version.vue +++ b/src/web/app/common/views/widgets/version.vue @@ -3,8 +3,8 @@ </template> <script lang="ts"> -import { version } from '../../../../config'; -import define from '../../../../common/define-widget'; +import { version } from '../../../config'; +import define from '../../../common/define-widget'; export default define({ name: 'version' }).extend({ diff --git a/src/web/app/desktop/script.ts b/src/web/app/desktop/script.ts index bbd8e9598b..f0412805ef 100644 --- a/src/web/app/desktop/script.ts +++ b/src/web/app/desktop/script.ts @@ -37,6 +37,7 @@ init(async (launch) => { // Register components require('./views/components'); + require('./views/widgets'); // Launch the app const [app, os] = launch(os => ({ diff --git a/src/web/app/desktop/views/components/autocomplete.vue b/src/web/app/desktop/views/components/autocomplete.vue deleted file mode 100644 index a99d405e82..0000000000 --- a/src/web/app/desktop/views/components/autocomplete.vue +++ /dev/null @@ -1,190 +0,0 @@ -<template> -<div class="mk-autocomplete"> - <ol class="users" ref="users" v-if="users.length > 0"> - <li v-for="user in users" @click="complete(user)" @keydown="onKeydown" tabindex="-1"> - <img class="avatar" :src="`${user.avatar_url}?thumbnail&size=32`" alt=""/> - <span class="name">{{ user.name }}</span> - <span class="username">@{{ user.username }}</span> - </li> - </ol> -</div> -</template> - -<script lang="ts"> -import Vue from 'vue'; -import contains from '../../../common/scripts/contains'; - -export default Vue.extend({ - props: ['q', 'textarea', 'complete', 'close'], - data() { - return { - fetching: true, - users: [], - select: -1 - } - }, - mounted() { - this.textarea.addEventListener('keydown', this.onKeydown); - - Array.from(document.querySelectorAll('body *')).forEach(el => { - el.addEventListener('mousedown', this.onMousedown); - }); - - (this as any).api('users/search_by_username', { - query: this.q, - limit: 30 - }).then(users => { - this.users = users; - this.fetching = false; - }); - }, - beforeDestroy() { - this.textarea.removeEventListener('keydown', this.onKeydown); - - Array.from(document.querySelectorAll('body *')).forEach(el => { - el.removeEventListener('mousedown', this.onMousedown); - }); - }, - methods: { - onMousedown(e) { - if (!contains(this.$el, e.target) && (this.$el != e.target)) this.close(); - }, - - onKeydown(e) { - const cancel = () => { - e.preventDefault(); - e.stopPropagation(); - }; - - switch (e.which) { - case 10: // [ENTER] - case 13: // [ENTER] - if (this.select !== -1) { - cancel(); - this.complete(this.users[this.select]); - } else { - this.close(); - } - break; - - case 27: // [ESC] - cancel(); - this.close(); - break; - - case 38: // [↑] - if (this.select !== -1) { - cancel(); - this.selectPrev(); - } else { - this.close(); - } - break; - - case 9: // [TAB] - case 40: // [↓] - cancel(); - this.selectNext(); - break; - - default: - this.close(); - } - }, - - selectNext() { - if (++this.select >= this.users.length) this.select = 0; - this.applySelect(); - }, - - selectPrev() { - if (--this.select < 0) this.select = this.users.length - 1; - this.applySelect(); - }, - - applySelect() { - const els = (this.$refs.users as Element).children; - - Array.from(els).forEach(el => { - el.removeAttribute('data-selected'); - }); - - els[this.select].setAttribute('data-selected', 'true'); - (els[this.select] as any).focus(); - } - } -}); -</script> - -<style lang="stylus" scoped> -.mk-autocomplete - position absolute - z-index 65535 - margin-top calc(1em + 8px) - overflow hidden - background #fff - border solid 1px rgba(0, 0, 0, 0.1) - border-radius 4px - - > .users - display block - margin 0 - padding 4px 0 - max-height 190px - max-width 500px - overflow auto - list-style none - - > li - display block - padding 4px 12px - white-space nowrap - overflow hidden - font-size 0.9em - color rgba(0, 0, 0, 0.8) - cursor default - - &, * - user-select none - - &:hover - &[data-selected='true'] - color #fff - background $theme-color - - .name - color #fff - - .username - color #fff - - &:active - color #fff - background darken($theme-color, 10%) - - .name - color #fff - - .username - color #fff - - .avatar - vertical-align middle - min-width 28px - min-height 28px - max-width 28px - max-height 28px - margin 0 8px 0 0 - border-radius 100% - - .name - margin 0 8px 0 0 - /*font-weight bold*/ - font-weight normal - color rgba(0, 0, 0, 0.8) - - .username - font-weight normal - color rgba(0, 0, 0, 0.3) - -</style> diff --git a/src/web/app/desktop/views/components/home.vue b/src/web/app/desktop/views/components/home.vue index 8a61c378ed..59fd2aa36e 100644 --- a/src/web/app/desktop/views/components/home.vue +++ b/src/web/app/desktop/views/components/home.vue @@ -60,7 +60,7 @@ </template> <template v-else> <div v-for="place in ['left', 'right']" :class="place"> - <component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" @chosen="warp"/> + <component v-for="widget in widgets[place]" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" @chosen="warp"/> </div> <div class="main"> <mk-post-form v-if="os.i.client_settings.showPostFormOnTopOfTl"/> @@ -90,6 +90,8 @@ export default Vue.extend({ }, data() { return { + connection: null, + connectionId: null, widgetAdderSelected: null, trash: [], widgets: { @@ -131,6 +133,16 @@ export default Vue.extend({ deep: true }); }, + mounted() { + this.connection = (this as any).os.stream.getConnection(); + this.connectionId = (this as any).os.stream.use(); + + this.connection.on('home_updated', this.onHomeUpdated); + }, + beforeDestroy() { + this.connection.off('home_updated', this.onHomeUpdated); + (this as any).os.stream.dispose(this.connectionId); + }, methods: { hint() { (this as any).apis.dialog({ @@ -147,6 +159,22 @@ export default Vue.extend({ onTlLoaded() { this.$emit('loaded'); }, + onHomeUpdated(data) { + if (data.home) { + (this as any).os.i.client_settings.home = data.home; + this.widgets.left = data.home.filter(w => w.place == 'left'); + this.widgets.right = data.home.filter(w => w.place == 'right'); + } else { + const w = (this as any).os.i.client_settings.home.find(w => w.id == data.id); + if (w != null) { + w.data = data.data; + this.$refs[w.id][0].preventSave = true; + this.$refs[w.id][0].props = w.data; + this.widgets.left = (this as any).os.i.client_settings.home.filter(w => w.place == 'left'); + this.widgets.right = (this as any).os.i.client_settings.home.filter(w => w.place == 'right'); + } + } + }, onWidgetContextmenu(widgetId) { const w = (this.$refs[widgetId] as any)[0]; if (w.func) w.func(); @@ -179,7 +207,7 @@ export default Vue.extend({ }); }, warp(date) { - (this.$refs.tl as any)[0].warp(date); + (this.$refs.tl as any).warp(date); } } }); diff --git a/src/web/app/desktop/views/components/index.ts b/src/web/app/desktop/views/components/index.ts index 7584cb4983..52b9680baa 100644 --- a/src/web/app/desktop/views/components/index.ts +++ b/src/web/app/desktop/views/components/index.ts @@ -29,18 +29,6 @@ import following from './following.vue'; import usersList from './users-list.vue'; import widgetContainer from './widget-container.vue'; -//#region widgets -import wNotifications from './widgets/notifications.vue'; -import wTimemachine from './widgets/timemachine.vue'; -import wActivity from './widgets/activity.vue'; -import wTrends from './widgets/trends.vue'; -import wUsers from './widgets/users.vue'; -import wPolls from './widgets/polls.vue'; -import wPostForm from './widgets/post-form.vue'; -import wMessaging from './widgets/messaging.vue'; -import wChannel from './widgets/channel.vue'; -//#endregion - Vue.component('mk-ui', ui); Vue.component('mk-ui-notification', uiNotification); Vue.component('mk-home', home); @@ -69,15 +57,3 @@ Vue.component('mk-followers', followers); Vue.component('mk-following', following); Vue.component('mk-users-list', usersList); Vue.component('mk-widget-container', widgetContainer); - -//#region widgets -Vue.component('mkw-notifications', wNotifications); -Vue.component('mkw-timemachine', wTimemachine); -Vue.component('mkw-activity', wActivity); -Vue.component('mkw-trends', wTrends); -Vue.component('mkw-users', wUsers); -Vue.component('mkw-polls', wPolls); -Vue.component('mkw-post-form', wPostForm); -Vue.component('mkw-messaging', wMessaging); -Vue.component('mkw-channel', wChannel); -//#endregion diff --git a/src/web/app/desktop/views/components/posts.post.vue b/src/web/app/desktop/views/components/posts.post.vue index 4ae980648c..4898de0b6b 100644 --- a/src/web/app/desktop/views/components/posts.post.vue +++ b/src/web/app/desktop/views/components/posts.post.vue @@ -37,7 +37,7 @@ <a :href="`${_CH_URL_}/${p.channel.id}`" target="_blank">{{ p.channel.title }}</a>: </p> <a class="reply" v-if="p.reply">%fa:reply%</a> - <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i"/> + <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i" :class="$style.text"/> <a class="quote" v-if="p.repost">RP:</a> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> </div> @@ -413,9 +413,6 @@ export default Vue.extend({ font-size 1.1em color #717171 - > .dummy - display none - .mk-url-preview margin-top 8px @@ -431,27 +428,6 @@ export default Vue.extend({ font-style oblique color #a0bf46 - code - padding 4px 8px - margin 0 0.5em - font-size 80% - color #525252 - background #f8f8f8 - border-radius 2px - - pre > code - padding 16px - margin 0 - - [data-is-me]:after - content "you" - padding 0 4px - margin-left 4px - font-size 80% - color $theme-color-foreground - background $theme-color - border-radius 4px - > .mk-poll font-size 80% @@ -505,3 +481,26 @@ export default Vue.extend({ </style> +<style lang="stylus" module> +.text + code + padding 4px 8px + margin 0 0.5em + font-size 80% + color #525252 + background #f8f8f8 + border-radius 2px + + pre > code + padding 16px + margin 0 + + [data-is-me]:after + content "you" + padding 0 4px + margin-left 4px + font-size 80% + color $theme-color-foreground + background $theme-color + border-radius 4px +</style> diff --git a/src/web/app/desktop/views/directives/index.ts b/src/web/app/desktop/views/directives/index.ts index 3d0c73b6b2..324e07596d 100644 --- a/src/web/app/desktop/views/directives/index.ts +++ b/src/web/app/desktop/views/directives/index.ts @@ -1,8 +1,6 @@ import Vue from 'vue'; import userPreview from './user-preview'; -import autocomplete from './autocomplete'; Vue.directive('userPreview', userPreview); Vue.directive('user-preview', userPreview); -Vue.directive('autocomplete', autocomplete); diff --git a/src/web/app/desktop/views/components/widgets/activity.vue b/src/web/app/desktop/views/widgets/activity.vue index 2ff5fe4f03..0bdf4622af 100644 --- a/src/web/app/desktop/views/components/widgets/activity.vue +++ b/src/web/app/desktop/views/widgets/activity.vue @@ -7,7 +7,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'activity', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/channel.channel.form.vue b/src/web/app/desktop/views/widgets/channel.channel.form.vue index 392ba5924b..392ba5924b 100644 --- a/src/web/app/desktop/views/components/widgets/channel.channel.form.vue +++ b/src/web/app/desktop/views/widgets/channel.channel.form.vue diff --git a/src/web/app/desktop/views/components/widgets/channel.channel.post.vue b/src/web/app/desktop/views/widgets/channel.channel.post.vue index faaf0fb731..faaf0fb731 100644 --- a/src/web/app/desktop/views/components/widgets/channel.channel.post.vue +++ b/src/web/app/desktop/views/widgets/channel.channel.post.vue diff --git a/src/web/app/desktop/views/components/widgets/channel.channel.vue b/src/web/app/desktop/views/widgets/channel.channel.vue index 09154390c4..70dac316cf 100644 --- a/src/web/app/desktop/views/components/widgets/channel.channel.vue +++ b/src/web/app/desktop/views/widgets/channel.channel.vue @@ -11,7 +11,7 @@ <script lang="ts"> import Vue from 'vue'; -import ChannelStream from '../../../../common/scripts/streaming/channel-stream'; +import ChannelStream from '../../../common/scripts/streaming/channel-stream'; import XForm from './channel.channel.form.vue'; import XPost from './channel.channel.post.vue'; diff --git a/src/web/app/desktop/views/components/widgets/channel.vue b/src/web/app/desktop/views/widgets/channel.vue index 5c3afd9ecf..fc143bb1df 100644 --- a/src/web/app/desktop/views/components/widgets/channel.vue +++ b/src/web/app/desktop/views/widgets/channel.vue @@ -10,7 +10,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; import XChannel from './channel.channel.vue'; export default define({ diff --git a/src/web/app/desktop/views/widgets/index.ts b/src/web/app/desktop/views/widgets/index.ts new file mode 100644 index 0000000000..77d771d6b3 --- /dev/null +++ b/src/web/app/desktop/views/widgets/index.ts @@ -0,0 +1,23 @@ +import Vue from 'vue'; + +import wNotifications from './notifications.vue'; +import wTimemachine from './timemachine.vue'; +import wActivity from './activity.vue'; +import wTrends from './trends.vue'; +import wUsers from './users.vue'; +import wPolls from './polls.vue'; +import wPostForm from './post-form.vue'; +import wMessaging from './messaging.vue'; +import wChannel from './channel.vue'; +import wProfile from './profile.vue'; + +Vue.component('mkw-notifications', wNotifications); +Vue.component('mkw-timemachine', wTimemachine); +Vue.component('mkw-activity', wActivity); +Vue.component('mkw-trends', wTrends); +Vue.component('mkw-users', wUsers); +Vue.component('mkw-polls', wPolls); +Vue.component('mkw-post-form', wPostForm); +Vue.component('mkw-messaging', wMessaging); +Vue.component('mkw-channel', wChannel); +Vue.component('mkw-profile', wProfile); diff --git a/src/web/app/desktop/views/components/widgets/messaging.vue b/src/web/app/desktop/views/widgets/messaging.vue index ae7d6934af..2c9f473bd1 100644 --- a/src/web/app/desktop/views/components/widgets/messaging.vue +++ b/src/web/app/desktop/views/widgets/messaging.vue @@ -6,8 +6,8 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; -import MkMessagingRoomWindow from '../messaging-room-window.vue'; +import define from '../../../common/define-widget'; +import MkMessagingRoomWindow from '../components/messaging-room-window.vue'; export default define({ name: 'messaging', diff --git a/src/web/app/desktop/views/components/widgets/notifications.vue b/src/web/app/desktop/views/widgets/notifications.vue index 978cf5218e..1a2b3d3f89 100644 --- a/src/web/app/desktop/views/components/widgets/notifications.vue +++ b/src/web/app/desktop/views/widgets/notifications.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'notifications', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/polls.vue b/src/web/app/desktop/views/widgets/polls.vue index f1b34ceed0..fda4e17d87 100644 --- a/src/web/app/desktop/views/components/widgets/polls.vue +++ b/src/web/app/desktop/views/widgets/polls.vue @@ -15,7 +15,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'polls', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/post-form.vue b/src/web/app/desktop/views/widgets/post-form.vue index ab87ba7217..e51b4f3577 100644 --- a/src/web/app/desktop/views/components/widgets/post-form.vue +++ b/src/web/app/desktop/views/widgets/post-form.vue @@ -9,7 +9,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'post-form', props: () => ({ diff --git a/src/web/app/common/views/components/widgets/profile.vue b/src/web/app/desktop/views/widgets/profile.vue index 68cf469788..e067a0eb24 100644 --- a/src/web/app/common/views/components/widgets/profile.vue +++ b/src/web/app/desktop/views/widgets/profile.vue @@ -21,7 +21,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'profile', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/timemachine.vue b/src/web/app/desktop/views/widgets/timemachine.vue index 7420482168..6db3b14c62 100644 --- a/src/web/app/desktop/views/components/widgets/timemachine.vue +++ b/src/web/app/desktop/views/widgets/timemachine.vue @@ -5,7 +5,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'timemachine', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/trends.vue b/src/web/app/desktop/views/widgets/trends.vue index 934351b8a5..09cad9ba4a 100644 --- a/src/web/app/desktop/views/components/widgets/trends.vue +++ b/src/web/app/desktop/views/widgets/trends.vue @@ -14,7 +14,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'trends', props: () => ({ diff --git a/src/web/app/desktop/views/components/widgets/users.vue b/src/web/app/desktop/views/widgets/users.vue index f3a1509cfd..f7af8205ec 100644 --- a/src/web/app/desktop/views/components/widgets/users.vue +++ b/src/web/app/desktop/views/widgets/users.vue @@ -22,7 +22,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; const limit = 3; diff --git a/src/web/app/init.ts b/src/web/app/init.ts index aa2ec25c96..4a8f34f8d1 100644 --- a/src/web/app/init.ts +++ b/src/web/app/init.ts @@ -2,11 +2,6 @@ * App initializer */ -declare const _VERSION_: string; -declare const _LANG_: string; -declare const _HOST_: string; -//declare const __CONSTS__: any; - import Vue from 'vue'; import VueRouter from 'vue-router'; import VModal from 'vue-js-modal'; @@ -19,6 +14,7 @@ require('./common/views/directives'); // Register global components require('./common/views/components'); +require('./common/views/widgets'); // Register global filters require('./common/filters'); @@ -35,24 +31,28 @@ import App from './app.vue'; import checkForUpdate from './common/scripts/check-for-update'; import MiOS, { API } from './common/mios'; +import { version, host, lang } from './config'; /** * APP ENTRY POINT! */ -console.info(`Misskey v${_VERSION_} (葵 aoi)`); +console.info(`Misskey v${version} (葵 aoi)`); +console.info( + '%cここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。', + 'color: red; background: yellow; font-size: 16px;'); // BootTimer解除 window.clearTimeout((window as any).mkBootTimer); delete (window as any).mkBootTimer; -if (_HOST_ != 'localhost') { - document.domain = _HOST_; +if (host != 'localhost') { + document.domain = host; } //#region Set lang attr const html = document.documentElement; -html.setAttribute('lang', _LANG_); +html.setAttribute('lang', lang); //#endregion //#region Set description meta tag @@ -63,9 +63,6 @@ meta.setAttribute('content', '%i18n:common.misskey%'); head.appendChild(meta); //#endregion -// Set global configuration -//(riot as any).mixin(__CONSTS__); - // iOSでプライベートモードだとlocalStorageが使えないので既存のメソッドを上書きする try { localStorage.setItem('kyoppie', 'yuppie'); @@ -129,10 +126,14 @@ export default (callback: (launch: (api: (os: MiOS) => API) => [Vue, MiOS]) => v panic(e); } - // 更新チェック - setTimeout(() => { - checkForUpdate(os); - }, 3000); + //#region 更新チェック + const preventUpdate = localStorage.getItem('preventUpdate') == 'true'; + if (!preventUpdate) { + setTimeout(() => { + checkForUpdate(os); + }, 3000); + } + //#endregion }); }; @@ -149,7 +150,7 @@ function panic(e) { + '<hr>' + `<p>エラーコード: ${e.toString()}</p>` + `<p>ブラウザ バージョン: ${navigator.userAgent}</p>` - + `<p>クライアント バージョン: ${_VERSION_}</p>` + + `<p>クライアント バージョン: ${version}</p>` + '<hr>' + '<p>問題が解決しない場合は、上記の情報をお書き添えの上 syuilotan@yahoo.co.jp までご連絡ください。</p>' + '<p>Thank you for using Misskey.</p>' diff --git a/src/web/app/mobile/script.ts b/src/web/app/mobile/script.ts index fe73155c7c..eeadfd92b0 100644 --- a/src/web/app/mobile/script.ts +++ b/src/web/app/mobile/script.ts @@ -38,6 +38,7 @@ init((launch) => { // Register components require('./views/components'); + require('./views/widgets'); // http://qiita.com/junya/items/3ff380878f26ca447f85 document.body.setAttribute('ontouchstart', ''); diff --git a/src/web/app/mobile/views/components/index.ts b/src/web/app/mobile/views/components/index.ts index d372f22332..fe65aab207 100644 --- a/src/web/app/mobile/views/components/index.ts +++ b/src/web/app/mobile/views/components/index.ts @@ -21,10 +21,6 @@ import userTimeline from './user-timeline.vue'; import activity from './activity.vue'; import widgetContainer from './widget-container.vue'; -//#region widgets -import wActivity from './widgets/activity.vue'; -//#endregion - Vue.component('mk-ui', ui); Vue.component('mk-timeline', timeline); Vue.component('mk-posts', posts); @@ -45,7 +41,3 @@ Vue.component('mk-user-preview', userPreview); Vue.component('mk-user-timeline', userTimeline); Vue.component('mk-activity', activity); Vue.component('mk-widget-container', widgetContainer); - -//#region widgets -Vue.component('mkw-activity', wActivity); -//#endregion diff --git a/src/web/app/mobile/views/components/posts.post.vue b/src/web/app/mobile/views/components/posts.post.vue index 87b8032c89..ae1dfc59ab 100644 --- a/src/web/app/mobile/views/components/posts.post.vue +++ b/src/web/app/mobile/views/components/posts.post.vue @@ -34,7 +34,7 @@ <a class="reply" v-if="p.reply"> %fa:reply% </a> - <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i"/> + <mk-post-html v-if="p.ast" :ast="p.ast" :i="os.i" :class="$style.text"/> <mk-url-preview v-for="url in urls" :url="url" :key="url"/> <a class="quote" v-if="p.repost != null">RP:</a> </div> @@ -364,18 +364,6 @@ export default Vue.extend({ font-style oblique color #a0bf46 - code - padding 4px 8px - margin 0 0.5em - font-size 80% - color #525252 - background #f8f8f8 - border-radius 2px - - pre > code - padding 16px - margin 0 - [data-is-me]:after content "you" padding 0 4px @@ -445,3 +433,17 @@ export default Vue.extend({ </style> +<style lang="stylus" module> +.text + code + padding 4px 8px + margin 0 0.5em + font-size 80% + color #525252 + background #f8f8f8 + border-radius 2px + + pre > code + padding 16px + margin 0 +</style> diff --git a/src/web/app/mobile/views/pages/home.vue b/src/web/app/mobile/views/pages/home.vue index e4ba2e7193..f4f458068a 100644 --- a/src/web/app/mobile/views/pages/home.vue +++ b/src/web/app/mobile/views/pages/home.vue @@ -51,7 +51,7 @@ </x-draggable> </template> <template v-else> - <component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/> + <component class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :ref="widget.id" :widget="widget" :is-mobile="true" @chosen="warp"/> </template> </div> </main> @@ -124,12 +124,14 @@ export default Vue.extend({ this.connectionId = (this as any).os.stream.use(); this.connection.on('post', this.onStreamPost); + this.connection.on('mobile_home_updated', this.onHomeUpdated); document.addEventListener('visibilitychange', this.onVisibilitychange, false); Progress.start(); }, beforeDestroy() { this.connection.off('post', this.onStreamPost); + this.connection.off('mobile_home_updated', this.onHomeUpdated); (this as any).os.stream.dispose(this.connectionId); document.removeEventListener('visibilitychange', this.onVisibilitychange); }, @@ -152,6 +154,20 @@ export default Vue.extend({ document.title = 'Misskey'; } }, + onHomeUpdated(data) { + if (data.home) { + (this as any).os.i.client_settings.mobile_home = data.home; + this.widgets = data.home; + } else { + const w = (this as any).os.i.client_settings.mobile_home.find(w => w.id == data.id); + if (w != null) { + w.data = data.data; + this.$refs[w.id][0].preventSave = true; + this.$refs[w.id][0].props = w.data; + this.widgets = (this as any).os.i.client_settings.mobile_home; + } + } + }, hint() { alert('ウィジェットを追加/削除したり並べ替えたりできます。ウィジェットを移動するには「三」をドラッグします。ウィジェットを削除するには「x」をタップします。いくつかのウィジェットはタップすることで表示を変更できます。'); }, @@ -177,6 +193,7 @@ export default Vue.extend({ this.saveHome(); }, saveHome() { + (this as any).os.i.client_settings.mobile_home = this.widgets; (this as any).api('i/update_mobile_home', { home: this.widgets }); diff --git a/src/web/app/mobile/views/components/widgets/activity.vue b/src/web/app/mobile/views/widgets/activity.vue index c4d30b07af..48dcafb3ed 100644 --- a/src/web/app/mobile/views/components/widgets/activity.vue +++ b/src/web/app/mobile/views/widgets/activity.vue @@ -10,7 +10,7 @@ </template> <script lang="ts"> -import define from '../../../../common/define-widget'; +import define from '../../../common/define-widget'; export default define({ name: 'activity', diff --git a/src/web/app/mobile/views/widgets/index.ts b/src/web/app/mobile/views/widgets/index.ts new file mode 100644 index 0000000000..4de912b64c --- /dev/null +++ b/src/web/app/mobile/views/widgets/index.ts @@ -0,0 +1,7 @@ +import Vue from 'vue'; + +import wActivity from './activity.vue'; +import wProfile from './profile.vue'; + +Vue.component('mkw-activity', wActivity); +Vue.component('mkw-profile', wProfile); diff --git a/src/web/app/mobile/views/widgets/profile.vue b/src/web/app/mobile/views/widgets/profile.vue new file mode 100644 index 0000000000..6bc7bfffc6 --- /dev/null +++ b/src/web/app/mobile/views/widgets/profile.vue @@ -0,0 +1,62 @@ +<template> +<div class="mkw-profile"> + <mk-widget-container> + <div :class="$style.banner" + :style="os.i.banner_url ? `background-image: url(${os.i.banner_url}?thumbnail&size=256)` : ''" + ></div> + <img :class="$style.avatar" + :src="`${os.i.avatar_url}?thumbnail&size=96`" + alt="avatar" + /> + <router-link :class="$style.name" :to="`/${os.i.username}`">{{ os.i.name }}</router-link> + </mk-widget-container> +</div> +</template> + +<script lang="ts"> +import define from '../../../common/define-widget'; +export default define({ + name: 'profile' +}); +</script> + +<style lang="stylus" module> +.banner + height 100px + background-color #f5f5f5 + background-size cover + background-position center + cursor pointer + +.banner:before + content "" + display block + width 100% + height 100% + background rgba(0, 0, 0, 0.5) + +.avatar + display block + position absolute + width 58px + height 58px + margin 0 + vertical-align bottom + top ((100px - 58px) / 2) + left ((100px - 58px) / 2) + border none + border-radius 100% + box-shadow 0 0 16px rgba(0, 0, 0, 0.5) + +.name + display block + position absolute + top 0 + left 92px + margin 0 + line-height 100px + color #fff + font-weight bold + text-shadow 0 0 8px rgba(0, 0, 0, 0.5) + +</style> diff --git a/src/web/app/stats/script.ts b/src/web/app/stats/script.ts deleted file mode 100644 index 3bbd80c339..0000000000 --- a/src/web/app/stats/script.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Stats - */ - -// Style -import './style.styl'; - -import * as riot from 'riot'; -require('./tags'); -import init from '../init'; - -document.title = 'Misskey Statistics'; - -/** - * init - */ -init(() => { - mount(document.createElement('mk-index')); -}); - -function mount(content) { - riot.mount(document.getElementById('app').appendChild(content)); -} diff --git a/src/web/app/status/script.ts b/src/web/app/status/script.ts deleted file mode 100644 index 84483acab7..0000000000 --- a/src/web/app/status/script.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Status - */ - -// Style -import './style.styl'; - -import * as riot from 'riot'; -require('./tags'); -import init from '../init'; - -document.title = 'Misskey System Status'; - -/** - * init - */ -init(() => { - mount(document.createElement('mk-index')); -}); - -function mount(content) { - riot.mount(document.getElementById('app').appendChild(content)); -} |