Merge branch 'develop' into chat

This commit is contained in:
syuilo
2025-03-19 19:37:31 +09:00
196 changed files with 648 additions and 978 deletions

View File

@@ -8,6 +8,7 @@
- Feat: 設定の管理が強化されました
- 自動でバックアップされるように
- 任意の設定項目をデバイス間で同期できるように
- Feat: 画面を重ねて表示するオプションを実装(実験的)
- Enhance: プラグインの管理が強化されました
- インストール/アンインストール/設定の変更時にリロード不要になりました
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
@@ -17,6 +18,7 @@
- Enhance: 投稿フォームの設定メニューを改良
- 投稿フォームをリセットできるように
- 文字数カウントを復活
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server

View File

@@ -273,7 +273,6 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
query?: Record<string, string>;
loginRequired?: boolean;
hash?: string;
globalCacheKey?: string;
children?: RouteDef[];
}
```

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
makeActive: "Activar"
display: "Veure"
copy: "Copiar"
copiedToClipboard: "Copiat al porta papers"
metrics: "Mètriques"
overview: "Visió General"
logs: "Registres"
@@ -1139,7 +1140,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d
thisChannelArchived: "Aquest Canal ha sigut arxivat."
displayOfNote: "Mostrar notes"
initialAccountSetting: "Configuració del perfil"
youFollowing: "Seguint"
youFollowing: "Segueixes "
preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
options: "Opcions"
@@ -1332,6 +1333,7 @@ preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronitzac
paste: "Pegar"
emojiPalette: "Calaix d'emojis"
postForm: "Formulari de publicació"
textCount: "Nombre de caràcters "
_emojiPalette:
palettes: "Calaixos d'emojis"
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
@@ -1355,6 +1357,8 @@ _settings:
appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer."
soundsBanner: "Configuració dels sons que reproduirà el client."
timelineAndNote: "Línia de temps i nota"
makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables"
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
_preferencesProfile:
profileName: "Nom del perfil"
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
@@ -2519,6 +2523,7 @@ _notification:
achievementEarned: "Assoliment desbloquejat"
exportCompleted: "Exportació completada"
login: "Iniciar sessió"
createToken: "Creació de tokens d'accés "
test: "Prova la notificació"
app: "Notificacions d'aplicacions"
_actions:

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate"
display: "Display"
copy: "Copy"
copiedToClipboard: "Copied to clipboard"
metrics: "Metrics"
overview: "Overview"
logs: "Logs"
@@ -1323,7 +1324,21 @@ untitled: "Untitled"
noName: "No name"
skip: "Skip"
restore: "Restore"
syncBetweenDevices: "Sync between devices"
preferenceSyncConflictTitle: "The configured value exists on the server."
preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?"
preferenceSyncConflictChoiceServer: "Configured value on server"
preferenceSyncConflictChoiceDevice: "Configured value on device"
preferenceSyncConflictChoiceCancel: "Cancel enabling sync"
paste: "Paste"
emojiPalette: "Emoji palette"
postForm: "Posting form"
textCount: "Character count"
_emojiPalette:
palettes: "Palette"
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
paletteForMain: "Main palette"
paletteForReaction: "Reaction palette"
_settings:
driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings."
pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually."
@@ -1341,6 +1356,9 @@ _settings:
preferencesBanner: "You can configure the overall behavior of the client according to your preferences."
appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences."
soundsBanner: "You can configure the sound settings for playback in the client."
timelineAndNote: "Timeline and note"
makeEveryTextElementsSelectable: "Make all text elements selectable"
makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
_preferencesProfile:
profileName: "Profile name"
profileNameDescription: "Set a name that identifies this device."
@@ -2505,6 +2523,7 @@ _notification:
achievementEarned: "Achievement unlocked"
exportCompleted: "The export has been completed"
login: "Sign In"
createToken: "Create access token"
test: "Notification test"
app: "Notifications from linked apps"
_actions:
@@ -2532,6 +2551,7 @@ _deck:
useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
flexible: "Auto-adjust width"
enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices"
_columns:
main: "Main"
widgets: "Widgets"

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva"
display: "Visualizza"
copy: "Copia"
copiedToClipboard: "Copiato negli appunti"
metrics: "Statistiche"
overview: "Anteprima"
logs: "Log"
@@ -973,7 +974,7 @@ check: "Verifica"
driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal sistema."
typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo"
document: "Documentazione"
@@ -1323,7 +1324,21 @@ untitled: "Senza titolo"
noName: "Senza nome"
skip: "Salta"
restore: "Ripristina"
syncBetweenDevices: "Sincronizzazione tra i dispositivi"
preferenceSyncConflictTitle: "Sul server esiste già il valore impostato"
preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?"
preferenceSyncConflictChoiceServer: "Valore del server"
preferenceSyncConflictChoiceDevice: "Valore del dispositivo"
preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione"
paste: "Incolla"
emojiPalette: "Tavolozza emoji"
postForm: "Finestra di pubblicazione"
textCount: "Il numero di caratteri"
_emojiPalette:
palettes: "Tavolozza"
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
paletteForMain: "Tavolozza principale"
paletteForReaction: "Tavolozza per reazioni"
_settings:
driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file."
pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente."
@@ -1341,6 +1356,9 @@ _settings:
preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo."
appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze."
soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo."
timelineAndNote: "Note e Timeline"
makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile"
makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni."
_preferencesProfile:
profileName: "Nome del profilo"
profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
@@ -2505,6 +2523,7 @@ _notification:
achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata"
login: "Accessi"
createToken: "Creare un token di accesso"
test: "Notifiche di test"
app: "Notifiche da applicazioni"
_actions:
@@ -2532,6 +2551,7 @@ _deck:
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
flexible: "Larghezza flessibile"
enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi"
_columns:
main: "Principale"
widgets: "Riquadri"

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
makeActive: "启用"
display: "显示"
copy: "复制"
copiedToClipboard: "已复制到剪贴板"
metrics: "指标"
overview: "概览"
logs: "日志"
@@ -1332,6 +1333,7 @@ preferenceSyncConflictChoiceCancel: "取消同步"
paste: "粘贴"
emojiPalette: "表情符号调色板"
postForm: "投稿窗口"
textCount: "字数"
_emojiPalette:
palettes: "调色板"
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"

View File

@@ -698,6 +698,7 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話"
makeActive: "啟用"
display: "檢視"
copy: "複製"
copiedToClipboard: "已複製到剪貼簿"
metrics: "指標"
overview: "概覽"
logs: "日誌"
@@ -1332,6 +1333,7 @@ preferenceSyncConflictChoiceCancel: "取消啟用同步"
paste: "貼上"
emojiPalette: "表情符號調色盤"
postForm: "發文視窗"
textCount: "字數"
_emojiPalette:
palettes: "調色盤"
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
@@ -1355,6 +1357,8 @@ _settings:
appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。"
soundsBanner: "您可以調整用戶端播放的聲音設定。"
timelineAndNote: "時間軸及貼文"
makeEveryTextElementsSelectable: "允許選取所有文字"
makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
_preferencesProfile:
profileName: "設定檔案名稱"
profileNameDescription: "設定一個名稱來識別此裝置。"
@@ -2519,6 +2523,7 @@ _notification:
achievementEarned: "獲得成就"
exportCompleted: "已完成匯出。"
login: "登入"
createToken: "建立存取權杖"
test: "通知測試"
app: "應用程式通知"
_actions:

View File

@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.3.2-beta.3",
"version": "2025.3.2-beta.4",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@@ -6,6 +6,7 @@
import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common';
import { ZipReader } from 'slacc';
import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js';
import type Logger from '@/logger.js';
@@ -86,6 +87,7 @@ export class ImportCustomEmojisProcessorService {
const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({
name: emojiInfo.name,
host: IsNull(),
});
try {

View File

@@ -12,6 +12,7 @@ const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site
export const host = address.host;
export const hostname = address.hostname;
export const url = address.origin;
export const port = address.port;
export const apiUrl = location.origin + '/api';
export const wsOrigin = location.origin;
export const lang = localStorage.getItem('lang') ?? 'en-US';

View File

@@ -64,12 +64,12 @@ initialize({
initLocalStorage();
queueMicrotask(() => {
Promise.all([
import('../src/components'),
import('../src/directives'),
import('../src/widgets'),
import('../src/theme'),
import('../src/preferences'),
import('../src/os'),
import('../src/components/index.js'),
import('../src/directives/index.js'),
import('../src/widgets/index.js'),
import('../src/theme.js'),
import('../src/preferences.js'),
import('../src/os.js'),
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
setup((app) => {
moduleInitialized = true;

View File

@@ -26,8 +26,6 @@ import { deckStore } from '@/ui/deck/deck-store.js';
import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/main.js';
import { createMainRouter } from '@/router/definition.js';
import { prefer } from '@/preferences.js';
import { $i } from '@/i.js';
@@ -267,8 +265,6 @@ export async function common(createVue: () => App<Element>) {
const app = createVue();
setupRouter(app, createMainRouter);
if (_DEV_) {
app.config.performance = true;
}

View File

@@ -24,7 +24,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
import { initializeSw } from '@/utility/initialize-sw.js';
import { emojiPicker } from '@/utility/emoji-picker.js';
import { mainRouter } from '@/router/main.js';
import { mainRouter } from '@/router.js';
import { makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js';

View File

@@ -88,9 +88,9 @@ import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue';
import RouterView from '@/components/global/RouterView.vue';
import { useRouterFactory } from '@/router/supplier';
import MkTextarea from '@/components/MkTextarea.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { createRouter } from '@/router.js';
const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@@ -100,10 +100,9 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
const routerFactory = useRouterFactory();
const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`);
targetRouter.init();
const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`);
reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? '');

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -47,7 +47,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
const uiWindow = useTemplateRef('uiWindow');
const comment = ref(props.initialComment ?? '');
function send() {

View File

@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef } from 'vue';
import { onMounted, onUnmounted, useTemplateRef } from 'vue';
import isChromatic from 'chromatic/isChromatic';
const canvasEl = shallowRef<HTMLCanvasElement>();
const canvasEl = useTemplateRef('canvasEl');
const props = withDefaults(defineProps<{
scale?: number;

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue';
import { onMounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -37,8 +37,8 @@ const props = withDefaults(defineProps<{
}>(), {
});
const rootEl = shallowRef<HTMLDivElement>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const rootEl = useTemplateRef('rootEl');
const modal = useTemplateRef('modal');
async function ok() {
if (props.announcement.needConfirmationToRead) {

View File

@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import XAntennaEditor from '@/components/MkAntennaEditor.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
(ev: 'closed'): void,
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna);

View File

@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import sanitizeHtml from 'sanitize-html';
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
@@ -139,7 +139,7 @@ const emit = defineEmits<{
}>();
const suggests = ref<Element>();
const rootEl = shallowRef<HTMLDivElement>();
const rootEl = useTemplateRef('rootEl');
const fetching = ref(true);
const users = ref<any[]>([]);

View File

@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, shallowRef } from 'vue';
import { nextTick, onMounted, useTemplateRef } from 'vue';
const props = defineProps<{
type?: 'button' | 'submit' | 'reset';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
(ev: 'click', payload: MouseEvent): void;
}>();
const el = shallowRef<HTMLElement | null>(null);
const ripples = shallowRef<HTMLElement | null>(null);
const el = useTemplateRef('el');
const ripples = useTemplateRef('ripples');
onMounted(() => {
if (props.autofocus) {

View File

@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
import { store } from '@/store.js';
// APIs provided by Captcha services
@@ -69,7 +69,7 @@ const emit = defineEmits<{
const available = ref(false);
const captchaEl = shallowRef<HTMLDivElement | undefined>();
const captchaEl = useTemplateRef('captchaEl');
const captchaWidgetId = ref<string | undefined>(undefined);
const testcaptchaInput = ref('');
const testcaptchaPassed = ref(false);

View File

@@ -45,12 +45,8 @@ export type ChartSrc =
</script>
<script lang="ts" setup>
/* eslint-disable id-denylist --
Chart.js has a `data` attribute in most chart definitions, which triggers the
id-denylist violation when setting it. This is causing about 60+ lint issues.
As this is part of Chart.js's API it makes sense to disable the check here.
*/
import { onMounted, ref, shallowRef, watch } from 'vue';
import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import { misskeyApiGet } from '@/utility/misskey-api.js';
@@ -96,7 +92,7 @@ const props = withDefaults(defineProps<{
nowForChromatic: undefined,
});
const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
const legendEl = useTemplateRef('legendEl');
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x);
@@ -134,7 +130,7 @@ let chartData: {
bytes?: boolean;
} | null = null;
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const chartEl = useTemplateRef('chartEl');
const fetching = ref(true);
const getDate = (ago: number) => {
@@ -849,7 +845,7 @@ watch(() => [props.src, props.span], fetchAndRender);
onMounted(() => {
fetchAndRender();
});
/* eslint-enable id-denylist */
</script>
<style lang="scss" module>

View File

@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
import { ref, watch, toRefs, useTemplateRef, nextTick } from 'vue';
import { debounce } from 'throttle-debounce';
import MkButton from '@/components/MkButton.vue';
import { i18n } from '@/i18n.js';
@@ -61,7 +61,7 @@ const { modelValue } = toRefs(props);
const v = ref<string>(modelValue.value ?? '');
const focused = ref(false);
const changed = ref(false);
const inputEl = shallowRef<HTMLTextAreaElement>();
const inputEl = useTemplateRef('inputEl');
const focus = () => inputEl.value?.focus();

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, toRefs } from 'vue';
import { ref, useTemplateRef, toRefs } from 'vue';
const props = defineProps<{
modelValue: string | null;
@@ -39,7 +39,7 @@ const emit = defineEmits<{
const { modelValue } = toRefs(props);
const v = ref(modelValue.value);
const inputEl = shallowRef<HTMLElement>();
const inputEl = useTemplateRef('inputEl');
const onInput = () => {
emit('update:modelValue', v.value ?? '');

View File

@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
@@ -58,9 +58,9 @@ const props = withDefaults(defineProps<{
maxHeight: null,
});
const rootEl = shallowRef<HTMLElement>();
const contentEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const contentEl = useTemplateRef('contentEl');
const headerEl = useTemplateRef('headerEl');
const showBody = ref(props.expanded);
const ignoreOmit = ref(false);
const omitted = ref(false);

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
import contains from '@/utility/contains.js';
@@ -34,7 +34,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const rootEl = shallowRef<HTMLDivElement>();
const rootEl = useTemplateRef('rootEl');
const zIndex = ref<number>(os.claimZIndex('high'));

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue';
import { onMounted, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
@@ -56,8 +56,8 @@ const props = defineProps<{
}>();
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const imgEl = shallowRef<HTMLImageElement>();
const dialogEl = useTemplateRef('dialogEl');
const imgEl = useTemplateRef('imgEl');
let cropper: Cropper | null = null;
const loading = ref(true);

View File

@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import MkLink from '@/components/MkLink.vue';
import { i18n } from '@/i18n.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -73,7 +73,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialogEl = useTemplateRef('dialogEl');
function cancel() {
emit('cancel');

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed } from 'vue';
import { ref, useTemplateRef, computed } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@@ -117,7 +117,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);

View File

@@ -47,7 +47,7 @@ import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
import { deviceKind } from '@/utility/device-kind.js';
import { useRouter } from '@/router/supplier.js';
import { useRouter } from '@/router.js';
const router = useRouter();

View File

@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkButton from './MkButton.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -129,8 +129,8 @@ const emit = defineEmits<{
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
}>();
const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
const fileInput = shallowRef<HTMLInputElement>();
const loadMoreFiles = useTemplateRef('loadMoreFiles');
const fileInput = useTemplateRef('fileInput');
const folder = ref<Misskey.entities.DriveFolder | null>(null);
const files = ref<Misskey.entities.DriveFile[]>([]);

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue';
import { ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import XDrive from '@/components/MkDrive.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -43,7 +43,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);

View File

@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
import { useTemplateRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
import { url } from '@@/js/config.js';
import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
@@ -121,7 +121,7 @@ const props = defineProps<{
}>();
//#region Modalの制御
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialogEl = useTemplateRef('dialogEl');
function cancel() {
emit('cancel');
@@ -198,9 +198,9 @@ function doCopy() {
//#endregion
//#region プレビューのリサイズ
const resizerRootEl = shallowRef<HTMLDivElement>();
const resizerRootEl = useTemplateRef('resizerRootEl');
const iframeLoading = ref(true);
const iframeEl = shallowRef<HTMLIFrameElement>();
const iframeEl = useTemplateRef('iframeEl');
const iframeHeight = ref(0);
const iframeScale = ref(1);
const iframeStyle = computed(() => {

View File

@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed, watch, onMounted } from 'vue';
import { ref, useTemplateRef, computed, watch, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import {
emojilist,
@@ -157,8 +157,8 @@ const emit = defineEmits<{
(ev: 'esc'): void;
}>();
const searchEl = shallowRef<HTMLInputElement>();
const emojisEl = shallowRef<HTMLDivElement>();
const searchEl = useTemplateRef('searchEl');
const emojisEl = useTemplateRef('emojisEl');
const {
emojiPickerScale,

View File

@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
import { prefer } from '@/preferences.js';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
const modal = useTemplateRef('modal');
const picker = useTemplateRef('picker');
function chosen(emoji: string) {
emit('done', emoji);

View File

@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef, ref } from 'vue';
import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@@ -42,7 +42,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const caption = ref(props.default);

View File

@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, shallowRef, watch } from 'vue';
import { onMounted, ref, useTemplateRef, watch } from 'vue';
import { miLocalStorage } from '@/local-storage.js';
import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js';
@@ -46,7 +46,7 @@ const props = withDefaults(defineProps<{
persistKey: null,
});
const rootEl = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const parentBg = ref<string | null>(null);
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, ref, shallowRef } from 'vue';
import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js';
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
spacerMax: 22,
});
const rootEl = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const bgSame = ref(false);
const opened = ref(props.defaultOpen);
const openedAtLeastOnce = ref(props.defaultOpen);

View File

@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { reactive, shallowRef } from 'vue';
import { reactive, useTemplateRef } from 'vue';
import MkInput from './MkInput.vue';
import MkTextarea from './MkTextarea.vue';
import MkSwitch from './MkSwitch.vue';
@@ -99,7 +99,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const values = reactive({});
for (const item in props.form) {

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
import { onMounted, nextTick, watch, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as Misskey from 'misskey-js';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<{
label: '',
});
const rootEl = shallowRef<HTMLDivElement | null>(null);
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const rootEl = useTemplateRef('rootEl');
const chartEl = useTemplateRef('chartEl');
const now = new Date();
let chartInstance: Chart | null = null;
const fetching = ref(true);

View File

@@ -26,12 +26,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed, nextTick, watch } from 'vue';
import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
import { prefer } from '@/preferences.js';
const rootEl = shallowRef<HTMLDivElement>();
const rootEl = useTemplateRef('rootEl');
const tabModel = defineModel<string>('tab');

View File

@@ -83,7 +83,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
</script>
<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha';
import { prefer } from '@/preferences.js';
@@ -120,9 +120,9 @@ const props = withDefaults(defineProps<{
});
const viewId = uuid();
const canvas = shallowRef<HTMLCanvasElement>();
const root = shallowRef<HTMLDivElement>();
const img = shallowRef<HTMLImageElement>();
const canvas = useTemplateRef('canvas');
const root = useTemplateRef('root');
const img = useTemplateRef('img');
const loaded = ref(false);
const canvasWidth = ref(64);
const canvasHeight = ref(64);

View File

@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
import { debounce } from 'throttle-debounce';
import { useInterval } from '@@/js/use-interval.js';
import type { InputHTMLAttributes } from 'vue';
@@ -92,9 +92,9 @@ const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = shallowRef<HTMLInputElement>();
const prefixEl = shallowRef<HTMLElement>();
const suffixEl = shallowRef<HTMLElement>();
const inputEl = useTemplateRef('inputEl');
const prefixEl = useTemplateRef('prefixEl');
const suffixEl = useTemplateRef('suffixEl');
const height =
props.small ? 33 :
props.large ? 39 :

View File

@@ -84,8 +84,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, shallowRef } from 'vue';
import { onMounted, ref, computed, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue';
import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -95,7 +96,6 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import MkHeatmap from '@/components/MkHeatmap.vue';
import type { HeatmapSource } from '@/components/MkHeatmap.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
@@ -109,8 +109,8 @@ const chartLimit = 500;
const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users');
const heatmapSrc = ref<HeatmapSource>('active-users');
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const subDoughnutEl = useTemplateRef('subDoughnutEl');
const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: 'middle',

View File

@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import { navbarItemDef } from '@/navbar.js';
import { deviceKind } from '@/utility/device-kind.js';
@@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
deviceKind === 'smartphone' ? 'drawer' :
'dialog';
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
const menu = prefer.s.menu;

View File

@@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js';
@@ -151,8 +151,8 @@ function hasFocus() {
return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
}
const playerEl = shallowRef<HTMLDivElement>();
const audioEl = shallowRef<HTMLAudioElement>();
const playerEl = useTemplateRef('playerEl');
const audioEl = useTemplateRef('audioEl');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));

View File

@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe';
@@ -46,7 +46,7 @@ const props = defineProps<{
raw?: boolean;
}>();
const gallery = shallowRef<HTMLDivElement>();
const gallery = useTemplateRef('gallery');
const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
const count = computed(() => props.mediaList.filter(media => previewable(media)).length);

View File

@@ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
import * as Misskey from 'misskey-js';
import type { MenuItem } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js';
@@ -299,8 +299,8 @@ async function toggleSensitive(file: Misskey.entities.DriveFile) {
}
// MediaControl: Video State
const videoEl = shallowRef<HTMLVideoElement>();
const playerEl = shallowRef<HTMLDivElement>();
const videoEl = useTemplateRef('videoEl');
const playerEl = useTemplateRef('playerEl');
const isHoverring = ref(false);
const controlsShowing = computed(() => {
if (!oncePlayed.value) return true;

View File

@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue';
import { nextTick, onMounted, onUnmounted, provide, useTemplateRef, watch } from 'vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
provide('isNestingMenu', true);
const el = shallowRef<HTMLElement>();
const el = useTemplateRef('el');
const align = 'left';
const SCROLLBAR_THICKNESS = 16;

View File

@@ -214,7 +214,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch } from 'vue';
import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
import type { Keymap } from '@/utility/hotkey.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
@@ -247,11 +247,11 @@ const big = isTouchUsing;
const isNestingMenu = inject<boolean>('isNestingMenu', false);
const itemsEl = shallowRef<HTMLElement>();
const itemsEl = useTemplateRef('itemsEl');
const items2 = ref<InnerMenuItem[]>();
const child = shallowRef<InstanceType<typeof XChild>>();
const child = useTemplateRef('child');
const keymap = {
'up|k|shift+tab': {
@@ -292,7 +292,7 @@ watch(() => props.items, () => {
});
const childMenu = ref<MenuItem[] | null>();
const childTarget = shallowRef<HTMLElement | null>();
const childTarget = useTemplateRef('childTarget');
function closeChild() {
childMenu.value = null;

View File

@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue';
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, useTemplateRef, computed } from 'vue';
import type { Keymap } from '@/utility/hotkey.js';
import * as os from '@/os.js';
import { isTouchUsing } from '@/utility/touch.js';
@@ -100,8 +100,8 @@ const maxHeight = ref<number>();
const fixed = ref(false);
const transformOrigin = ref('center');
const showing = ref(true);
const modalRootEl = shallowRef<HTMLElement>();
const content = shallowRef<HTMLElement>();
const modalRootEl = useTemplateRef('modalRootEl');
const content = useTemplateRef('content');
const zIndex = os.claimZIndex(props.zPriority);
const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{
@@ -47,9 +47,9 @@ const emit = defineEmits<{
(event: 'esc'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const modal = useTemplateRef('modal');
const rootEl = useTemplateRef('rootEl');
const headerEl = useTemplateRef('headerEl');
const bodyWidth = ref(0);
const bodyHeight = ref(0);

View File

@@ -178,7 +178,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, watch, provide } from 'vue';
import { computed, inject, onMounted, ref, useTemplateRef, watch, provide } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@@ -271,14 +271,14 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
const renoteButton = useTemplateRef('renoteButton');
const renoteTime = useTemplateRef('renoteTime');
const reactButton = useTemplateRef('reactButton');
const clipButton = useTemplateRef('clipButton');
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const galleryEl = useTemplateRef('galleryEl');
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);

View File

@@ -211,7 +211,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue';
import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js';
@@ -290,14 +290,14 @@ if (noteViewInterruptors.length > 0) {
const isRenote = Misskey.note.isPureRenote(note.value);
const rootEl = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
const renoteButton = shallowRef<HTMLElement>();
const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
const menuButton = useTemplateRef('menuButton');
const renoteButton = useTemplateRef('renoteButton');
const renoteTime = useTemplateRef('renoteTime');
const reactButton = useTemplateRef('reactButton');
const clipButton = useTemplateRef('clipButton');
const appearNote = computed(() => getAppearNote(note.value));
const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
const galleryEl = useTemplateRef('galleryEl');
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);

View File

@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import type { Paging } from '@/components/MkPagination.vue';
import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
@@ -46,7 +46,7 @@ const props = defineProps<{
disableAutoLoad?: boolean;
}>();
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
const pagingComponent = useTemplateRef('pagingComponent');
defineExpose({
pagingComponent,

View File

@@ -30,13 +30,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue';
import type { Ref } from 'vue';
import { ref, useTemplateRef } from 'vue';
import { notificationTypes } from '@@/js/const.js';
import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue';
import type { Ref } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { notificationTypes } from '@@/js/const.js';
import { i18n } from '@/i18n.js';
type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
@@ -52,7 +52,7 @@ const props = withDefaults(defineProps<{
excludeTypes: () => [],
});
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
import { onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue';
import * as Misskey from 'misskey-js';
import type { notificationTypes } from '@@/js/const.js';
import MkPagination from '@/components/MkPagination.vue';
@@ -41,7 +41,7 @@ const props = defineProps<{
excludeTypes?: typeof notificationTypes[number][];
}>();
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
const pagingComponent = useTemplateRef('pagingComponent');
const pagination = computed(() => prefer.r.useGroupedNotifications.value ? {
endpoint: 'i/notifications-grouped' as const,

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
import { i18n } from '@/i18n.js';
const props = withDefaults(defineProps<{
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<{
maxHeight: 200,
});
const content = shallowRef<HTMLElement>();
const content = useTemplateRef('content');
const omitted = ref(false);
const ignoreOmit = ref(false);

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef } from 'vue';
import { url } from '@@/js/config.js';
import type { PageMetadata } from '@/page.js';
import RouterView from '@/components/global/RouterView.vue';
@@ -41,8 +41,7 @@ import { i18n } from '@/i18n.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/utility/achievements.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
import { createRouter, mainRouter } from '@/router.js';
import { analytics } from '@/analytics.js';
import { DI } from '@/di.js';
import { prefer } from '@/preferences.js';
@@ -55,14 +54,12 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const routerFactory = useRouterFactory();
const windowRouter = routerFactory(props.initialPath);
const windowRouter = createRouter(props.initialPath);
const pageMetadata = ref<null | PageMetadata>(null);
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: string; }[]>([{
path: windowRouter.getCurrentPath(),
key: windowRouter.getCurrentKey(),
const windowEl = useTemplateRef('windowEl');
const history = ref<{ path: string; }[]>([{
path: windowRouter.getCurrentFullPath(),
}]);
const buttonsLeft = computed(() => {
const buttons: Record<string, unknown>[] = [];
@@ -100,20 +97,20 @@ function getSearchMarker(path: string) {
const searchMarkerId = ref<string | null>(getSearchMarker(props.initialPath));
windowRouter.addListener('push', ctx => {
history.value.push({ path: ctx.path, key: ctx.key });
history.value.push({ path: ctx.fullPath });
});
windowRouter.addListener('replace', ctx => {
history.value.pop();
history.value.push({ path: ctx.path, key: ctx.key });
history.value.push({ path: ctx.fullPath });
});
windowRouter.addListener('change', ctx => {
if (_DEV_) console.log('windowRouter: change', ctx.path);
searchMarkerId.value = getSearchMarker(ctx.path);
if (_DEV_) console.log('windowRouter: change', ctx.fullPath);
searchMarkerId.value = getSearchMarker(ctx.fullPath);
analytics.page({
path: ctx.path,
title: ctx.path,
path: ctx.fullPath,
title: ctx.fullPath,
});
});
@@ -142,20 +139,20 @@ const contextmenu = computed(() => ([{
icon: 'ti ti-external-link',
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
window.open(url + windowRouter.getCurrentFullPath(), '_blank', 'noopener');
windowEl.value?.close();
},
}, {
icon: 'ti ti-link',
text: i18n.ts.copyLink,
action: () => {
copyToClipboard(url + windowRouter.getCurrentPath());
copyToClipboard(url + windowRouter.getCurrentFullPath());
},
}]));
function back() {
history.value.pop();
windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
windowRouter.replace(history.value.at(-1)!.path);
}
function reload() {
@@ -167,12 +164,12 @@ function close() {
}
function expand() {
mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
mainRouter.push(windowRouter.getCurrentFullPath(), 'forcePage');
windowEl.value?.close();
}
function popout() {
_popout(windowRouter.getCurrentPath(), windowEl.value?.$el);
_popout(windowRouter.getCurrentFullPath(), windowEl.value?.$el);
windowEl.value?.close();
}

View File

@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
@@ -106,7 +106,7 @@ const emit = defineEmits<{
(ev: 'status', error: boolean): void;
}>();
const rootEl = shallowRef<HTMLElement>();
const rootEl = useTemplateRef('rootEl');
// 遡り中かどうか
const backed = ref(false);

View File

@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef, ref } from 'vue';
import { onMounted, useTemplateRef, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -54,8 +54,8 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
const dialog = useTemplateRef('dialog');
const passwordInput = useTemplateRef('passwordInput');
const password = ref('');
const isBackupCode = ref(false);
const token = ref<string | null>(null);

View File

@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef } from 'vue';
import { ref, useTemplateRef } from 'vue';
import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue';
import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: 'closing'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
const manualShowing = ref(true);
const hiding = ref(false);

View File

@@ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue';
import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@@ -165,11 +165,11 @@ const emit = defineEmits<{
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
}>();
const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement>();
const otherSettingsButton = shallowRef<HTMLElement>();
const textareaEl = useTemplateRef('textareaEl');
const cwInputEl = useTemplateRef('cwInputEl');
const hashtagsInputEl = useTemplateRef('hashtagsInputEl');
const visibilityButton = useTemplateRef('visibilityButton');
const otherSettingsButton = useTemplateRef('otherSettingsButton');
const posting = ref(false);
const posted = ref(false);

View File

@@ -25,10 +25,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import type { PostFormProps } from '@/types/post-form.js';
import MkModal from '@/components/MkModal.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import type { PostFormProps } from '@/types/post-form.js';
const props = withDefaults(defineProps<PostFormProps & {
instant?: boolean;
@@ -42,8 +42,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const form = shallowRef<InstanceType<typeof MkPostForm>>();
const modal = useTemplateRef('modal');
function onPosted() {
modal.value?.close({

View File

@@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
import { i18n } from '@/i18n.js';
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
import { getScrollContainer } from '@@/js/scroll.js';
import { i18n } from '@/i18n.js';
import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
const SCROLL_STOP = 10;
@@ -43,7 +43,7 @@ const pullDistance = ref(0);
let supportPointerDesktop = false;
let startScreenY: number | null = null;
const rootEl = shallowRef<HTMLDivElement>();
const rootEl = useTemplateRef('rootEl');
let scrollEl: HTMLElement | null = null;
let disabled = false;

View File

@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
import { isTouchUsing } from '@/utility/touch.js';
import * as os from '@/os.js';
@@ -58,8 +58,8 @@ const emit = defineEmits<{
(ev: 'dragEnded', value: number): void;
}>();
const containerEl = shallowRef<HTMLElement>();
const thumbEl = shallowRef<HTMLElement>();
const containerEl = useTemplateRef('containerEl');
const thumbEl = useTemplateRef('thumbEl');
const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
const steppedRawValue = computed(() => {

View File

@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent, shallowRef } from 'vue';
import { defineAsyncComponent, useTemplateRef } from 'vue';
import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
@@ -20,7 +20,7 @@ const props = defineProps<{
withTooltip?: boolean;
}>();
const elRef = shallowRef();
const elRef = useTemplateRef('elRef');
if (props.withTooltip) {
useTooltip(elRef, (showing) => {

View File

@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, inject, onMounted, shallowRef, watch } from 'vue';
import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { getUnicodeEmoji } from '@@/js/emojilist.js';
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
@@ -50,7 +50,7 @@ const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void;
}>();
const buttonEl = shallowRef<HTMLElement>();
const buttonEl = useTemplateRef('buttonEl');
const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, shallowRef, ref } from 'vue';
import { onMounted, nextTick, useTemplateRef, ref } from 'vue';
import { Chart } from 'chart.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { store } from '@/store.js';
@@ -23,8 +23,8 @@ import { initChart } from '@/utility/init-chart.js';
initChart();
const rootEl = shallowRef<HTMLDivElement | null>(null);
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const rootEl = useTemplateRef('rootEl');
const chartEl = useTemplateRef('chartEl');
let chartInstance: Chart | null = null;
const fetching = ref(true);

View File

@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue';
import { onMounted, useTemplateRef } from 'vue';
import { Chart } from 'chart.js';
import tinycolor from 'tinycolor2';
import { store } from '@/store.js';
@@ -20,7 +20,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
initChart();
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const chartEl = useTemplateRef('chartEl');
const { handler: externalTooltipHandler } = useChartTooltip();

View File

@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { shallowRef } from 'vue';
import { useTemplateRef } from 'vue';
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
import MkSignin from '@/components/MkSignin.vue';
import MkModal from '@/components/MkModal.vue';
@@ -46,7 +46,7 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
function onClose() {
emit('cancelled');

View File

@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef, ref } from 'vue';
import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XSignup from '@/components/MkSignupDialog.form.vue';
import XServerRules from '@/components/MkSignupDialog.rules.vue';
@@ -52,7 +52,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const isAcceptedServerRule = ref(false);

View File

@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
const particles = ref<{
id: string,
@@ -66,7 +66,7 @@ const particles = ref<{
dur: number,
color: string
}[]>([]);
const el = shallowRef<HTMLElement>();
const el = useTemplateRef('el');
const width = ref(0);
const height = ref(0);
const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];

View File

@@ -98,7 +98,7 @@ import type { SearchIndexItem } from '@/utility/autogen/settings-search-index.js
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@@/js/scroll.js';
import { useRouter } from '@/router/supplier.js';
import { useRouter } from '@/router.js';
import { initIntlString, compareStringIncludes } from '@/utility/intl-string.js';
const props = defineProps<{

View File

@@ -92,15 +92,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import type {
MkSystemWebhookEditorProps,
MkSystemWebhookResult,
SystemWebhookEventType,
} from '@/components/MkSystemWebhookEditor.impl.js';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
@@ -122,7 +122,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialogEl = useTemplateRef('dialogEl');
const props = defineProps<MkSystemWebhookEditorProps>();

View File

@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue';
import { onMounted, watch, onBeforeUnmount, ref, useTemplateRef } from 'vue';
import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas;
@@ -24,9 +24,9 @@ const computedStyle = getComputedStyle(document.documentElement);
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const available = ref(false);
const rootEl = shallowRef<HTMLElement | null>(null);
const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
const tagsEl = shallowRef<HTMLElement | null>(null);
const rootEl = useTemplateRef('rootEl');
const canvasEl = useTemplateRef('canvasEl');
const tagsEl = useTemplateRef('tagsEl');
const width = ref(300);
watch(available, () => {

View File

@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, useTemplateRef } from 'vue';
import { debounce } from 'throttle-debounce';
import type { SuggestionType } from '@/utility/autocomplete.js';
import MkButton from '@/components/MkButton.vue';
@@ -75,7 +75,7 @@ const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = shallowRef<HTMLTextAreaElement>();
const inputEl = useTemplateRef('inputEl');
const preview = ref(false);
let autocompleteWorker: Autocomplete | null = null;

View File

@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
import { computed, watch, onUnmounted, provide, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import type { BasicTimelineType } from '@/timelines.js';
import type { Paging } from '@/components/MkPagination.vue';
@@ -67,8 +67,8 @@ type TimelineQueryType = {
roleId?: string
};
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
const prComponent = useTemplateRef('prComponent');
const tlComponent = useTemplateRef('tlComponent');
let tlNotesCount = 0;

View File

@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef, ref } from 'vue';
import { useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkInput from './MkInput.vue';
import MkSwitch from './MkSwitch.vue';
@@ -77,7 +77,7 @@ const emit = defineEmits<{
const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
const name = ref(props.initialName);
const permissionSwitches = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
const permissionSwitchesForAdmin = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);

View File

@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue';
import { nextTick, onMounted, onUnmounted, useTemplateRef } from 'vue';
import * as os from '@/os.js';
import { calcPopupPosition } from '@/utility/popup-position.js';
import { prefer } from '@/preferences.js';
@@ -51,7 +51,7 @@ const emit = defineEmits<{
// タイミングによっては最初から showing = false な場合があり、その場合に closed 扱いにしないと永久にDOMに残ることになる
if (!props.showing) emit('closed');
const el = shallowRef<HTMLElement>();
const el = useTemplateRef('el');
const zIndex = os.claimZIndex('high');
function setPosition() {

View File

@@ -148,7 +148,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, watch } from 'vue';
import { ref, useTemplateRef, watch } from 'vue';
import { host } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import XNote from '@/components/MkTutorialDialog.Note.vue';
@@ -158,7 +159,6 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { host } from '@@/js/config.js';
import { claimAchievement } from '@/utility/achievements.js';
import * as os from '@/os.js';
@@ -170,7 +170,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const page = ref(props.initialPage ?? 0);

View File

@@ -15,15 +15,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue';
import { onMounted, useTemplateRef } from 'vue';
import { version } from '@@/js/config.js';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkSparkle from '@/components/MkSparkle.vue';
import { version } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { confetti } from '@/utility/confetti.js';
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
function whatIsNew() {
modal.value?.close();

View File

@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref, computed, shallowRef } from 'vue';
import { onMounted, ref, computed, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import { host as currentHost, hostname } from '@@/js/config.js';
import MkInput from '@/components/MkInput.vue';
@@ -94,7 +94,7 @@ const host = ref('');
const users = ref<Misskey.entities.UserLite[]>([]);
const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
const selected = ref<Misskey.entities.UserLite | null>(null);
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialogEl = useTemplateRef('dialogEl');
function search() {
if (username.value === '' && host.value === '') {

View File

@@ -128,7 +128,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, watch, nextTick, defineAsyncComponent } from 'vue';
import { ref, useTemplateRef, watch, nextTick, defineAsyncComponent } from 'vue';
import { host } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
@@ -137,7 +138,6 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
import MkAnimBg from '@/components/MkAnimBg.vue';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { host } from '@@/js/config.js';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
import { store } from '@/store.js';
import * as os from '@/os.js';
@@ -146,9 +146,8 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = useTemplateRef('dialog');
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
const page = ref(store.s.accountSetupWizard);
watch(page, () => {

View File

@@ -42,12 +42,12 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, shallowRef, ref } from 'vue';
import { nextTick, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import MkModal from '@/components/MkModal.vue';
import { i18n } from '@/i18n.js';
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
const props = withDefaults(defineProps<{
currentVisibility: typeof Misskey.noteVisibilities[number];

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef, ref, nextTick } from 'vue';
import { onMounted, useTemplateRef, ref, nextTick } from 'vue';
import { Chart } from 'chart.js';
import gradient from 'chartjs-plugin-gradient';
import tinycolor from 'tinycolor2';
@@ -25,7 +25,7 @@ import { initChart } from '@/utility/init-chart.js';
initChart();
const chartEl = shallowRef<HTMLCanvasElement | null>(null);
const chartEl = useTemplateRef('chartEl');
const now = new Date();
let chartInstance: Chart | null = null;
const chartLimit = 30;

View File

@@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { watch, shallowRef } from 'vue';
import { watch, useTemplateRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
const modal = shallowRef<InstanceType<typeof MkModal>>();
const modal = useTemplateRef('modal');
const props = defineProps<{
success: boolean;

View File

@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
import { onBeforeUnmount, onMounted, provide, useTemplateRef, ref } from 'vue';
import type { MenuItem } from '@/types/menu.js';
import contains from '@/utility/contains.js';
import * as os from '@/os.js';
@@ -114,7 +114,7 @@ const emit = defineEmits<{
provide('inWindow', true);
const rootEl = shallowRef<HTMLElement | null>();
const rootEl = useTemplateRef('rootEl');
const showing = ref(true);
let beforeClickedAt = 0;
const maximized = ref(false);

View File

@@ -14,12 +14,12 @@ export type MkABehavior = 'window' | 'browser' | null;
</script>
<script lang="ts" setup>
import { computed, inject, shallowRef } from 'vue';
import { computed, inject, useTemplateRef } from 'vue';
import { url } from '@@/js/config.js';
import * as os from '@/os.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js';
import { useRouter } from '@/router/supplier.js';
import { useRouter } from '@/router.js';
const props = withDefaults(defineProps<{
to: string;
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null);
const el = shallowRef<HTMLElement>();
const el = useTemplateRef('el');
defineExpose({ $el: el });

View File

@@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, shallowRef } from 'vue';
import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, useTemplateRef } from 'vue';
const rootEl = shallowRef<HTMLDivElement>();
const rootEl = useTemplateRef('rootEl');
const showing = ref(false);
const observer = new IntersectionObserver(

View File

@@ -53,7 +53,7 @@ export type Tab = {
</script>
<script lang="ts" setup>
import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
import { nextTick, onMounted, onUnmounted, useTemplateRef, watch } from 'vue';
import { prefer } from '@/preferences.js';
const props = withDefaults(defineProps<{
@@ -69,9 +69,9 @@ const emit = defineEmits<{
(ev: 'tabClick', key: string);
}>();
const el = shallowRef<HTMLElement | null>(null);
const el = useTemplateRef('el');
const tabHighlightEl = useTemplateRef('tabHighlightEl');
const tabRefs: Record<string, HTMLElement | null> = {};
const tabHighlightEl = shallowRef<HTMLElement | null>(null);
function onTabMousedown(tab: Tab, ev: MouseEvent): void {
// ユーザビリティの観点からmousedown時にはonClickは呼ばない

View File

@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
import tinycolor from 'tinycolor2';
import { scrollToTop } from '@@/js/scroll.js';
import XTabs from './MkPageHeader.tabs.vue';
@@ -75,7 +75,7 @@ const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMe
const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
const thin_ = props.thin || inject('shouldHeaderThin', false);
const el = shallowRef<HTMLElement | undefined>(undefined);
const el = useTemplateRef('el');
const bg = ref<string | undefined>(undefined);
const narrow = ref(false);
const hasTabs = computed(() => props.tabs.length > 0);

View File

@@ -23,9 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { onMounted, onUnmounted, provide, inject, ref, watch, useTemplateRef } from 'vue';
import type { Ref } from 'vue';
import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
import type { Ref } from 'vue';
const rootEl = useTemplateRef('rootEl');
const headerEl = useTemplateRef('headerEl');

View File

@@ -14,13 +14,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef } from 'vue';
import type { IRouter, Resolved } from '@/nirax.js';
import { inject, provide, ref, shallowRef } from 'vue';
import type { Router } from '@/router.js';
import type { PathResolvedResult } from '@/lib/nirax.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';
const props = defineProps<{
router?: IRouter;
router?: Router;
}>();
const router = props.router ?? inject(DI.router);
@@ -32,7 +33,7 @@ if (router == null) {
const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1);
function resolveNested(current: Resolved, d = 0): Resolved | null {
function resolveNested(current: PathResolvedResult, d = 0): PathResolvedResult | null {
if (d === currentDepth) {
return current;
} else {
@@ -47,19 +48,13 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
const current = resolveNested(router.current)!;
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props);
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
const key = ref(router.getCurrentFullPath());
function onChange({ resolved, key: newKey }) {
router.useListener('change', ({ resolved }) => {
const current = resolveNested(resolved);
if (current == null || 'redirect' in current.route) return;
currentPageComponent.value = current.route.component;
currentPageProps.value = current.props;
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
}
router.addListener('change', onChange);
onBeforeUnmount(() => {
router.removeListener('change', onChange);
key.value = router.getCurrentFullPath();
});
</script>

View File

@@ -5,10 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_pageContainer" style="height: 100%;">
<KeepAlive
:max="prefer.s.numberOfPageCache"
:exclude="pageCacheController"
>
<KeepAlive :max="prefer.s.numberOfPageCache">
<Suspense :timeout="0">
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
@@ -21,15 +18,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
import { inject, provide, ref, shallowRef } from 'vue';
import type { Router } from '@/router.js';
import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';
const props = defineProps<{
router?: IRouter;
router?: Router;
}>();
const router = props.router ?? inject(DI.router);
@@ -44,45 +40,12 @@ provide(DI.routerCurrentDepth, currentDepth + 1);
const current = router.current!;
const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
const currentPageProps = ref(current.props);
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
const key = ref(router.getCurrentFullPath());
function onChange({ resolved, key: newKey }) {
router.useListener('change', ({ resolved }) => {
if (resolved == null || 'redirect' in resolved.route) return;
currentPageComponent.value = resolved.route.component;
currentPageProps.value = resolved.props;
key.value = newKey + JSON.stringify(Object.fromEntries(resolved.props));
nextTick(() => {
// ページ遷移完了後に再びキャッシュを有効化
if (clearCacheRequested.value) {
clearCacheRequested.value = false;
}
});
}
router.addListener('change', onChange);
// #region キャッシュ制御
/**
* キャッシュクリアが有効になったら、全キャッシュをクリアする
*
* keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。
* キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること
*/
const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
const clearCacheRequested = ref(false);
globalEvents.on('requestClearPageCache', () => {
if (_DEV_) console.log('clear page cache requested');
if (!clearCacheRequested.value) {
clearCacheRequested.value = true;
}
});
// #endregion
onBeforeUnmount(() => {
router.removeListener('change', onChange);
key.value = router.getCurrentFullPath();
});
</script>

View File

@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:duration="200"
tag="div" :class="$style.tabs"
>
<div v-for="(tab, i) in tabs" :key="tab.key" :class="$style.tab" :style="{ '--i': i - 1 }">
<div v-for="(tab, i) in tabs" :key="tab.fullPath" :class="$style.tab" :style="{ '--i': i - 1 }">
<div v-if="i > 0" :class="$style.tabBg" @click="back()"></div>
<div :class="$style.tabFg" @click.stop="back()">
<div v-if="i > 0" :class="$style.tabMenu">
@@ -41,16 +41,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
import { inject, provide, shallowRef } from 'vue';
import type { Router } from '@/router.js';
import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';
import { deepEqual } from '@/utility/deep-equal.js';
const props = defineProps<{
router?: IRouter;
router?: Router;
}>();
const router = props.router ?? inject(DI.router);
@@ -63,26 +62,36 @@ const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1);
const tabs = shallowRef([{
key: router.getCurrentPath(),
path: router.getCurrentPath(),
route: router.current.route.path,
fullPath: router.getCurrentFullPath(),
routePath: router.current.route.path,
component: 'component' in router.current.route ? router.current.route.component : MkLoadingPage,
props: router.current.props,
}]);
function onChange({ resolved }) {
function mount() {
const currentTab = tabs.value[tabs.value.length - 1];
const route = resolved.route.path;
if (resolved == null || 'redirect' in resolved.route) return;
if (resolved.route.path === currentTab.path && deepEqual(resolved.props, currentTab.props)) return;
const fullPath = router.getCurrentPath();
tabs.value = [currentTab];
}
if (tabs.value.some(tab => tab.route === route && deepEqual(resolved.props, tab.props))) {
function back() {
const prev = tabs.value[tabs.value.length - 2];
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
router.replace(prev.fullPath);
}
router.useListener('change', ({ resolved }) => {
const currentTab = tabs.value[tabs.value.length - 1];
const routePath = resolved.route.path;
if (resolved == null || 'redirect' in resolved.route) return;
if (resolved.route.path === currentTab.routePath && deepEqual(resolved.props, currentTab.props)) return;
const fullPath = router.getCurrentFullPath();
if (tabs.value.some(tab => tab.routePath === routePath && deepEqual(resolved.props, tab.props))) {
const newTabs = [];
for (const tab of tabs.value) {
newTabs.push(tab);
if (tab.route === route && deepEqual(resolved.props, tab.props)) {
if (tab.routePath === routePath && deepEqual(resolved.props, tab.props)) {
break;
}
}
@@ -93,45 +102,23 @@ function onChange({ resolved }) {
tabs.value = tabs.value.length >= prefer.s.numberOfPageCache ? [
...tabs.value.slice(1),
{
key: fullPath,
path: fullPath,
route,
fullPath: fullPath,
routePath,
component: resolved.route.component,
props: resolved.props,
},
] : [...tabs.value, {
key: fullPath,
path: fullPath,
route,
fullPath: fullPath,
routePath,
component: resolved.route.component,
props: resolved.props,
}];
}
});
function onReplace({ path }) {
router.useListener('replace', ({ fullPath }) => {
const currentTab = tabs.value[tabs.value.length - 1];
console.log('replace', currentTab.path, path);
currentTab.path = path;
currentTab.fullPath = fullPath;
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1), currentTab];
}
function mount() {
const currentTab = tabs.value[tabs.value.length - 1];
tabs.value = [currentTab];
}
function back() {
const prev = tabs.value[tabs.value.length - 2];
tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)];
router.replace(prev.path, prev.key);
}
router.addListener('replace', onReplace);
router.addListener('change', onChange);
onBeforeUnmount(() => {
router.removeListener('replace', onReplace);
router.removeListener('change', onChange);
});
</script>

View File

@@ -39,10 +39,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ cell.value }}
</div>
<div v-else-if="cellType === 'boolean'">
<div :class="[$style.bool, {
<div
:class="[$style.bool, {
[$style.boolTrue]: cell.value === true,
'ti ti-check': cell.value === true,
}]"></div>
}]"
></div>
</div>
<div v-else-if="cellType === 'image'">
<img
@@ -88,14 +90,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, toRefs, watch } from 'vue';
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, useTemplateRef, toRefs, watch } from 'vue';
import type { Size } from '@/components/grid/grid.js';
import type { CellValue, GridCell } from '@/components/grid/cell.js';
import type { GridRowSetting } from '@/components/grid/row.js';
import { GridEventEmitter } from '@/components/grid/grid.js';
import { useTooltip } from '@/use/use-tooltip.js';
import * as os from '@/os.js';
import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
import type { Size } from '@/components/grid/grid.js';
import type { CellValue, GridCell } from '@/components/grid/cell.js';
import type { GridRowSetting } from '@/components/grid/row.js';
const emit = defineEmits<{
(ev: 'operation:beginEdit', sender: GridCell): void;
@@ -111,9 +113,9 @@ const props = defineProps<{
const { cell, bus } = toRefs(props);
const rootEl = shallowRef<InstanceType<typeof HTMLTableCellElement>>();
const contentAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
const inputAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
const rootEl = useTemplateRef('rootEl');
const contentAreaEl = useTemplateRef('contentAreaEl');
const inputAreaEl = useTemplateRef('inputAreaEl');
/** 値が編集中かどうか */
const editing = ref<boolean>(false);

View File

@@ -4,11 +4,11 @@
*/
import type { InjectionKey, Ref } from 'vue';
import type { IRouter } from '@/nirax.js';
import type { Router } from '@/router.js';
export const DI = {
routerCurrentDepth: Symbol() as InjectionKey<number>,
router: Symbol() as InjectionKey<IRouter>,
router: Symbol() as InjectionKey<Router>,
mock: Symbol() as InjectionKey<boolean>,
pageMetadata: Symbol() as InjectionKey<Ref<Record<string, any>>>,
};

View File

@@ -10,5 +10,4 @@ export const globalEvents = new EventEmitter<{
themeChanging: () => void;
themeChanged: () => void;
clientNotification: (notification: Misskey.entities.Notification) => void;
requestClearPageCache: () => void;
}>();

View File

@@ -5,7 +5,7 @@
// NIRAX --- A lightweight router
import { onMounted, shallowRef } from 'vue';
import { onBeforeUnmount, onMounted, shallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';
import type { Component, ShallowRef } from 'vue';
@@ -23,7 +23,6 @@ interface RouteDefBase {
loginRequired?: boolean;
name?: string;
hash?: string;
globalCacheKey?: string;
children?: RouteDef[];
}
@@ -46,31 +45,28 @@ type ParsedPath = (string | {
optional?: boolean;
})[];
export type RouterEvent = {
export type RouterEvents = {
change: (ctx: {
beforePath: string;
path: string;
resolved: Resolved;
key: string;
beforeFullPath: string;
fullPath: string;
resolved: PathResolvedResult;
}) => void;
replace: (ctx: {
path: string;
key: string;
fullPath: string;
}) => void;
push: (ctx: {
beforePath: string;
path: string;
beforeFullPath: string;
fullPath: string;
route: RouteDef | null;
props: Map<string, string> | null;
key: string;
}) => void;
same: () => void;
};
export type Resolved = {
export type PathResolvedResult = {
route: RouteDef;
props: Map<string, string | boolean>;
child?: Resolved;
child?: PathResolvedResult;
redirected?: boolean;
/** @internal */
@@ -106,124 +102,39 @@ function parsePath(path: string): ParsedPath {
return res;
}
export interface IRouter extends EventEmitter<RouterEvent> {
current: Resolved;
currentRef: ShallowRef<Resolved>;
currentRoute: ShallowRef<RouteDef>;
navHook: ((path: string, flag?: RouterFlag) => boolean) | null;
/**
* eventListenerの定義後に必ず呼び出すこと
*/
init(): void;
resolve(path: string): Resolved | null;
getCurrentPath(): string;
getCurrentKey(): string;
push(path: string, flag?: RouterFlag): void;
replace(path: string, key?: string | null): void;
/** @see EventEmitter */
eventNames(): Array<EventEmitter.EventNames<RouterEvent>>;
/** @see EventEmitter */
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
event: T
): Array<EventEmitter.EventListener<RouterEvent, T>>;
/** @see EventEmitter */
listenerCount(
event: EventEmitter.EventNames<RouterEvent>
): number;
/** @see EventEmitter */
emit<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
...args: EventEmitter.EventArgs<RouterEvent, T>
): boolean;
/** @see EventEmitter */
on<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
once<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
): this;
/** @see EventEmitter */
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
off<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
): this;
/** @see EventEmitter */
removeAllListeners(
event?: EventEmitter.EventNames<RouterEvent>
): this;
}
export class Router extends EventEmitter<RouterEvent> implements IRouter {
private routes: RouteDef[];
public current: Resolved;
public currentRef: ShallowRef<Resolved>;
export class Nirax<DEF extends RouteDef[]> extends EventEmitter<RouterEvents> {
private routes: DEF;
public current: PathResolvedResult;
public currentRef: ShallowRef<PathResolvedResult>;
public currentRoute: ShallowRef<RouteDef>;
private currentPath: string;
private currentFullPath: string; // /foo/bar?baz=qux#hash
private isLoggedIn: boolean;
private notFoundPageComponent: Component;
private currentKey = Date.now().toString();
private redirectCount = 0;
public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null;
constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
constructor(routes: DEF, currentFullPath: Nirax<DEF>['currentFullPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
super();
this.routes = routes;
this.current = this.resolve(currentPath)!;
this.current = this.resolve(currentFullPath)!;
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
this.currentPath = currentPath;
this.currentFullPath = currentFullPath;
this.isLoggedIn = isLoggedIn;
this.notFoundPageComponent = notFoundPageComponent;
}
public init() {
const res = this.navigate(this.currentPath, null, false);
const res = this.navigate(this.currentFullPath, false);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
fullPath: res._parsedRoute.fullPath,
});
}
public resolve(path: string): Resolved | null {
const fullPath = path;
public resolve(fullPath: string): PathResolvedResult | null {
let path = fullPath;
let queryString: string | null = null;
let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
@@ -242,7 +153,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
hash,
};
function check(routes: RouteDef[], _parts: string[]): Resolved | null {
function check(routes: RouteDef[], _parts: string[]): PathResolvedResult | null {
forEachRouteLoop:
for (const route of routes) {
let parts = [..._parts];
@@ -345,14 +256,14 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
return check(this.routes, _parts);
}
private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
const beforePath = this.currentPath;
this.currentPath = path;
private navigate(fullPath: string, emitChange = true, _redirected = false): PathResolvedResult {
const beforeFullPath = this.currentFullPath;
this.currentFullPath = fullPath;
const res = this.resolve(this.currentPath);
const res = this.resolve(this.currentFullPath);
if (res == null) {
throw new Error('no route found for: ' + path);
throw new Error('no route found for: ' + fullPath);
}
if ('redirect' in res.route) {
@@ -366,7 +277,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
if (_redirected && this.redirectCount++ > 10) {
throw new Error('redirect loop detected');
}
return this.navigate(redirectPath, null, emitChange, true);
return this.navigate(redirectPath, emitChange, true);
}
if (res.route.loginRequired && !this.isLoggedIn) {
@@ -374,19 +285,15 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
res.props.set('showLoginPopup', true);
}
const isSamePath = beforePath === path;
if (isSamePath && key == null) key = this.currentKey;
this.current = res;
this.currentRef.value = res;
this.currentRoute.value = res.route;
this.currentKey = res.route.globalCacheKey ?? key ?? path;
if (emitChange && res.route.path !== '/:(*)') {
this.emit('change', {
beforePath,
path,
beforeFullPath,
fullPath,
resolved: res,
key: this.currentKey,
});
}
@@ -397,70 +304,45 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
};
}
public getCurrentPath() {
return this.currentPath;
public getCurrentFullPath() {
return this.currentFullPath;
}
public getCurrentKey() {
return this.currentKey;
}
public push(path: string, flag?: RouterFlag) {
const beforePath = this.currentPath;
if (path === beforePath) {
public push(fullPath: string, flag?: RouterFlag) {
const beforeFullPath = this.currentFullPath;
if (fullPath === beforeFullPath) {
this.emit('same');
return;
}
if (this.navHook) {
const cancel = this.navHook(path, flag);
const cancel = this.navHook(fullPath, flag);
if (cancel) return;
}
const res = this.navigate(path, null);
const res = this.navigate(fullPath);
if (res.route.path === '/:(*)') {
location.href = path;
location.href = fullPath;
} else {
this.emit('push', {
beforePath,
path: res._parsedRoute.fullPath,
beforeFullPath,
fullPath: res._parsedRoute.fullPath,
route: res.route,
props: res.props,
key: this.currentKey,
});
}
}
public replace(path: string, key?: string | null) {
const res = this.navigate(path, key);
public replace(fullPath: string) {
const res = this.navigate(fullPath);
this.emit('replace', {
path: res._parsedRoute.fullPath,
key: this.currentKey,
fullPath: res._parsedRoute.fullPath,
});
}
public useListener<E extends keyof RouterEvents, L = RouterEvents[E]>(event: E, listener: L) {
this.addListener(event, listener);
onBeforeUnmount(() => {
this.removeListener(event, listener);
});
}
}
export function useScrollPositionManager(getScrollContainer: () => HTMLElement | null, router: IRouter) {
const scrollPosStore = new Map<string, number>();
onMounted(() => {
const scrollContainer = getScrollContainer();
if (scrollContainer == null) return;
scrollContainer.addEventListener('scroll', () => {
scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop);
}, { passive: true });
router.addListener('change', ctx => {
const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
if (scrollPos !== 0) {
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
}, 100);
}
});
router.addListener('same', () => {
scrollContainer.scroll({ top: 0, behavior: 'smooth' });
});
});
}

Some files were not shown because too many files have changed in this diff Show More