From 64be9baed0b64d882115bbfd3732a0791baa3ff8 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 17:22:06 +0900 Subject: [PATCH 01/36] chore(client): tweak style --- packages/client/src/ui/_common_/sidebar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index a72bf786ad..f082af9aa8 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -244,7 +244,7 @@ function more(ev: MouseEvent) { padding: 18px 0; width: 100%; text-align: center; - font-size: $ui-font-size * 1.1; + font-size: $ui-font-size; line-height: initial; > .icon, From 242538ddce37de41d924f12ab8a3637c264d231c Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 17:42:12 +0900 Subject: [PATCH 02/36] refactor(client): rename menu(sidebar) -> navbar --- locales/ja-JP.yml | 1 + packages/client/src/components/launch-pad.vue | 4 ++-- packages/client/src/{menu.ts => navbar.ts} | 2 +- packages/client/src/pages/settings/index.vue | 16 +++++++++------- .../src/pages/settings/{menu.vue => navbar.vue} | 10 +++++----- .../src/pages/settings/statusbars.statusbar.vue | 1 - ...ebar-for-mobile.vue => navbar-for-mobile.vue} | 14 +++++++------- .../src/ui/_common_/{sidebar.vue => navbar.vue} | 12 ++++++------ packages/client/src/ui/classic.header.vue | 16 ++++++++-------- packages/client/src/ui/classic.sidebar.vue | 16 ++++++++-------- packages/client/src/ui/classic.vue | 1 - packages/client/src/ui/deck.vue | 10 +++++----- packages/client/src/ui/universal.vue | 10 +++++----- 13 files changed, 57 insertions(+), 56 deletions(-) rename packages/client/src/{menu.ts => navbar.ts} (98%) rename packages/client/src/pages/settings/{menu.vue => navbar.vue} (90%) rename packages/client/src/ui/_common_/{sidebar-for-mobile.vue => navbar-for-mobile.vue} (85%) rename packages/client/src/ui/_common_/{sidebar.vue => navbar.vue} (89%) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0e278bead6..fd8aca868b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -887,6 +887,7 @@ beta: "ベータ" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、インスタンスによっては自動で設定されることがあります。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" +navbar: "ナビゲーションバー" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/client/src/components/launch-pad.vue b/packages/client/src/components/launch-pad.vue index a6025f8b27..4693df2916 100644 --- a/packages/client/src/components/launch-pad.vue +++ b/packages/client/src/components/launch-pad.vue @@ -36,7 +36,7 @@ <script lang="ts" setup> import { } from 'vue'; import MkModal from '@/components/ui/modal.vue'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { instanceName } from '@/config'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; @@ -62,7 +62,7 @@ const modal = $ref<InstanceType<typeof MkModal>>(); const menu = defaultStore.state.menu; -const items = Object.keys(menuDef).filter(k => !menu.includes(k)).map(k => menuDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({ +const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k => navbarItemDef[k]).filter(def => def.show == null ? true : def.show).map(def => ({ type: def.to ? 'link' : 'button', text: i18n.ts[def.title], icon: def.icon, diff --git a/packages/client/src/menu.ts b/packages/client/src/navbar.ts similarity index 98% rename from packages/client/src/menu.ts rename to packages/client/src/navbar.ts index 31b2ed597c..03e00b1c17 100644 --- a/packages/client/src/menu.ts +++ b/packages/client/src/navbar.ts @@ -6,7 +6,7 @@ import { i18n } from '@/i18n'; import { ui } from '@/config'; import { unisonReload } from '@/scripts/unison-reload'; -export const menuDef = reactive({ +export const navbarItemDef = reactive({ notifications: { title: 'notifications', icon: 'fas fa-bell', diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 76410ec12f..f970660a4a 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -114,15 +114,15 @@ const menuDef = computed(() => [{ to: '/settings/theme', active: props.initialPage === 'theme', }, { - icon: 'fas fa-list-ul', + icon: 'fas fa-bars', + text: i18n.ts.navbar, + to: '/settings/navbar', + active: props.initialPage === 'navbar', + }, { + icon: 'fas fa-bars-progress', text: i18n.ts.statusbar, to: '/settings/statusbars', active: props.initialPage === 'statusbars', - }, { - icon: 'fas fa-list-ul', - text: i18n.ts.menu, - to: '/settings/menu', - active: props.initialPage === 'menu', }, { icon: 'fas fa-music', text: i18n.ts.sounds, @@ -225,7 +225,7 @@ const component = computed(() => { case 'theme': return defineAsyncComponent(() => import('./theme.vue')); case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue')); case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue')); - case 'menu': return defineAsyncComponent(() => import('./menu.vue')); + case 'navbar': return defineAsyncComponent(() => import('./navbar.vue')); case 'statusbars': return defineAsyncComponent(() => import('./statusbars.vue')); case 'sounds': return defineAsyncComponent(() => import('./sounds.vue')); case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue')); @@ -291,6 +291,8 @@ const headerActions = $computed(() => []); const headerTabs = $computed(() => []); definePageMetadata(INFO); +// w 890 +// h 700 </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/navbar.vue similarity index 90% rename from packages/client/src/pages/settings/menu.vue rename to packages/client/src/pages/settings/navbar.vue index 076654c105..534112c3e0 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/navbar.vue @@ -1,7 +1,7 @@ <template> <div class="_formRoot"> <FormTextarea v-model="items" tall manual-save class="_formBlock"> - <template #label>{{ i18n.ts.menu }}</template> + <template #label>{{ i18n.ts.navbar }}</template> <template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template> </FormTextarea> @@ -23,7 +23,7 @@ import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { defaultStore } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; import { i18n } from '@/i18n'; @@ -45,11 +45,11 @@ async function reloadAsk() { } async function addItem() { - const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k)); + const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k)); const { canceled, result: item } = await os.select({ title: i18n.ts.addItem, items: [...menu.map(k => ({ - value: k, text: i18n.ts[menuDef[k].title], + value: k, text: i18n.ts[navbarItemDef[k].title], })), { value: '-', text: i18n.ts.divider, }], @@ -81,7 +81,7 @@ const headerActions = $computed(() => []); const headerTabs = $computed(() => []); definePageMetadata({ - title: i18n.ts.menu, + title: i18n.ts.navbar, icon: 'fas fa-list-ul', }); </script> diff --git a/packages/client/src/pages/settings/statusbars.statusbar.vue b/packages/client/src/pages/settings/statusbars.statusbar.vue index 206979925e..e19690209a 100644 --- a/packages/client/src/pages/settings/statusbars.statusbar.vue +++ b/packages/client/src/pages/settings/statusbars.statusbar.vue @@ -86,7 +86,6 @@ import FormRadios from '@/components/form/radios.vue'; import FormButton from '@/components/ui/button.vue'; import FormRange from '@/components/form/range.vue'; import * as os from '@/os'; -import { menuDef } from '@/menu'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/navbar-for-mobile.vue similarity index 85% rename from packages/client/src/ui/_common_/sidebar-for-mobile.vue rename to packages/client/src/ui/_common_/navbar-for-mobile.vue index e789ae5e06..8ac4c1150a 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/navbar-for-mobile.vue @@ -9,9 +9,9 @@ </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> - <i class="icon fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> - <span v-if="menuDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> </component> </template> <div class="divider"></div> @@ -37,7 +37,7 @@ import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } fr import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { openAccountMenu } from '@/account'; import { defaultStore } from '@/store'; @@ -45,9 +45,9 @@ export default defineComponent({ setup(props, context) { const menu = toRef(defaultStore.state, 'menu'); const otherMenuItemIndicated = computed(() => { - for (const def in menuDef) { + for (const def in navbarItemDef) { if (menu.value.includes(def)) continue; - if (menuDef[def].indicated) return true; + if (navbarItemDef[def].indicated) return true; } return false; }); @@ -57,7 +57,7 @@ export default defineComponent({ accounts: [], connection: null, menu, - menuDef: menuDef, + navbarItemDef: navbarItemDef, otherMenuItemIndicated, post: os.post, search, diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/navbar.vue similarity index 89% rename from packages/client/src/ui/_common_/sidebar.vue rename to packages/client/src/ui/_common_/navbar.vue index f082af9aa8..4fb3980142 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/navbar.vue @@ -9,9 +9,9 @@ </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> - <i class="icon fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> - <span v-if="menuDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> </component> </template> <div class="divider"></div> @@ -35,7 +35,7 @@ <script lang="ts" setup> import { computed, defineAsyncComponent, ref, watch } from 'vue'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; import { defaultStore } from '@/store'; @@ -43,9 +43,9 @@ const iconOnly = ref(false); const menu = computed(() => defaultStore.state.menu); const otherMenuItemIndicated = computed(() => { - for (const def in menuDef) { + for (const def in navbarItemDef) { if (menu.value.includes(def)) continue; - if (menuDef[def].indicated) return true; + if (navbarItemDef[def].indicated) return true; } return false; }); diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index 57008aeaed..131767c0e3 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -7,9 +7,9 @@ </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime v-tooltip="$ts[menuDef[item].title]" class="item _button" :class="item" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> - <i class="fa-fw" :class="menuDef[item].icon"></i> - <span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="$ts[navbarItemDef[item].title]" class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="fa-fw" :class="navbarItemDef[item].icon"></i> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> </component> </template> <div class="divider"></div> @@ -43,7 +43,7 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { openAccountMenu } from '@/account'; import MkButton from '@/components/ui/button.vue'; @@ -57,7 +57,7 @@ export default defineComponent({ host: host, accounts: [], connection: null, - menuDef: menuDef, + navbarItemDef: navbarItemDef, settingsWindowed: false, }; }, @@ -68,9 +68,9 @@ export default defineComponent({ }, otherNavItemIndicated(): boolean { - for (const def in this.menuDef) { + for (const def in this.navbarItemDef) { if (this.menu.includes(def)) continue; - if (this.menuDef[def].indicated) return true; + if (this.navbarItemDef[def].indicated) return true; } return false; }, @@ -113,7 +113,7 @@ export default defineComponent({ withExtraOperation: true, }, ev); }, - } + }, }); </script> diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index 6c0ce023e4..172401f420 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -14,9 +14,9 @@ </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="item" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> - <i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> - <span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> </component> </template> <div class="divider"></div> @@ -45,7 +45,7 @@ import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { openAccountMenu } from '@/account'; import MkButton from '@/components/ui/button.vue'; import { StickySidebar } from '@/scripts/sticky-sidebar'; @@ -62,7 +62,7 @@ export default defineComponent({ host: host, accounts: [], connection: null, - menuDef: menuDef, + navbarItemDef: navbarItemDef, iconOnly: false, settingsWindowed: false, }; @@ -74,9 +74,9 @@ export default defineComponent({ }, otherNavItemIndicated(): boolean { - for (const def in this.menuDef) { + for (const def in this.navbarItemDef) { if (this.menu.includes(def)) continue; - if (this.menuDef[def].indicated) return true; + if (this.navbarItemDef[def].indicated) return true; } return false; }, @@ -131,7 +131,7 @@ export default defineComponent({ withExtraOperation: true, }, ev); }, - } + }, }); </script> diff --git a/packages/client/src/ui/classic.vue b/packages/client/src/ui/classic.vue index 70db7ed12b..c42407f5b0 100644 --- a/packages/client/src/ui/classic.vue +++ b/packages/client/src/ui/classic.vue @@ -47,7 +47,6 @@ import XCommon from './_common_/common.vue'; import { instanceName } from '@/config'; import { StickySidebar } from '@/scripts/sticky-sidebar'; import * as os from '@/os'; -import { menuDef } from '@/menu'; import { mainRouter } from '@/router'; import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; import { defaultStore } from '@/store'; diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 19a99a95aa..f330c99814 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -69,12 +69,12 @@ import { v4 as uuid } from 'uuid'; import XCommon from './_common_/common.vue'; import { deckStore, addColumn as addColumnToStore, loadDeck } from './deck/deck-store'; import DeckColumnCore from '@/ui/deck/column-core.vue'; -import XSidebar from '@/ui/_common_/sidebar.vue'; -import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue'; +import XSidebar from '@/ui/_common_/navbar.vue'; +import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import MkButton from '@/components/ui/button.vue'; import { getScrollContainer } from '@/scripts/scroll'; import * as os from '@/os'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { $i } from '@/account'; import { i18n } from '@/i18n'; import { mainRouter } from '@/router'; @@ -105,8 +105,8 @@ const columns = deckStore.reactiveState.columns; const layout = deckStore.reactiveState.layout; const menuIndicated = computed(() => { if ($i == null) return false; - for (const def in menuDef) { - if (menuDef[def].indicated) return true; + for (const def in navbarItemDef) { + if (navbarItemDef[def].indicated) return true; } return false; }); diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index 2edfb3f12d..fe4fc425cd 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -61,17 +61,17 @@ import { defineAsyncComponent, provide, onMounted, computed, ref, watch, Compute import XCommon from './_common_/common.vue'; import { instanceName } from '@/config'; import { StickySidebar } from '@/scripts/sticky-sidebar'; -import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue'; +import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os'; import { defaultStore } from '@/store'; -import { menuDef } from '@/menu'; +import { navbarItemDef } from '@/navbar'; import { i18n } from '@/i18n'; import { $i } from '@/account'; import { Router } from '@/nirax'; import { mainRouter } from '@/router'; import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); -const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/sidebar.vue')); +const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const DESKTOP_THRESHOLD = 1100; @@ -97,9 +97,9 @@ provideMetadataReceiver((info) => { }); const menuIndicated = computed(() => { - for (const def in menuDef) { + for (const def in navbarItemDef) { if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから - if (menuDef[def].indicated) return true; + if (navbarItemDef[def].indicated) return true; } return false; }); From f3b0c6f1e7a37973f2d43a8ff56fbe19adc28ac4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 17:52:59 +0900 Subject: [PATCH 03/36] chore(client): tweak ui --- packages/client/src/components/ui/tooltip.vue | 2 +- packages/client/src/ui/_common_/navbar.vue | 30 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index 152c939a1a..f81bf2fc5b 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -116,7 +116,7 @@ const setPosition = () => { let top: number; if (props.targetElement) { - left = (rect.left + window.pageXOffset) + props.innerMargin; + left = (rect.left + props.targetElement.offsetWidth + window.pageXOffset) + props.innerMargin; top = rect.top + window.pageYOffset + (props.targetElement.offsetHeight / 2); } else { left = props.x + props.innerMargin; diff --git a/packages/client/src/ui/_common_/navbar.vue b/packages/client/src/ui/_common_/navbar.vue index 4fb3980142..924dda25d7 100644 --- a/packages/client/src/ui/_common_/navbar.vue +++ b/packages/client/src/ui/_common_/navbar.vue @@ -4,29 +4,38 @@ <button v-click-anime class="item _button account" @click="openAccountMenu"> <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> </button> - <MkA v-click-anime class="item index" active-class="active" to="/" exact> - <i class="icon fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> + <MkA v-click-anime v-tooltip.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact> + <i class="icon fas fa-home fa-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> </MkA> <template v-for="item in menu"> <div v-if="item === '-'" class="divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> - <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> + <component + :is="navbarItemDef[item].to ? 'MkA' : 'button'" + v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" + v-click-anime v-tooltip.right="i18n.ts[navbarItemDef[item].title]" + class="item _button" + :class="[item, { active: navbarItemDef[item].active }]" + active-class="active" + :to="navbarItemDef[item].to" + v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" + > + <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ i18n.ts[navbarItemDef[item].title] }}</span> <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> </component> </template> <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> - <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin"> + <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> </MkA> <button v-click-anime class="item _button" @click="more"> - <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span> + <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ i18n.ts.more }}</span> <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span> </button> - <MkA v-click-anime class="item" active-class="active" to="/settings"> - <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span> + <MkA v-click-anime v-tooltip.right="i18n.ts.settings" class="item" active-class="active" to="/settings"> + <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ i18n.ts.settings }}</span> </MkA> <button class="item _button post" data-cy-open-post-form @click="os.post"> - <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span> + <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ i18n.ts.note }}</span> </button> </div> </div> @@ -38,6 +47,7 @@ import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; const iconOnly = ref(false); From 61e26696aa901033b28a604f0bcb4a856a3df3a3 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 20:29:45 +0900 Subject: [PATCH 04/36] log error while client boot --- packages/backend/src/server/web/boot.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 0a5cc0e0dc..f3116eb653 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -14,9 +14,11 @@ // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので (async () => { window.onerror = (e) => { + console.error(e); renderError('SOMETHING_HAPPENED', e); }; window.onunhandledrejection = (e) => { + console.error(e); renderError('SOMETHING_HAPPENED_IN_PROMISE', e); }; From cb00786f1e7ffcadf2b9da87bfffa8e875a1a28b Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 20:41:17 +0900 Subject: [PATCH 05/36] improve error handling of client boot --- packages/backend/src/server/web/boot.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index f3116eb653..fa3ebbc2a2 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -59,6 +59,7 @@ import(`/assets/${CLIENT_ENTRY}`) .catch(async e => { await checkUpdate(); + console.error(e); renderError('APP_FETCH_FAILED', e); }) //#endregion @@ -271,17 +272,22 @@ // eslint-disable-next-line no-inner-declarations async function checkUpdate() { - // TODO: サーバーが落ちている場合などのエラーハンドリング - const res = await fetch('/api/meta', { - method: 'POST', - cache: 'no-cache' - }); + try { + const res = await fetch('/api/meta', { + method: 'POST', + cache: 'no-cache' + }); - const meta = await res.json(); + const meta = await res.json(); - if (meta.version != v) { - localStorage.setItem('v', meta.version); - refresh(); + if (meta.version != v) { + localStorage.setItem('v', meta.version); + refresh(); + } + } catch (e) { + console.error(e); + renderError('UPDATE_CHECK_FAILED', e); + throw e; } } From d78faf11348b284b1e1911a90d9a8f15e067ed64 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:02:45 +0900 Subject: [PATCH 06/36] chore: use tab --- packages/backend/src/server/web/boot.js | 38 ++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index fa3ebbc2a2..ca5f0639f8 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -117,33 +117,33 @@ if (!errorsElement) { document.documentElement.innerHTML = ` <svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> - <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> - <path d="M12 9v2m0 4v.01"></path> - <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> + <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> + <path d="M12 9v2m0 4v.01"></path> + <path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path> </svg> <h1>An error has occurred!</h1> <button class="button-big" onclick="location.reload(true);"> <span class="button-label-big">Refresh</span> </button> - <p class="dont-worry">Don't worry, it's (probably) not your fault.</p> + <p class="dont-worry">Don't worry, it's (probably) not your fault.</p> <p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p> - <a href="/flush"> - <button class="button-small"> - <span class="button-label-small">Clear preferences and cache</span> - </button> - </a> + <a href="/flush"> + <button class="button-small"> + <span class="button-label-small">Clear preferences and cache</span> + </button> + </a> <br> - <a href="/cli"> - <button class="button-small"> - <span class="button-label-small">Start the simple client</span> - </button> - </a> + <a href="/cli"> + <button class="button-small"> + <span class="button-label-small">Start the simple client</span> + </button> + </a> <br> - <a href="/bios"> - <button class="button-small"> - <span class="button-label-small">Start the repair tool</span> - </button> - </a> + <a href="/bios"> + <button class="button-small"> + <span class="button-label-small">Start the repair tool</span> + </button> + </a> <br> <div id="errors"></div> `; From 0ddabdbf68767566fd8f4fdeab1de82535dc44cb Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:06:07 +0900 Subject: [PATCH 07/36] fix(client): revert es2017 --- packages/client/src/init.ts | 732 ++++++++++++++++----------------- packages/client/vite.config.ts | 1 - 2 files changed, 365 insertions(+), 368 deletions(-) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 94e7f9f6b3..98f69c701f 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -39,403 +39,401 @@ import { reactionPicker } from '@/scripts/reaction-picker'; import { getUrlWithoutLoginId } from '@/scripts/login-id'; import { getAccountFromId } from '@/scripts/get-account-from-id'; -(async () => { - console.info(`Misskey v${version}`); +console.info(`Misskey v${version}`); - if (_DEV_) { - console.warn('Development mode!!!'); +if (_DEV_) { + console.warn('Development mode!!!'); - console.info(`vue ${vueVersion}`); + console.info(`vue ${vueVersion}`); - (window as any).$i = $i; - (window as any).$store = defaultStore; + (window as any).$i = $i; + (window as any).$store = defaultStore; - window.addEventListener('error', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled error', - text: event.message - }); - */ + window.addEventListener('error', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled error', + text: event.message }); - - window.addEventListener('unhandledrejection', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled promise rejection', - text: event.reason - }); - */ - }); - } - - // タッチデバイスでCSSの:hoverを機能させる - document.addEventListener('touchend', () => {}, { passive: true }); - - // 一斉リロード - reloadChannel.addEventListener('message', path => { - if (path !== null) location.href = path; - else location.reload(); + */ }); - //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ - // TODO: いつの日にか消したい + window.addEventListener('unhandledrejection', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled promise rejection', + text: event.reason + }); + */ + }); +} + +// タッチデバイスでCSSの:hoverを機能させる +document.addEventListener('touchend', () => {}, { passive: true }); + +// 一斉リロード +reloadChannel.addEventListener('message', path => { + if (path !== null) location.href = path; + else location.reload(); +}); + +//#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ +// TODO: いつの日にか消したい +const vh = window.innerHeight * 0.01; +document.documentElement.style.setProperty('--vh', `${vh}px`); +window.addEventListener('resize', () => { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); - window.addEventListener('resize', () => { - const vh = window.innerHeight * 0.01; - document.documentElement.style.setProperty('--vh', `${vh}px`); - }); - //#endregion +}); +//#endregion - // If mobile, insert the viewport meta tag - if (['smartphone', 'tablet'].includes(deviceKind)) { - const viewport = document.getElementsByName('viewport').item(0); - viewport.setAttribute('content', - `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); - } +// If mobile, insert the viewport meta tag +if (['smartphone', 'tablet'].includes(deviceKind)) { + const viewport = document.getElementsByName('viewport').item(0); + viewport.setAttribute('content', + `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); +} - //#region Set lang attr - const html = document.documentElement; - html.setAttribute('lang', lang); - //#endregion +//#region Set lang attr +const html = document.documentElement; +html.setAttribute('lang', lang); +//#endregion - //#region loginId - const params = new URLSearchParams(location.search); - const loginId = params.get('loginId'); +//#region loginId +const params = new URLSearchParams(location.search); +const loginId = params.get('loginId'); - if (loginId) { - const target = getUrlWithoutLoginId(location.href); +if (loginId) { + const target = getUrlWithoutLoginId(location.href); - if (!$i || $i.id !== loginId) { - const account = await getAccountFromId(loginId); - if (account) { - await login(account.token, target); - } + if (!$i || $i.id !== loginId) { + const account = await getAccountFromId(loginId); + if (account) { + await login(account.token, target); } - - history.replaceState({ misskey: 'loginId' }, '', target); } - //#endregion + history.replaceState({ misskey: 'loginId' }, '', target); +} - //#region Fetch user - if ($i && $i.token) { +//#endregion + +//#region Fetch user +if ($i && $i.token) { + if (_DEV_) { + console.log('account cache found. refreshing...'); + } + + refreshAccount(); +} else { + if (_DEV_) { + console.log('no account cache found.'); + } + + // 連携ログインの場合用にCookieを参照する + const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; + + if (i != null && i !== 'null') { if (_DEV_) { - console.log('account cache found. refreshing...'); + console.log('signing...'); } - refreshAccount(); + try { + document.body.innerHTML = '<div>Please wait...</div>'; + await login(i); + } catch (err) { + // Render the error screen + // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな) + document.body.innerHTML = '<div id="err">Oops!</div>'; + } } else { if (_DEV_) { - console.log('no account cache found.'); - } - - // 連携ログインの場合用にCookieを参照する - const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; - - if (i != null && i !== 'null') { - if (_DEV_) { - console.log('signing...'); - } - - try { - document.body.innerHTML = '<div>Please wait...</div>'; - await login(i); - } catch (err) { - // Render the error screen - // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな) - document.body.innerHTML = '<div id="err">Oops!</div>'; - } - } else { - if (_DEV_) { - console.log('not signed in'); - } + console.log('not signed in'); } } - //#endregion +} +//#endregion - const fetchInstanceMetaPromise = fetchInstance(); +const fetchInstanceMetaPromise = fetchInstance(); - fetchInstanceMetaPromise.then(() => { - localStorage.setItem('v', instance.version); +fetchInstanceMetaPromise.then(() => { + localStorage.setItem('v', instance.version); - // Init service worker - initializeSw(); - }); + // Init service worker + initializeSw(); +}); - const app = createApp( - window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : - !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : - ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : - ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : - defineAsyncComponent(() => import('@/ui/universal.vue')), - ); +const app = createApp( + window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : + !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : + ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : + ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : + defineAsyncComponent(() => import('@/ui/universal.vue')), +); - if (_DEV_) { - app.config.performance = true; +if (_DEV_) { + app.config.performance = true; +} + +app.config.globalProperties = { + $i, + $store: defaultStore, + $instance: instance, + $t: i18n.t, + $ts: i18n.ts, +}; + +widgets(app); +directives(app); +components(app); + +const splash = document.getElementById('splash'); +// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) +if (splash) splash.addEventListener('transitionend', () => { + splash.remove(); +}); + +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 +// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する +const rootEl = (() => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentEl) { + console.warn('multiple import detected'); + return currentEl; } - app.config.globalProperties = { - $i, - $store: defaultStore, - $instance: instance, - $t: i18n.t, - $ts: i18n.ts, - }; - - widgets(app); - directives(app); - components(app); - - const splash = document.getElementById('splash'); - // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) - if (splash) splash.addEventListener('transitionend', () => { - splash.remove(); - }); - - // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 - // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する - const rootEl = (() => { - const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - - const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); - - if (currentEl) { - console.warn('multiple import detected'); - return currentEl; - } - - const rootEl = document.createElement('div'); - rootEl.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(rootEl); - return rootEl; - })(); - - app.mount(rootEl); - - // boot.jsのやつを解除 - window.onerror = null; - window.onunhandledrejection = null; - - reactionPicker.init(); - - if (splash) { - splash.style.opacity = '0'; - splash.style.pointerEvents = 'none'; - } - - // クライアントが更新されたか? - const lastVersion = localStorage.getItem('lastVersion'); - if (lastVersion !== version) { - localStorage.setItem('lastVersion', version); - - // テーマリビルドするため - localStorage.removeItem('theme'); - - try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため - if (lastVersion != null && compareVersions(version, lastVersion) === 1) { - // ログインしてる場合だけ - if ($i) { - popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); - } - } - } catch (err) { - } - } - - // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) - watch(defaultStore.reactiveState.darkMode, (darkMode) => { - applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); - }, { immediate: localStorage.theme == null }); - - const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); - const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); - - watch(darkTheme, (theme) => { - if (defaultStore.state.darkMode) { - applyTheme(theme); - } - }); - - watch(lightTheme, (theme) => { - if (!defaultStore.state.darkMode) { - applyTheme(theme); - } - }); - - //#region Sync dark mode - if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', isDeviceDarkmode()); - } - - window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { - if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', mql.matches); - } - }); - //#endregion - - fetchInstanceMetaPromise.then(() => { - if (defaultStore.state.themeInitial) { - if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme)); - defaultStore.set('themeInitial', false); - } - }); - - watch(defaultStore.reactiveState.useBlurEffectForModal, v => { - document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); - }, { immediate: true }); - - watch(defaultStore.reactiveState.useBlurEffect, v => { - if (v) { - document.documentElement.style.removeProperty('--blur'); - } else { - document.documentElement.style.setProperty('--blur', 'none'); - } - }, { immediate: true }); - - let reloadDialogShowing = false; - stream.on('_disconnected_', async () => { - if (defaultStore.state.serverDisconnectedBehavior === 'reload') { - location.reload(); - } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { - if (reloadDialogShowing) return; - reloadDialogShowing = true; - const { canceled } = await confirm({ - type: 'warning', - title: i18n.ts.disconnectedFromServer, - text: i18n.ts.reloadConfirm, - }); - reloadDialogShowing = false; - if (!canceled) { - location.reload(); - } - } - }); - - stream.on('emojiAdded', emojiData => { - // TODO - //store.commit('instance/set', ); - }); - - for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { - import('./plugin').then(({ install }) => { - install(plugin); - }); - } - - const hotkeys = { - 'd': (): void => { - defaultStore.set('darkMode', !defaultStore.state.darkMode); - }, - 's': search, - }; - - if ($i) { - // only add post shortcuts if logged in - hotkeys['p|n'] = post; - - if ($i.isDeleted) { - alert({ - type: 'warning', - text: i18n.ts.accountDeletionInProgress, - }); - } - - const lastUsed = localStorage.getItem('lastUsed'); - if (lastUsed) { - const lastUsedDate = parseInt(lastUsed, 10); - // 二時間以上前なら - if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { - toast(i18n.t('welcomeBackWithName', { - name: $i.name || $i.username, - })); - } - } - localStorage.setItem('lastUsed', Date.now().toString()); - - if ('Notification' in window) { - // 許可を得ていなかったらリクエスト - if (Notification.permission === 'default') { - Notification.requestPermission(); - } - } - - const main = markRaw(stream.useChannel('main', null, 'System')); - - // 自分の情報が更新されたとき - main.on('meUpdated', i => { - updateAccount(i); - }); - - main.on('readAllNotifications', () => { - updateAccount({ hasUnreadNotification: false }); - }); - - main.on('unreadNotification', () => { - updateAccount({ hasUnreadNotification: true }); - }); - - main.on('unreadMention', () => { - updateAccount({ hasUnreadMentions: true }); - }); - - main.on('readAllUnreadMentions', () => { - updateAccount({ hasUnreadMentions: false }); - }); - - main.on('unreadSpecifiedNote', () => { - updateAccount({ hasUnreadSpecifiedNotes: true }); - }); - - main.on('readAllUnreadSpecifiedNotes', () => { - updateAccount({ hasUnreadSpecifiedNotes: false }); - }); - - main.on('readAllMessagingMessages', () => { - updateAccount({ hasUnreadMessagingMessage: false }); - }); - - main.on('unreadMessagingMessage', () => { - updateAccount({ hasUnreadMessagingMessage: true }); - sound.play('chatBg'); - }); - - main.on('readAllAntennas', () => { - updateAccount({ hasUnreadAntenna: false }); - }); - - main.on('unreadAntenna', () => { - updateAccount({ hasUnreadAntenna: true }); - sound.play('antenna'); - }); - - main.on('readAllAnnouncements', () => { - updateAccount({ hasUnreadAnnouncement: false }); - }); - - main.on('readAllChannels', () => { - updateAccount({ hasUnreadChannel: false }); - }); - - main.on('unreadChannel', () => { - updateAccount({ hasUnreadChannel: true }); - sound.play('channel'); - }); - - // トークンが再生成されたとき - // このままではMisskeyが利用できないので強制的にサインアウトさせる - main.on('myTokenRegenerated', () => { - signout(); - }); - } - - // shortcut - document.addEventListener('keydown', makeHotkey(hotkeys)); + const rootEl = document.createElement('div'); + rootEl.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(rootEl); + return rootEl; })(); + +app.mount(rootEl); + +// boot.jsのやつを解除 +window.onerror = null; +window.onunhandledrejection = null; + +reactionPicker.init(); + +if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; +} + +// クライアントが更新されたか? +const lastVersion = localStorage.getItem('lastVersion'); +if (lastVersion !== version) { + localStorage.setItem('lastVersion', version); + + // テーマリビルドするため + localStorage.removeItem('theme'); + + try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため + if (lastVersion != null && compareVersions(version, lastVersion) === 1) { + // ログインしてる場合だけ + if ($i) { + popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); + } + } + } catch (err) { + } +} + +// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) +watch(defaultStore.reactiveState.darkMode, (darkMode) => { + applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); +}, { immediate: localStorage.theme == null }); + +const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); +const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); + +watch(darkTheme, (theme) => { + if (defaultStore.state.darkMode) { + applyTheme(theme); + } +}); + +watch(lightTheme, (theme) => { + if (!defaultStore.state.darkMode) { + applyTheme(theme); + } +}); + +//#region Sync dark mode +if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', isDeviceDarkmode()); +} + +window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { + if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', mql.matches); + } +}); +//#endregion + +fetchInstanceMetaPromise.then(() => { + if (defaultStore.state.themeInitial) { + if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); + if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme)); + defaultStore.set('themeInitial', false); + } +}); + +watch(defaultStore.reactiveState.useBlurEffectForModal, v => { + document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); +}, { immediate: true }); + +watch(defaultStore.reactiveState.useBlurEffect, v => { + if (v) { + document.documentElement.style.removeProperty('--blur'); + } else { + document.documentElement.style.setProperty('--blur', 'none'); + } +}, { immediate: true }); + +let reloadDialogShowing = false; +stream.on('_disconnected_', async () => { + if (defaultStore.state.serverDisconnectedBehavior === 'reload') { + location.reload(); + } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { + if (reloadDialogShowing) return; + reloadDialogShowing = true; + const { canceled } = await confirm({ + type: 'warning', + title: i18n.ts.disconnectedFromServer, + text: i18n.ts.reloadConfirm, + }); + reloadDialogShowing = false; + if (!canceled) { + location.reload(); + } + } +}); + +stream.on('emojiAdded', emojiData => { + // TODO + //store.commit('instance/set', ); +}); + +for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { + import('./plugin').then(({ install }) => { + install(plugin); + }); +} + +const hotkeys = { + 'd': (): void => { + defaultStore.set('darkMode', !defaultStore.state.darkMode); + }, + 's': search, +}; + +if ($i) { + // only add post shortcuts if logged in + hotkeys['p|n'] = post; + + if ($i.isDeleted) { + alert({ + type: 'warning', + text: i18n.ts.accountDeletionInProgress, + }); + } + + const lastUsed = localStorage.getItem('lastUsed'); + if (lastUsed) { + const lastUsedDate = parseInt(lastUsed, 10); + // 二時間以上前なら + if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { + toast(i18n.t('welcomeBackWithName', { + name: $i.name || $i.username, + })); + } + } + localStorage.setItem('lastUsed', Date.now().toString()); + + if ('Notification' in window) { + // 許可を得ていなかったらリクエスト + if (Notification.permission === 'default') { + Notification.requestPermission(); + } + } + + const main = markRaw(stream.useChannel('main', null, 'System')); + + // 自分の情報が更新されたとき + main.on('meUpdated', i => { + updateAccount(i); + }); + + main.on('readAllNotifications', () => { + updateAccount({ hasUnreadNotification: false }); + }); + + main.on('unreadNotification', () => { + updateAccount({ hasUnreadNotification: true }); + }); + + main.on('unreadMention', () => { + updateAccount({ hasUnreadMentions: true }); + }); + + main.on('readAllUnreadMentions', () => { + updateAccount({ hasUnreadMentions: false }); + }); + + main.on('unreadSpecifiedNote', () => { + updateAccount({ hasUnreadSpecifiedNotes: true }); + }); + + main.on('readAllUnreadSpecifiedNotes', () => { + updateAccount({ hasUnreadSpecifiedNotes: false }); + }); + + main.on('readAllMessagingMessages', () => { + updateAccount({ hasUnreadMessagingMessage: false }); + }); + + main.on('unreadMessagingMessage', () => { + updateAccount({ hasUnreadMessagingMessage: true }); + sound.play('chatBg'); + }); + + main.on('readAllAntennas', () => { + updateAccount({ hasUnreadAntenna: false }); + }); + + main.on('unreadAntenna', () => { + updateAccount({ hasUnreadAntenna: true }); + sound.play('antenna'); + }); + + main.on('readAllAnnouncements', () => { + updateAccount({ hasUnreadAnnouncement: false }); + }); + + main.on('readAllChannels', () => { + updateAccount({ hasUnreadChannel: false }); + }); + + main.on('unreadChannel', () => { + updateAccount({ hasUnreadChannel: true }); + sound.play('channel'); + }); + + // トークンが再生成されたとき + // このままではMisskeyが利用できないので強制的にサインアウトさせる + main.on('myTokenRegenerated', () => { + signout(); + }); +} + +// shortcut +document.addEventListener('keydown', makeHotkey(hotkeys)); diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index f23c621131..5800cf5021 100644 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -49,7 +49,6 @@ export default defineConfig(({ command, mode }) => { 'chrome100', 'firefox100', 'safari15', - 'es2017', // TODO: そのうち消す ], manifest: 'manifest.json', rollupOptions: { From 1dec3461cda82802acb45fd9a89933f8940b3d62 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:06:38 +0900 Subject: [PATCH 08/36] 12.114.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93df550f84..6aa9913746 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.113.0", + "version": "12.114.0-beta.1", "codename": "indigo", "repository": { "type": "git", From fa5140310f05c00d641e794329073eb7d44f6825 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:32:21 +0900 Subject: [PATCH 09/36] debug --- packages/client/src/init.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 98f69c701f..243d287543 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -74,12 +74,14 @@ if (_DEV_) { // タッチデバイスでCSSの:hoverを機能させる document.addEventListener('touchend', () => {}, { passive: true }); +console.info('1'); // 一斉リロード reloadChannel.addEventListener('message', path => { if (path !== null) location.href = path; else location.reload(); }); +console.info('2'); //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // TODO: いつの日にか消したい @@ -90,6 +92,7 @@ window.addEventListener('resize', () => { document.documentElement.style.setProperty('--vh', `${vh}px`); }); //#endregion +console.info('3'); // If mobile, insert the viewport meta tag if (['smartphone', 'tablet'].includes(deviceKind)) { @@ -97,15 +100,18 @@ if (['smartphone', 'tablet'].includes(deviceKind)) { viewport.setAttribute('content', `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); } +console.info('4'); //#region Set lang attr const html = document.documentElement; html.setAttribute('lang', lang); //#endregion +console.info('5'); //#region loginId const params = new URLSearchParams(location.search); const loginId = params.get('loginId'); +console.info('6', loginId); if (loginId) { const target = getUrlWithoutLoginId(location.href); @@ -119,6 +125,7 @@ if (loginId) { history.replaceState({ misskey: 'loginId' }, '', target); } +console.info('7'); //#endregion @@ -127,12 +134,14 @@ if ($i && $i.token) { if (_DEV_) { console.log('account cache found. refreshing...'); } + console.info('8'); refreshAccount(); } else { if (_DEV_) { console.log('no account cache found.'); } + console.info('9'); // 連携ログインの場合用にCookieを参照する const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; @@ -166,6 +175,7 @@ fetchInstanceMetaPromise.then(() => { // Init service worker initializeSw(); }); +console.info('10'); const app = createApp( window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : @@ -175,6 +185,8 @@ const app = createApp( defineAsyncComponent(() => import('@/ui/universal.vue')), ); +console.info('11'); + if (_DEV_) { app.config.performance = true; } @@ -191,12 +203,16 @@ widgets(app); directives(app); components(app); +console.info('12'); + const splash = document.getElementById('splash'); // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) if (splash) splash.addEventListener('transitionend', () => { splash.remove(); }); +console.info('13'); + // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する const rootEl = (() => { @@ -215,19 +231,27 @@ const rootEl = (() => { return rootEl; })(); +console.info('14'); + app.mount(rootEl); +console.info('15'); + // boot.jsのやつを解除 window.onerror = null; window.onunhandledrejection = null; reactionPicker.init(); +console.info('16'); + if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; } +console.info('17'); + // クライアントが更新されたか? const lastVersion = localStorage.getItem('lastVersion'); if (lastVersion !== version) { @@ -247,6 +271,8 @@ if (lastVersion !== version) { } } +console.info('18'); + // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) watch(defaultStore.reactiveState.darkMode, (darkMode) => { applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); @@ -267,6 +293,8 @@ watch(lightTheme, (theme) => { } }); +console.info('19'); + //#region Sync dark mode if (ColdDeviceStorage.get('syncDeviceDarkMode')) { defaultStore.set('darkMode', isDeviceDarkmode()); @@ -279,6 +307,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { }); //#endregion +console.info('20'); + fetchInstanceMetaPromise.then(() => { if (defaultStore.state.themeInitial) { if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); From 7a43cac6b359b01275b93386505a949fc0225709 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:32:33 +0900 Subject: [PATCH 10/36] 12.114.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6aa9913746..bf2f681e39 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.1", + "version": "12.114.0-beta.2", "codename": "indigo", "repository": { "type": "git", From 800bbc4328e27944e785d2ad50da0293df0ee114 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:40:30 +0900 Subject: [PATCH 11/36] debug --- packages/client/src/init.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 243d287543..c826b6a33b 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -216,18 +216,26 @@ console.info('13'); // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する const rootEl = (() => { + console.info('13-1'); const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + console.info('13-2'); const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + console.info('13-3'); if (currentEl) { + console.info('13-4'); console.warn('multiple import detected'); return currentEl; } + console.info('13-5'); const rootEl = document.createElement('div'); + console.info('13-6'); rootEl.id = MISSKEY_MOUNT_DIV_ID; + console.info('13-7'); document.body.appendChild(rootEl); + console.info('13-8'); return rootEl; })(); From d43eb123b1dc88c044fdf83a6e25703d9eab46c4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:40:55 +0900 Subject: [PATCH 12/36] 12.114.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf2f681e39..cc83a9f467 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.2", + "version": "12.114.0-beta.3", "codename": "indigo", "repository": { "type": "git", From 10f4815d34817c4cb8f8857ef017b426c2457ef1 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 21:52:58 +0900 Subject: [PATCH 13/36] tweak boot.js --- packages/backend/src/server/web/boot.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index ca5f0639f8..7357081425 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -49,7 +49,7 @@ localStorage.setItem('localeVersion', v); } else { await checkUpdate(); - renderError('LOCALE_FETCH_FAILED'); + renderError('LOCALE_FETCH'); return; } } @@ -60,7 +60,7 @@ .catch(async e => { await checkUpdate(); console.error(e); - renderError('APP_FETCH_FAILED', e); + renderError('APP_IMPORT', e); }) //#endregion @@ -115,7 +115,7 @@ let errorsElement = document.getElementById('errors'); if (!errorsElement) { - document.documentElement.innerHTML = ` + document.body.innerHTML = ` <svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 9v2m0 4v.01"></path> @@ -286,7 +286,7 @@ } } catch (e) { console.error(e); - renderError('UPDATE_CHECK_FAILED', e); + renderError('UPDATE_CHECK', e); throw e; } } From f2e91f4d629cd40c8f956cca2d30058048192dfd Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:04:16 +0900 Subject: [PATCH 14/36] tweak boot.js --- packages/backend/src/server/web/boot.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 7357081425..b9f00bb21d 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -56,12 +56,14 @@ //#endregion //#region Script - import(`/assets/${CLIENT_ENTRY}`) - .catch(async e => { - await checkUpdate(); - console.error(e); - renderError('APP_IMPORT', e); - }) + window.addEventListener('DOMContentLoaded', () => { + import(`/assets/${CLIENT_ENTRY}`) + .catch(async e => { + await checkUpdate(); + console.error(e); + renderError('APP_IMPORT', e); + }); + }); //#endregion //#region Theme From 3053767c7193c0e8eeb0ccfcdd9167b45bb193d9 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:05:03 +0900 Subject: [PATCH 15/36] 12.114.0-beta.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cc83a9f467..3ac56e73f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.3", + "version": "12.114.0-beta.4", "codename": "indigo", "repository": { "type": "git", From 1ba559a98bfb389b94e75441ef835fe93d097029 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:13:00 +0900 Subject: [PATCH 16/36] Revert "debug" This reverts commit 800bbc4328e27944e785d2ad50da0293df0ee114. --- packages/client/src/init.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index c826b6a33b..243d287543 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -216,26 +216,18 @@ console.info('13'); // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する const rootEl = (() => { - console.info('13-1'); const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - console.info('13-2'); const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); - console.info('13-3'); if (currentEl) { - console.info('13-4'); console.warn('multiple import detected'); return currentEl; } - console.info('13-5'); const rootEl = document.createElement('div'); - console.info('13-6'); rootEl.id = MISSKEY_MOUNT_DIV_ID; - console.info('13-7'); document.body.appendChild(rootEl); - console.info('13-8'); return rootEl; })(); From e3bad795e057333019f937abd64270dd7f5bac4e Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:13:04 +0900 Subject: [PATCH 17/36] Revert "debug" This reverts commit fa5140310f05c00d641e794329073eb7d44f6825. --- packages/client/src/init.ts | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 243d287543..98f69c701f 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -74,14 +74,12 @@ if (_DEV_) { // タッチデバイスでCSSの:hoverを機能させる document.addEventListener('touchend', () => {}, { passive: true }); -console.info('1'); // 一斉リロード reloadChannel.addEventListener('message', path => { if (path !== null) location.href = path; else location.reload(); }); -console.info('2'); //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // TODO: いつの日にか消したい @@ -92,7 +90,6 @@ window.addEventListener('resize', () => { document.documentElement.style.setProperty('--vh', `${vh}px`); }); //#endregion -console.info('3'); // If mobile, insert the viewport meta tag if (['smartphone', 'tablet'].includes(deviceKind)) { @@ -100,18 +97,15 @@ if (['smartphone', 'tablet'].includes(deviceKind)) { viewport.setAttribute('content', `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); } -console.info('4'); //#region Set lang attr const html = document.documentElement; html.setAttribute('lang', lang); //#endregion -console.info('5'); //#region loginId const params = new URLSearchParams(location.search); const loginId = params.get('loginId'); -console.info('6', loginId); if (loginId) { const target = getUrlWithoutLoginId(location.href); @@ -125,7 +119,6 @@ if (loginId) { history.replaceState({ misskey: 'loginId' }, '', target); } -console.info('7'); //#endregion @@ -134,14 +127,12 @@ if ($i && $i.token) { if (_DEV_) { console.log('account cache found. refreshing...'); } - console.info('8'); refreshAccount(); } else { if (_DEV_) { console.log('no account cache found.'); } - console.info('9'); // 連携ログインの場合用にCookieを参照する const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; @@ -175,7 +166,6 @@ fetchInstanceMetaPromise.then(() => { // Init service worker initializeSw(); }); -console.info('10'); const app = createApp( window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : @@ -185,8 +175,6 @@ const app = createApp( defineAsyncComponent(() => import('@/ui/universal.vue')), ); -console.info('11'); - if (_DEV_) { app.config.performance = true; } @@ -203,16 +191,12 @@ widgets(app); directives(app); components(app); -console.info('12'); - const splash = document.getElementById('splash'); // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) if (splash) splash.addEventListener('transitionend', () => { splash.remove(); }); -console.info('13'); - // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する const rootEl = (() => { @@ -231,27 +215,19 @@ const rootEl = (() => { return rootEl; })(); -console.info('14'); - app.mount(rootEl); -console.info('15'); - // boot.jsのやつを解除 window.onerror = null; window.onunhandledrejection = null; reactionPicker.init(); -console.info('16'); - if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; } -console.info('17'); - // クライアントが更新されたか? const lastVersion = localStorage.getItem('lastVersion'); if (lastVersion !== version) { @@ -271,8 +247,6 @@ if (lastVersion !== version) { } } -console.info('18'); - // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) watch(defaultStore.reactiveState.darkMode, (darkMode) => { applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); @@ -293,8 +267,6 @@ watch(lightTheme, (theme) => { } }); -console.info('19'); - //#region Sync dark mode if (ColdDeviceStorage.get('syncDeviceDarkMode')) { defaultStore.set('darkMode', isDeviceDarkmode()); @@ -307,8 +279,6 @@ window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { }); //#endregion -console.info('20'); - fetchInstanceMetaPromise.then(() => { if (defaultStore.state.themeInitial) { if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); From 17500fc9c9fad0f4b00d8ce070597fb86d0b50fa Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:14:42 +0900 Subject: [PATCH 18/36] Revert "fix(client): revert es2017" This reverts commit 0ddabdbf68767566fd8f4fdeab1de82535dc44cb. --- packages/client/src/init.ts | 728 +++++++++++++++++---------------- packages/client/vite.config.ts | 1 + 2 files changed, 366 insertions(+), 363 deletions(-) diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index 98f69c701f..94e7f9f6b3 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -39,401 +39,403 @@ import { reactionPicker } from '@/scripts/reaction-picker'; import { getUrlWithoutLoginId } from '@/scripts/login-id'; import { getAccountFromId } from '@/scripts/get-account-from-id'; -console.info(`Misskey v${version}`); +(async () => { + console.info(`Misskey v${version}`); -if (_DEV_) { - console.warn('Development mode!!!'); + if (_DEV_) { + console.warn('Development mode!!!'); - console.info(`vue ${vueVersion}`); + console.info(`vue ${vueVersion}`); - (window as any).$i = $i; - (window as any).$store = defaultStore; + (window as any).$i = $i; + (window as any).$store = defaultStore; - window.addEventListener('error', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled error', - text: event.message + window.addEventListener('error', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled error', + text: event.message + }); + */ }); - */ + + window.addEventListener('unhandledrejection', event => { + console.error(event); + /* + alert({ + type: 'error', + title: 'DEV: Unhandled promise rejection', + text: event.reason + }); + */ + }); + } + + // タッチデバイスでCSSの:hoverを機能させる + document.addEventListener('touchend', () => {}, { passive: true }); + + // 一斉リロード + reloadChannel.addEventListener('message', path => { + if (path !== null) location.href = path; + else location.reload(); }); - window.addEventListener('unhandledrejection', event => { - console.error(event); - /* - alert({ - type: 'error', - title: 'DEV: Unhandled promise rejection', - text: event.reason - }); - */ - }); -} - -// タッチデバイスでCSSの:hoverを機能させる -document.addEventListener('touchend', () => {}, { passive: true }); - -// 一斉リロード -reloadChannel.addEventListener('message', path => { - if (path !== null) location.href = path; - else location.reload(); -}); - -//#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ -// TODO: いつの日にか消したい -const vh = window.innerHeight * 0.01; -document.documentElement.style.setProperty('--vh', `${vh}px`); -window.addEventListener('resize', () => { + //#region SEE: https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + // TODO: いつの日にか消したい const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); -}); -//#endregion + window.addEventListener('resize', () => { + const vh = window.innerHeight * 0.01; + document.documentElement.style.setProperty('--vh', `${vh}px`); + }); + //#endregion -// If mobile, insert the viewport meta tag -if (['smartphone', 'tablet'].includes(deviceKind)) { - const viewport = document.getElementsByName('viewport').item(0); - viewport.setAttribute('content', - `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); -} - -//#region Set lang attr -const html = document.documentElement; -html.setAttribute('lang', lang); -//#endregion - -//#region loginId -const params = new URLSearchParams(location.search); -const loginId = params.get('loginId'); - -if (loginId) { - const target = getUrlWithoutLoginId(location.href); - - if (!$i || $i.id !== loginId) { - const account = await getAccountFromId(loginId); - if (account) { - await login(account.token, target); - } + // If mobile, insert the viewport meta tag + if (['smartphone', 'tablet'].includes(deviceKind)) { + const viewport = document.getElementsByName('viewport').item(0); + viewport.setAttribute('content', + `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); } - history.replaceState({ misskey: 'loginId' }, '', target); -} + //#region Set lang attr + const html = document.documentElement; + html.setAttribute('lang', lang); + //#endregion -//#endregion + //#region loginId + const params = new URLSearchParams(location.search); + const loginId = params.get('loginId'); -//#region Fetch user -if ($i && $i.token) { - if (_DEV_) { - console.log('account cache found. refreshing...'); - } + if (loginId) { + const target = getUrlWithoutLoginId(location.href); - refreshAccount(); -} else { - if (_DEV_) { - console.log('no account cache found.'); - } - - // 連携ログインの場合用にCookieを参照する - const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; - - if (i != null && i !== 'null') { - if (_DEV_) { - console.log('signing...'); - } - - try { - document.body.innerHTML = '<div>Please wait...</div>'; - await login(i); - } catch (err) { - // Render the error screen - // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな) - document.body.innerHTML = '<div id="err">Oops!</div>'; - } - } else { - if (_DEV_) { - console.log('not signed in'); - } - } -} -//#endregion - -const fetchInstanceMetaPromise = fetchInstance(); - -fetchInstanceMetaPromise.then(() => { - localStorage.setItem('v', instance.version); - - // Init service worker - initializeSw(); -}); - -const app = createApp( - window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : - !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : - ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : - ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : - defineAsyncComponent(() => import('@/ui/universal.vue')), -); - -if (_DEV_) { - app.config.performance = true; -} - -app.config.globalProperties = { - $i, - $store: defaultStore, - $instance: instance, - $t: i18n.t, - $ts: i18n.ts, -}; - -widgets(app); -directives(app); -components(app); - -const splash = document.getElementById('splash'); -// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) -if (splash) splash.addEventListener('transitionend', () => { - splash.remove(); -}); - -// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 -// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する -const rootEl = (() => { - const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - - const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); - - if (currentEl) { - console.warn('multiple import detected'); - return currentEl; - } - - const rootEl = document.createElement('div'); - rootEl.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(rootEl); - return rootEl; -})(); - -app.mount(rootEl); - -// boot.jsのやつを解除 -window.onerror = null; -window.onunhandledrejection = null; - -reactionPicker.init(); - -if (splash) { - splash.style.opacity = '0'; - splash.style.pointerEvents = 'none'; -} - -// クライアントが更新されたか? -const lastVersion = localStorage.getItem('lastVersion'); -if (lastVersion !== version) { - localStorage.setItem('lastVersion', version); - - // テーマリビルドするため - localStorage.removeItem('theme'); - - try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため - if (lastVersion != null && compareVersions(version, lastVersion) === 1) { - // ログインしてる場合だけ - if ($i) { - popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); + if (!$i || $i.id !== loginId) { + const account = await getAccountFromId(loginId); + if (account) { + await login(account.token, target); } } - } catch (err) { + + history.replaceState({ misskey: 'loginId' }, '', target); } -} -// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) -watch(defaultStore.reactiveState.darkMode, (darkMode) => { - applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); -}, { immediate: localStorage.theme == null }); + //#endregion -const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); -const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); + //#region Fetch user + if ($i && $i.token) { + if (_DEV_) { + console.log('account cache found. refreshing...'); + } -watch(darkTheme, (theme) => { - if (defaultStore.state.darkMode) { - applyTheme(theme); - } -}); - -watch(lightTheme, (theme) => { - if (!defaultStore.state.darkMode) { - applyTheme(theme); - } -}); - -//#region Sync dark mode -if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', isDeviceDarkmode()); -} - -window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { - if (ColdDeviceStorage.get('syncDeviceDarkMode')) { - defaultStore.set('darkMode', mql.matches); - } -}); -//#endregion - -fetchInstanceMetaPromise.then(() => { - if (defaultStore.state.themeInitial) { - if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme)); - defaultStore.set('themeInitial', false); - } -}); - -watch(defaultStore.reactiveState.useBlurEffectForModal, v => { - document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); -}, { immediate: true }); - -watch(defaultStore.reactiveState.useBlurEffect, v => { - if (v) { - document.documentElement.style.removeProperty('--blur'); + refreshAccount(); } else { - document.documentElement.style.setProperty('--blur', 'none'); - } -}, { immediate: true }); + if (_DEV_) { + console.log('no account cache found.'); + } -let reloadDialogShowing = false; -stream.on('_disconnected_', async () => { - if (defaultStore.state.serverDisconnectedBehavior === 'reload') { - location.reload(); - } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { - if (reloadDialogShowing) return; - reloadDialogShowing = true; - const { canceled } = await confirm({ - type: 'warning', - title: i18n.ts.disconnectedFromServer, - text: i18n.ts.reloadConfirm, - }); - reloadDialogShowing = false; - if (!canceled) { + // 連携ログインの場合用にCookieを参照する + const i = (document.cookie.match(/igi=(\w+)/) || [null, null])[1]; + + if (i != null && i !== 'null') { + if (_DEV_) { + console.log('signing...'); + } + + try { + document.body.innerHTML = '<div>Please wait...</div>'; + await login(i); + } catch (err) { + // Render the error screen + // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな) + document.body.innerHTML = '<div id="err">Oops!</div>'; + } + } else { + if (_DEV_) { + console.log('not signed in'); + } + } + } + //#endregion + + const fetchInstanceMetaPromise = fetchInstance(); + + fetchInstanceMetaPromise.then(() => { + localStorage.setItem('v', instance.version); + + // Init service worker + initializeSw(); + }); + + const app = createApp( + window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : + !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : + ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : + ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : + defineAsyncComponent(() => import('@/ui/universal.vue')), + ); + + if (_DEV_) { + app.config.performance = true; + } + + app.config.globalProperties = { + $i, + $store: defaultStore, + $instance: instance, + $t: i18n.t, + $ts: i18n.ts, + }; + + widgets(app); + directives(app); + components(app); + + const splash = document.getElementById('splash'); + // 念のためnullチェック(HTMLが古い場合があるため(そのうち消す)) + if (splash) splash.addEventListener('transitionend', () => { + splash.remove(); + }); + + // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 + // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する + const rootEl = (() => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentEl) { + console.warn('multiple import detected'); + return currentEl; + } + + const rootEl = document.createElement('div'); + rootEl.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(rootEl); + return rootEl; + })(); + + app.mount(rootEl); + + // boot.jsのやつを解除 + window.onerror = null; + window.onunhandledrejection = null; + + reactionPicker.init(); + + if (splash) { + splash.style.opacity = '0'; + splash.style.pointerEvents = 'none'; + } + + // クライアントが更新されたか? + const lastVersion = localStorage.getItem('lastVersion'); + if (lastVersion !== version) { + localStorage.setItem('lastVersion', version); + + // テーマリビルドするため + localStorage.removeItem('theme'); + + try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため + if (lastVersion != null && compareVersions(version, lastVersion) === 1) { + // ログインしてる場合だけ + if ($i) { + popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); + } + } + } catch (err) { + } + } + + // NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため) + watch(defaultStore.reactiveState.darkMode, (darkMode) => { + applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme')); + }, { immediate: localStorage.theme == null }); + + const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme')); + const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme')); + + watch(darkTheme, (theme) => { + if (defaultStore.state.darkMode) { + applyTheme(theme); + } + }); + + watch(lightTheme, (theme) => { + if (!defaultStore.state.darkMode) { + applyTheme(theme); + } + }); + + //#region Sync dark mode + if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', isDeviceDarkmode()); + } + + window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => { + if (ColdDeviceStorage.get('syncDeviceDarkMode')) { + defaultStore.set('darkMode', mql.matches); + } + }); + //#endregion + + fetchInstanceMetaPromise.then(() => { + if (defaultStore.state.themeInitial) { + if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme)); + if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme)); + defaultStore.set('themeInitial', false); + } + }); + + watch(defaultStore.reactiveState.useBlurEffectForModal, v => { + document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); + }, { immediate: true }); + + watch(defaultStore.reactiveState.useBlurEffect, v => { + if (v) { + document.documentElement.style.removeProperty('--blur'); + } else { + document.documentElement.style.setProperty('--blur', 'none'); + } + }, { immediate: true }); + + let reloadDialogShowing = false; + stream.on('_disconnected_', async () => { + if (defaultStore.state.serverDisconnectedBehavior === 'reload') { location.reload(); + } else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') { + if (reloadDialogShowing) return; + reloadDialogShowing = true; + const { canceled } = await confirm({ + type: 'warning', + title: i18n.ts.disconnectedFromServer, + text: i18n.ts.reloadConfirm, + }); + reloadDialogShowing = false; + if (!canceled) { + location.reload(); + } } - } -}); - -stream.on('emojiAdded', emojiData => { - // TODO - //store.commit('instance/set', ); -}); - -for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { - import('./plugin').then(({ install }) => { - install(plugin); }); -} -const hotkeys = { - 'd': (): void => { - defaultStore.set('darkMode', !defaultStore.state.darkMode); - }, - 's': search, -}; + stream.on('emojiAdded', emojiData => { + // TODO + //store.commit('instance/set', ); + }); -if ($i) { - // only add post shortcuts if logged in - hotkeys['p|n'] = post; - - if ($i.isDeleted) { - alert({ - type: 'warning', - text: i18n.ts.accountDeletionInProgress, + for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) { + import('./plugin').then(({ install }) => { + install(plugin); }); } - const lastUsed = localStorage.getItem('lastUsed'); - if (lastUsed) { - const lastUsedDate = parseInt(lastUsed, 10); - // 二時間以上前なら - if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { - toast(i18n.t('welcomeBackWithName', { - name: $i.name || $i.username, - })); - } - } - localStorage.setItem('lastUsed', Date.now().toString()); + const hotkeys = { + 'd': (): void => { + defaultStore.set('darkMode', !defaultStore.state.darkMode); + }, + 's': search, + }; - if ('Notification' in window) { - // 許可を得ていなかったらリクエスト - if (Notification.permission === 'default') { - Notification.requestPermission(); + if ($i) { + // only add post shortcuts if logged in + hotkeys['p|n'] = post; + + if ($i.isDeleted) { + alert({ + type: 'warning', + text: i18n.ts.accountDeletionInProgress, + }); } + + const lastUsed = localStorage.getItem('lastUsed'); + if (lastUsed) { + const lastUsedDate = parseInt(lastUsed, 10); + // 二時間以上前なら + if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) { + toast(i18n.t('welcomeBackWithName', { + name: $i.name || $i.username, + })); + } + } + localStorage.setItem('lastUsed', Date.now().toString()); + + if ('Notification' in window) { + // 許可を得ていなかったらリクエスト + if (Notification.permission === 'default') { + Notification.requestPermission(); + } + } + + const main = markRaw(stream.useChannel('main', null, 'System')); + + // 自分の情報が更新されたとき + main.on('meUpdated', i => { + updateAccount(i); + }); + + main.on('readAllNotifications', () => { + updateAccount({ hasUnreadNotification: false }); + }); + + main.on('unreadNotification', () => { + updateAccount({ hasUnreadNotification: true }); + }); + + main.on('unreadMention', () => { + updateAccount({ hasUnreadMentions: true }); + }); + + main.on('readAllUnreadMentions', () => { + updateAccount({ hasUnreadMentions: false }); + }); + + main.on('unreadSpecifiedNote', () => { + updateAccount({ hasUnreadSpecifiedNotes: true }); + }); + + main.on('readAllUnreadSpecifiedNotes', () => { + updateAccount({ hasUnreadSpecifiedNotes: false }); + }); + + main.on('readAllMessagingMessages', () => { + updateAccount({ hasUnreadMessagingMessage: false }); + }); + + main.on('unreadMessagingMessage', () => { + updateAccount({ hasUnreadMessagingMessage: true }); + sound.play('chatBg'); + }); + + main.on('readAllAntennas', () => { + updateAccount({ hasUnreadAntenna: false }); + }); + + main.on('unreadAntenna', () => { + updateAccount({ hasUnreadAntenna: true }); + sound.play('antenna'); + }); + + main.on('readAllAnnouncements', () => { + updateAccount({ hasUnreadAnnouncement: false }); + }); + + main.on('readAllChannels', () => { + updateAccount({ hasUnreadChannel: false }); + }); + + main.on('unreadChannel', () => { + updateAccount({ hasUnreadChannel: true }); + sound.play('channel'); + }); + + // トークンが再生成されたとき + // このままではMisskeyが利用できないので強制的にサインアウトさせる + main.on('myTokenRegenerated', () => { + signout(); + }); } - const main = markRaw(stream.useChannel('main', null, 'System')); - - // 自分の情報が更新されたとき - main.on('meUpdated', i => { - updateAccount(i); - }); - - main.on('readAllNotifications', () => { - updateAccount({ hasUnreadNotification: false }); - }); - - main.on('unreadNotification', () => { - updateAccount({ hasUnreadNotification: true }); - }); - - main.on('unreadMention', () => { - updateAccount({ hasUnreadMentions: true }); - }); - - main.on('readAllUnreadMentions', () => { - updateAccount({ hasUnreadMentions: false }); - }); - - main.on('unreadSpecifiedNote', () => { - updateAccount({ hasUnreadSpecifiedNotes: true }); - }); - - main.on('readAllUnreadSpecifiedNotes', () => { - updateAccount({ hasUnreadSpecifiedNotes: false }); - }); - - main.on('readAllMessagingMessages', () => { - updateAccount({ hasUnreadMessagingMessage: false }); - }); - - main.on('unreadMessagingMessage', () => { - updateAccount({ hasUnreadMessagingMessage: true }); - sound.play('chatBg'); - }); - - main.on('readAllAntennas', () => { - updateAccount({ hasUnreadAntenna: false }); - }); - - main.on('unreadAntenna', () => { - updateAccount({ hasUnreadAntenna: true }); - sound.play('antenna'); - }); - - main.on('readAllAnnouncements', () => { - updateAccount({ hasUnreadAnnouncement: false }); - }); - - main.on('readAllChannels', () => { - updateAccount({ hasUnreadChannel: false }); - }); - - main.on('unreadChannel', () => { - updateAccount({ hasUnreadChannel: true }); - sound.play('channel'); - }); - - // トークンが再生成されたとき - // このままではMisskeyが利用できないので強制的にサインアウトさせる - main.on('myTokenRegenerated', () => { - signout(); - }); -} - -// shortcut -document.addEventListener('keydown', makeHotkey(hotkeys)); + // shortcut + document.addEventListener('keydown', makeHotkey(hotkeys)); +})(); diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts index 5800cf5021..f23c621131 100644 --- a/packages/client/vite.config.ts +++ b/packages/client/vite.config.ts @@ -49,6 +49,7 @@ export default defineConfig(({ command, mode }) => { 'chrome100', 'firefox100', 'safari15', + 'es2017', // TODO: そのうち消す ], manifest: 'manifest.json', rollupOptions: { From 5fa8c62305291d40be1371a7b749722b2119351f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:15:08 +0900 Subject: [PATCH 19/36] 12.114.0-beta.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ac56e73f1..9a5fe0ea59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.4", + "version": "12.114.0-beta.5", "codename": "indigo", "repository": { "type": "git", From 44f560b453fe1161cc6e1dcad6e48fd0ac812cd4 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:25:57 +0900 Subject: [PATCH 20/36] tweak boot.js --- packages/backend/src/server/web/boot.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index b9f00bb21d..9570115423 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -56,14 +56,23 @@ //#endregion //#region Script - window.addEventListener('DOMContentLoaded', () => { + function importAppScript() { import(`/assets/${CLIENT_ENTRY}`) .catch(async e => { await checkUpdate(); console.error(e); renderError('APP_IMPORT', e); }); - }); + } + + // タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある + if (document.readyState !== 'loading') { + importAppScript(); + } else { + window.addEventListener('DOMContentLoaded', () => { + importAppScript(); + }); + } //#endregion //#region Theme From c30ffec1aff4bee5858f936e6dc89fbf066b2ebe Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 22:26:02 +0900 Subject: [PATCH 21/36] 12.114.0-beta.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a5fe0ea59..133e885aa1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.5", + "version": "12.114.0-beta.6", "codename": "indigo", "repository": { "type": "git", From 7b7fe019c09402538c5959e8fe604123aaa83590 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 23:24:10 +0900 Subject: [PATCH 22/36] Update CHANGELOG.md --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91d4edf120..2e1666b080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ You should also include the user name that made the change. --> +## 12.x.x (unreleased) + +### Improvements + +### Bugfixes +- クライアントが起動しなくなることがある問題を修正 @syuilo + ## 12.113.0 (2022/07/13) ### Improvements From de241319933eeb2601d9bf535d60a3c6e32cb8d6 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 14 Jul 2022 23:31:01 +0900 Subject: [PATCH 23/36] use MkStickyContainer (#9000) --- packages/client/src/directives/index.ts | 2 -- .../client/src/directives/sticky-container.ts | 17 ----------- .../client/src/pages/user/index.timeline.vue | 28 +++++++++---------- 3 files changed, 14 insertions(+), 33 deletions(-) delete mode 100644 packages/client/src/directives/sticky-container.ts diff --git a/packages/client/src/directives/index.ts b/packages/client/src/directives/index.ts index fc9b6f86da..401a917cba 100644 --- a/packages/client/src/directives/index.ts +++ b/packages/client/src/directives/index.ts @@ -8,7 +8,6 @@ import tooltip from './tooltip'; import hotkey from './hotkey'; import appear from './appear'; import anim from './anim'; -import stickyContainer from './sticky-container'; import clickAnime from './click-anime'; import panel from './panel'; import adaptiveBorder from './adaptive-border'; @@ -24,7 +23,6 @@ export default function(app: App) { app.directive('appear', appear); app.directive('anim', anim); app.directive('click-anime', clickAnime); - app.directive('sticky-container', stickyContainer); app.directive('panel', panel); app.directive('adaptive-border', adaptiveBorder); } diff --git a/packages/client/src/directives/sticky-container.ts b/packages/client/src/directives/sticky-container.ts deleted file mode 100644 index 3cf813054b..0000000000 --- a/packages/client/src/directives/sticky-container.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Directive } from 'vue'; - -export default { - mounted(src, binding, vn) { - //const query = binding.value; - - const header = src.children[0]; - const body = src.children[1]; - const currentStickyTop = getComputedStyle(src).getPropertyValue('--stickyTop') || '0px'; - src.style.setProperty('--stickyTop', `calc(${currentStickyTop} + ${header.offsetHeight}px)`); - if (body) body.dataset.stickyContainerHeaderHeight = header.offsetHeight.toString(); - header.style.setProperty('--stickyTop', currentStickyTop); - header.style.position = 'sticky'; - header.style.top = 'var(--stickyTop)'; - header.style.zIndex = '1'; - }, -} as Directive; diff --git a/packages/client/src/pages/user/index.timeline.vue b/packages/client/src/pages/user/index.timeline.vue index a1329a7411..1bcc0a1b85 100644 --- a/packages/client/src/pages/user/index.timeline.vue +++ b/packages/client/src/pages/user/index.timeline.vue @@ -1,12 +1,14 @@ <template> -<div v-sticky-container class="yrzkoczt"> - <MkTab v-model="include" class="tab"> - <option :value="null">{{ $ts.notes }}</option> - <option value="replies">{{ $ts.notesAndReplies }}</option> - <option value="files">{{ $ts.withFiles }}</option> - </MkTab> +<MkStickyContainer> + <template #header> + <MkTab v-model="include" :class="$style.tab"> + <option :value="null">{{ $ts.notes }}</option> + <option value="replies">{{ $ts.notesAndReplies }}</option> + <option value="files">{{ $ts.withFiles }}</option> + </MkTab> + </template> <XNotes :no-gap="true" :pagination="pagination"/> -</div> +</MkStickyContainer> </template> <script lang="ts" setup> @@ -33,12 +35,10 @@ const pagination = { }; </script> -<style lang="scss" scoped> -.yrzkoczt { - > .tab { - margin: calc(var(--margin) / 2) 0; - padding: calc(var(--margin) / 2) 0; - background: var(--bg); - } +<style lang="scss" module> +.tab { + margin: calc(var(--margin) / 2) 0; + padding: calc(var(--margin) / 2) 0; + background: var(--bg); } </style> From 77c2a7cd71efea318f4fd7ba0ee54b32e9c8f7ba Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 14 Jul 2022 23:32:00 +0900 Subject: [PATCH 24/36] refactor(client): remove useCssModule (#8999) * refactor(client): remove useCssModule() * use MkStickyContainer * Revert "use MkStickyContainer" This reverts commit 639746786bb7e3342db9cbd3452854fc29aacf88. --- packages/client/src/components/global/loading.vue | 4 +--- packages/client/src/components/mention.vue | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 5a7e362fcf..bcc6dfac01 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -16,9 +16,7 @@ </template> <script lang="ts" setup> -import { useCssModule } from 'vue'; - -useCssModule(); +import { } from 'vue'; const props = withDefaults(defineProps<{ inline?: boolean; diff --git a/packages/client/src/components/mention.vue b/packages/client/src/components/mention.vue index cf69437771..3091b435e4 100644 --- a/packages/client/src/components/mention.vue +++ b/packages/client/src/components/mention.vue @@ -16,7 +16,7 @@ <script lang="ts" setup> import { toUnicode } from 'punycode'; -import { useCssModule } from 'vue'; +import { } from 'vue'; import tinycolor from 'tinycolor2'; import { host as localHost } from '@/config'; import { $i } from '@/account'; @@ -37,8 +37,6 @@ const isMe = $i && ( const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention')); bg.setAlpha(0.1); const bgCss = bg.toRgbString(); - -useCssModule(); </script> <style lang="scss" scoped> From ddc899938a295ea83128a3f5fd0b922819242ff9 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 23:50:07 +0900 Subject: [PATCH 25/36] chore(client): improve usability --- packages/client/src/pages/settings/security.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index eb3efa9afb..d926acce5d 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -12,7 +12,7 @@ <FormSection> <template #label>{{ i18n.ts.signinHistory }}</template> - <MkPagination :pagination="pagination"> + <MkPagination :pagination="pagination" disable-auto-load> <template #default="{items}"> <div> <div v-for="item in items" :key="item.id" v-panel class="timnmucd"> From a4b5a0072d427ba2be4170041f98c11f8ea13e44 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Thu, 14 Jul 2022 23:54:52 +0900 Subject: [PATCH 26/36] move rollup to devDeps --- packages/client/package.json | 2 +- packages/client/vite.json5.ts | 52 +++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 8d2efd0b92..94f6688dd2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -56,7 +56,6 @@ "random-seed": "0.3.0", "reflect-metadata": "0.1.13", "rndstr": "1.0.0", - "rollup": "2.76.0", "s-age": "1.1.2", "sass": "1.53.0", "seedrandom": "3.0.5", @@ -102,6 +101,7 @@ "@types/ws": "8.5.3", "@typescript-eslint/eslint-plugin": "5.30.6", "@typescript-eslint/parser": "5.30.6", + "rollup": "2.76.0", "cross-env": "7.0.3", "cypress": "10.3.0", "eslint": "8.19.0", diff --git a/packages/client/vite.json5.ts b/packages/client/vite.json5.ts index 693ee7be06..0a37fbff44 100644 --- a/packages/client/vite.json5.ts +++ b/packages/client/vite.json5.ts @@ -6,33 +6,33 @@ import { createFilter, dataToEsm } from '@rollup/pluginutils'; import { RollupJsonOptions } from '@rollup/plugin-json'; export default function json5(options: RollupJsonOptions = {}): Plugin { - const filter = createFilter(options.include, options.exclude); - const indent = 'indent' in options ? options.indent : '\t'; + const filter = createFilter(options.include, options.exclude); + const indent = 'indent' in options ? options.indent : '\t'; - return { - name: 'json5', + return { + name: 'json5', - // eslint-disable-next-line no-shadow - transform(json, id) { - if (id.slice(-6) !== '.json5' || !filter(id)) return null; + // eslint-disable-next-line no-shadow + transform(json, id) { + if (id.slice(-6) !== '.json5' || !filter(id)) return null; - try { - const parsed = JSON5.parse(json); - return { - code: dataToEsm(parsed, { - preferConst: options.preferConst, - compact: options.compact, - namedExports: options.namedExports, - indent - }), - map: { mappings: '' } - }; - } catch (err) { - const message = 'Could not parse JSON file'; - const position = parseInt(/[\d]/.exec(err.message)[0], 10); - this.warn({ message, id, position }); - return null; - } - } - }; + try { + const parsed = JSON5.parse(json); + return { + code: dataToEsm(parsed, { + preferConst: options.preferConst, + compact: options.compact, + namedExports: options.namedExports, + indent, + }), + map: { mappings: '' }, + }; + } catch (err) { + const message = 'Could not parse JSON file'; + const position = parseInt(/[\d]/.exec(err.message)[0], 10); + this.warn({ message, id, position }); + return null; + } + }, + }; } From 165c4b2c00bf35dcd041bae8330cf236f54e8069 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 17:01:13 +0900 Subject: [PATCH 27/36] chore(client): tweak ui --- packages/client/src/components/note.vue | 2 -- packages/client/src/components/ui/menu.vue | 2 +- packages/client/src/components/ui/window.vue | 2 +- packages/client/src/pages/settings/general.vue | 8 ++++---- packages/client/src/style.scss | 14 +++++++------- packages/client/src/ui/_common_/statusbars.vue | 1 + packages/client/src/ui/deck/column.vue | 3 +-- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index c96cdddef2..27716cf73d 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -592,8 +592,6 @@ function readPromo() { } &.max-width_300px { - font-size: 0.825em; - > .article { > .avatar { width: 44px; diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index 1f3d508975..6ad63c2ad7 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -140,7 +140,7 @@ function focusDown() { width: 100%; box-sizing: border-box; white-space: nowrap; - font-size: 0.85em; + font-size: 0.9em; line-height: 20px; text-align: left; overflow: hidden; diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index 6892b1924e..d155033824 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -393,7 +393,7 @@ export default defineComponent({ border-radius: var(--radius); > .header { - --height: 45px; + --height: 42px; &.mini { --height: 38px; diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index 74fa0bc926..cd2bcc581a 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -56,10 +56,10 @@ <FormRadios v-model="fontSize" class="_formBlock"> <template #label>{{ i18n.ts.fontSize }}</template> - <option value="small"><span style="font-size: 14px;">Aa</span></option> - <option :value="null"><span style="font-size: 16px;">Aa</span></option> - <option value="large"><span style="font-size: 18px;">Aa</span></option> - <option value="veryLarge"><span style="font-size: 20px;">Aa</span></option> + <option :value="null"><span style="font-size: 14px;">Aa</span></option> + <option value="1"><span style="font-size: 15px;">Aa</span></option> + <option value="2"><span style="font-size: 16px;">Aa</span></option> + <option value="3"><span style="font-size: 17px;">Aa</span></option> </FormRadios> </FormSection> diff --git a/packages/client/src/style.scss b/packages/client/src/style.scss index 0f892d2e19..27e33702ad 100644 --- a/packages/client/src/style.scss +++ b/packages/client/src/style.scss @@ -30,7 +30,7 @@ html { overflow: auto; overflow-wrap: break-word; font-family: "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; - font-size: 15px; + font-size: 14px; line-height: 1.35; text-size-adjust: 100%; tab-size: 2; @@ -61,16 +61,16 @@ html { } } - &.f-small { - font-size: 0.9em; + &.f-1 { + font-size: 15px; } - &.f-large { - font-size: 1.1em; + &.f-2 { + font-size: 16px; } - &.f-veryLarge { - font-size: 1.2em; + &.f-3 { + font-size: 17px; } &.useSystemFont { diff --git a/packages/client/src/ui/_common_/statusbars.vue b/packages/client/src/ui/_common_/statusbars.vue index e50b4e54c3..5358b26ece 100644 --- a/packages/client/src/ui/_common_/statusbars.vue +++ b/packages/client/src/ui/_common_/statusbars.vue @@ -28,6 +28,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') <style lang="scss" scoped> .dlrsnxqu { + font-size: 15px; background: var(--panel); > .item { diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index e8e554d72b..4d34ca9b8e 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -23,7 +23,7 @@ <slot name="action"></slot> </div> <span class="header"><slot name="header"></slot></span> - <button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="fas fa-cog"></i></button> + <button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="fas fa-ellipsis"></i></button> </header> <div v-show="active" ref="body"> <slot></slot> @@ -361,7 +361,6 @@ function onDrop(ev) { z-index: 1; width: var(--deckColumnHeaderHeight); line-height: var(--deckColumnHeaderHeight); - font-size: 16px; color: var(--faceTextButton); &:hover { From 6f45208ab63de75023f50984d465f905e82b0003 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 17:12:08 +0900 Subject: [PATCH 28/36] =?UTF-8?q?enhance(client):=20RSS=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=83=E3=82=AB=E3=83=BC=E3=81=A7=E8=A1=A8=E7=A4=BA=E9=A0=86?= =?UTF-8?q?=E5=BA=8F=E3=82=92=E3=82=B7=E3=83=A3=E3=83=83=E3=83=95=E3=83=AB?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + locales/ja-JP.yml | 1 + .../pages/settings/statusbars.statusbar.vue | 4 ++++ packages/client/src/scripts/shuffle.ts | 19 +++++++++++++++++++ .../client/src/ui/_common_/statusbar-rss.vue | 5 +++++ .../client/src/ui/_common_/statusbars.vue | 2 +- packages/client/src/widgets/rss-ticker.vue | 8 ++++++++ 7 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 packages/client/src/scripts/shuffle.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1666b080..efb0d684aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ You should also include the user name that made the change. ## 12.x.x (unreleased) ### Improvements +- RSSティッカーで表示順序をシャッフルできるように @syuilo ### Bugfixes - クライアントが起動しなくなることがある問題を修正 @syuilo diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fd8aca868b..bdff7e38da 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -888,6 +888,7 @@ enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、インスタンスによっては自動で設定されることがあります。" activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" navbar: "ナビゲーションバー" +shuffle: "シャッフル" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/client/src/pages/settings/statusbars.statusbar.vue b/packages/client/src/pages/settings/statusbars.statusbar.vue index e19690209a..2f0c6fc1ee 100644 --- a/packages/client/src/pages/settings/statusbars.statusbar.vue +++ b/packages/client/src/pages/settings/statusbars.statusbar.vue @@ -28,6 +28,9 @@ <MkInput v-model="statusbar.props.url" manual-save class="_formBlock" type="url"> <template #label>URL</template> </MkInput> + <MkSwitch v-model="statusbar.props.shuffle" class="_formBlock"> + <template #label>{{ i18n.ts.shuffle }}</template> + </MkSwitch> <MkInput v-model="statusbar.props.refreshIntervalSec" manual-save class="_formBlock" type="number"> <template #label>{{ i18n.ts.refreshInterval }}</template> </MkInput> @@ -100,6 +103,7 @@ watch(() => statusbar.type, () => { if (statusbar.type === 'rss') { statusbar.name = 'NEWS'; statusbar.props.url = 'http://feeds.afpbb.com/rss/afpbb/afpbbnews'; + statusbar.props.shuffle = true; statusbar.props.refreshIntervalSec = 120; statusbar.props.display = 'marquee'; statusbar.props.marqueeDuration = 100; diff --git a/packages/client/src/scripts/shuffle.ts b/packages/client/src/scripts/shuffle.ts new file mode 100644 index 0000000000..05e6cdfbcf --- /dev/null +++ b/packages/client/src/scripts/shuffle.ts @@ -0,0 +1,19 @@ +/** + * 配列をシャッフル (破壊的) + */ +export function shuffle<T extends any[]>(array: T): T { + let currentIndex = array.length, randomIndex; + + // While there remain elements to shuffle. + while (currentIndex !== 0) { + // Pick a remaining element. + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; + + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], array[currentIndex]]; + } + + return array; +} diff --git a/packages/client/src/ui/_common_/statusbar-rss.vue b/packages/client/src/ui/_common_/statusbar-rss.vue index 88604a38a7..635b875ca1 100644 --- a/packages/client/src/ui/_common_/statusbar-rss.vue +++ b/packages/client/src/ui/_common_/statusbar-rss.vue @@ -20,9 +20,11 @@ import { computed, defineAsyncComponent, ref, toRef, watch } from 'vue'; import MarqueeText from '@/components/marquee.vue'; import * as os from '@/os'; import { useInterval } from '@/scripts/use-interval'; +import { shuffle } from '@/scripts/shuffle'; const props = defineProps<{ url?: string; + shuffle?: boolean; display?: 'marquee' | 'oneByOne'; marqueeDuration?: number; marqueeReverse?: boolean; @@ -37,6 +39,9 @@ let key = $ref(0); const tick = () => { fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { res.json().then(feed => { + if (props.shuffle) { + shuffle(feed.items); + } items.value = feed.items; fetching.value = false; key++; diff --git a/packages/client/src/ui/_common_/statusbars.vue b/packages/client/src/ui/_common_/statusbars.vue index 5358b26ece..114ca5be8c 100644 --- a/packages/client/src/ui/_common_/statusbars.vue +++ b/packages/client/src/ui/_common_/statusbars.vue @@ -10,7 +10,7 @@ }]" > <span class="name">{{ x.name }}</span> - <XRss v-if="x.type === 'rss'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url"/> + <XRss v-if="x.type === 'rss'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/> <XFederation v-else-if="x.type === 'federation'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/> <XUserList v-else-if="x.type === 'userList'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :user-list-id="x.props.userListId"/> </div> diff --git a/packages/client/src/widgets/rss-ticker.vue b/packages/client/src/widgets/rss-ticker.vue index 06995bc865..c692c0c4ff 100644 --- a/packages/client/src/widgets/rss-ticker.vue +++ b/packages/client/src/widgets/rss-ticker.vue @@ -26,6 +26,7 @@ import { GetFormResultType } from '@/scripts/form'; import * as os from '@/os'; import MkContainer from '@/components/ui/container.vue'; import { useInterval } from '@/scripts/use-interval'; +import { shuffle } from '@/scripts/shuffle'; const name = 'rssTicker'; @@ -34,6 +35,10 @@ const widgetPropsDef = { type: 'string' as const, default: 'http://feeds.afpbb.com/rss/afpbb/afpbbnews', }, + shuffle: { + type: 'boolean' as const, + default: true, + }, refreshIntervalSec: { type: 'number' as const, default: 60, @@ -80,6 +85,9 @@ let key = $ref(0); const tick = () => { fetch(`/api/fetch-rss?url=${widgetProps.url}`, {}).then(res => { res.json().then(feed => { + if (widgetProps.shuffle) { + shuffle(feed.items); + } items.value = feed.items; fetching.value = false; key++; From 7cb5b5c8c28451b4b9bd03055955f7d8ce80c6ee Mon Sep 17 00:00:00 2001 From: Johann150 <johann.galle@protonmail.com> Date: Fri, 15 Jul 2022 10:14:05 +0200 Subject: [PATCH 29/36] refactor: signup component as composition api (#8957) --- packages/client/src/components/signup.vue | 453 +++++++++++----------- 1 file changed, 216 insertions(+), 237 deletions(-) diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue index dd4a2b18b8..c35d65d5de 100644 --- a/packages/client/src/components/signup.vue +++ b/packages/client/src/components/signup.vue @@ -1,255 +1,234 @@ <template> <form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit"> - <template v-if="meta"> - <MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required> - <template #label>{{ $ts.invitationCode }}</template> - <template #prefix><i class="fas fa-key"></i></template> - </MkInput> - <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> - <template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> - <template #prefix>@</template> - <template #suffix>@{{ host }}</template> - <template #caption> - <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> - <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> - <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> - <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> - <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> - <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> - <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> + <MkInput v-if="instance.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required> + <template #label>{{ $ts.invitationCode }}</template> + <template #prefix><i class="fas fa-key"></i></template> + </MkInput> + <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> + <template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> + <template #prefix>@</template> + <template #suffix>@{{ host }}</template> + <template #caption> + <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> + <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> + <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> + <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> + <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span> + <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span> + <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> + </template> + </MkInput> + <MkInput v-if="instance.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> + <template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> + <template #prefix><i class="fas fa-envelope"></i></template> + <template #caption> + <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> + <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> + <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.used }}</span> + <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.format }}</span> + <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.disposable }}</span> + <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.mx }}</span> + <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.smtp }}</span> + <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> + <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> + </template> + </MkInput> + <MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> + <template #label>{{ $ts.password }}</template> + <template #prefix><i class="fas fa-lock"></i></template> + <template #caption> + <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span> + <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span> + <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> + </template> + </MkInput> + <MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> + <template #label>{{ $ts.password }} ({{ $ts.retype }})</template> + <template #prefix><i class="fas fa-lock"></i></template> + <template #caption> + <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span> + <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> + </template> + </MkInput> + <MkSwitch v-if="instance.tosUrl" v-model="ToSAgreement" class="_formBlock tou"> + <I18n :src="$ts.agreeTo"> + <template #0> + <a :href="instance.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a> </template> - </MkInput> - <MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> - <template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> - <template #prefix><i class="fas fa-envelope"></i></template> - <template #caption> - <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span> - <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span> - <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.used }}</span> - <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.format }}</span> - <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.disposable }}</span> - <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.mx }}</span> - <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts._emailUnavailable.smtp }}</span> - <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span> - <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> - </template> - </MkInput> - <MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> - <template #label>{{ $ts.password }}</template> - <template #prefix><i class="fas fa-lock"></i></template> - <template #caption> - <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.weakPassword }}</span> - <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="fas fa-check fa-fw"></i> {{ $ts.normalPassword }}</span> - <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> - </template> - </MkInput> - <MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> - <template #label>{{ $ts.password }} ({{ $ts.retype }})</template> - <template #prefix><i class="fas fa-lock"></i></template> - <template #caption> - <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.passwordMatched }}</span> - <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span> - </template> - </MkInput> - <MkSwitch v-if="meta.tosUrl" v-model="ToSAgreement" class="_formBlock tou"> - <I18n :src="$ts.agreeTo"> - <template #0> - <a :href="meta.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a> - </template> - </I18n> - </MkSwitch> - <MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> - <MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> - <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> - </template> + </I18n> + </MkSwitch> + <MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/> + <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> + <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> </form> </template> -<script lang="ts"> -import { defineComponent, defineAsyncComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import getPasswordStrength from 'syuilo-password-strength'; import { toUnicode } from 'punycode/'; import MkButton from './ui/button.vue'; +import MkCaptcha from './captcha.vue'; import MkInput from './form/input.vue'; import MkSwitch from './form/switch.vue'; -import { host, url } from '@/config'; +import * as config from '@/config'; import * as os from '@/os'; import { login } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkSwitch, - MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')), - }, - - props: { - autoSet: { - type: Boolean, - required: false, - default: false, - }, - }, - - emits: ['signup'], - - data() { - return { - host: toUnicode(host), - username: '', - password: '', - retypedPassword: '', - invitationCode: '', - email: '', - url, - usernameState: null, - emailState: null, - passwordStrength: '', - passwordRetypeState: null, - submitting: false, - ToSAgreement: false, - hCaptchaResponse: null, - reCaptchaResponse: null, - }; - }, - - computed: { - meta() { - return this.$instance; - }, - - shouldDisableSubmitting(): boolean { - return this.submitting || - this.meta.tosUrl && !this.ToSAgreement || - this.meta.enableHcaptcha && !this.hCaptchaResponse || - this.meta.enableRecaptcha && !this.reCaptchaResponse || - this.passwordRetypeState === 'not-match'; - }, - - shouldShowProfileUrl(): boolean { - return (this.username !== '' && - this.usernameState !== 'invalid-format' && - this.usernameState !== 'min-range' && - this.usernameState !== 'max-range'); - }, - }, - - methods: { - onChangeUsername() { - if (this.username === '') { - this.usernameState = null; - return; - } - - const err = - !this.username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : - this.username.length < 1 ? 'min-range' : - this.username.length > 20 ? 'max-range' : - null; - - if (err) { - this.usernameState = err; - return; - } - - this.usernameState = 'wait'; - - os.api('username/available', { - username: this.username, - }).then(result => { - this.usernameState = result.available ? 'ok' : 'unavailable'; - }).catch(err => { - this.usernameState = 'error'; - }); - }, - - onChangeEmail() { - if (this.email === '') { - this.emailState = null; - return; - } - - this.emailState = 'wait'; - - os.api('email-address/available', { - emailAddress: this.email, - }).then(result => { - this.emailState = result.available ? 'ok' : - result.reason === 'used' ? 'unavailable:used' : - result.reason === 'format' ? 'unavailable:format' : - result.reason === 'disposable' ? 'unavailable:disposable' : - result.reason === 'mx' ? 'unavailable:mx' : - result.reason === 'smtp' ? 'unavailable:smtp' : - 'unavailable'; - }).catch(err => { - this.emailState = 'error'; - }); - }, - - onChangePassword() { - if (this.password === '') { - this.passwordStrength = ''; - return; - } - - const strength = getPasswordStrength(this.password); - this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; - }, - - onChangePasswordRetype() { - if (this.retypedPassword === '') { - this.passwordRetypeState = null; - return; - } - - this.passwordRetypeState = this.password === this.retypedPassword ? 'match' : 'not-match'; - }, - - onSubmit() { - if (this.submitting) return; - this.submitting = true; - - os.api('signup', { - username: this.username, - password: this.password, - emailAddress: this.email, - invitationCode: this.invitationCode, - 'hcaptcha-response': this.hCaptchaResponse, - 'g-recaptcha-response': this.reCaptchaResponse, - }).then(() => { - if (this.meta.emailRequiredForSignup) { - os.alert({ - type: 'success', - title: this.$ts._signup.almostThere, - text: this.$t('_signup.emailSent', { email: this.email }), - }); - this.$emit('signupEmailPending'); - } else { - os.api('signin', { - username: this.username, - password: this.password, - }).then(res => { - this.$emit('signup', res); - - if (this.autoSet) { - login(res.i); - } - }); - } - }).catch(() => { - this.submitting = false; - this.$refs.hcaptcha?.reset?.(); - this.$refs.recaptcha?.reset?.(); - - os.alert({ - type: 'error', - text: this.$ts.somethingHappened, - }); - }); - }, - }, +const props = withDefaults(defineProps<{ + autoSet?: boolean; +}>(), { + autoSet: false, }); + +const emit = defineEmits<{ + (ev: 'signup', user: Record<string, any>): void; + (ev: 'signupEmailPending'): void; +}>(); + +const host = toUnicode(config.host); + +let hcaptcha = $ref(); +let recaptcha = $ref(); + +let username: string = $ref(''); +let password: string = $ref(''); +let retypedPassword: string = $ref(''); +let invitationCode: string = $ref(''); +let email = $ref(''); +let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null); +let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null); +let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref(''); +let passwordRetypeState: null | 'match' | 'not-match' = $ref(null); +let submitting: boolean = $ref(false); +let ToSAgreement: boolean = $ref(false); +let hCaptchaResponse = $ref(null); +let reCaptchaResponse = $ref(null); + +const shouldDisableSubmitting = $computed((): boolean => { + return submitting || + instance.tosUrl && !ToSAgreement || + instance.enableHcaptcha && !hCaptchaResponse || + instance.enableRecaptcha && !reCaptchaResponse || + passwordRetypeState === 'not-match'; +}); + +function onChangeUsername(): void { + if (username === '') { + usernameState = null; + return; + } + + { + const err = + !username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' : + username.length < 1 ? 'min-range' : + username.length > 20 ? 'max-range' : + null; + + if (err) { + usernameState = err; + return; + } + } + + usernameState = 'wait'; + + os.api('username/available', { + username, + }).then(result => { + usernameState = result.available ? 'ok' : 'unavailable'; + }).catch(() => { + usernameState = 'error'; + }); +} + +function onChangeEmail(): void { + if (email === '') { + emailState = null; + return; + } + + emailState = 'wait'; + + os.api('email-address/available', { + emailAddress: email, + }).then(result => { + emailState = result.available ? 'ok' : + result.reason === 'used' ? 'unavailable:used' : + result.reason === 'format' ? 'unavailable:format' : + result.reason === 'disposable' ? 'unavailable:disposable' : + result.reason === 'mx' ? 'unavailable:mx' : + result.reason === 'smtp' ? 'unavailable:smtp' : + 'unavailable'; + }).catch(() => { + emailState = 'error'; + }); +} + +function onChangePassword(): void { + if (password === '') { + passwordStrength = ''; + return; + } + + const strength = getPasswordStrength(password); + passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; +} + +function onChangePasswordRetype(): void { + if (retypedPassword === '') { + passwordRetypeState = null; + return; + } + + passwordRetypeState = password === retypedPassword ? 'match' : 'not-match'; +} + +function onSubmit(): void { + if (submitting) return; + submitting = true; + + os.api('signup', { + username, + password, + emailAddress: email, + invitationCode, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }).then(() => { + if (instance.emailRequiredForSignup) { + os.alert({ + type: 'success', + title: i18n.ts._signup.almostThere, + text: i18n.t('_signup.emailSent', { email }), + }); + emit('signupEmailPending'); + } else { + os.api('signin', { + username, + password, + }).then(res => { + emit('signup', res); + + if (props.autoSet) { + login(res.i); + } + }); + } + }).catch(() => { + submitting = false; + hcaptcha.reset?.(); + recaptcha.reset?.(); + + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); + }); +} </script> <style lang="scss" scoped> From 47186c0fff56c920393fd3293a54e8784d2e82a0 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 17:14:41 +0900 Subject: [PATCH 30/36] 12.114.0-beta.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 133e885aa1..e252c1c89c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.6", + "version": "12.114.0-beta.7", "codename": "indigo", "repository": { "type": "git", From 92792719bdb74bf10dd327219636f9d26f8e1408 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 19:15:23 +0900 Subject: [PATCH 31/36] chore(client): tweak style --- packages/client/src/components/drive.vue | 61 ++++++++++--------- .../client/src/components/ui/modal-window.vue | 3 +- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index 6c2c8acad0..9e2ef1b930 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -26,7 +26,8 @@ </div> <button class="menu _button" @click="showMenu"><i class="fas fa-ellipsis-h"></i></button> </nav> - <div ref="main" class="main" + <div + ref="main" class="main" :class="{ uploading: uploadings.length > 0, fetching }" @dragover.prevent.stop="onDragover" @dragenter="onDragenter" @@ -142,7 +143,7 @@ const isDragSource = ref(false); const fetching = ref(true); const ilFilesObserver = new IntersectionObserver( - (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() + (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(), ); watch(folder, () => emit('cd', folder.value)); @@ -232,7 +233,7 @@ function onDrop(ev: DragEvent): any { removeFile(file.id); os.api('drive/files/update', { fileId: file.id, - folderId: folder.value ? folder.value.id : null + folderId: folder.value ? folder.value.id : null, }); } //#endregion @@ -248,7 +249,7 @@ function onDrop(ev: DragEvent): any { removeFolder(droppedFolder.id); os.api('drive/folders/update', { folderId: droppedFolder.id, - parentId: folder.value ? folder.value.id : null + parentId: folder.value ? folder.value.id : null, }).then(() => { // noop }).catch(err => { @@ -256,13 +257,13 @@ function onDrop(ev: DragEvent): any { case 'detected-circular-definition': os.alert({ title: i18n.ts.unableToProcess, - text: i18n.ts.circularReferenceFolder + text: i18n.ts.circularReferenceFolder, }); break; default: os.alert({ type: 'error', - text: i18n.ts.somethingHappened + text: i18n.ts.somethingHappened, }); } }); @@ -278,17 +279,17 @@ function urlUpload() { os.inputText({ title: i18n.ts.uploadFromUrl, type: 'url', - placeholder: i18n.ts.uploadFromUrlDescription + placeholder: i18n.ts.uploadFromUrlDescription, }).then(({ canceled, result: url }) => { if (canceled || !url) return; os.api('drive/files/upload-from-url', { url: url, - folderId: folder.value ? folder.value.id : undefined + folderId: folder.value ? folder.value.id : undefined, }); os.alert({ title: i18n.ts.uploadFromUrlRequested, - text: i18n.ts.uploadFromUrlMayTakeTime + text: i18n.ts.uploadFromUrlMayTakeTime, }); }); } @@ -296,12 +297,12 @@ function urlUpload() { function createFolder() { os.inputText({ title: i18n.ts.createFolder, - placeholder: i18n.ts.folderName + placeholder: i18n.ts.folderName, }).then(({ canceled, result: name }) => { if (canceled) return; os.api('drive/folders/create', { name: name, - parentId: folder.value ? folder.value.id : undefined + parentId: folder.value ? folder.value.id : undefined, }).then(createdFolder => { addFolder(createdFolder, true); }); @@ -312,12 +313,12 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) { os.inputText({ title: i18n.ts.renameFolder, placeholder: i18n.ts.inputNewFolderName, - default: folderToRename.name + default: folderToRename.name, }).then(({ canceled, result: name }) => { if (canceled) return; os.api('drive/folders/update', { folderId: folderToRename.id, - name: name + name: name, }).then(updatedFolder => { // FIXME: 画面を更新するために自分自身に移動 move(updatedFolder); @@ -327,7 +328,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) { function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { os.api('drive/folders/delete', { - folderId: folderToDelete.id + folderId: folderToDelete.id, }).then(() => { // 削除時に親フォルダに移動 move(folderToDelete.parentId); @@ -337,15 +338,15 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { os.alert({ type: 'error', title: i18n.ts.unableToDelete, - text: i18n.ts.hasChildFilesOrFolders + text: i18n.ts.hasChildFilesOrFolders, }); break; default: os.alert({ type: 'error', - text: i18n.ts.unableToDelete + text: i18n.ts.unableToDelete, }); - } + } }); } @@ -411,7 +412,7 @@ function move(target?: Misskey.entities.DriveFolder) { fetching.value = true; os.api('drive/folders/show', { - folderId: target + folderId: target, }).then(folderToMove => { folder.value = folderToMove; hierarchyFolders.value = []; @@ -510,7 +511,7 @@ async function fetch() { const foldersPromise = os.api('drive/folders', { folderId: folder.value ? folder.value.id : null, - limit: foldersMax + 1 + limit: foldersMax + 1, }).then(fetchedFolders => { if (fetchedFolders.length === foldersMax + 1) { moreFolders.value = true; @@ -522,7 +523,7 @@ async function fetch() { const filesPromise = os.api('drive/files', { folderId: folder.value ? folder.value.id : null, type: props.type, - limit: filesMax + 1 + limit: filesMax + 1, }).then(fetchedFiles => { if (fetchedFiles.length === filesMax + 1) { moreFiles.value = true; @@ -549,7 +550,7 @@ function fetchMoreFiles() { folderId: folder.value ? folder.value.id : null, type: props.type, untilId: files.value[files.value.length - 1].id, - limit: max + 1 + limit: max + 1, }).then(files => { if (files.length === max + 1) { moreFiles.value = true; @@ -569,30 +570,30 @@ function getMenu() { ref: keepOriginal, }, null, { text: i18n.ts.addFile, - type: 'label' + type: 'label', }, { text: i18n.ts.upload, icon: 'fas fa-upload', - action: () => { selectLocalFile(); } + action: () => { selectLocalFile(); }, }, { text: i18n.ts.fromUrl, icon: 'fas fa-link', - action: () => { urlUpload(); } + action: () => { urlUpload(); }, }, null, { text: folder.value ? folder.value.name : i18n.ts.drive, - type: 'label' + type: 'label', }, folder.value ? { text: i18n.ts.renameFolder, icon: 'fas fa-i-cursor', - action: () => { renameFolder(folder.value); } + action: () => { renameFolder(folder.value); }, } : undefined, folder.value ? { text: i18n.ts.deleteFolder, icon: 'fas fa-trash-alt', - action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); } + action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); }, } : undefined, { text: i18n.ts.createFolder, icon: 'fas fa-folder-plus', - action: () => { createFolder(); } + action: () => { createFolder(); }, }]; } @@ -662,14 +663,14 @@ onBeforeUnmount(() => { > .path { display: inline-block; vertical-align: bottom; - line-height: 50px; + line-height: 42px; white-space: nowrap; > * { display: inline-block; margin: 0; padding: 0 8px; - line-height: 50px; + line-height: 42px; cursor: pointer; * { diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue index b7faea736b..b29ea4fd81 100644 --- a/packages/client/src/components/ui/modal-window.vue +++ b/packages/client/src/components/ui/modal-window.vue @@ -98,7 +98,7 @@ defineExpose({ } > .header { - $height: 58px; + $height: 46px; $height-narrow: 42px; display: flex; flex-shrink: 0; @@ -138,6 +138,7 @@ defineExpose({ } > .body { + flex: 1; overflow: auto; background: var(--panel); } From 113df688432206afd3accf634ac0ec3013d28f38 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 19:31:04 +0900 Subject: [PATCH 32/36] chore(client): tweak style --- packages/client/src/components/notification.vue | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 32f9fd07d8..10cbe20902 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -177,13 +177,7 @@ useTooltip(reactionRef, (showing) => { &.max-width_500px { padding: 12px; - font-size: 0.8em; - } - - &:after { - content: ""; - display: block; - clear: both; + font-size: 0.85em; } > .head { From 92e8a5dbd6d445f94440e4da7c1101276611a98f Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 22:09:05 +0900 Subject: [PATCH 33/36] chore(client): tweak ui --- locales/ja-JP.yml | 1 + .../src/ui/_common_/navbar-for-mobile.vue | 381 +++++++----- packages/client/src/ui/_common_/navbar.vue | 573 ++++++++++++------ packages/client/src/ui/deck.vue | 3 +- packages/client/src/ui/universal.vue | 4 +- 5 files changed, 609 insertions(+), 353 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index bdff7e38da..c797c0d55e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -889,6 +889,7 @@ enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利 activeEmailValidationDescription: "ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。" navbar: "ナビゲーションバー" shuffle: "シャッフル" +account: "アカウント" _sensitiveMediaDetection: description: "機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。" diff --git a/packages/client/src/ui/_common_/navbar-for-mobile.vue b/packages/client/src/ui/_common_/navbar-for-mobile.vue index 8ac4c1150a..cae1d25304 100644 --- a/packages/client/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/client/src/ui/_common_/navbar-for-mobile.vue @@ -1,192 +1,166 @@ <template> <div class="kmwsukvl"> <div class="body"> - <button v-click-anime class="item _button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> - </button> - <MkA v-click-anime class="item index" active-class="active" to="/" exact> - <i class="icon fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> - </MkA> - <template v-for="item in menu"> - <div v-if="item === '-'" class="divider"></div> - <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> - <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> - <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> - </component> - </template> - <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> - <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span> - </MkA> - <button v-click-anime class="item _button" @click="more"> - <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span> - <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span> - </button> - <MkA v-click-anime class="item" active-class="active" to="/settings"> - <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span> - </MkA> - <button class="item _button post" data-cy-open-post-form @click="post"> - <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span> - </button> + <div class="top"> + <div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div> + <button v-click-anime v-tooltip.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu"> + <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> + </button> + </div> + <div class="middle"> + <MkA v-click-anime class="item index" active-class="active" to="/" exact> + <i class="icon fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> + </MkA> + <template v-for="item in menu"> + <div v-if="item === '-'" class="divider"></div> + <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> + <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ $ts[navbarItemDef[item].title] }}</span> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> + </component> + </template> + <div class="divider"></div> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> + <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span> + </MkA> + <button v-click-anime class="item _button" @click="more"> + <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span> + <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span> + </button> + <MkA v-click-anime class="item" active-class="active" to="/settings"> + <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span> + </MkA> + </div> + <div class="bottom"> + <button class="item _button post" data-cy-open-post-form @click="os.post"> + <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span> + </button> + <button v-click-anime class="item _button account" @click="openAccountMenu"> + <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> + </button> + </div> </div> </div> </template> -<script lang="ts"> +<script lang="ts" setup> import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; -import { openAccountMenu } from '@/account'; +import { openAccountMenu as openAccountMenu_ } from '@/account'; import { defaultStore } from '@/store'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - setup(props, context) { - const menu = toRef(defaultStore.state, 'menu'); - const otherMenuItemIndicated = computed(() => { - for (const def in navbarItemDef) { - if (menu.value.includes(def)) continue; - if (navbarItemDef[def].indicated) return true; - } - return false; - }); - - return { - host: host, - accounts: [], - connection: null, - menu, - navbarItemDef: navbarItemDef, - otherMenuItemIndicated, - post: os.post, - search, - openAccountMenu: (ev) => { - openAccountMenu({ - withExtraOperation: true, - }, ev); - }, - more: () => { - os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {}, { - }, 'closed'); - }, - }; - }, +const menu = toRef(defaultStore.state, 'menu'); +const otherMenuItemIndicated = computed(() => { + for (const def in navbarItemDef) { + if (menu.value.includes(def)) continue; + if (navbarItemDef[def].indicated) return true; + } + return false; }); + +function openAccountMenu(ev: MouseEvent) { + openAccountMenu_({ + withExtraOperation: true, + }, ev); +} + +function openInstanceMenu(ev: MouseEvent) { + os.popupMenu([{ + text: instance.name ?? host, + type: 'label', + }, { + type: 'link', + text: i18n.ts.instanceInfo, + icon: 'fas fa-info-circle', + to: '/about', + }, { + type: 'link', + text: i18n.ts.customEmojis, + icon: 'fas fa-laugh', + to: '/about#emojis', + }, { + type: 'link', + text: i18n.ts.federation, + icon: 'fas fa-globe', + to: '/about#federation', + }], ev.currentTarget ?? ev.target, { + align: 'left', + }); +} + +function more() { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {}, { + }, 'closed'); +} </script> <style lang="scss" scoped> .kmwsukvl { - $ui-font-size: 1em; // TODO: どこかに集約したい - $avatar-size: 32px; - $avatar-margin: 8px; - > .body { + display: flex; + flex-direction: column; - > .divider { - margin: 16px 16px; - border-top: solid 0.5px var(--divider); - } + > .top { + position: sticky; + top: 0; + z-index: 1; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); - > .item { - position: relative; - display: block; - padding-left: 24px; - font-size: $ui-font-size; - line-height: 2.85rem; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - text-align: left; - box-sizing: border-box; - color: var(--navFg); - - > .icon { - position: relative; - width: 32px; - } - - > .icon, - > .avatar { - margin-right: $avatar-margin; - } - - > .avatar { - width: $avatar-size; - height: $avatar-size; - vertical-align: middle; - } - - > .indicator { + > .banner { position: absolute; top: 0; - left: 20px; - color: var(--navIndicator); - font-size: 8px; - animation: blink 1s infinite; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); } - > .text { + > .instance { position: relative; - font-size: 0.9em; - } + display: block; + text-align: center; + width: 100%; - &:hover { - text-decoration: none; - color: var(--navHoverFg); - } - - &.active { - color: var(--navActive); - } - - &:hover, &.active { - &:before { - content: ""; - display: block; - width: calc(100% - 24px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--accentedBg); + > .icon { + display: inline-block; + width: 38px; + aspect-ratio: 1; } } + } - &:first-child, &:last-child { - position: sticky; - z-index: 1; - padding-top: 8px; - padding-bottom: 8px; - background: var(--X14); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - } + > .bottom { + position: sticky; + bottom: 0; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); - &:first-child { - top: 0; - - &:hover, &.active { - &:before { - content: none; - } - } - } - - &:last-child { - bottom: 0; + > .post { + position: relative; + display: block; + width: 100%; + height: 40px; color: var(--fgOnAccent); + font-weight: bold; + text-align: left; &:before { content: ""; display: block; - width: calc(100% - 20px); - height: calc(100% - 20px); + width: calc(100% - 38px); + height: 100%; margin: auto; position: absolute; top: 0; @@ -196,12 +170,113 @@ export default defineComponent({ border-radius: 999px; background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); } - + &:hover, &.active { &:before { background: var(--accentLighten); } } + + > .icon { + position: relative; + margin-left: 30px; + margin-right: 8px; + width: 32px; + } + + > .text { + position: relative; + } + } + + > .account { + position: relative; + display: flex; + align-items: center; + padding-left: 30px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + margin-top: 16px; + + > .avatar { + position: relative; + width: 32px; + aspect-ratio: 1; + margin-right: 8px; + } + } + } + + > .middle { + flex: 1; + + > .divider { + margin: 16px 16px; + border-top: solid 0.5px var(--divider); + } + + > .item { + position: relative; + display: block; + padding-left: 24px; + line-height: 2.85rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + color: var(--navFg); + + > .icon { + position: relative; + width: 32px; + margin-right: 8px; + } + + > .indicator { + position: absolute; + top: 0; + left: 20px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + > .text { + position: relative; + font-size: 0.9em; + } + + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } + + &.active { + color: var(--navActive); + } + + &:hover, &.active { + &:before { + content: ""; + display: block; + width: calc(100% - 24px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: var(--accentedBg); + } + } } } } diff --git a/packages/client/src/ui/_common_/navbar.vue b/packages/client/src/ui/_common_/navbar.vue index 924dda25d7..fbac8425d7 100644 --- a/packages/client/src/ui/_common_/navbar.vue +++ b/packages/client/src/ui/_common_/navbar.vue @@ -1,42 +1,53 @@ <template> <div class="mvcprjjd" :class="{ iconOnly }"> <div class="body"> - <button v-click-anime class="item _button account" @click="openAccountMenu"> - <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> - </button> - <MkA v-click-anime v-tooltip.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact> - <i class="icon fas fa-home fa-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> - </MkA> - <template v-for="item in menu"> - <div v-if="item === '-'" class="divider"></div> - <component - :is="navbarItemDef[item].to ? 'MkA' : 'button'" - v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" - v-click-anime v-tooltip.right="i18n.ts[navbarItemDef[item].title]" - class="item _button" - :class="[item, { active: navbarItemDef[item].active }]" - active-class="active" - :to="navbarItemDef[item].to" - v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" - > - <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ i18n.ts[navbarItemDef[item].title] }}</span> - <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> - </component> - </template> - <div class="divider"></div> - <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin"> - <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> - </MkA> - <button v-click-anime class="item _button" @click="more"> - <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span> - </button> - <MkA v-click-anime v-tooltip.right="i18n.ts.settings" class="item" active-class="active" to="/settings"> - <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ i18n.ts.settings }}</span> - </MkA> - <button class="item _button post" data-cy-open-post-form @click="os.post"> - <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ i18n.ts.note }}</span> - </button> + <div class="top"> + <div class="banner" :style="{ backgroundImage: `url(${ $instance.bannerUrl })` }"></div> + <button v-click-anime v-tooltip.right="$instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu"> + <img :src="$instance.iconUrl || $instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/> + </button> + </div> + <div class="middle"> + <MkA v-click-anime v-tooltip.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact> + <i class="icon fas fa-home fa-fw"></i><span class="text">{{ i18n.ts.timeline }}</span> + </MkA> + <template v-for="item in menu"> + <div v-if="item === '-'" class="divider"></div> + <component + :is="navbarItemDef[item].to ? 'MkA' : 'button'" + v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" + v-click-anime + v-tooltip.right="i18n.ts[navbarItemDef[item].title]" + class="item _button" + :class="[item, { active: navbarItemDef[item].active }]" + active-class="active" + :to="navbarItemDef[item].to" + v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" + > + <i class="icon fa-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ i18n.ts[navbarItemDef[item].title] }}</span> + <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon fas fa-circle"></i></span> + </component> + </template> + <div class="divider"></div> + <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin"> + <i class="icon fas fa-door-open fa-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span> + </MkA> + <button v-click-anime class="item _button" @click="more"> + <i class="icon fa fa-ellipsis-h fa-fw"></i><span class="text">{{ i18n.ts.more }}</span> + <span v-if="otherMenuItemIndicated" class="indicator"><i class="icon fas fa-circle"></i></span> + </button> + <MkA v-click-anime v-tooltip.right="i18n.ts.settings" class="item" active-class="active" to="/settings"> + <i class="icon fas fa-cog fa-fw"></i><span class="text">{{ i18n.ts.settings }}</span> + </MkA> + </div> + <div class="bottom"> + <button v-tooltip.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post"> + <i class="icon fas fa-pencil-alt fa-fw"></i><span class="text">{{ i18n.ts.note }}</span> + </button> + <button v-click-anime v-tooltip.right="i18n.ts.account" class="item _button account" @click="openAccountMenu"> + <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> + </button> + </div> </div> </div> </template> @@ -48,6 +59,8 @@ import { navbarItemDef } from '@/navbar'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import { instance } from '@/instance'; +import { host } from '@/config'; const iconOnly = ref(false); @@ -78,6 +91,30 @@ function openAccountMenu(ev: MouseEvent) { }, ev); } +function openInstanceMenu(ev: MouseEvent) { + os.popupMenu([{ + text: instance.name ?? host, + type: 'label', + }, { + type: 'link', + text: i18n.ts.instanceInfo, + icon: 'fas fa-info-circle', + to: '/about', + }, { + type: 'link', + text: i18n.ts.customEmojis, + icon: 'fas fa-laugh', + to: '/about#emojis', + }, { + type: 'link', + text: i18n.ts.federation, + icon: 'fas fa-globe', + to: '/about#federation', + }], ev.currentTarget ?? ev.target, { + align: 'left', + }); +} + function more(ev: MouseEvent) { os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, @@ -88,11 +125,8 @@ function more(ev: MouseEvent) { <style lang="scss" scoped> .mvcprjjd { - $ui-font-size: 1em; // TODO: どこかに集約したい $nav-width: 250px; $nav-icon-only-width: 86px; - $avatar-size: 32px; - $avatar-margin: 8px; flex: 0 0 $nav-width; width: $nav-width; @@ -103,7 +137,7 @@ function more(ev: MouseEvent) { top: 0; left: 0; z-index: 1001; - width: $nav-width; + width: $nav-icon-only-width; // ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ height: calc(var(--vh, 1vh) * 100); box-sizing: border-box; @@ -111,126 +145,188 @@ function more(ev: MouseEvent) { overflow-x: clip; background: var(--navBg); contain: strict; + display: flex; + flex-direction: column; + } - > .divider { - margin: 16px 16px; - border-top: solid 0.5px var(--divider); - } + &:not(.iconOnly) { + > .body { + width: $nav-width; - > .item { - position: relative; - display: block; - padding-left: 24px; - font-size: $ui-font-size; - line-height: 2.85rem; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - text-align: left; - box-sizing: border-box; - color: var(--navFg); - - > .icon { - position: relative; - width: 32px; - } - - > .icon, - > .avatar { - margin-right: $avatar-margin; - } - - > .avatar { - width: $avatar-size; - height: $avatar-size; - vertical-align: middle; - } - - > .indicator { - position: absolute; - top: 0; - left: 20px; - color: var(--navIndicator); - font-size: 8px; - animation: blink 1s infinite; - } - - > .text { - position: relative; - font-size: 0.9em; - } - - &:hover { - text-decoration: none; - color: var(--navHoverFg); - } - - &.active { - color: var(--navActive); - } - - &:hover, &.active { - color: var(--accent); - - &:before { - content: ""; - display: block; - width: calc(100% - 24px); - height: 100%; - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: var(--accentedBg); - } - } - - &:first-child, &:last-child { + > .top { position: sticky; + top: 0; z-index: 1; - padding-top: 8px; - padding-bottom: 8px; + padding: 20px 0; background: var(--X14); -webkit-backdrop-filter: var(--blur, blur(8px)); backdrop-filter: var(--blur, blur(8px)); - } - &:first-child { - top: 0; + > .banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + } - &:hover, &.active { - &:before { - content: none; + > .instance { + position: relative; + display: block; + text-align: center; + width: 100%; + + > .icon { + display: inline-block; + width: 38px; + aspect-ratio: 1; } } } - &:last-child { + > .bottom { + position: sticky; bottom: 0; - color: var(--fgOnAccent); + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); - &:before { - content: ""; + > .post { + position: relative; display: block; - width: calc(100% - 20px); - height: calc(100% - 20px); - margin: auto; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border-radius: 999px; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - } - - &:hover, &.active { + width: 100%; + height: 40px; + color: var(--fgOnAccent); + font-weight: bold; + text-align: left; + &:before { - background: var(--accentLighten); + content: ""; + display: block; + width: calc(100% - 38px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + } + + &:hover, &.active { + &:before { + background: var(--accentLighten); + } + } + + > .icon { + position: relative; + margin-left: 30px; + margin-right: 8px; + width: 32px; + } + + > .text { + position: relative; + } + } + + > .account { + position: relative; + display: flex; + align-items: center; + padding-left: 30px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + margin-top: 16px; + + > .avatar { + position: relative; + width: 32px; + aspect-ratio: 1; + margin-right: 8px; + } + } + } + + > .middle { + flex: 1; + + > .divider { + margin: 16px 16px; + border-top: solid 0.5px var(--divider); + } + + > .item { + position: relative; + display: block; + padding-left: 30px; + line-height: 2.85rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + color: var(--navFg); + + > .icon { + position: relative; + width: 32px; + margin-right: 8px; + } + + > .indicator { + position: absolute; + top: 0; + left: 20px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + > .text { + position: relative; + font-size: 0.9em; + } + + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } + + &.active { + color: var(--navActive); + } + + &:hover, &.active { + color: var(--accent); + + &:before { + content: ""; + display: block; + width: calc(100% - 34px); + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: var(--accentedBg); + } } } } @@ -244,67 +340,150 @@ function more(ev: MouseEvent) { > .body { width: $nav-icon-only-width; - > .divider { - margin: 8px auto; - width: calc(100% - 32px); - } + > .top { + position: sticky; + top: 0; + z-index: 1; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); - > .item { - padding-left: 0; - padding: 18px 0; - width: 100%; - text-align: center; - font-size: $ui-font-size; - line-height: initial; - - > .icon, - > .avatar { + > .instance { display: block; - margin: 0 auto; - } - - > .icon { - opacity: 0.7; - } - - > .text { - display: none; - } - - &:hover, &.active { - > .icon, > .text { - opacity: 1; - } - } - - &:first-child { - margin-bottom: 8px; - } - - &:last-child { - margin-top: 8px; - } - - &:before { - width: min-content; - height: 100%; - aspect-ratio: 1/1; - border-radius: 8px; - } - - &.post { - height: $nav-icon-only-width; + text-align: center; + width: 100%; > .icon { - opacity: 1; + display: inline-block; + width: 38px; + aspect-ratio: 1; + } + } + } + + > .bottom { + position: sticky; + bottom: 0; + padding: 20px 0; + background: var(--X14); + -webkit-backdrop-filter: var(--blur, blur(8px)); + backdrop-filter: var(--blur, blur(8px)); + + > .post { + display: block; + position: relative; + width: 100%; + height: 52px; + margin-bottom: 16px; + text-align: center; + + &:before { + content: ""; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + width: 52px; + aspect-ratio: 1/1; + border-radius: 100%; + background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + } + + &:hover, &.active { + &:before { + background: var(--accentLighten); + } + } + + > .icon { + position: relative; + color: var(--fgOnAccent); + } + + > .text { + display: none; } } - &.post:before { - width: calc(100% - 28px); - height: auto; - aspect-ratio: 1/1; - border-radius: 100%; + > .account { + display: block; + text-align: center; + width: 100%; + + > .avatar { + display: inline-block; + width: 38px; + aspect-ratio: 1; + } + + > .text { + display: none; + } + } + } + + > .middle { + flex: 1; + + > .divider { + margin: 8px auto; + width: calc(100% - 32px); + border-top: solid 0.5px var(--divider); + } + + > .item { + display: block; + position: relative; + padding: 18px 0; + width: 100%; + text-align: center; + + > .icon { + display: block; + margin: 0 auto; + opacity: 0.7; + } + + > .text { + display: none; + } + + > .indicator { + position: absolute; + top: 6px; + left: 24px; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + &:hover, &.active { + text-decoration: none; + color: var(--accent); + + &:before { + content: ""; + display: block; + height: 100%; + aspect-ratio: 1; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: var(--accentedBg); + } + + > .icon, > .text { + opacity: 1; + } + } } } } diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index f330c99814..94fee1424e 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -359,9 +359,10 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { height: calc(var(--vh, 1vh) * 100); width: 240px; box-sizing: border-box; + contain: strict; overflow: auto; overscroll-behavior: contain; - background: var(--bg); + background: var(--navBg); } } </style> diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue index fe4fc425cd..e4b5de9918 100644 --- a/packages/client/src/ui/universal.vue +++ b/packages/client/src/ui/universal.vue @@ -365,11 +365,11 @@ const wallpaper = localStorage.getItem('wallpaper') != null; height: calc(var(--vh, 1vh) * 100); width: 240px; box-sizing: border-box; + contain: strict; overflow: auto; overscroll-behavior: contain; - background: var(--bg); + background: var(--navBg); } - } </style> From 3d0870f414604129d170d1c55cbd3bfd34de74d1 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 22:10:02 +0900 Subject: [PATCH 34/36] 12.114.0-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e252c1c89c..75dcf9683b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.7", + "version": "12.114.0-beta.8", "codename": "indigo", "repository": { "type": "git", From aa5eab746ac985dba4932f54f2fd3ec1a88a92f7 Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 22:43:42 +0900 Subject: [PATCH 35/36] New Crowdin updates (#9001) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (German) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Vietnamese) * New translations ja-JP.yml (Slovak) * New translations ja-JP.yml (German) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Arabic) * New translations ja-JP.yml (Slovak) * New translations ja-JP.yml (Kabyle) * New translations ja-JP.yml (Thai) * New translations ja-JP.yml (Bengali) * New translations ja-JP.yml (Indonesian) * New translations ja-JP.yml (English) * New translations ja-JP.yml (Chinese Simplified) * New translations ja-JP.yml (Russian) * New translations ja-JP.yml (Vietnamese) * New translations ja-JP.yml (Spanish) * New translations ja-JP.yml (Chinese Traditional) * New translations ja-JP.yml (French) * New translations ja-JP.yml (Polish) * New translations ja-JP.yml (German) * New translations ja-JP.yml (Italian) * New translations ja-JP.yml (Korean) --- locales/ar-SA.yml | 1 + locales/bn-BD.yml | 1 + locales/de-DE.yml | 3 +++ locales/en-US.yml | 3 +++ locales/es-ES.yml | 1 + locales/fr-FR.yml | 1 + locales/id-ID.yml | 1 + locales/it-IT.yml | 1 + locales/kab-KAB.yml | 1 + locales/ko-KR.yml | 1 + locales/pl-PL.yml | 1 + locales/ru-RU.yml | 1 + locales/sk-SK.yml | 2 ++ locales/th-TH.yml | 1 + locales/vi-VN.yml | 3 +++ locales/zh-CN.yml | 1 + locales/zh-TW.yml | 2 ++ 17 files changed, 25 insertions(+) diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 1289fba44d..992eeb0f50 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -808,6 +808,7 @@ reverse: "اقلب" colored: "ملوّن" label: "التسمية" localOnly: "المحلي فقط" +account: "الحسابات" _emailUnavailable: used: "هذا البريد الإلكتروني مستخدم" format: "صيغة البريد الإلكتروني غير صالحة" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 6017566bb0..f6547646cf 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -848,6 +848,7 @@ reverse: "উল্টান" colored: "রঙ্গিন" label: "লেবেল" localOnly: "শুধুমাত্র লোকাল" +account: "অ্যাকাউন্টগুলি" _emailUnavailable: used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index dfa65198be..b7941cbaf1 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "NSFW-Automarkierung" enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Machine Learning automatisch NSFW-Markierungen für Medien, die NSFW-Anteile beinhalten. Auch wenn du diese Option deaktiviert hast, ist sie möglicherweise auf Instanzebene aktiviert." activeEmailValidationDescription: "Aktivert strengere Überprüfung von E-Mail-Adressen, d.h. Testen auf Wegwerfadressen und darauf, ob mit der Adresse tatsächlich kommuniziert werden kann. Ist dies deaktiviert, so wird nur das Format der E-Mail überprüft." +navbar: "Navigationsleiste" +shuffle: "Mischen" +account: "Benutzerkonten" _sensitiveMediaDetection: description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht." sensitivity: "Erkennungssensitivität" diff --git a/locales/en-US.yml b/locales/en-US.yml index f0a9924cd7..520b541292 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "Automatic NSFW-Marking" enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide." activeEmailValidationDescription: "Enables stricter validation of email addresses, which includes checking for disposable addresses and by whether it can actually be communicated with. When unchecked, only the format of the email is validated." +navbar: "Navigation bar" +shuffle: "Shuffle" +account: "Accounts" _sensitiveMediaDetection: description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server." sensitivity: "Detection sensitivity" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index a8f6aaa79d..8383864ede 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -865,6 +865,7 @@ reverse: "Echar de un capirotazo" colored: "Color" label: "Etiqueta" localOnly: "Solo local" +account: "Cuentas" _emailUnavailable: used: "Ya fue usado" format: "Formato no válido." diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index c5f803bb88..b14d3d14b6 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -843,6 +843,7 @@ reverse: "Inverser" colored: "Coloré" label: "Étiquette" localOnly: "Local seulement" +account: "Comptes" _emailUnavailable: used: "Non disponible" format: "Le format de cette adresse de courriel est invalide" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index d543fae862..315b690b0f 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -852,6 +852,7 @@ reverse: "Balik" colored: "Diwarnai" label: "Label" localOnly: "Hanya lokal" +account: "Akun" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index eb7478dd51..ac14bed133 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -814,6 +814,7 @@ reverse: "Inverti" colored: "Colorato" label: "Etichetta" localOnly: "Soltanto locale" +account: "Account" _emailUnavailable: used: "Email già in uso" format: "Formato email non valido" diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml index f0297c66a0..29eca64c7a 100644 --- a/locales/kab-KAB.yml +++ b/locales/kab-KAB.yml @@ -57,6 +57,7 @@ selectAccount: "Fren amiḍan" accounts: "Imiḍan" searchByGoogle: "Nadi" file: "Ifuyla" +account: "Imiḍan" _email: _follow: title: "Yeṭṭafaṛ-ik·em-id" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 781de61062..2ba05505c5 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -886,6 +886,7 @@ beta: "베타" enableAutoSensitive: "자동 NSFW 탐지" enableAutoSensitiveDescription: "이용 가능할 경우 기계학습을 통해 자동으로 미디어 NSFW를 설정합니다. 이 기능을 해제하더라도, 인스턴스 정책에 따라 자동으로 설정될 수 있습니다." activeEmailValidationDescription: "유저가 입력한 메일 주소가 일회용 메일인지, 실제로 통신할 수 있는 지 엄격하게 검사합니다. 해제할 경우 이메일 형식에 대해서만 검사합니다." +account: "계정" _sensitiveMediaDetection: description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다." sensitivity: "탐지 민감도" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index af7d48ea75..41ba8fdd35 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -764,6 +764,7 @@ file: "Pliki" reverse: "Odwróć" colored: "Kolorowe" label: "Etykieta" +account: "Konta" _ffVisibility: public: "Publikuj" _ad: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index c49cbe1388..4386e1c87c 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -842,6 +842,7 @@ reverse: "Переворот" colored: "Выделена цветом" label: "Метка" localOnly: "Локально" +account: "Учётные записи" _sensitiveMediaDetection: description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно." setSensitiveFlagAutomatically: "Установить флаг NSFW" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index a5641e68f4..c5a5d6d0b1 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -883,6 +883,8 @@ beta: "Beta" enableAutoSensitive: "Automatická detekcia NSFW" enableAutoSensitiveDescription: "Ak je zapnuté, príznak NSFW sa na médiách automaticky nastaví pomocou strojového učenia. Aj keď je táto funkcia vypnutá, v niektorých prípadoch sa môže nastaviť automaticky." activeEmailValidationDescription: "Dôkladnejšie overí e-mailovú adresu používateľa tým, že zistí, či ide o vyradenú e-mailovú adresu a či sa s ňou dá skutočne komunikovať. Ak nie je začiarknuté, e-mailová adresa sa kontroluje len ako text." +navbar: "Navigačný panel" +account: "Účty" _sensitiveMediaDetection: description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera." sensitivity: "Citlivosť detekcie" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 1170185db4..0c695ff5b4 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -822,6 +822,7 @@ deleteAccountConfirm: "การดำเนินการนี้จะลบ incorrectPassword: "รหัสผ่านไม่ถูกต้อง" searchByGoogle: "ค้นหา" file: "ไฟล์" +account: "บัญชีผู้ใช้" _ffVisibility: public: "เผยแพร่" _ad: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 0680654879..2384915505 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -887,6 +887,9 @@ beta: "Beta" enableAutoSensitive: "Tự động đánh dấu NSFW" enableAutoSensitiveDescription: "Cho phép tự động phát hiện và đánh dấu media NSFW thông qua học máy, nếu có thể. Ngay cả khi tùy chọn này bị tắt, nó vẫn có thể được bật trên toàn máy chủ." activeEmailValidationDescription: "Cho phép xác minh địa chỉ email chặt chẽ hơn, bao gồm việc kiểm tra các địa chỉ dùng một lần và xem nó có thực sự được giao tiếp hay không. Khi bỏ chọn, chỉ định dạng của email được xác minh." +navbar: "Thanh điều hướng" +shuffle: "Xáo trộn" +account: "Tài khoản của bạn" _sensitiveMediaDetection: description: "Giảm nỗ lực kiểm duyệt máy chủ thông qua việc tự động nhận dạng media NSFW thông qua học máy. Điều này sẽ làm tăng một chút áp lực trên máy chủ." sensitivity: "Phát hiện nhạy cảm" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 26f745e65e..cb97a1b685 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -878,6 +878,7 @@ cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法 cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。" beta: "测试" enableAutoSensitive: "自动 NSFW 识别" +account: "账户" _sensitiveMediaDetection: description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。" sensitivity: "检测敏感度" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 945080ee44..0359b03b23 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -887,6 +887,8 @@ beta: "Beta" enableAutoSensitive: "自動NSFW判定" enableAutoSensitiveDescription: "如果可用,請利用機器學習在媒體上自動設置 NSFW 旗標。 即使關閉此功能,依實例而定也可能會自動設置。" activeEmailValidationDescription: "積極地驗證用戶的電子郵件地址,判斷它是否為免洗地址,或者它是否可以通信。 若關閉,則只會檢查字元是否正確。" +navbar: "導覽列" +account: "帳戶" _sensitiveMediaDetection: description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。" sensitivity: "檢測敏感度" From 7d6311894132f616f008a7db23ee68663bfadfeb Mon Sep 17 00:00:00 2001 From: syuilo <Syuilotan@yahoo.co.jp> Date: Fri, 15 Jul 2022 22:44:53 +0900 Subject: [PATCH 36/36] 12.114.0 --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efb0d684aa..d8129bf7e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ You should also include the user name that made the change. --> -## 12.x.x (unreleased) +## 12.114.0 (2022/07/15) ### Improvements - RSSティッカーで表示順序をシャッフルできるように @syuilo diff --git a/package.json b/package.json index 75dcf9683b..24cf9a6182 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.114.0-beta.8", + "version": "12.114.0", "codename": "indigo", "repository": { "type": "git",