From c548ec9906947c72743e611254a6557e8e8d057c Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:01:44 +0900 Subject: refactor(frontend): verbatimModuleSyntaxを有効化 (#15323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * wip * wip * wip * revert unnecessary changes * wip * refactor(frontend): enforce verbatimModuleSyntax * fix * refactor(frontend-shared): enforce verbatimModuleSyntax * wip * refactor(frontend-embed): enforce verbatimModuleSyntax * enforce consistent-type-imports * fix lint config * attemt to fix ci * fix lint * fix * fix * fix --- packages/frontend/src/ui/classic.vue | 3 ++- packages/frontend/src/ui/deck/antenna-column.vue | 5 +++-- packages/frontend/src/ui/deck/channel-column.vue | 5 +++-- packages/frontend/src/ui/deck/column.vue | 3 ++- packages/frontend/src/ui/deck/deck-store.ts | 2 +- packages/frontend/src/ui/deck/direct-column.vue | 2 +- packages/frontend/src/ui/deck/list-column.vue | 5 +++-- packages/frontend/src/ui/deck/main-column.vue | 6 ++++-- packages/frontend/src/ui/deck/mentions-column.vue | 2 +- packages/frontend/src/ui/deck/notifications-column.vue | 3 ++- packages/frontend/src/ui/deck/role-timeline-column.vue | 5 +++-- packages/frontend/src/ui/deck/tl-column.vue | 5 +++-- packages/frontend/src/ui/deck/tl-note-notification.ts | 7 ++++--- packages/frontend/src/ui/deck/widgets-column.vue | 3 ++- packages/frontend/src/ui/minimum.vue | 3 ++- packages/frontend/src/ui/universal.vue | 6 ++++-- packages/frontend/src/ui/visitor.vue | 3 ++- packages/frontend/src/ui/zen.vue | 3 ++- 18 files changed, 44 insertions(+), 27 deletions(-) (limited to 'packages/frontend/src/ui') diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 5ea9bf7068..da5059bb59 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -52,7 +52,8 @@ import XCommon from './_common_/common.vue'; import { instanceName } from '@@/js/config.js'; import { StickySidebar } from '@/scripts/sticky-sidebar.js'; import * as os from '@/os.js'; -import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; +import { provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; +import type { PageMetadata } from '@/scripts/page-metadata.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue index a41639e71c..07ae17b982 100644 --- a/packages/frontend/src/ui/deck/antenna-column.vue +++ b/packages/frontend/src/ui/deck/antenna-column.vue @@ -17,14 +17,15 @@ SPDX-License-Identifier: AGPL-3.0-only import { onMounted, ref, shallowRef, watch, defineAsyncComponent } from 'vue'; import type { entities as MisskeyEntities } from 'misskey-js'; import XColumn from './column.vue'; -import { updateColumn, Column } from './deck-store.js'; +import { updateColumn } from './deck-store.js'; +import type { Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import type { MenuItem } from '@/types/menu.js'; import { antennasCache } from '@/cache.js'; -import { SoundStore } from '@/store.js'; +import type { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index 5479b53d90..d656eec60a 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -22,7 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, shallowRef, watch } from 'vue'; import * as Misskey from 'misskey-js'; import XColumn from './column.vue'; -import { updateColumn, Column } from './deck-store.js'; +import { updateColumn } from './deck-store.js'; +import type { Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; @@ -30,7 +31,7 @@ import { favoritedChannelsCache } from '@/cache.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import type { MenuItem } from '@/types/menu.js'; -import { SoundStore } from '@/store.js'; +import type { SoundStore } from '@/store.js'; import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; import * as sound from '@/scripts/sound.js'; diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index da0bf24a56..34d7b2d4b5 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -43,9 +43,10 @@ SPDX-License-Identifier: AGPL-3.0-only - - diff --git a/locales/index.d.ts b/locales/index.d.ts index 947b577792..6810d204cb 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4971,7 +4971,7 @@ export interface Locale extends ILocale { */ "disableStreamingTimeline": string; /** - * 通知をグルーピングして表示する + * 通知をグルーピング */ "useGroupedNotifications": string; /** @@ -5270,6 +5270,14 @@ export interface Locale extends ILocale { * このメディアのセンシティブ指定を解除しますか? */ "unmarkAsSensitiveConfirm": string; + /** + * 環境設定 + */ + "preferences": string; + /** + * アクセシビリティ + */ + "accessibility": string; "_accountSettings": { /** * コンテンツの表示にログインを必須にする diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index fbe4d98896..7a5d2f795e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1238,7 +1238,7 @@ releaseToRefresh: "離してリロード" refreshing: "リロード中" pullDownToRefresh: "引っ張ってリロード" disableStreamingTimeline: "タイムラインのリアルタイム更新を無効にする" -useGroupedNotifications: "通知をグルーピングして表示する" +useGroupedNotifications: "通知をグルーピング" signupPendingError: "メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。" cwNotationRequired: "「内容を隠す」がオンの場合は注釈の記述が必要です。" doReaction: "リアクションする" @@ -1313,6 +1313,8 @@ confirmOnReact: "リアクションする際に確認する" reactAreYouSure: "\" {emoji} \" をリアクションしますか?" markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?" unmarkAsSensitiveConfirm: "このメディアのセンシティブ指定を解除しますか?" +preferences: "環境設定" +accessibility: "アクセシビリティ" _accountSettings: requireSigninToViewContents: "コンテンツの表示にログインを必須にする" diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts index 9f318cf449..c1119c2523 100644 --- a/packages/frontend/.storybook/main.ts +++ b/packages/frontend/.storybook/main.ts @@ -39,6 +39,10 @@ const config = { if (~replacePluginForIsChromatic) { config.plugins?.splice(replacePluginForIsChromatic, 1); } + + //pluginsからcreateSearchIndexを削除、複数あるかもしれないので全て削除 + config.plugins = config.plugins?.filter((plugin: Plugin) => plugin && plugin.name !== 'createSearchIndex') ?? []; + return mergeConfig(config, { plugins: [ { diff --git a/packages/frontend/lib/vite-plugin-create-search-index.ts b/packages/frontend/lib/vite-plugin-create-search-index.ts new file mode 100644 index 0000000000..509eb804cb --- /dev/null +++ b/packages/frontend/lib/vite-plugin-create-search-index.ts @@ -0,0 +1,1496 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { parse as vueSfcParse } from 'vue/compiler-sfc'; +import type { LogOptions, Plugin } from 'vite'; +import fs from 'node:fs'; +import { glob } from 'glob'; +import JSON5 from 'json5'; +import MagicString from 'magic-string'; +import path from 'node:path' +import { hash, toBase62 } from '../vite.config'; +import { createLogger } from 'vite'; + +interface VueAstNode { + type: number; + tag?: string; + loc?: { + start: { offset: number, line: number, column: number }, + end: { offset: number, line: number, column: number }, + source?: string + }; + props?: Array<{ + name: string; + type: number; + value?: { content?: string }; + arg?: { content?: string }; + exp?: { content?: string; loc?: any }; + }>; + children?: VueAstNode[]; + content?: any; + __markerId?: string; + __children?: string[]; +} + +export type AnalysisResult = { + filePath: string; + usage: SearchIndexItem[]; +} + +export type SearchIndexItem = { + id: string; + path?: string; + label: string; + keywords: string | string[]; + icon?: string; + inlining?: string[]; + children?: SearchIndexItem[]; +}; + +export type Options = { + targetFilePaths: string[], + exportFilePath: string, + verbose?: boolean, +}; + +// 関連するノードタイプの定数化 +const NODE_TYPES = { + ELEMENT: 1, + EXPRESSION: 2, + TEXT: 3, + INTERPOLATION: 5, // Mustache +}; + +// マーカー関係を表す型 +interface MarkerRelation { + parentId?: string; + markerId: string; + node: VueAstNode; +} + +// ロガー +let logger = { + info: (msg: string, options?: LogOptions) => { }, + warn: (msg: string, options?: LogOptions) => { }, + error: (msg: string, options?: LogOptions) => { }, +}; +let loggerInitialized = false; + +function initLogger(options: Options) { + if (loggerInitialized) return; + loggerInitialized = true; + const viteLogger = createLogger(options.verbose ? 'info' : 'warn'); + + logger.info = (msg, options) => { + msg = `[create-search-index] ${msg}`; + viteLogger.info(msg, options); + } + + logger.warn = (msg, options) => { + msg = `[create-search-index] ${msg}`; + viteLogger.warn(msg, options); + } + + logger.error = (msg, options) => { + msg = `[create-search-index] ${msg}`; + viteLogger.error(msg, options); + } +} + +/** + * 解析結果をTypeScriptファイルとして出力する + */ +function outputAnalysisResultAsTS(outputPath: string, analysisResults: AnalysisResult[]): void { + logger.info(`Processing ${analysisResults.length} files for output`); + + // 新しいツリー構造を構築 + const allMarkers = new Map(); + + // 1. すべてのマーカーを一旦フラットに収集 + for (const file of analysisResults) { + logger.info(`Processing file: ${file.filePath} with ${file.usage.length} markers`); + + for (const marker of file.usage) { + if (marker.id) { + // キーワードとchildren処理を共通化 + const processedMarker = { + ...marker, + keywords: processMarkerProperty(marker.keywords, 'keywords'), + children: processMarkerProperty(marker.children || [], 'children') + }; + + allMarkers.set(marker.id, processedMarker); + } + } + } + + logger.info(`Collected total ${allMarkers.size} unique markers`); + + // 2. 子マーカーIDの収集 + const childIds = collectChildIds(allMarkers); + logger.info(`Found ${childIds.size} child markers`); + + // 3. ルートマーカーの特定(他の誰かの子でないマーカー) + const rootMarkers = identifyRootMarkers(allMarkers, childIds); + logger.info(`Found ${rootMarkers.length} root markers`); + + // 4. 子マーカーの参照を解決 + const resolvedRootMarkers = resolveChildReferences(rootMarkers, allMarkers); + + // 5. デバッグ情報を生成 + const { totalMarkers, totalChildren } = countMarkers(resolvedRootMarkers); + logger.info(`Total markers in tree: ${totalMarkers} (${resolvedRootMarkers.length} roots + ${totalChildren} nested children)`); + + // 6. 結果をTS形式で出力 + writeOutputFile(outputPath, resolvedRootMarkers); +} + +/** + * マーカーのプロパティ(keywordsやchildren)を処理する + */ +function processMarkerProperty(propValue: any, propType: 'keywords' | 'children'): any { + // 文字列の配列表現を解析 + if (typeof propValue === 'string' && propValue.startsWith('[') && propValue.endsWith(']')) { + try { + // JSON5解析を試みる + return JSON5.parse(propValue.replace(/'/g, '"')); + } catch (e) { + // 解析に失敗した場合 + logger.warn(`Could not parse ${propType}: ${propValue}, using ${propType === 'children' ? 'empty array' : 'as is'}`); + return propType === 'children' ? [] : propValue; + } + } + + return propValue; +} + +/** + * 全マーカーから子IDを収集する + */ +function collectChildIds(allMarkers: Map): Set { + const childIds = new Set(); + + allMarkers.forEach((marker, id) => { + // 通常のchildren処理 + const children = marker.children; + if (Array.isArray(children)) { + children.forEach(childId => { + if (typeof childId === 'string') { + if (!allMarkers.has(childId)) { + logger.warn(`Warning: Child marker ID ${childId} referenced but not found`); + } else { + childIds.add(childId); + } + } + }); + } + + // inlining処理を追加 + if (marker.inlining) { + let inliningIds: string[] = []; + + // 文字列の場合は配列に変換 + if (typeof marker.inlining === 'string') { + try { + const inliningStr = (marker.inlining as string).trim(); + if (inliningStr.startsWith('[') && inliningStr.endsWith(']')) { + inliningIds = JSON5.parse(inliningStr.replace(/'/g, '"')); + logger.info(`Parsed inlining string to array: ${inliningStr} -> ${JSON.stringify(inliningIds)}`); + } else { + inliningIds = [inliningStr]; + } + } catch (e) { + logger.error(`Failed to parse inlining string: ${marker.inlining}`, e); + } + } + // 既に配列の場合 + else if (Array.isArray(marker.inlining)) { + inliningIds = marker.inlining; + } + + // inliningで指定されたIDを子セットに追加 + for (const inlineId of inliningIds) { + if (typeof inlineId === 'string') { + if (!allMarkers.has(inlineId)) { + logger.warn(`Warning: Inlining marker ID ${inlineId} referenced but not found`); + } else { + // inliningで参照されているマーカーも子として扱う + childIds.add(inlineId); + logger.info(`Added inlined marker ${inlineId} as child in collectChildIds`); + } + } + } + } + }); + + return childIds; +} + +/** + * ルートマーカー(他の子でないマーカー)を特定する + */ +function identifyRootMarkers( + allMarkers: Map, + childIds: Set +): SearchIndexItem[] { + const rootMarkers: SearchIndexItem[] = []; + + allMarkers.forEach((marker, id) => { + if (!childIds.has(id)) { + rootMarkers.push(marker); + logger.info(`Added root marker to output: ${id} with label ${marker.label}`); + } + }); + + return rootMarkers; +} + +/** + * 子マーカーの参照をIDから実際のオブジェクトに解決する + */ +function resolveChildReferences( + rootMarkers: SearchIndexItem[], + allMarkers: Map +): SearchIndexItem[] { + function resolveChildrenForMarker(marker: SearchIndexItem): SearchIndexItem { + // マーカーのディープコピーを作成 + const resolvedMarker = { ...marker }; + // 明示的に子マーカー配列を作成 + const resolvedChildren: SearchIndexItem[] = []; + + // 通常のchildren処理 + if (Array.isArray(marker.children)) { + for (const childId of marker.children) { + if (typeof childId === 'string') { + const childMarker = allMarkers.get(childId); + if (childMarker) { + // 子マーカーの子も再帰的に解決 + const resolvedChild = resolveChildrenForMarker(childMarker); + resolvedChildren.push(resolvedChild); + logger.info(`Resolved regular child ${childId} for parent ${marker.id}`); + } + } + } + } + + // inlining属性の処理 + let inliningIds: string[] = []; + + // 文字列の場合は配列に変換。例: "['2fa']" -> ['2fa'] + if (typeof marker.inlining === 'string') { + try { + // 文字列形式の配列を実際の配列に変換 + const inliningStr = (marker.inlining as string).trim(); + if (inliningStr.startsWith('[') && inliningStr.endsWith(']')) { + inliningIds = JSON5.parse(inliningStr.replace(/'/g, '"')); + logger.info(`Converted string inlining to array: ${inliningStr} -> ${JSON.stringify(inliningIds)}`); + } else { + // 単一値の場合は配列に + inliningIds = [inliningStr]; + logger.info(`Converted single string inlining to array: ${inliningStr}`); + } + } catch (e) { + logger.error(`Failed to parse inlining string: ${marker.inlining}`, e); + } + } + // 既に配列の場合はそのまま使用 + else if (Array.isArray(marker.inlining)) { + inliningIds = marker.inlining; + } + + // インライン指定されたマーカーを子として追加 + for (const inlineId of inliningIds) { + if (typeof inlineId === 'string') { + const inlineMarker = allMarkers.get(inlineId); + if (inlineMarker) { + // インライン指定されたマーカーを再帰的に解決 + const resolvedInline = resolveChildrenForMarker(inlineMarker); + delete resolvedInline.path + resolvedChildren.push(resolvedInline); + logger.info(`Added inlined marker ${inlineId} as child to ${marker.id}`); + } else { + logger.warn(`Inlining target not found: ${inlineId} referenced by ${marker.id}`); + } + } + } + + // 解決した子が存在する場合のみchildrenプロパティを設定 + if (resolvedChildren.length > 0) { + resolvedMarker.children = resolvedChildren; + } else { + delete resolvedMarker.children; + } + + return resolvedMarker; + } + + // すべてのルートマーカーの子を解決 + return rootMarkers.map(marker => resolveChildrenForMarker(marker)); +} + +/** + * マーカー数を数える(デバッグ用) + */ +function countMarkers(markers: SearchIndexItem[]): { totalMarkers: number, totalChildren: number } { + let totalMarkers = markers.length; + let totalChildren = 0; + + function countNested(items: SearchIndexItem[]): void { + for (const marker of items) { + if (marker.children && Array.isArray(marker.children)) { + totalChildren += marker.children.length; + totalMarkers += marker.children.length; + countNested(marker.children as SearchIndexItem[]); + } + } + } + + countNested(markers); + return { totalMarkers, totalChildren }; +} + +/** + * 最終的なTypeScriptファイルを出力 + */ +function writeOutputFile(outputPath: string, resolvedRootMarkers: SearchIndexItem[]): void { + try { + const tsOutput = generateTypeScriptCode(resolvedRootMarkers); + fs.writeFileSync(outputPath, tsOutput, 'utf-8'); + // 強制的に出力させるためにViteロガーを使わない + console.log(`Successfully wrote search index to ${outputPath} with ${resolvedRootMarkers.length} root entries`); + } catch (error) { + logger.error('[create-search-index]: error writing output: ', error); + } +} + +/** + * TypeScriptコード生成 + */ +function generateTypeScriptCode(resolvedRootMarkers: SearchIndexItem[]): string { + return ` +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// This file was automatically generated by create-search-index. +// Do not edit this file. + +import { i18n } from '@/i18n.js'; + +export type SearchIndexItem = { + id: string; + path?: string; + label: string; + keywords: string[]; + icon?: string; + children?: SearchIndexItem[]; +}; + +export const searchIndexes: SearchIndexItem[] = ${customStringify(resolvedRootMarkers)} as const; + +export type SearchIndex = typeof searchIndexes; +`; +} + +/** + * オブジェクトを特殊な形式の文字列に変換する + * i18n参照を保持しつつ適切な形式に変換 + */ +function customStringify(obj: any, depth = 0): string { + const INDENT_STR = '\t'; + + // 配列の処理 + if (Array.isArray(obj)) { + if (obj.length === 0) return '[]'; + const indent = INDENT_STR.repeat(depth); + const childIndent = INDENT_STR.repeat(depth + 1); + + // 配列要素の処理 + const items = obj.map(item => { + // オブジェクト要素 + if (typeof item === 'object' && item !== null) { + return `${childIndent}${customStringify(item, depth + 1)}`; + } + + // i18n参照を含む文字列要素 + if (typeof item === 'string' && item.includes('i18n.ts.')) { + return `${childIndent}${item}`; // クォートなしでそのまま出力 + } + + // その他の要素 + return `${childIndent}${JSON5.stringify(item)}`; + }).join(',\n'); + + return `[\n${items},\n${indent}]`; + } + + // null または非オブジェクト + if (obj === null || typeof obj !== 'object') { + return JSON5.stringify(obj); + } + + // オブジェクトの処理 + const indent = INDENT_STR.repeat(depth); + const childIndent = INDENT_STR.repeat(depth + 1); + + const entries = Object.entries(obj) + // 不要なプロパティを除去 + .filter(([key, value]) => { + if (value === undefined) return false; + if (key === 'children' && Array.isArray(value) && value.length === 0) return false; + if (key === 'inlining') return false; + return true; + }) + // 各プロパティを変換 + .map(([key, value]) => { + // 子要素配列の特殊処理 + if (key === 'children' && Array.isArray(value) && value.length > 0) { + return `${childIndent}${key}: ${customStringify(value, depth + 1)}`; + } + + // ラベルやその他プロパティを処理 + return `${childIndent}${key}: ${formatSpecialProperty(key, value)}`; + }); + + if (entries.length === 0) return '{}'; + return `{\n${entries.join(',\n')},\n${indent}}`; +} + +/** + * 特殊プロパティの書式設定 + */ +function formatSpecialProperty(key: string, value: any): string { + // 値がundefinedの場合は空文字列を返す + if (value === undefined) { + return '""'; + } + + // childrenが配列の場合は特別に処理 + if (key === 'children' && Array.isArray(value)) { + return customStringify(value); + } + + // keywordsが配列の場合、特別に処理 + if (key === 'keywords' && Array.isArray(value)) { + return `[${formatArrayForOutput(value)}]`; + } + + // 文字列値の場合の特別処理 + if (typeof value === 'string') { + // i18n.ts 参照を含む場合 - クォートなしでそのまま出力 + if (isI18nReference(value)) { + logger.info(`Preserving i18n reference in output: ${value}`); + return value; + } + + // keywords が配列リテラルの形式の場合 + if (key === 'keywords' && value.startsWith('[') && value.endsWith(']')) { + return value; + } + } + + // 上記以外は通常の JSON5 文字列として返す + return JSON5.stringify(value); +} + +/** + * 配列式の文字列表現を生成 + */ +function formatArrayForOutput(items: any[]): string { + return items.map(item => { + // i18n.ts. 参照の文字列はそのままJavaScript式として出力 + if (typeof item === 'string' && isI18nReference(item)) { + logger.info(`Preserving i18n reference in array: ${item}`); + return item; // クォートなしでそのまま + } + + // その他の値はJSON5形式で文字列化 + return JSON5.stringify(item); + }).join(', '); +} + +/** + * 要素ノードからテキスト内容を抽出する + * 各抽出方法を分離して可読性を向上 + */ +function extractElementText(node: VueAstNode): string | null { + if (!node) return null; + + logger.info(`Extracting text from node type=${node.type}, tag=${node.tag || 'unknown'}`); + + // 1. 直接コンテンツの抽出を試行 + const directContent = extractDirectContent(node); + if (directContent) return directContent; + + // 子要素がない場合は終了 + if (!node.children || !Array.isArray(node.children)) { + return null; + } + + // 2. インターポレーションノードを検索 + const interpolationContent = extractInterpolationContent(node.children); + if (interpolationContent) return interpolationContent; + + // 3. 式ノードを検索 + const expressionContent = extractExpressionContent(node.children); + if (expressionContent) return expressionContent; + + // 4. テキストノードを検索 + const textContent = extractTextContent(node.children); + if (textContent) return textContent; + + // 5. 再帰的に子ノードを探索 + return extractNestedContent(node.children); +} +/** + * ノードから直接コンテンツを抽出 + */ +function extractDirectContent(node: VueAstNode): string | null { + if (!node.content) return null; + + const content = typeof node.content === 'string' + ? node.content.trim() + : (node.content.content ? node.content.content.trim() : null); + + if (!content) return null; + + logger.info(`Direct node content found: ${content}`); + + // Mustache構文のチェック + const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/; + const mustacheMatch = content.match(mustachePattern); + + if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { + const extractedContent = mustacheMatch[1].trim(); + logger.info(`Extracted i18n reference from mustache: ${extractedContent}`); + return extractedContent; + } + + // 直接i18n参照を含む場合 + if (isI18nReference(content)) { + logger.info(`Direct i18n reference found: ${content}`); + return content; + } + + // その他のコンテンツ + return content; +} + +/** + * インターポレーションノード(Mustache)からコンテンツを抽出 + */ +function extractInterpolationContent(children: VueAstNode[]): string | null { + for (const child of children) { + if (child.type === NODE_TYPES.INTERPOLATION) { + logger.info(`Found interpolation node (Mustache): ${JSON.stringify(child.content).substring(0, 100)}...`); + + if (child.content && child.content.type === 4 && child.content.content) { + const content = child.content.content.trim(); + logger.info(`Interpolation content: ${content}`); + + if (isI18nReference(content)) { + return content; + } + } else if (child.content && typeof child.content === 'object') { + // オブジェクト形式のcontentを探索 + logger.info(`Complex interpolation node: ${JSON.stringify(child.content).substring(0, 100)}...`); + + if (child.content.content) { + const content = child.content.content.trim(); + + if (isI18nReference(content)) { + logger.info(`Found i18n reference in complex interpolation: ${content}`); + return content; + } + } + } + } + } + + return null; +} + +/** + * 式ノードからコンテンツを抽出 + */ +function extractExpressionContent(children: VueAstNode[]): string | null { + // i18n.ts. 参照パターンを持つものを優先 + for (const child of children) { + if (child.type === NODE_TYPES.EXPRESSION && child.content) { + const expr = child.content.trim(); + + if (isI18nReference(expr)) { + logger.info(`Found i18n reference in expression node: ${expr}`); + return expr; + } + } + } + + // その他の式 + for (const child of children) { + if (child.type === NODE_TYPES.EXPRESSION && child.content) { + const expr = child.content.trim(); + logger.info(`Found expression: ${expr}`); + return expr; + } + } + + return null; +} + +/** + * テキストノードからコンテンツを抽出 + */ +function extractTextContent(children: VueAstNode[]): string | null { + for (const child of children) { + if (child.type === NODE_TYPES.TEXT && child.content) { + const text = child.content.trim(); + + if (text) { + logger.info(`Found text node: ${text}`); + + // Mustache構文のチェック + const mustachePattern = /^\s*{{\s*(.*?)\s*}}\s*$/; + const mustacheMatch = text.match(mustachePattern); + + if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { + logger.info(`Extracted i18n ref from text mustache: ${mustacheMatch[1]}`); + return mustacheMatch[1].trim(); + } + + return text; + } + } + } + + return null; +} + +/** + * 子ノードを再帰的に探索してコンテンツを抽出 + */ +function extractNestedContent(children: VueAstNode[]): string | null { + for (const child of children) { + if (child.children && Array.isArray(child.children) && child.children.length > 0) { + const nestedContent = extractElementText(child); + + if (nestedContent) { + logger.info(`Found nested content: ${nestedContent}`); + return nestedContent; + } + } else if (child.type === NODE_TYPES.ELEMENT) { + // childrenがなくても内部を調査 + const nestedContent = extractElementText(child); + + if (nestedContent) { + logger.info(`Found content in childless element: ${nestedContent}`); + return nestedContent; + } + } + } + + return null; +} + + +/** + * SearchLabelとSearchKeywordを探して抽出する関数 + */ +function extractLabelsAndKeywords(nodes: VueAstNode[]): { label: string | null, keywords: any[] } { + let label: string | null = null; + const keywords: any[] = []; + + logger.info(`Extracting labels and keywords from ${nodes.length} nodes`); + + // 再帰的にSearchLabelとSearchKeywordを探索(ネストされたSearchMarkerは処理しない) + function findComponents(nodes: VueAstNode[]) { + for (const node of nodes) { + if (node.type === NODE_TYPES.ELEMENT) { + logger.info(`Checking element: ${node.tag}`); + + // SearchMarkerの場合は、その子要素は別スコープなのでスキップ + if (node.tag === 'SearchMarker') { + logger.info(`Found nested SearchMarker - skipping its content to maintain scope isolation`); + continue; // このSearchMarkerの中身は処理しない (スコープ分離) + } + + // SearchLabelの処理 + if (node.tag === 'SearchLabel') { + logger.info(`Found SearchLabel node, structure: ${JSON.stringify(node).substring(0, 200)}...`); + + // まず完全なノード内容の抽出を試みる + const content = extractElementText(node); + if (content) { + label = content; + logger.info(`SearchLabel content extracted: ${content}`); + } else { + logger.info(`SearchLabel found but extraction failed, trying direct children inspection`); + + // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認 + if (node.children && Array.isArray(node.children)) { + for (const child of node.children) { + // Mustacheインターポレーション + if (child.type === NODE_TYPES.INTERPOLATION && child.content) { + // content内の式を取り出す + const expression = child.content.content || + (child.content.type === 4 ? child.content.content : null) || + JSON.stringify(child.content); + + logger.info(`Interpolation expression: ${expression}`); + if (typeof expression === 'string' && isI18nReference(expression)) { + label = expression.trim(); + logger.info(`Found i18n in interpolation: ${label}`); + break; + } + } + // 式ノード + else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) { + label = child.content.trim(); + logger.info(`Found i18n in expression: ${label}`); + break; + } + // テキストノードでもMustache構文を探す + else if (child.type === NODE_TYPES.TEXT && child.content) { + const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); + if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { + label = mustacheMatch[1].trim(); + logger.info(`Found i18n in text mustache: ${label}`); + break; + } + } + } + } + } + } + // SearchKeywordの処理 + else if (node.tag === 'SearchKeyword') { + logger.info(`Found SearchKeyword node`); + + // まず完全なノード内容の抽出を試みる + const content = extractElementText(node); + if (content) { + keywords.push(content); + logger.info(`SearchKeyword content extracted: ${content}`); + } else { + logger.info(`SearchKeyword found but extraction failed, trying direct children inspection`); + + // バックアップ: 子直接確認 - type=5のMustacheインターポレーションを重点的に確認 + if (node.children && Array.isArray(node.children)) { + for (const child of node.children) { + // Mustacheインターポレーション + if (child.type === NODE_TYPES.INTERPOLATION && child.content) { + // content内の式を取り出す + const expression = child.content.content || + (child.content.type === 4 ? child.content.content : null) || + JSON.stringify(child.content); + + logger.info(`Keyword interpolation: ${expression}`); + if (typeof expression === 'string' && isI18nReference(expression)) { + const keyword = expression.trim(); + keywords.push(keyword); + logger.info(`Found i18n keyword in interpolation: ${keyword}`); + break; + } + } + // 式ノード + else if (child.type === NODE_TYPES.EXPRESSION && child.content && isI18nReference(child.content)) { + const keyword = child.content.trim(); + keywords.push(keyword); + logger.info(`Found i18n keyword in expression: ${keyword}`); + break; + } + // テキストノードでもMustache構文を探す + else if (child.type === NODE_TYPES.TEXT && child.content) { + const mustacheMatch = child.content.trim().match(/^\s*{{\s*(.*?)\s*}}\s*$/); + if (mustacheMatch && mustacheMatch[1] && isI18nReference(mustacheMatch[1])) { + const keyword = mustacheMatch[1].trim(); + keywords.push(keyword); + logger.info(`Found i18n keyword in text mustache: ${keyword}`); + break; + } + } + } + } + } + } + + // 子要素を再帰的に調査(ただしSearchMarkerは除外) + if (node.children && Array.isArray(node.children)) { + findComponents(node.children); + } + } + } + } + + findComponents(nodes); + + // デバッグ情報 + logger.info(`Extraction completed: label=${label}, keywords=[${keywords.join(', ')}]`); + return { label, keywords }; +} + + +function extractUsageInfoFromTemplateAst( + templateAst: any, + id: string, +): SearchIndexItem[] { + const allMarkers: SearchIndexItem[] = []; + const markerMap = new Map(); + const childrenIds = new Set(); + const normalizedId = id.replace(/\\/g, '/'); + + if (!templateAst) return allMarkers; + + // マーカーの基本情報を収集 + function collectMarkers(node: VueAstNode, parentId: string | null = null) { + if (node.type === 1 && node.tag === 'SearchMarker') { + // マーカーID取得 + const markerIdProp = node.props?.find((p: any) => p.name === 'markerId'); + const markerId = markerIdProp?.value?.content || + node.__markerId; + + // SearchMarkerにマーカーIDがない場合はエラー + if (markerId == null) { + logger.error(`Marker ID not found for node: ${JSON.stringify(node)}`); + throw new Error(`Marker ID not found in file ${id}`); + } + + // マーカー基本情報 + const markerInfo: SearchIndexItem = { + id: markerId, + children: [], + label: '', // デフォルト値 + keywords: [], + }; + + // 静的プロパティを取得 + if (node.props && Array.isArray(node.props)) { + for (const prop of node.props) { + if (prop.type === 6 && prop.name && prop.name !== 'markerId') { + if (prop.name === 'path') markerInfo.path = prop.value?.content || ''; + else if (prop.name === 'icon') markerInfo.icon = prop.value?.content || ''; + else if (prop.name === 'label') markerInfo.label = prop.value?.content || ''; + } + } + } + + // バインドプロパティを取得 + const bindings = extractNodeBindings(node); + if (bindings.path) markerInfo.path = bindings.path; + if (bindings.icon) markerInfo.icon = bindings.icon; + if (bindings.label) markerInfo.label = bindings.label; + if (bindings.children) markerInfo.children = bindings.children; + if (bindings.inlining) { + markerInfo.inlining = bindings.inlining; + logger.info(`Added inlining ${JSON.stringify(bindings.inlining)} to marker ${markerId}`); + } + if (bindings.keywords) { + if (Array.isArray(bindings.keywords)) { + markerInfo.keywords = bindings.keywords; + } else { + markerInfo.keywords = bindings.keywords || []; + } + } + + //pathがない場合はファイルパスを設定 + if (markerInfo.path == null && parentId == null) { + markerInfo.path = normalizedId.match(/.*(\/(admin|settings)\/[^\/]+)\.vue$/)?.[1]; + } + + // SearchLabelとSearchKeywordを抽出 (AST全体を探索) + if (node.children && Array.isArray(node.children)) { + logger.info(`Processing marker ${markerId} for labels and keywords`); + const extracted = extractLabelsAndKeywords(node.children); + + // SearchLabelからのラベル取得は最優先で適用 + if (extracted.label) { + markerInfo.label = extracted.label; + logger.info(`Using extracted label for ${markerId}: ${extracted.label}`); + } else if (markerInfo.label) { + logger.info(`Using existing label for ${markerId}: ${markerInfo.label}`); + } else { + markerInfo.label = 'Unnamed marker'; + logger.info(`No label found for ${markerId}, using default`); + } + + // SearchKeywordからのキーワード取得を追加 + if (extracted.keywords.length > 0) { + const existingKeywords = Array.isArray(markerInfo.keywords) ? + [...markerInfo.keywords] : + (markerInfo.keywords ? [markerInfo.keywords] : []); + + // i18n参照のキーワードは最優先で追加 + const combinedKeywords = [...existingKeywords]; + for (const kw of extracted.keywords) { + combinedKeywords.push(kw); + logger.info(`Added extracted keyword to ${markerId}: ${kw}`); + } + + markerInfo.keywords = combinedKeywords; + } + } + + // マーカーを登録 + markerMap.set(markerId, markerInfo); + allMarkers.push(markerInfo); + + // 親子関係を記録 + if (parentId) { + const parent = markerMap.get(parentId); + if (parent) { + childrenIds.add(markerId); + } + } + + // 子ノードを処理 + if (node.children && Array.isArray(node.children)) { + node.children.forEach((child: VueAstNode) => { + collectMarkers(child, markerId); + }); + } + + return markerId; + } + // SearchMarkerでない場合は再帰的に子ノードを処理 + else if (node.children && Array.isArray(node.children)) { + node.children.forEach((child: VueAstNode) => { + collectMarkers(child, parentId); + }); + } + + return null; + } + + // AST解析開始 + collectMarkers(templateAst); + return allMarkers; +} + +// バインドプロパティの処理を修正する関数 +function extractNodeBindings(node: VueAstNode): Record { + const bindings: Record = {}; + + if (!node.props || !Array.isArray(node.props)) return bindings; + + // バインド式を収集 + for (const prop of node.props) { + if (prop.type === 7 && prop.name === 'bind' && prop.arg?.content) { + const propName = prop.arg.content; + const propContent = prop.exp?.content || ''; + + logger.info(`Processing bind prop ${propName}: ${propContent}`); + + // inliningプロパティの処理を追加 + if (propName === 'inlining') { + try { + const content = propContent.trim(); + + // 配列式の場合 + if (content.startsWith('[') && content.endsWith(']')) { + // 配列要素を解析 + const elements = parseArrayExpression(content); + if (elements.length > 0) { + bindings.inlining = elements; + logger.info(`Parsed inlining array: ${JSON5.stringify(elements)}`); + } else { + bindings.inlining = []; + } + } + // 文字列の場合は配列に変換 + else if (content) { + bindings.inlining = [content]; // 単一の値を配列に + logger.info(`Converting inlining to array: [${content}]`); + } + } catch (e) { + logger.error(`Failed to parse inlining binding: ${propContent}`, e); + } + } + // keywordsの特殊処理 + if (propName === 'keywords') { + try { + const content = propContent.trim(); + + // 配列式の場合 + if (content.startsWith('[') && content.endsWith(']')) { + // i18n参照や特殊な式を保持するため、各要素を個別に解析 + const elements = parseArrayExpression(content); + if (elements.length > 0) { + bindings.keywords = elements; + logger.info(`Parsed keywords array: ${JSON5.stringify(elements)}`); + } else { + bindings.keywords = []; + logger.info('Empty keywords array'); + } + } + // その他の式(非配列) + else if (content) { + bindings.keywords = content; // 式をそのまま保持 + logger.info(`Keeping keywords as expression: ${content}`); + } else { + bindings.keywords = []; + logger.info('No keywords provided'); + } + } catch (e) { + logger.error(`Failed to parse keywords binding: ${propContent}`, e); + // エラーが起きても何らかの値を設定 + bindings.keywords = propContent || []; + } + } + // その他のプロパティ + else if (propName === 'label') { + // ラベルの場合も式として保持 + bindings[propName] = propContent; + logger.info(`Set label from bind expression: ${propContent}`); + } + else { + bindings[propName] = propContent; + } + } + } + + return bindings; +} + +// 配列式をパースする補助関数(文字列リテラル処理を改善) +function parseArrayExpression(expr: string): any[] { + try { + // 単純なケースはJSON5でパースを試みる + return JSON5.parse(expr.replace(/'/g, '"')); + } catch (e) { + // 複雑なケース(i18n.ts.xxx などの式を含む場合)は手動パース + logger.info(`Complex array expression, trying manual parsing: ${expr}`); + + // "["と"]"を取り除く + const content = expr.substring(1, expr.length - 1).trim(); + if (!content) return []; + + const result: any[] = []; + let currentItem = ''; + let depth = 0; + let inString = false; + let stringChar = ''; + + // カンマで区切る(ただし文字列内や入れ子の配列内のカンマは無視) + for (let i = 0; i < content.length; i++) { + const char = content[i]; + + if (inString) { + if (char === stringChar && content[i - 1] !== '\\') { + inString = false; + } + currentItem += char; + } else if (char === '"' || char === "'") { + inString = true; + stringChar = char; + currentItem += char; + } else if (char === '[') { + depth++; + currentItem += char; + } else if (char === ']') { + depth--; + currentItem += char; + } else if (char === ',' && depth === 0) { + // 項目の区切りを検出 + const trimmed = currentItem.trim(); + + // 純粋な文字列リテラルの場合、実際の値に変換 + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + try { + result.push(JSON5.parse(trimmed)); + } catch (err) { + result.push(trimmed); + } + } else { + // それ以外の式はそのまま(i18n.ts.xxx など) + result.push(trimmed); + } + + currentItem = ''; + } else { + currentItem += char; + } + } + + // 最後の項目を処理 + if (currentItem.trim()) { + const trimmed = currentItem.trim(); + + // 純粋な文字列リテラルの場合、実際の値に変換 + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + try { + result.push(JSON5.parse(trimmed)); + } catch (err) { + result.push(trimmed); + } + } else { + // それ以外の式はそのまま(i18n.ts.xxx など) + result.push(trimmed); + } + } + + logger.info(`Parsed complex array expression: ${expr} -> ${JSON.stringify(result)}`); + return result; + } +} + +export async function analyzeVueProps(options: Options & { + transformedCodeCache: Record, +}): Promise { + initLogger(options); + + const allMarkers: SearchIndexItem[] = []; + + // 対象ファイルパスを glob で展開 + const filePaths = options.targetFilePaths.reduce((acc, filePathPattern) => { + const matchedFiles = glob.sync(filePathPattern); + return [...acc, ...matchedFiles]; + }, []); + + logger.info(`Found ${filePaths.length} matching files to analyze`); + + for (const filePath of filePaths) { + const absolutePath = path.join(process.cwd(), filePath); + const id = absolutePath.replace(/\\/g, '/'); // 絶対パスに変換 + const code = options.transformedCodeCache[id]; // options 経由でキャッシュ参照 + if (!code) { // キャッシュミスの場合 + logger.error(`Error: No cached code found for: ${id}.`); // エラーログ + throw new Error(`No cached code found for: ${id}.`); // エラーを投げる + } + + try { + const { descriptor, errors } = vueSfcParse(options.transformedCodeCache[id], { + filename: filePath, + }); + + if (errors.length > 0) { + logger.error(`Compile Error: ${filePath}, ${errors}`); + continue; // エラーが発生したファイルはスキップ + } + + const fileMarkers = extractUsageInfoFromTemplateAst(descriptor.template?.ast, id); + + if (fileMarkers && fileMarkers.length > 0) { + allMarkers.push(...fileMarkers); // すべてのマーカーを収集 + logger.info(`Successfully extracted ${fileMarkers.length} markers from ${filePath}`); + } else { + logger.info(`No markers found in ${filePath}`); + } + } catch (error) { + logger.error(`Error analyzing file ${filePath}:`, error); + } + } + + // 収集したすべてのマーカー情報を使用 + const analysisResult: AnalysisResult[] = [ + { + filePath: "combined-markers", // すべてのファイルのマーカーを1つのエントリとして扱う + usage: allMarkers, + } + ]; + + outputAnalysisResultAsTS(options.exportFilePath, analysisResult); // すべてのマーカー情報を渡す +} + +interface MarkerRelation { + parentId?: string; + markerId: string; + node: VueAstNode; +} + +async function processVueFile( + code: string, + id: string, + options: Options, + transformedCodeCache: Record +): Promise<{ + code: string, + map: any, + transformedCodeCache: Record +}> { + const normalizedId = id.replace(/\\/g, '/'); // ファイルパスを正規化 + // すでにキャッシュに存在する場合は、そのまま返す + if (transformedCodeCache[normalizedId] && transformedCodeCache[normalizedId].includes('markerId=')) { + logger.info(`Using cached version for ${id}`); + return { + code: transformedCodeCache[normalizedId], + map: null, + transformedCodeCache + }; + } + + const s = new MagicString(code); // magic-string のインスタンスを作成 + const parsed = vueSfcParse(code, { filename: id }); + if (!parsed.descriptor.template) { + return { + code, + map: null, + transformedCodeCache + }; + } + const ast = parsed.descriptor.template.ast; // テンプレート AST を取得 + const markerRelations: MarkerRelation[] = []; // MarkerRelation 配列を初期化 + + if (ast) { + function traverse(node: any, currentParent?: any) { + if (node.type === 1 && node.tag === 'SearchMarker') { + // 行番号はコード先頭からの改行数で取得 + const lineNumber = code.slice(0, node.loc.start.offset).split('\n').length; + // ファイルパスと行番号からハッシュ値を生成 + // この際実行環境で差が出ないようにファイルパスを正規化 + const idKey = id.replace(/\\/g, '/').split('packages/frontend/')[1] + const generatedMarkerId = toBase62(hash(`${idKey}:${lineNumber}`)); + + const props = node.props || []; + const hasMarkerIdProp = props.some((prop: any) => prop.type === 6 && prop.name === 'markerId'); + const nodeMarkerId = hasMarkerIdProp + ? props.find((prop: any) => prop.type === 6 && prop.name === 'markerId')?.value?.content as string + : generatedMarkerId; + node.__markerId = nodeMarkerId; + + // 子マーカーの場合、親ノードに __children を設定しておく + if (currentParent && currentParent.type === 1 && currentParent.tag === 'SearchMarker') { + currentParent.__children = currentParent.__children || []; + currentParent.__children.push(nodeMarkerId); + } + + const parentMarkerId = currentParent && currentParent.__markerId; + markerRelations.push({ + parentId: parentMarkerId, + markerId: nodeMarkerId, + node: node, + }); + + if (!hasMarkerIdProp) { + const nodeStart = node.loc.start.offset; + let endOfStartTag; + + if (node.children && node.children.length > 0) { + // 子要素がある場合、最初の子要素の開始位置を基準にする + endOfStartTag = code.lastIndexOf('>', node.children[0].loc.start.offset); + } else if (node.loc.end.offset > nodeStart) { + // 子要素がない場合、自身の終了位置から逆算 + const nodeSource = code.substring(nodeStart, node.loc.end.offset); + // 自己終了タグか通常の終了タグかを判断 + if (nodeSource.includes('/>')) { + endOfStartTag = code.indexOf('/>', nodeStart) - 1; + } else { + endOfStartTag = code.indexOf('>', nodeStart); + } + } + + if (endOfStartTag !== undefined && endOfStartTag !== -1) { + // markerId が既に存在しないことを確認 + const tagText = code.substring(nodeStart, endOfStartTag + 1); + const markerIdRegex = /\s+markerId\s*=\s*["'][^"']*["']/; + + if (!markerIdRegex.test(tagText)) { + s.appendRight(endOfStartTag, ` markerId="${generatedMarkerId}" data-in-app-search-marker-id="${generatedMarkerId}"`); + logger.info(`Adding markerId="${generatedMarkerId}" to ${id}:${lineNumber}`); + } else { + logger.info(`markerId already exists in ${id}:${lineNumber}`); + } + } + } + } + + const newParent = node.type === 1 && node.tag === 'SearchMarker' ? node : currentParent; + if (node.children && Array.isArray(node.children)) { + node.children.forEach(child => traverse(child, newParent)); + } + } + + traverse(ast); // AST を traverse (1段階目: ID 生成と親子関係記録) + + // 2段階目: :children 属性の追加 + // 最初に親マーカーごとに子マーカーIDを集約する処理を追加 + const parentChildrenMap = new Map(); + + // 1. まず親ごとのすべての子マーカーIDを収集 + markerRelations.forEach(relation => { + if (relation.parentId) { + if (!parentChildrenMap.has(relation.parentId)) { + parentChildrenMap.set(relation.parentId, []); + } + parentChildrenMap.get(relation.parentId)?.push(relation.markerId); + } + }); + + // 2. 親ごとにまとめて :children 属性を処理 + for (const [parentId, childIds] of parentChildrenMap.entries()) { + const parentRelation = markerRelations.find(r => r.markerId === parentId); + if (!parentRelation || !parentRelation.node) continue; + + const parentNode = parentRelation.node; + const childrenProp = parentNode.props?.find((prop: any) => prop.type === 7 && prop.name === 'bind' && prop.arg?.content === 'children'); + + // 親ノードの開始位置を特定 + const parentNodeStart = parentNode.loc!.start.offset; + const endOfParentStartTag = parentNode.children && parentNode.children.length > 0 + ? code.lastIndexOf('>', parentNode.children[0].loc!.start.offset) + : code.indexOf('>', parentNodeStart); + + if (endOfParentStartTag === -1) continue; + + // 親タグのテキストを取得 + const parentTagText = code.substring(parentNodeStart, endOfParentStartTag + 1); + + if (childrenProp) { + // AST で :children 属性が検出された場合、それを更新 + try { + const childrenStart = code.indexOf('[', childrenProp.exp!.loc.start.offset); + const childrenEnd = code.indexOf(']', childrenProp.exp!.loc.start.offset); + if (childrenStart !== -1 && childrenEnd !== -1) { + const childrenArrayStr = code.slice(childrenStart, childrenEnd + 1); + let childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"')); + + // 新しいIDを追加(重複は除外) + const newIds = childIds.filter(id => !childrenArray.includes(id)); + if (newIds.length > 0) { + childrenArray = [...childrenArray, ...newIds]; + const updatedChildrenArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'"); + s.overwrite(childrenStart, childrenEnd + 1, updatedChildrenArrayStr); + logger.info(`Added ${newIds.length} child markerIds to existing :children in ${id}`); + } + } + } catch (e) { + logger.error('Error updating :children attribute:', e); + } + } else { + // AST では検出されなかった場合、タグテキストを調べる + const childrenRegex = /:children\s*=\s*["']\[(.*?)\]["']/; + const childrenMatch = parentTagText.match(childrenRegex); + + if (childrenMatch) { + // テキストから :children 属性値を解析して更新 + try { + const childrenContent = childrenMatch[1]; + const childrenArrayStr = `[${childrenContent}]`; + const childrenArray = JSON5.parse(childrenArrayStr.replace(/'/g, '"')); + + // 新しいIDを追加(重複は除外) + const newIds = childIds.filter(id => !childrenArray.includes(id)); + if (newIds.length > 0) { + childrenArray.push(...newIds); + + // :children="[...]" の位置を特定して上書き + const attrStart = parentTagText.indexOf(':children='); + if (attrStart > -1) { + const attrValueStart = parentTagText.indexOf('[', attrStart); + const attrValueEnd = parentTagText.indexOf(']', attrValueStart) + 1; + if (attrValueStart > -1 && attrValueEnd > -1) { + const absoluteStart = parentNodeStart + attrValueStart; + const absoluteEnd = parentNodeStart + attrValueEnd; + const updatedArrayStr = JSON5.stringify(childrenArray).replace(/"/g, "'"); + s.overwrite(absoluteStart, absoluteEnd, updatedArrayStr); + logger.info(`Updated existing :children in tag text for ${id}`); + } + } + } + } catch (e) { + logger.error('Error updating :children in tag text:', e); + } + } else { + // :children 属性がまだない場合、新規作成 + s.appendRight(endOfParentStartTag, ` :children="${JSON5.stringify(childIds).replace(/"/g, "'")}"`); + logger.info(`Created new :children attribute with ${childIds.length} markerIds in ${id}`); + } + } + } + } + + const transformedCode = s.toString(); // 変換後のコードを取得 + transformedCodeCache[normalizedId] = transformedCode; // 変換後のコードをキャッシュに保存 + + return { + code: transformedCode, // 変更後のコードを返す + map: s.generateMap({ source: id, includeContent: true }), // ソースマップも生成 (sourceMap: true が必要) + transformedCodeCache // キャッシュも返す + }; +} + + +// Rollup プラグインとして export +export default function pluginCreateSearchIndex(options: Options): Plugin { + let transformedCodeCache: Record = {}; // キャッシュオブジェクトをプラグインスコープで定義 + const isDevServer = process.env.NODE_ENV === 'development'; // 開発サーバーかどうか + + initLogger(options); // ロガーを初期化 + + return { + name: 'createSearchIndex', + enforce: 'pre', + + async buildStart() { + if (!isDevServer) { + return; + } + + const filePaths = options.targetFilePaths.reduce((acc, filePathPattern) => { + const matchedFiles = glob.sync(filePathPattern); + return [...acc, ...matchedFiles]; + }, []); + + for (const filePath of filePaths) { + const id = path.resolve(filePath); // 絶対パスに変換 + const code = fs.readFileSync(filePath, 'utf-8'); // ファイル内容を読み込む + const { transformedCodeCache: newCache } = await processVueFile(code, id, options, transformedCodeCache); // processVueFile 関数を呼び出す + transformedCodeCache = newCache; // キャッシュを更新 + } + + await analyzeVueProps({ ...options, transformedCodeCache }); // 開発サーバー起動時にも analyzeVueProps を実行 + }, + + async transform(code, id) { + if (!id.endsWith('.vue')) { + return; + } + + // targetFilePaths にマッチするファイルのみ処理を行う + // glob パターンでマッチング + let isMatch = false; // isMatch の初期値を false に設定 + for (const pattern of options.targetFilePaths) { // パターンごとにマッチング確認 + const globbedFiles = glob.sync(pattern); + for (const globbedFile of globbedFiles) { + const normalizedGlobbedFile = path.resolve(globbedFile); // glob 結果を絶対パスに + const normalizedId = path.resolve(id); // id を絶対パスに + if (normalizedGlobbedFile === normalizedId) { // 絶対パス同士で比較 + isMatch = true; + break; // マッチしたらループを抜ける + } + } + if (isMatch) break; // いずれかのパターンでマッチしたら、outer loop も抜ける + } + + + if (!isMatch) { + return; + } + + const transformed = await processVueFile(code, id, options, transformedCodeCache); + transformedCodeCache = transformed.transformedCodeCache; // キャッシュを更新 + if (isDevServer) { + await analyzeVueProps({ ...options, transformedCodeCache }); // analyzeVueProps を呼び出す + } + return transformed; + }, + + async writeBundle() { + await analyzeVueProps({ ...options, transformedCodeCache }); // ビルド時にも analyzeVueProps を実行 + }, + }; +} + +// i18n参照を検出するためのヘルパー関数を追加 +function isI18nReference(text: string | null | undefined): boolean { + if (!text) return false; + // ドット記法(i18n.ts.something) + const dotPattern = /i18n\.ts\.\w+/; + // ブラケット記法(i18n.ts['something']) + const bracketPattern = /i18n\.ts\[['"][^'"]+['"]\]/; + return dotPattern.test(text) || bracketPattern.test(text); +} diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 2bf7728d0a..5b40a8cb9f 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -51,6 +51,7 @@ "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", "json5": "2.2.3", + "magic-string": "0.30.17", "matter-js": "0.20.0", "mfm-js": "0.24.0", "misskey-bubble-game": "workspace:*", diff --git a/packages/frontend/src/components/MkDisableSection.vue b/packages/frontend/src/components/MkDisableSection.vue new file mode 100644 index 0000000000..f596d5e3b5 --- /dev/null +++ b/packages/frontend/src/components/MkDisableSection.vue @@ -0,0 +1,41 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index e725d2a15d..c3fc1961eb 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -91,6 +91,14 @@ const buttonsRight = computed(() => { }); const reloadCount = ref(0); +function getSearchMarker(path: string) { + const hash = path.split('#')[1]; + if (hash == null) return null; + return hash; +} + +const searchMarkerId = ref(getSearchMarker(props.initialPath)); + windowRouter.addListener('push', ctx => { history.value.push({ path: ctx.path, key: ctx.key }); }); @@ -101,7 +109,8 @@ windowRouter.addListener('replace', ctx => { }); windowRouter.addListener('change', ctx => { - console.log('windowRouter: change', ctx.path); + if (_DEV_) console.log('windowRouter: change', ctx.path); + searchMarkerId.value = getSearchMarker(ctx.path); analytics.page({ path: ctx.path, title: ctx.path, @@ -111,6 +120,7 @@ windowRouter.addListener('change', ctx => { windowRouter.init(); provide('router', windowRouter); +provide('inAppSearchMarkerId', searchMarkerId); provideMetadataReceiver((metadataGetter) => { const info = metadataGetter(); pageMetadata.value = info; diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 397aa68ed6..d8dec3aa2f 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -4,27 +4,60 @@ SPDX-License-Identifier: AGPL-3.0-only --> @@ -58,10 +91,98 @@ export type SuperMenuDef = { diff --git a/packages/frontend/src/components/global/SearchKeyword.vue b/packages/frontend/src/components/global/SearchKeyword.vue new file mode 100644 index 0000000000..27a284faf0 --- /dev/null +++ b/packages/frontend/src/components/global/SearchKeyword.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/frontend/src/components/global/SearchLabel.vue b/packages/frontend/src/components/global/SearchLabel.vue new file mode 100644 index 0000000000..27a284faf0 --- /dev/null +++ b/packages/frontend/src/components/global/SearchLabel.vue @@ -0,0 +1,14 @@ + + + + + + + diff --git a/packages/frontend/src/components/global/SearchMarker.vue b/packages/frontend/src/components/global/SearchMarker.vue new file mode 100644 index 0000000000..c5ec626cf4 --- /dev/null +++ b/packages/frontend/src/components/global/SearchMarker.vue @@ -0,0 +1,116 @@ + + + + + + + diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts index 0252bf0252..ebbad3e5b8 100644 --- a/packages/frontend/src/components/index.ts +++ b/packages/frontend/src/components/index.ts @@ -3,8 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { App } from 'vue'; - import Mfm from './global/MkMfm.js'; import MkA from './global/MkA.vue'; import MkAcct from './global/MkAcct.vue'; @@ -26,6 +24,11 @@ import MkSpacer from './global/MkSpacer.vue'; import MkFooterSpacer from './global/MkFooterSpacer.vue'; import MkStickyContainer from './global/MkStickyContainer.vue'; import MkLazy from './global/MkLazy.vue'; +import SearchMarker from './global/SearchMarker.vue'; +import SearchLabel from './global/SearchLabel.vue'; +import SearchKeyword from './global/SearchKeyword.vue'; + +import type { App } from 'vue'; export default function(app: App) { for (const [key, value] of Object.entries(components)) { @@ -55,6 +58,9 @@ export const components = { MkFooterSpacer: MkFooterSpacer, MkStickyContainer: MkStickyContainer, MkLazy: MkLazy, + SearchMarker: SearchMarker, + SearchLabel: SearchLabel, + SearchKeyword: SearchKeyword, }; declare module '@vue/runtime-core' { @@ -80,5 +86,8 @@ declare module '@vue/runtime-core' { MkFooterSpacer: typeof MkFooterSpacer; MkStickyContainer: typeof MkStickyContainer; MkLazy: typeof MkLazy; + SearchMarker: typeof SearchMarker; + SearchLabel: typeof SearchLabel; + SearchKeyword: typeof SearchKeyword; } } diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index 776f59dda3..806599e801 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -4,74 +4,82 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/pages/settings/appearance.vue b/packages/frontend/src/pages/settings/appearance.vue new file mode 100644 index 0000000000..465c2a38c2 --- /dev/null +++ b/packages/frontend/src/pages/settings/appearance.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index 9fca306f9f..79be2b9b1e 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -4,44 +4,46 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue index 5acbc50756..6b67a9a1a8 100644 --- a/packages/frontend/src/pages/settings/import-export.vue +++ b/packages/frontend/src/pages/settings/import-export.vue @@ -4,118 +4,143 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 41e475eade..2e453aeb8f 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -8,11 +8,11 @@ SPDX-License-Identifier: AGPL-3.0-only ref="buttonEl" v-ripple="canToggle" class="_button" - :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]" + :class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: prefer.s.reactionsDisplaySize === 'small', [$style.large]: prefer.s.reactionsDisplaySize === 'large' }]" @click="toggleReaction()" @contextmenu.prevent.stop="menu" > - + {{ count }} @@ -30,11 +30,11 @@ import { useTooltip } from '@/scripts/use-tooltip.js'; import { $i } from '@/account.js'; import MkReactionEffect from '@/components/MkReactionEffect.vue'; import { claimAchievement } from '@/scripts/achievements.js'; -import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as sound from '@/scripts/sound.js'; import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; import { customEmojisMap } from '@/custom-emojis.js'; +import { prefer } from '@/preferences.js'; const props = defineProps<{ reaction: string; @@ -90,7 +90,7 @@ async function toggleReaction() { } }); } else { - if (defaultStore.state.confirmOnReact) { + if (prefer.s.confirmOnReact) { const confirm = await os.confirm({ type: 'question', text: i18n.tsx.reactAreYouSure({ emoji: props.reaction.replace('@.', '') }), @@ -135,7 +135,7 @@ async function menu(ev) { } function anime() { - if (document.hidden || !defaultStore.state.animation || buttonEl.value == null) return; + if (document.hidden || !prefer.s.animation || buttonEl.value == null) return; const rect = buttonEl.value.getBoundingClientRect(); const x = rect.left + 16; diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue index 63b202f9f3..bb60db8d34 100644 --- a/packages/frontend/src/components/MkReactionsViewer.vue +++ b/packages/frontend/src/components/MkReactionsViewer.vue @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only -- cgit v1.2.3-freya From 7f534a41a65ec93a0eafd02796b782309b3d0702 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 20 Mar 2025 16:07:52 +0900 Subject: fix lint --- packages/frontend/src/_boot_.ts | 2 +- packages/frontend/src/accounts.ts | 2 +- packages/frontend/src/boot/common.ts | 12 ++++++------ packages/frontend/src/boot/main-boot.ts | 8 ++++---- packages/frontend/src/components/MkEmbedCodeGenDialog.vue | 2 +- packages/frontend/src/components/global/MkA.vue | 2 +- packages/frontend/src/lib/nirax.ts | 2 +- packages/frontend/src/navbar.ts | 2 +- packages/frontend/src/pages/auth.vue | 2 +- packages/frontend/src/pages/lookup.vue | 6 +++--- packages/frontend/src/pages/miauth.vue | 2 +- packages/frontend/src/pages/share.vue | 4 ++-- packages/frontend/src/router.ts | 4 ++-- packages/frontend/src/ui/_common_/stream-indicator.vue | 2 +- packages/frontend/src/ui/classic.vue | 2 +- packages/frontend/src/ui/universal.vue | 2 +- packages/frontend/src/ui/zen.vue | 2 +- packages/frontend/src/utility/reload-ask.ts | 2 +- packages/frontend/src/utility/unison-reload.ts | 4 ++-- 19 files changed, 32 insertions(+), 32 deletions(-) (limited to 'packages/frontend/src/ui') diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts index 24fafce18c..3241f2dc92 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -14,7 +14,7 @@ import { subBoot } from '@/boot/sub-boot.js'; const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions']; -if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { +if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) { subBoot(); } else { mainBoot(); diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index dbeb13eb63..a25f3c51d1 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -191,7 +191,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string) // 他のタブは再読み込みするだけ reloadChannel.postMessage(null); // このページはredirectで指定された先に移動 - location.href = redirect; + window.location.href = redirect; return; } diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 402ccca587..8096c46960 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -98,14 +98,14 @@ export async function common(createVue: () => App) { document.addEventListener('touchend', () => {}, { passive: true }); // URLに#pswpを含む場合は取り除く - if (location.hash === '#pswp') { - history.replaceState(null, '', location.href.replace('#pswp', '')); + if (window.location.hash === '#pswp') { + history.replaceState(null, '', window.location.href.replace('#pswp', '')); } // 一斉リロード reloadChannel.addEventListener('message', path => { - if (path !== null) location.href = path; - else location.reload(); + if (path !== null) window.location.href = path; + else window.location.reload(); }); // If mobile, insert the viewport meta tag @@ -130,11 +130,11 @@ export async function common(createVue: () => App) { }); //#region loginId - const params = new URLSearchParams(location.search); + const params = new URLSearchParams(window.location.search); const loginId = params.get('loginId'); if (loginId) { - const target = getUrlWithoutLoginId(location.href); + const target = getUrlWithoutLoginId(window.location.href); if (!$i || $i.id !== loginId) { const account = await getAccountFromId(loginId); diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 62ee0c5d72..f7360a7340 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -43,7 +43,7 @@ export async function mainBoot() { if (!$i) uiStyle = 'visitor'; if (searchParams.has('zen')) uiStyle = 'zen'; - if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && location.pathname !== '/') uiStyle = 'zen'; + if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && window.location.pathname !== '/') uiStyle = 'zen'; if (searchParams.has('ui')) uiStyle = searchParams.get('ui'); @@ -216,7 +216,7 @@ export async function mainBoot() { let reloadDialogShowing = false; stream.on('_disconnected_', async () => { if (prefer.s.serverDisconnectedBehavior === 'reload') { - location.reload(); + window.location.reload(); } else if (prefer.s.serverDisconnectedBehavior === 'dialog') { if (reloadDialogShowing) return; reloadDialogShowing = true; @@ -227,7 +227,7 @@ export async function mainBoot() { }); reloadDialogShowing = false; if (!canceled) { - location.reload(); + window.location.reload(); } } }); @@ -458,7 +458,7 @@ export async function mainBoot() { const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); - if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) { + if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !window.location.pathname.startsWith('/miauth')) { if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, { closed: () => dispose(), diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue index a4e9547f90..d18fe0ed0c 100644 --- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue +++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue @@ -180,7 +180,7 @@ function applyToPreview() { nextTick(() => { if (currentPreviewUrl === embedPreviewUrl.value) { // URLが変わらなくてもリロード - iframeEl.value?.contentWindow?.location.reload(); + iframeEl.value?.contentWindow?.window.location.reload(); } }); } diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue index 336160ec17..4004db5b12 100644 --- a/packages/frontend/src/components/global/MkA.vue +++ b/packages/frontend/src/components/global/MkA.vue @@ -87,7 +87,7 @@ function openWindow() { function nav(ev: MouseEvent) { if (behavior === 'browser') { - location.href = props.to; + window.location.href = props.to; return; } diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts index 8783874bc2..a97803e879 100644 --- a/packages/frontend/src/lib/nirax.ts +++ b/packages/frontend/src/lib/nirax.ts @@ -320,7 +320,7 @@ export class Nirax extends EventEmitter { } const res = this.navigate(fullPath); if (res.route.path === '/:(*)') { - location.href = fullPath; + window.location.href = fullPath; } else { this.emit('push', { beforeFullPath, diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index c0a6a370fc..d478ece641 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -167,7 +167,7 @@ export const navbarItemDef = reactive({ title: i18n.ts.reload, icon: 'ti ti-refresh', action: (ev) => { - location.reload(); + window.location.reload(); }, }, profile: { diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index e4699379f0..0dcdc5e9cb 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -64,7 +64,7 @@ function accepted() { if (session.value && session.value.app.callbackUrl) { const url = new URL(session.value.app.callbackUrl); if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url'); - location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`; + window.location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`; } } diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue index fafad8af4a..4eb4808048 100644 --- a/packages/frontend/src/pages/lookup.vue +++ b/packages/frontend/src/pages/lookup.vue @@ -31,7 +31,7 @@ import MkButton from '@/components/MkButton.vue'; const state = ref<'fetching' | 'done'>('fetching'); function fetch() { - const params = new URL(location.href).searchParams; + const params = new URL(window.location.href).searchParams; // acctのほうはdeprecated let uri = params.get('uri') ?? params.get('acct'); @@ -76,12 +76,12 @@ function close(): void { // 閉じなければ100ms後タイムラインに window.setTimeout(() => { - location.href = '/'; + window.location.href = '/'; }, 100); } function goToMisskey(): void { - location.href = '/'; + window.location.href = '/'; } fetch(); diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue index 687315e9b7..e809395848 100644 --- a/packages/frontend/src/pages/miauth.vue +++ b/packages/frontend/src/pages/miauth.vue @@ -61,7 +61,7 @@ async function onAccept(token: string) { const cbUrl = new URL(props.callback); if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url'); cbUrl.searchParams.set('session', props.session); - location.href = cbUrl.toString(); + window.location.href = cbUrl.toString(); } else { authRoot.value?.showUI('success'); } diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue index abd84c8590..f88f9ebc1e 100644 --- a/packages/frontend/src/pages/share.vue +++ b/packages/frontend/src/pages/share.vue @@ -182,12 +182,12 @@ function close(): void { // 閉じなければ100ms後タイムラインに window.setTimeout(() => { - location.href = '/'; + window.location.href = '/'; }, 100); } function goToMisskey(): void { - location.href = '/'; + window.location.href = '/'; } function onPosted(): void { diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index dd70571d64..3b79569995 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -17,10 +17,10 @@ export function createRouter(fullPath: string): Router { return new Nirax(ROUTE_DEF, fullPath, !!$i, page(() => import('@/pages/not-found.vue'))); } -export const mainRouter = createRouter(location.pathname + location.search + location.hash); +export const mainRouter = createRouter(window.location.pathname + window.location.search + window.location.hash); window.addEventListener('popstate', (event) => { - mainRouter.replace(location.pathname + location.search + location.hash); + mainRouter.replace(window.location.pathname + window.location.search + window.location.hash); }); mainRouter.addListener('push', ctx => { diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue index 1eb809d198..5f7600881f 100644 --- a/packages/frontend/src/ui/_common_/stream-indicator.vue +++ b/packages/frontend/src/ui/_common_/stream-indicator.vue @@ -34,7 +34,7 @@ function resetDisconnected() { } function reload() { - location.reload(); + window.location.reload(); } useStream().on('_disconnected_', onDisconnected); diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index c17e78bb03..8f35ce0c68 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -139,7 +139,7 @@ if (window.innerWidth < 1024) { const currentUI = miLocalStorage.getItem('ui'); miLocalStorage.setItem('ui_temp', currentUI ?? 'default'); miLocalStorage.setItem('ui', 'default'); - location.reload(); + window.location.reload(); } document.documentElement.style.overflowY = 'scroll'; diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index d85745bde6..92b5d253d3 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -170,7 +170,7 @@ if (window.innerWidth > 1024) { if (tempUI) { miLocalStorage.setItem('ui', tempUI); miLocalStorage.removeItem('ui_temp'); - location.reload(); + window.location.reload(); } } diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index 8a09ad80d9..bfb2ef634b 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -37,7 +37,7 @@ const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index'); const pageMetadata = ref(null); -const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck'; +const showBottom = !(new URLSearchParams(window.location.search)).has('zen') && ui === 'deck'; provide(DI.router, mainRouter); provideMetadataReceiver((metadataGetter) => { diff --git a/packages/frontend/src/utility/reload-ask.ts b/packages/frontend/src/utility/reload-ask.ts index 057f57471a..7c7ea113d4 100644 --- a/packages/frontend/src/utility/reload-ask.ts +++ b/packages/frontend/src/utility/reload-ask.ts @@ -35,6 +35,6 @@ export async function reloadAsk(opts: { if (opts.unison) { unisonReload(); } else { - location.reload(); + window.location.reload(); } } diff --git a/packages/frontend/src/utility/unison-reload.ts b/packages/frontend/src/utility/unison-reload.ts index a24941d02e..c4804192f8 100644 --- a/packages/frontend/src/utility/unison-reload.ts +++ b/packages/frontend/src/utility/unison-reload.ts @@ -12,9 +12,9 @@ export const reloadChannel = new BroadcastChannel('reload'); export function unisonReload(path?: string) { if (path !== undefined) { reloadChannel.postMessage(path); - location.href = path; + window.location.href = path; } else { reloadChannel.postMessage(null); - location.reload(); + window.location.reload(); } } -- cgit v1.2.3-freya From 6015254e59ba0526efbfa139c89546458663ccbd Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 20 Mar 2025 19:00:09 +0900 Subject: lint fixes --- packages/frontend/.storybook/fake-utils.ts | 2 +- packages/frontend/.storybook/preview.ts | 6 ++--- packages/frontend/src/boot/common.ts | 30 +++++++++++----------- packages/frontend/src/boot/main-boot.ts | 6 ++--- packages/frontend/src/components/MkAnalogClock.vue | 2 +- .../frontend/src/components/MkAutocomplete.vue | 4 +-- packages/frontend/src/components/MkButton.vue | 2 +- packages/frontend/src/components/MkCaptcha.vue | 6 ++--- packages/frontend/src/components/MkContextMenu.vue | 4 +-- .../frontend/src/components/MkCropperDialog.vue | 2 +- packages/frontend/src/components/MkFolder.vue | 2 +- .../frontend/src/components/MkImgWithBlurhash.vue | 4 +-- .../frontend/src/components/MkInstanceStats.vue | 2 +- packages/frontend/src/components/MkMediaAudio.vue | 2 +- packages/frontend/src/components/MkMediaList.vue | 6 ++--- packages/frontend/src/components/MkMediaVideo.vue | 8 +++--- packages/frontend/src/components/MkMenu.vue | 16 ++++++------ packages/frontend/src/components/MkMiniChart.vue | 2 +- .../frontend/src/components/MkNotifications.vue | 2 +- packages/frontend/src/components/MkPagination.vue | 2 +- packages/frontend/src/components/MkRange.vue | 8 +++--- .../src/components/MkReactionsViewer.reaction.vue | 2 +- .../src/components/MkRetentionLineChart.vue | 2 +- packages/frontend/src/components/MkTagCloud.vue | 4 +-- .../MkVisitorDashboard.ActiveUsersChart.vue | 2 +- packages/frontend/src/components/MkWindow.vue | 2 +- .../src/components/global/MkPageHeader.tabs.vue | 2 +- .../src/components/global/MkPageHeader.vue | 4 +-- .../frontend/src/components/global/RouterView.vue | 6 ++--- .../src/components/global/SearchMarker.vue | 2 +- packages/frontend/src/directives/hotkey.ts | 4 +-- packages/frontend/src/directives/panel.ts | 2 +- packages/frontend/src/directives/tooltip.ts | 2 +- packages/frontend/src/directives/user-preview.ts | 4 +-- packages/frontend/src/instance.ts | 2 +- packages/frontend/src/os.ts | 4 +-- packages/frontend/src/pages/admin/_header_.vue | 2 +- packages/frontend/src/pages/admin/overview.pie.vue | 2 +- packages/frontend/src/pages/auth.form.vue | 2 +- .../frontend/src/pages/drop-and-fusion.game.vue | 2 +- packages/frontend/src/pages/oauth.vue | 18 ++++++------- .../frontend/src/pages/settings/2fa.qrdialog.vue | 2 +- packages/frontend/src/preferences.ts | 6 ++--- packages/frontend/src/preferences/utility.ts | 4 +-- packages/frontend/src/server-context.ts | 2 +- packages/frontend/src/stream.ts | 6 ++--- packages/frontend/src/theme.ts | 12 ++++----- packages/frontend/src/ui/_common_/common.vue | 2 +- packages/frontend/src/ui/_common_/navbar.vue | 4 +-- packages/frontend/src/ui/classic.vue | 6 ++--- packages/frontend/src/ui/deck.vue | 4 +-- packages/frontend/src/ui/minimum.vue | 4 +-- packages/frontend/src/ui/universal.vue | 12 ++++----- packages/frontend/src/ui/visitor.vue | 4 +-- packages/frontend/src/ui/zen.vue | 4 +-- packages/frontend/src/use/use-note-capture.ts | 2 +- packages/frontend/src/use/use-tooltip.ts | 4 +-- packages/frontend/src/utility/focus-trap.ts | 4 +-- packages/frontend/src/utility/focus.ts | 4 +-- packages/frontend/src/utility/fullscreen.ts | 4 +-- packages/frontend/src/utility/hotkey.ts | 6 ++--- packages/frontend/src/utility/init-chart.ts | 2 +- packages/frontend/src/utility/physics.ts | 2 +- packages/frontend/src/utility/select-file.ts | 2 +- packages/frontend/src/utility/snowfall-effect.ts | 6 ++--- packages/frontend/src/utility/sound.ts | 4 +-- packages/frontend/src/utility/sticky-sidebar.ts | 2 +- .../frontend/src/utility/upload/isWebpSupported.ts | 2 +- packages/frontend/src/widgets/WidgetRss.vue | 2 +- packages/frontend/src/widgets/WidgetRssTicker.vue | 2 +- packages/frontend/test/scroll.test.ts | 12 ++++----- 71 files changed, 160 insertions(+), 160 deletions(-) (limited to 'packages/frontend/src/ui') diff --git a/packages/frontend/.storybook/fake-utils.ts b/packages/frontend/.storybook/fake-utils.ts index c777cbbe72..44e2263ca0 100644 --- a/packages/frontend/.storybook/fake-utils.ts +++ b/packages/frontend/.storybook/fake-utils.ts @@ -131,7 +131,7 @@ export function imageDataUrl(options?: { alpha?: number, } }, seed?: string): string { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = options?.size?.width ?? 100; canvas.height = options?.size?.height ?? 100; diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index 3f6bfb62ff..269bc4fb9a 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -23,9 +23,9 @@ let misskeyOS = null; function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) { unobserve(); - const theme = themes[document.documentElement.dataset.misskeyTheme]; + const theme = themes[window.document.documentElement.dataset.misskeyTheme]; if (theme) { - applyTheme(themes[document.documentElement.dataset.misskeyTheme]); + applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]); } else { applyTheme(themes['l-light']); } @@ -42,7 +42,7 @@ function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) { } } }); - observer.observe(document.documentElement, { + observer.observe(window.document.documentElement, { attributes: true, attributeFilter: ['data-misskey-theme'], }); diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 75b74b41f8..9a505ca9f8 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -95,7 +95,7 @@ export async function common(createVue: () => App) { //#endregion // タッチデバイスでCSSの:hoverを機能させる - document.addEventListener('touchend', () => {}, { passive: true }); + window.document.addEventListener('touchend', () => {}, { passive: true }); // URLに#pswpを含む場合は取り除く if (window.location.hash === '#pswp') { @@ -110,13 +110,13 @@ export async function common(createVue: () => App) { // If mobile, insert the viewport meta tag if (['smartphone', 'tablet'].includes(deviceKind)) { - const viewport = document.getElementsByName('viewport').item(0); + const viewport = window.document.getElementsByName('viewport').item(0); viewport.setAttribute('content', `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); } //#region Set lang attr - const html = document.documentElement; + const html = window.document.documentElement; html.setAttribute('lang', lang); //#endregion @@ -155,7 +155,7 @@ export async function common(createVue: () => App) { ); }, { immediate: miLocalStorage.getItem('theme') == null }); - document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; + window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; const darkTheme = prefer.model('darkTheme'); const lightTheme = prefer.model('lightTheme'); @@ -201,20 +201,20 @@ export async function common(createVue: () => App) { }, { immediate: true }); watch(prefer.r.useBlurEffectForModal, v => { - document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); + window.document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); }, { immediate: true }); watch(prefer.r.useBlurEffect, v => { if (v) { - document.documentElement.style.removeProperty('--MI-blur'); + window.document.documentElement.style.removeProperty('--MI-blur'); } else { - document.documentElement.style.setProperty('--MI-blur', 'none'); + window.document.documentElement.style.setProperty('--MI-blur', 'none'); } }, { immediate: true }); // Keep screen on - const onVisibilityChange = () => document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { + const onVisibilityChange = () => window.document.addEventListener('visibilitychange', () => { + if (window.document.visibilityState === 'visible') { navigator.wakeLock.request('screen'); } }); @@ -224,7 +224,7 @@ export async function common(createVue: () => App) { .catch(() => { // On WebKit-based browsers, user activation is required to send wake lock request // https://webkit.org/blog/13862/the-user-activation-api/ - document.addEventListener( + window.document.addEventListener( 'click', () => navigator.wakeLock.request('screen').then(onVisibilityChange), { once: true }, @@ -233,7 +233,7 @@ export async function common(createVue: () => App) { } if (prefer.s.makeEveryTextElementsSelectable) { - document.documentElement.classList.add('forceSelectableAll'); + window.document.documentElement.classList.add('forceSelectableAll'); } //#region Fetch user @@ -278,16 +278,16 @@ export async function common(createVue: () => App) { const rootEl = ((): HTMLElement => { const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; - const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID); + const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID); if (currentRoot) { console.warn('multiple import detected'); return currentRoot; } - const root = document.createElement('div'); + const root = window.document.createElement('div'); root.id = MISSKEY_MOUNT_DIV_ID; - document.body.appendChild(root); + window.document.body.appendChild(root); return root; })(); @@ -330,7 +330,7 @@ export async function common(createVue: () => App) { } function removeSplash() { - const splash = document.getElementById('splash'); + const splash = window.document.getElementById('splash'); if (splash) { splash.style.opacity = '0'; splash.style.pointerEvents = 'none'; diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index f7360a7340..134490e317 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -398,7 +398,7 @@ export async function mainBoot() { let lastVisibilityChangedAt = Date.now(); function claimPlainLucky() { - if (document.visibilityState !== 'visible') { + if (window.document.visibilityState !== 'visible') { if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer); return; } @@ -413,7 +413,7 @@ export async function mainBoot() { window.addEventListener('visibilitychange', () => { const now = Date.now(); - if (document.visibilityState === 'visible') { + if (window.document.visibilityState === 'visible') { // タブを高速で切り替えたら取得処理が何度も走るのを防ぐ if ((now - lastVisibilityChangedAt) < 1000 * 10) { justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10); @@ -554,7 +554,7 @@ export async function mainBoot() { mainRouter.push('/search'); }, } as const satisfies Keymap; - document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); + window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); initializeSw(); } diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index b39bca5b27..eac1ea9534 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -192,7 +192,7 @@ function tick() { tick(); function calcColors() { - const computedStyle = getComputedStyle(document.documentElement); + const computedStyle = getComputedStyle(window.document.documentElement); const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark(); const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index 5b747a2a1a..03cf107231 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -359,7 +359,7 @@ onMounted(() => { props.textarea.addEventListener('keydown', onKeydown); - document.body.addEventListener('mousedown', onMousedown); + window.document.body.addEventListener('mousedown', onMousedown); nextTick(() => { exec(); @@ -375,7 +375,7 @@ onMounted(() => { onBeforeUnmount(() => { props.textarea.removeEventListener('keydown', onKeydown); - document.body.removeEventListener('mousedown', onMousedown); + window.document.body.removeEventListener('mousedown', onMousedown); }); diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index c81edc2a73..5e89dfba12 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -92,7 +92,7 @@ function onMousedown(evt: MouseEvent): void { const target = evt.target! as HTMLElement; const rect = target.getBoundingClientRect(); - const ripple = document.createElement('div'); + const ripple = window.document.createElement('div'); ripple.classList.add(ripples.value!.dataset.childrenClass!); ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index e4f953bda8..30940a34a9 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -112,7 +112,7 @@ watch(() => [props.instanceUrl, props.sitekey, props.secretKey], async () => { if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { available.value = true; } else if (src.value !== null) { - (document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), { + (window.document.getElementById(scriptId.value) ?? window.document.head.appendChild(Object.assign(window.document.createElement('script'), { async: true, id: scriptId.value, src: src.value, @@ -149,7 +149,7 @@ async function requestRender() { if (captcha.value.render && captchaEl.value instanceof Element && props.sitekey) { // reCAPTCHAのレンダリング重複判定を回避するため、captchaEl配下に仮のdivを用意する. // (同じdivに対して複数回renderを呼び出すとreCAPTCHAはエラーを返すので) - const elem = document.createElement('div'); + const elem = window.document.createElement('div'); captchaEl.value.appendChild(elem); captchaWidgetId.value = captcha.value.render(elem, { @@ -174,7 +174,7 @@ async function requestRender() { function clearWidget() { if (props.provider === 'mcaptcha') { - const container = document.getElementById('mcaptcha__widget-container'); + const container = window.document.getElementById('mcaptcha__widget-container'); if (container) { container.innerHTML = ''; } diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue index e47dba4bae..9c6397a72c 100644 --- a/packages/frontend/src/components/MkContextMenu.vue +++ b/packages/frontend/src/components/MkContextMenu.vue @@ -68,11 +68,11 @@ onMounted(() => { rootEl.value.style.left = `${left}px`; } - document.body.addEventListener('mousedown', onMousedown); + window.document.body.addEventListener('mousedown', onMousedown); }); onBeforeUnmount(() => { - document.body.removeEventListener('mousedown', onMousedown); + window.document.body.removeEventListener('mousedown', onMousedown); }); function onMousedown(evt: Event) { diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index bd2e6a9cbc..ba21394cbc 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -122,7 +122,7 @@ onMounted(() => { cropper = new Cropper(imgEl.value!, { }); - const computedStyle = getComputedStyle(document.documentElement); + const computedStyle = getComputedStyle(window.document.documentElement); const selection = cropper.getCropperSelection()!; selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index b13972f66d..afa09e3125 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -116,7 +116,7 @@ function toggle() { } onMounted(() => { - const computedStyle = getComputedStyle(document.documentElement); + const computedStyle = getComputedStyle(window.document.documentElement); const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent'; const myBg = computedStyle.getPropertyValue('--MI_THEME-panel'); bgSame.value = parentBg === myBg; diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 420e5e392f..e3a0a371b4 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -55,7 +55,7 @@ import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurha const canvasPromise = new Promise(resolve => { // テスト環境で Web Worker インスタンスは作成できない if (import.meta.env.MODE === 'test') { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); @@ -70,7 +70,7 @@ const canvasPromise = new Promise(resol ); resolve(workers); } else { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 64; canvas.height = 64; resolve(canvas); diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index 76bec9bc66..90391005bc 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -126,7 +126,7 @@ function createDoughnut(chartEl, tooltip, data) { labels: data.map(x => x.name), datasets: [{ backgroundColor: data.map(x => x.color), - borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'), + borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'), borderWidth: 2, hoverOffset: 0, data: data.map(x => x.value), diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index db6deb161a..b7052ad918 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -148,7 +148,7 @@ const keymap = { // PlayerElもしくはその子要素にフォーカスがあるかどうか function hasFocus() { if (!playerEl.value) return false; - return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement); + return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement); } const playerEl = useTemplateRef('playerEl'); diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 44a9d14a46..ae15776041 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -48,7 +48,7 @@ const props = defineProps<{ const gallery = useTemplateRef('gallery'); const pswpZIndex = os.claimZIndex('middle'); -document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); +window.document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString()); const count = computed(() => props.mediaList.filter(media => previewable(media)).length); let lightbox: PhotoSwipeLightbox | null = null; @@ -166,7 +166,7 @@ onMounted(() => { className: 'pswp__alt-text-container', appendTo: 'wrapper', onInit: (el, pswp) => { - const textBox = document.createElement('p'); + const textBox = window.document.createElement('p'); textBox.className = 'pswp__alt-text _acrylic'; el.appendChild(textBox); @@ -178,7 +178,7 @@ onMounted(() => { }); lightbox.on('afterInit', () => { - activeEl = document.activeElement instanceof HTMLElement ? document.activeElement : null; + activeEl = window.document.activeElement instanceof HTMLElement ? window.document.activeElement : null; focusParent(activeEl, true, true); lightbox?.pswp?.element?.focus({ preventScroll: true, diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 5475e703a5..629679a971 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -171,7 +171,7 @@ const keymap = { // PlayerElもしくはその子要素にフォーカスがあるかどうか function hasFocus() { if (!playerEl.value) return false; - return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement); + return playerEl.value === window.document.activeElement || playerEl.value.contains(window.document.activeElement); } // eslint-disable-next-line vue/no-setup-props-reactivity-loss @@ -216,7 +216,7 @@ function showMenu(ev: MouseEvent) { '2.0x': 2, }, }, - ...(document.pictureInPictureEnabled ? [{ + ...(window.document.pictureInPictureEnabled ? [{ text: i18n.ts._mediaControls.pip, icon: 'ti ti-picture-in-picture', action: togglePictureInPicture, @@ -384,8 +384,8 @@ function toggleFullscreen() { function togglePictureInPicture() { if (videoEl.value) { - if (document.pictureInPictureElement) { - document.exitPictureInPicture(); + if (window.document.pictureInPictureElement) { + window.document.exitPictureInPicture(); } else { videoEl.value.requestPictureInPicture(); } diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index e380d0bc37..a84bd9b256 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -358,10 +358,10 @@ function switchItem(item: MenuSwitch & { ref: any }) { function focusUp() { if (disposed) return; - if (!itemsEl.value?.contains(document.activeElement)) return; + if (!itemsEl.value?.contains(window.document.activeElement)) return; const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable); - const activeIndex = focusableElements.findIndex(el => el === document.activeElement); + const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement); const targetIndex = (activeIndex !== -1 && activeIndex !== 0) ? (activeIndex - 1) : (focusableElements.length - 1); const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value; @@ -370,10 +370,10 @@ function focusUp() { function focusDown() { if (disposed) return; - if (!itemsEl.value?.contains(document.activeElement)) return; + if (!itemsEl.value?.contains(window.document.activeElement)) return; const focusableElements = Array.from(itemsEl.value.children).filter(isFocusable); - const activeIndex = focusableElements.findIndex(el => el === document.activeElement); + const activeIndex = focusableElements.findIndex(el => el === window.document.activeElement); const targetIndex = (activeIndex !== -1 && activeIndex !== (focusableElements.length - 1)) ? (activeIndex + 1) : 0; const targetElement = focusableElements.at(targetIndex) ?? itemsEl.value; @@ -400,9 +400,9 @@ const onGlobalMousedown = (ev: MouseEvent) => { const setupHandlers = () => { if (!isNestingMenu) { - document.addEventListener('focusin', onGlobalFocusin, { passive: true }); + window.document.addEventListener('focusin', onGlobalFocusin, { passive: true }); } - document.addEventListener('mousedown', onGlobalMousedown, { passive: true }); + window.document.addEventListener('mousedown', onGlobalMousedown, { passive: true }); }; let disposed = false; @@ -410,9 +410,9 @@ let disposed = false; const disposeHandlers = () => { disposed = true; if (!isNestingMenu) { - document.removeEventListener('focusin', onGlobalFocusin); + window.document.removeEventListener('focusin', onGlobalFocusin); } - document.removeEventListener('mousedown', onGlobalMousedown); + window.document.removeEventListener('mousedown', onGlobalMousedown); }; onMounted(() => { diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index 7ea585ecc2..98bd471438 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -48,7 +48,7 @@ const polygonPoints = ref(''); const headX = ref(null); const headY = ref(null); const clock = ref(null); -const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); +const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent')); const color = accent.toRgbString(); function draw(): void { diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index 08fc846327..21f1967bfa 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -59,7 +59,7 @@ const pagination = computed(() => prefer.r.useGroupedNotifications.value ? { function onNotification(notification) { const isMuted = props.excludeTypes ? props.excludeTypes.includes(notification.type) : false; - if (isMuted || document.visibilityState === 'visible') { + if (isMuted || window.document.visibilityState === 'visible') { useStream().send('readNotification'); } diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index 6a1a91a9f4..ab8bda403b 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -142,7 +142,7 @@ const { } = prefer.r; const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value); -const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body); +const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : window.document.body); const visibility = useDocumentVisibility(); diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 118dbbe15a..734b624541 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -151,9 +151,9 @@ function onMousedown(ev: MouseEvent | TouchEvent) { closed: () => dispose(), }); - const style = document.createElement('style'); - style.appendChild(document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }')); - document.head.appendChild(style); + const style = window.document.createElement('style'); + style.appendChild(window.document.createTextNode('* { cursor: grabbing !important; } body * { pointer-events: none !important; }')); + window.document.head.appendChild(style); const thumbWidth = getThumbWidth(); @@ -172,7 +172,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { let beforeValue = finalValue.value; const onMouseup = () => { - document.head.removeChild(style); + window.document.head.removeChild(style); tooltipForDragShowing.value = false; window.removeEventListener('mousemove', onDrag); window.removeEventListener('touchmove', onDrag); diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 590b0f6f19..9d941a949a 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -136,7 +136,7 @@ async function menu(ev) { } function anime() { - if (document.hidden || !prefer.s.animation || buttonEl.value == null) return; + if (window.document.hidden || !prefer.s.animation || buttonEl.value == null) return; const rect = buttonEl.value.getBoundingClientRect(); const x = rect.left + 16; diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index 60b3f17b14..ba66ffecc0 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -44,7 +44,7 @@ onMounted(async () => { const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); + const accent = tinycolor(getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-accent')); const color = accent.toHex(); if (chartEl.value == null) return; diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 8ad9e14015..9d541c8acb 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -20,7 +20,7 @@ import tinycolor from 'tinycolor2'; const loaded = !!window.TagCanvas; const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz'; -const computedStyle = getComputedStyle(document.documentElement); +const computedStyle = getComputedStyle(window.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); @@ -57,7 +57,7 @@ onMounted(() => { if (loaded) { available.value = true; } else { - document.head.appendChild(Object.assign(document.createElement('script'), { + window.document.head.appendChild(Object.assign(window.document.createElement('script'), { async: true, src: '/client-assets/tagcanvas.min.js', })).addEventListener('load', () => available.value = true); diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index 2c2c515032..79c9e739c4 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -61,7 +61,7 @@ async function renderChart() { const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - const computedStyle = getComputedStyle(document.documentElement); + const computedStyle = getComputedStyle(window.document.documentElement); const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const colorRead = accent; diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index 98ec448a8e..e5ac791d0b 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -240,7 +240,7 @@ function onHeaderMousedown(evt: MouseEvent | TouchEvent) { const main = rootEl.value; if (main == null) return; - if (!contains(main, document.activeElement)) main.focus(); + if (!contains(main, window.document.activeElement)) main.focus(); const position = main.getBoundingClientRect(); diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 81adc07f26..358a17d3e8 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -170,7 +170,7 @@ onMounted(() => { if (props.rootEl) { ro2 = new ResizeObserver((entries, observer) => { - if (document.body.contains(el.value as HTMLElement)) { + if (window.document.body.contains(el.value as HTMLElement)) { nextTick(() => renderTab()); } }); diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index 59bf80cfca..d5680b8413 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -108,7 +108,7 @@ function onTabClick(): void { const calcBg = () => { const rawBg = 'var(--MI_THEME-bg)'; - const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); }; @@ -122,7 +122,7 @@ onMounted(() => { if (el.value && el.value.parentElement) { narrow.value = el.value.parentElement.offsetWidth < 500; ro = new ResizeObserver((entries, observer) => { - if (el.value && el.value.parentElement && document.body.contains(el.value as HTMLElement)) { + if (el.value && el.value.parentElement && window.document.body.contains(el.value as HTMLElement)) { narrow.value = el.value.parentElement.offsetWidth < 500; } }); diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index 45cb1e3bd5..1c0c35f34e 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -48,7 +48,7 @@ onMounted(() => { }); // view-transition-newなどのにはcss varが使えず、v-bindできないため直接スタイルを生成 -const viewTransitionStylesTag = document.createElement('style'); +const viewTransitionStylesTag = window.document.createElement('style'); viewTransitionStylesTag.textContent = ` @keyframes ${viewId}-old { to { transform: scale(0.95); opacity: 0; } @@ -89,8 +89,8 @@ router.useListener('change', ({ resolved }) => { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (prefer.s.animation && document.startViewTransition) { - document.startViewTransition(() => new Promise((res) => { + if (prefer.s.animation && window.document.startViewTransition) { + window.document.startViewTransition(() => new Promise((res) => { _(); nextTick(() => { res(); diff --git a/packages/frontend/src/components/global/SearchMarker.vue b/packages/frontend/src/components/global/SearchMarker.vue index 66a78cb7fd..061ce3f47d 100644 --- a/packages/frontend/src/components/global/SearchMarker.vue +++ b/packages/frontend/src/components/global/SearchMarker.vue @@ -42,7 +42,7 @@ const highlighted = ref(props.markerId === searchMarkerId.value); function checkChildren() { if (props.children?.includes(searchMarkerId.value)) { - const el = document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`); + const el = window.document.querySelector(`[data-in-app-search-marker-id="${searchMarkerId.value}"]`); highlighted.value = el == null; } } diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts index 75e022e98f..63637ab2ba 100644 --- a/packages/frontend/src/directives/hotkey.ts +++ b/packages/frontend/src/directives/hotkey.ts @@ -13,7 +13,7 @@ export default { el._keyHandler = makeHotkey(binding.value); if (el._hotkey_global) { - document.addEventListener('keydown', el._keyHandler, { passive: false }); + window.document.addEventListener('keydown', el._keyHandler, { passive: false }); } else { el.addEventListener('keydown', el._keyHandler, { passive: false }); } @@ -21,7 +21,7 @@ export default { unmounted(el) { if (el._hotkey_global) { - document.removeEventListener('keydown', el._keyHandler); + window.document.removeEventListener('keydown', el._keyHandler); } else { el.removeEventListener('keydown', el._keyHandler); } diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts index 17916fb6d3..0af19e6ca3 100644 --- a/packages/frontend/src/directives/panel.ts +++ b/packages/frontend/src/directives/panel.ts @@ -10,7 +10,7 @@ export default { mounted(src, binding, vn) { const parentBg = getBgColor(src.parentElement) ?? 'transparent'; - const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'); + const myBg = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'); if (parentBg === myBg) { src.style.backgroundColor = 'var(--MI_THEME-bg)'; diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts index 068186dfa0..750acd0588 100644 --- a/packages/frontend/src/directives/tooltip.ts +++ b/packages/frontend/src/directives/tooltip.ts @@ -47,7 +47,7 @@ export default { } self.show = () => { - if (!document.body.contains(el)) return; + if (!window.document.body.contains(el)) return; if (self._close) return; if (self.text == null) return; diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts index 43a93a0865..94deea82c7 100644 --- a/packages/frontend/src/directives/user-preview.ts +++ b/packages/frontend/src/directives/user-preview.ts @@ -31,7 +31,7 @@ export class UserPreview { } private show() { - if (!document.body.contains(this.el)) return; + if (!window.document.body.contains(this.el)) return; if (this.promise) return; const showing = ref(true); @@ -58,7 +58,7 @@ export class UserPreview { }; this.checkTimer = window.setInterval(() => { - if (!document.body.contains(this.el)) { + if (!window.document.body.contains(this.el)) { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.close(); diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts index c694d49c8a..e75e3dfd34 100644 --- a/packages/frontend/src/instance.ts +++ b/packages/frontend/src/instance.ts @@ -12,7 +12,7 @@ import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERR // TODO: 他のタブと永続化されたstateを同期 //#region loader -const providedMetaEl = document.getElementById('misskey_meta'); +const providedMetaEl = window.document.getElementById('misskey_meta'); let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null; let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0; diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index eed929432c..7f4f7c5be3 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -675,7 +675,7 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n src = null; } - let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(document.activeElement); + let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(window.document.activeElement); return new Promise(resolve => nextTick(() => { const { dispose } = popup(MkPopupMenu, { items, @@ -704,7 +704,7 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise { return Promise.resolve(); } - let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(document.activeElement); + let returnFocusTo = getHTMLElementOrNull(ev.currentTarget ?? ev.target) ?? getHTMLElementOrNull(window.document.activeElement); ev.preventDefault(); return new Promise(resolve => nextTick(() => { const { dispose } = popup(MkContextMenu, { diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue index 819ca2c127..0af56e1b15 100644 --- a/packages/frontend/src/pages/admin/_header_.vue +++ b/packages/frontend/src/pages/admin/_header_.vue @@ -120,7 +120,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void { const calcBg = () => { const rawBg = pageMetadata.value.bg ?? 'var(--MI_THEME-bg)'; - const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); }; diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index 32dd981ca9..86c5eff4da 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -41,7 +41,7 @@ onMounted(() => { labels: props.data.map(x => x.name), datasets: [{ backgroundColor: props.data.map(x => x.color), - borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'), + borderColor: getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-panel'), borderWidth: 2, hoverOffset: 0, data: props.data.map(x => x.value), diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue index 1917293c06..5b1fd1a386 100644 --- a/packages/frontend/src/pages/auth.form.vue +++ b/packages/frontend/src/pages/auth.form.vue @@ -38,7 +38,7 @@ const emit = defineEmits<{ const app = computed(() => props.session.app); const name = computed(() => { - const el = document.createElement('div'); + const el = window.document.createElement('div'); el.textContent = app.value.name; return el.innerHTML; }); diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 3b1a845f5b..b8b0d6aef6 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -875,7 +875,7 @@ function loadImage(url: string) { function getGameImageDriveFile() { return new Promise(res => { - const dcanvas = document.createElement('canvas'); + const dcanvas = window.document.createElement('canvas'); dcanvas.width = game.GAME_WIDTH; dcanvas.height = game.GAME_HEIGHT; const ctx = dcanvas.getContext('2d'); diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue index 49664c8b08..04bb1661cf 100644 --- a/packages/frontend/src/pages/oauth.vue +++ b/packages/frontend/src/pages/oauth.vue @@ -27,42 +27,42 @@ import MkPageWithAnimBg from '@/components/MkPageWithAnimBg.vue'; import { definePage } from '@/page.js'; import MkAuthConfirm from '@/components/MkAuthConfirm.vue'; -const transactionIdMeta = document.querySelector('meta[name="misskey:oauth:transaction-id"]'); +const transactionIdMeta = window.document.querySelector('meta[name="misskey:oauth:transaction-id"]'); if (transactionIdMeta) { transactionIdMeta.remove(); } -const name = document.querySelector('meta[name="misskey:oauth:client-name"]')?.content; -const logo = document.querySelector('meta[name="misskey:oauth:client-logo"]')?.content; -const permissions = document.querySelector('meta[name="misskey:oauth:scope"]')?.content.split(' ').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) ?? []; +const name = window.document.querySelector('meta[name="misskey:oauth:client-name"]')?.content; +const logo = window.document.querySelector('meta[name="misskey:oauth:client-logo"]')?.content; +const permissions = window.document.querySelector('meta[name="misskey:oauth:scope"]')?.content.split(' ').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) ?? []; function doPost(token: string, decision: 'accept' | 'deny') { - const form = document.createElement('form'); + const form = window.document.createElement('form'); form.action = '/oauth/decision'; form.method = 'post'; form.acceptCharset = 'utf-8'; - const loginToken = document.createElement('input'); + const loginToken = window.document.createElement('input'); loginToken.type = 'hidden'; loginToken.name = 'login_token'; loginToken.value = token; form.appendChild(loginToken); - const transactionId = document.createElement('input'); + const transactionId = window.document.createElement('input'); transactionId.type = 'hidden'; transactionId.name = 'transaction_id'; transactionId.value = transactionIdMeta?.content ?? ''; form.appendChild(transactionId); if (decision === 'deny') { - const cancel = document.createElement('input'); + const cancel = window.document.createElement('input'); cancel.type = 'hidden'; cancel.name = 'cancel'; cancel.value = 'cancel'; form.appendChild(cancel); } - document.body.appendChild(form); + window.document.body.appendChild(form); form.submit(); } diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue index a13f1a7813..03f973a33e 100644 --- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue +++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue @@ -160,7 +160,7 @@ async function tokenDone() { function downloadBackupCodes() { if (backupCodes.value !== undefined) { const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' }); - const dummya = document.createElement('a'); + const dummya = window.document.createElement('a'); dummya.href = URL.createObjectURL(txtBlob); dummya.download = `${$i.username}@${hostname}` + (port !== '' ? `_${port}` : '') + '-2fa-backup-codes.txt'; dummya.click(); diff --git a/packages/frontend/src/preferences.ts b/packages/frontend/src/preferences.ts index 7681333910..73c89e23af 100644 --- a/packages/frontend/src/preferences.ts +++ b/packages/frontend/src/preferences.ts @@ -125,8 +125,8 @@ function syncBetweenTabs() { window.setInterval(syncBetweenTabs, 5000); -document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { +window.document.addEventListener('visibilitychange', () => { + if (window.document.visibilityState === 'visible') { syncBetweenTabs(); } }); @@ -136,7 +136,7 @@ let latestBackupAt = 0; window.setInterval(() => { if ($i == null) return; if (!store.s.enablePreferencesAutoCloudBackup) return; - if (document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ + if (window.document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ if (prefer.profile.modifiedAt <= latestBackupAt) return; cloudBackup().then(() => { diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts index bf3dfa157f..7229b7348e 100644 --- a/packages/frontend/src/preferences/utility.ts +++ b/packages/frontend/src/preferences/utility.ts @@ -106,14 +106,14 @@ async function renameProfile() { function exportCurrentProfile() { const p = prefer.profile; const txtBlob = new Blob([JSON.stringify(p)], { type: 'text/plain' }); - const dummya = document.createElement('a'); + const dummya = window.document.createElement('a'); dummya.href = URL.createObjectURL(txtBlob); dummya.download = `${p.name || p.id}.misskeypreferences`; dummya.click(); } function importProfile() { - const input = document.createElement('input'); + const input = window.document.createElement('input'); input.type = 'file'; input.accept = '.misskeypreferences'; input.onchange = async () => { diff --git a/packages/frontend/src/server-context.ts b/packages/frontend/src/server-context.ts index e79d3fa314..744bfa4b7b 100644 --- a/packages/frontend/src/server-context.ts +++ b/packages/frontend/src/server-context.ts @@ -5,7 +5,7 @@ import * as Misskey from 'misskey-js'; -const providedContextEl = document.getElementById('misskey_clientCtx'); +const providedContextEl = window.document.getElementById('misskey_clientCtx'); export type ServerContext = { clip?: Misskey.entities.Clip; diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts index c97d7d4071..25544d9d88 100644 --- a/packages/frontend/src/stream.ts +++ b/packages/frontend/src/stream.ts @@ -29,10 +29,10 @@ export function useStream(): Misskey.IStream { timeoutHeartBeat = window.setTimeout(heartbeat, HEART_BEAT_INTERVAL); // send heartbeat right now when last send time is over HEART_BEAT_INTERVAL - document.addEventListener('visibilitychange', () => { + window.document.addEventListener('visibilitychange', () => { if ( !stream - || document.visibilityState !== 'visible' + || window.document.visibilityState !== 'visible' || Date.now() - lastHeartbeatCall < HEART_BEAT_INTERVAL ) return; heartbeat(); @@ -42,7 +42,7 @@ export function useStream(): Misskey.IStream { } function heartbeat(): void { - if (stream != null && document.visibilityState === 'visible') { + if (stream != null && window.document.visibilityState === 'visible') { stream.heartbeat(); } lastHeartbeatCall = Date.now(); diff --git a/packages/frontend/src/theme.ts b/packages/frontend/src/theme.ts index 970d143b97..cd44fff0c4 100644 --- a/packages/frontend/src/theme.ts +++ b/packages/frontend/src/theme.ts @@ -68,10 +68,10 @@ let timeout: number | null = null; export function applyTheme(theme: Theme, persist = true) { if (timeout) window.clearTimeout(timeout); - document.documentElement.classList.add('_themeChanging_'); + window.document.documentElement.classList.add('_themeChanging_'); timeout = window.setTimeout(() => { - document.documentElement.classList.remove('_themeChanging_'); + window.document.documentElement.classList.remove('_themeChanging_'); // 色計算など再度行えるようにクライアント全体に通知 globalEvents.emit('themeChanged'); @@ -79,7 +79,7 @@ export function applyTheme(theme: Theme, persist = true) { const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; - document.documentElement.dataset.colorScheme = colorScheme; + window.document.documentElement.dataset.colorScheme = colorScheme; // Deep copy const _theme = deepClone(theme); @@ -91,7 +91,7 @@ export function applyTheme(theme: Theme, persist = true) { const props = compile(_theme); - for (const tag of document.head.children) { + for (const tag of window.document.head.children) { if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { tag.setAttribute('content', props['htmlThemeColor']); break; @@ -99,10 +99,10 @@ export function applyTheme(theme: Theme, persist = true) { } for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); + window.document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } - document.documentElement.style.setProperty('color-scheme', colorScheme); + window.document.documentElement.style.setProperty('color-scheme', colorScheme); if (persist) { miLocalStorage.setItem('theme', JSON.stringify(props)); diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index a39a4ee86b..930f633c9f 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -67,7 +67,7 @@ const dev = _DEV_; const notifications = ref([]); function onNotification(notification: Misskey.entities.Notification, isClient = false) { - if (document.visibilityState === 'visible') { + if (window.document.visibilityState === 'visible') { if (!isClient && notification.type !== 'test') { // サーバーサイドのテスト通知の際は自動で既読をつけない(テストできないので) useStream().send('readNotification'); diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 754bf070fa..98d6f329ab 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -129,8 +129,8 @@ watch(store.r.menuDisplay, () => { }); function toggleIconOnly() { - if (document.startViewTransition && prefer.s.animation) { - document.startViewTransition(() => { + if (window.document.startViewTransition && prefer.s.animation) { + window.document.startViewTransition(() => { store.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon'); }); } else { diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 8f35ce0c68..f51577fc68 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -87,9 +87,9 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); @@ -142,7 +142,7 @@ if (window.innerWidth < 1024) { window.location.reload(); } -document.documentElement.style.overflowY = 'scroll'; +window.document.documentElement.style.overflowY = 'scroll'; onMounted(() => { window.addEventListener('resize', () => { diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index 4c33d7cb27..65aff8455a 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -202,8 +202,8 @@ function onWheel(ev: WheelEvent) { } } -document.documentElement.style.overflowY = 'hidden'; -document.documentElement.style.scrollBehavior = 'auto'; +window.document.documentElement.style.overflowY = 'hidden'; +window.document.documentElement.style.scrollBehavior = 'auto'; async function deleteProfile() { if (prefer.s['deck.profile'] == null) return; diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue index 94382b664a..ec20ac1114 100644 --- a/packages/frontend/src/ui/minimum.vue +++ b/packages/frontend/src/ui/minimum.vue @@ -30,9 +30,9 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 92b5d253d3..2742b4cd98 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -143,9 +143,9 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); @@ -205,12 +205,12 @@ provide>(CURRENT_STICKY_BOTTOM, navFooterHeight); watch(navFooter, () => { if (navFooter.value) { navFooterHeight.value = navFooter.value.offsetHeight; - document.body.style.setProperty('--MI-stickyBottom', `${navFooterHeight.value}px`); - document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)'); + window.document.body.style.setProperty('--MI-stickyBottom', `${navFooterHeight.value}px`); + window.document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)'); } else { navFooterHeight.value = 0; - document.body.style.setProperty('--MI-stickyBottom', '0px'); - document.body.style.setProperty('--MI-minBottomSpacing', '0px'); + window.document.body.style.setProperty('--MI-stickyBottom', '0px'); + window.document.body.style.setProperty('--MI-minBottomSpacing', '0px'); } }, { immediate: true, diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index ddc3761b04..3e07959458 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -51,9 +51,9 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index bfb2ef634b..3e4d452281 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -45,9 +45,9 @@ provideMetadataReceiver((metadataGetter) => { pageMetadata.value = info; if (pageMetadata.value) { if (isRoot.value && pageMetadata.value.title === instanceName) { - document.title = pageMetadata.value.title; + window.document.title = pageMetadata.value.title; } else { - document.title = `${pageMetadata.value.title} | ${instanceName}`; + window.document.title = `${pageMetadata.value.title} | ${instanceName}`; } } }); diff --git a/packages/frontend/src/use/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts index 0de2dbb3c5..97aec4c1f0 100644 --- a/packages/frontend/src/use/use-note-capture.ts +++ b/packages/frontend/src/use/use-note-capture.ts @@ -86,7 +86,7 @@ export function useNoteCapture(props: { function capture(withHandler = false): void { if (connection) { // TODO: このノートがストリーミング経由で流れてきた場合のみ sr する - connection.send(document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id }); + connection.send(window.document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id }); if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id }); if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated); } diff --git a/packages/frontend/src/use/use-tooltip.ts b/packages/frontend/src/use/use-tooltip.ts index d9ddfc8b5d..af76a3a1e8 100644 --- a/packages/frontend/src/use/use-tooltip.ts +++ b/packages/frontend/src/use/use-tooltip.ts @@ -29,7 +29,7 @@ export function useTooltip( if (!isHovering) return; if (elRef.value == null) return; const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el; - if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため + if (!window.document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため const showing = ref(true); onShow(showing); @@ -38,7 +38,7 @@ export function useTooltip( }; autoHidingTimer = window.setInterval(() => { - if (elRef.value == null || !document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) { + if (elRef.value == null || !window.document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) { if (!isHovering) return; isHovering = false; window.clearTimeout(timeoutId); diff --git a/packages/frontend/src/utility/focus-trap.ts b/packages/frontend/src/utility/focus-trap.ts index fd17fa38a0..13d3bc56d2 100644 --- a/packages/frontend/src/utility/focus-trap.ts +++ b/packages/frontend/src/utility/focus-trap.ts @@ -50,7 +50,7 @@ function releaseFocusTrap(el: HTMLElement): void { const highestZIndexElement = getHighestZIndexElement(); - if (el.parentElement != null && el !== document.body) { + if (el.parentElement != null && el !== window.document.body) { el.parentElement.childNodes.forEach((siblingNode) => { const siblingEl = getHTMLElementOrNull(siblingNode); if (!siblingEl) return; @@ -104,7 +104,7 @@ export function focusTrap(el: HTMLElement, hasInteractionWithOtherFocusTrappedEl el.inert = false; } - if (el.parentElement != null && el !== document.body) { + if (el.parentElement != null && el !== window.document.body) { el.parentElement.childNodes.forEach((siblingNode) => { const siblingEl = getHTMLElementOrNull(siblingNode); if (!siblingEl) return; diff --git a/packages/frontend/src/utility/focus.ts b/packages/frontend/src/utility/focus.ts index e3fd928d1d..cbbe8226d7 100644 --- a/packages/frontend/src/utility/focus.ts +++ b/packages/frontend/src/utility/focus.ts @@ -58,7 +58,7 @@ export const focusParent = (input: MaybeHTMLElement | null | undefined, self = f const focusOrScroll = (element: HTMLElement, scroll: boolean) => { if (scroll) { - const scrollContainer = getScrollContainer(element) ?? document.documentElement; + const scrollContainer = getScrollContainer(element) ?? window.document.documentElement; const scrollContainerTop = getScrollPosition(scrollContainer); const stickyTop = getStickyTop(element, scrollContainer); const stickyBottom = getStickyBottom(element, scrollContainer); @@ -74,7 +74,7 @@ const focusOrScroll = (element: HTMLElement, scroll: boolean) => { scrollContainer.scrollTo({ top: scrollTo, behavior: 'instant' }); } - if (document.activeElement !== element) { + if (window.document.activeElement !== element) { element.focus({ preventScroll: true }); } }; diff --git a/packages/frontend/src/utility/fullscreen.ts b/packages/frontend/src/utility/fullscreen.ts index 7a0a018ef3..6702393cf1 100644 --- a/packages/frontend/src/utility/fullscreen.ts +++ b/packages/frontend/src/utility/fullscreen.ts @@ -35,8 +35,8 @@ export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscr export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (document.exitFullscreen != null) { - document.exitFullscreen(); + if (window.document.exitFullscreen != null) { + window.document.exitFullscreen(); return; } if (videoEl.webkitExitFullscreen != null) { diff --git a/packages/frontend/src/utility/hotkey.ts b/packages/frontend/src/utility/hotkey.ts index fe62139a74..81fc28d7c8 100644 --- a/packages/frontend/src/utility/hotkey.ts +++ b/packages/frontend/src/utility/hotkey.ts @@ -54,9 +54,9 @@ export const makeHotkey = (keymap: Keymap) => { const actions = parseKeymap(keymap); return (ev: KeyboardEvent) => { if ('pswp' in window && window.pswp != null) return; - if (document.activeElement != null) { - if (IGNORE_ELEMENTS.includes(document.activeElement.tagName.toLowerCase())) return; - if (getHTMLElementOrNull(document.activeElement)?.isContentEditable) return; + if (window.document.activeElement != null) { + if (IGNORE_ELEMENTS.includes(window.document.activeElement.tagName.toLowerCase())) return; + if (getHTMLElementOrNull(window.document.activeElement)?.isContentEditable) return; } for (const action of actions) { if (matchPatterns(ev, action)) { diff --git a/packages/frontend/src/utility/init-chart.ts b/packages/frontend/src/utility/init-chart.ts index 9775b9fec4..260899c1d7 100644 --- a/packages/frontend/src/utility/init-chart.ts +++ b/packages/frontend/src/utility/init-chart.ts @@ -50,7 +50,7 @@ export function initChart() { ); // フォントカラー - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); + Chart.defaults.color = getComputedStyle(window.document.documentElement).getPropertyValue('--MI_THEME-fg'); Chart.defaults.borderColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; diff --git a/packages/frontend/src/utility/physics.ts b/packages/frontend/src/utility/physics.ts index 8a4e9319b3..5de34fd094 100644 --- a/packages/frontend/src/utility/physics.ts +++ b/packages/frontend/src/utility/physics.ts @@ -28,7 +28,7 @@ export function physics(container: HTMLElement) { // create renderer const render = Matter.Render.create({ engine: engine, - //element: document.getElementById('debug'), + //element: window.document.getElementById('debug'), options: { width: containerWidth, height: containerHeight, diff --git a/packages/frontend/src/utility/select-file.ts b/packages/frontend/src/utility/select-file.ts index 1bee4986f6..b9b3687483 100644 --- a/packages/frontend/src/utility/select-file.ts +++ b/packages/frontend/src/utility/select-file.ts @@ -25,7 +25,7 @@ export function chooseFileFromPc( const nameConverter = options?.nameConverter ?? (() => undefined); return new Promise((res, rej) => { - const input = document.createElement('input'); + const input = window.document.createElement('input'); input.type = 'file'; input.multiple = multiple; input.onchange = () => { diff --git a/packages/frontend/src/utility/snowfall-effect.ts b/packages/frontend/src/utility/snowfall-effect.ts index d88bdb6660..5c86969876 100644 --- a/packages/frontend/src/utility/snowfall-effect.ts +++ b/packages/frontend/src/utility/snowfall-effect.ts @@ -156,7 +156,7 @@ export class SnowfallEffect { easing: 0.0005, }; /** - * @throws {Error} - Thrown when it fails to get WebGL context for the canvas + * @throws {Error} - Thrown when it fails to get WebGL context for the canvas */ constructor(options: { sakura?: boolean; @@ -172,7 +172,7 @@ export class SnowfallEffect { const gl = canvas.getContext('webgl2', { antialias: true }); if (gl == null) throw new Error('Failed to get WebGL context'); - document.body.append(canvas); + window.document.body.append(canvas); this.canvas = canvas; this.gl = gl; @@ -190,7 +190,7 @@ export class SnowfallEffect { } private initCanvas(): HTMLCanvasElement { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); Object.assign(canvas.style, { position: 'fixed', diff --git a/packages/frontend/src/utility/sound.ts b/packages/frontend/src/utility/sound.ts index 120f480b63..796af0e5ca 100644 --- a/packages/frontend/src/utility/sound.ts +++ b/packages/frontend/src/utility/sound.ts @@ -226,7 +226,7 @@ export function createSourceNode(buffer: AudioBuffer, opts: { * @param file ファイルのURL(ドライブIDではない) */ export async function getSoundDuration(file: string): Promise { - const audioEl = document.createElement('audio'); + const audioEl = window.document.createElement('audio'); audioEl.src = file; return new Promise((resolve) => { const si = setInterval(() => { @@ -249,7 +249,7 @@ export function isMute(): boolean { } // noinspection RedundantIfStatementJS - if (prefer.s['sound.useSoundOnlyWhenActive'] && document.visibilityState === 'hidden') { + if (prefer.s['sound.useSoundOnlyWhenActive'] && window.document.visibilityState === 'hidden') { // ブラウザがアクティブな時のみサウンドを出力する return true; } diff --git a/packages/frontend/src/utility/sticky-sidebar.ts b/packages/frontend/src/utility/sticky-sidebar.ts index 50f1e6ecc8..867c9b8324 100644 --- a/packages/frontend/src/utility/sticky-sidebar.ts +++ b/packages/frontend/src/utility/sticky-sidebar.ts @@ -18,7 +18,7 @@ export class StickySidebar { this.container = container; this.el = this.container.children[0] as HTMLElement; this.el.style.position = 'sticky'; - this.spacer = document.createElement('div'); + this.spacer = window.document.createElement('div'); this.container.prepend(this.spacer); this.marginTop = marginTop; this.offsetTop = this.container.getBoundingClientRect().top; diff --git a/packages/frontend/src/utility/upload/isWebpSupported.ts b/packages/frontend/src/utility/upload/isWebpSupported.ts index 2511236ecc..affd81fd57 100644 --- a/packages/frontend/src/utility/upload/isWebpSupported.ts +++ b/packages/frontend/src/utility/upload/isWebpSupported.ts @@ -6,7 +6,7 @@ let isWebpSupportedCache: boolean | undefined; export function isWebpSupported() { if (isWebpSupportedCache === undefined) { - const canvas = document.createElement('canvas'); + const canvas = window.document.createElement('canvas'); canvas.width = 1; canvas.height = 1; isWebpSupportedCache = canvas.toDataURL('image/webp').startsWith('data:image/webp'); diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index a7213f4c21..132eb0a629 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -77,7 +77,7 @@ const fetchEndpoint = computed(() => { const intervalClear = ref<(() => void) | undefined>(); const tick = () => { - if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return; + if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return; window.fetch(fetchEndpoint.value, {}) .then(res => res.json()) diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index 13b76533d7..b5be4d35c2 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -108,7 +108,7 @@ const intervalClear = ref<(() => void) | undefined>(); const key = ref(0); const tick = () => { - if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return; + if (window.document.visibilityState === 'hidden' && rawItems.value.length !== 0) return; window.fetch(fetchEndpoint.value, {}) .then(res => res.json()) diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts index 32a5a1c558..34e7e64313 100644 --- a/packages/frontend/test/scroll.test.ts +++ b/packages/frontend/test/scroll.test.ts @@ -12,10 +12,10 @@ describe('Scroll', () => { /* 動作しない(happy-domのバグ?) test('Initial onScrollTop callback for connected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = window.document.createElement('div'); assert.strictEqual(div.scrollTop, 0); - document.body.append(div); + window.document.body.append(div); let called = false; onScrollTop(div as any as HTMLElement, () => called = true); @@ -26,7 +26,7 @@ describe('Scroll', () => { test('No onScrollTop callback for disconnected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = window.document.createElement('div'); assert.strictEqual(div.scrollTop, 0); let called = false; @@ -40,10 +40,10 @@ describe('Scroll', () => { /* 動作しない(happy-domのバグ?) test('Initial onScrollBottom callback for connected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = window.document.createElement('div'); assert.strictEqual(div.scrollTop, 0); - document.body.append(div); + window.document.body.append(div); let called = false; onScrollBottom(div as any as HTMLElement, () => called = true); @@ -54,7 +54,7 @@ describe('Scroll', () => { test('No onScrollBottom callback for disconnected elements', () => { const { document } = new Window(); - const div = document.createElement('div'); + const div = window.document.createElement('div'); assert.strictEqual(div.scrollTop, 0); let called = false; -- cgit v1.2.3-freya From 0471e457fe0a62b778219b132b44d71e446a2fe4 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Sun, 23 Mar 2025 21:23:52 +0900 Subject: fix(frontend): fix broken styles --- packages/frontend-shared/js/const.ts | 6 ------ packages/frontend/src/components/global/MkStickyContainer.vue | 11 +++++------ packages/frontend/src/di.ts | 2 ++ packages/frontend/src/style.scss | 4 ++-- packages/frontend/src/ui/universal.vue | 4 ---- 5 files changed, 9 insertions(+), 18 deletions(-) (limited to 'packages/frontend/src/ui') diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index 9e20479e26..9b821e650a 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -109,12 +109,6 @@ export const ROLE_POLICIES = [ 'canImportUserLists', ] as const; -// なんか動かない -//export const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP'); -//export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM'); -export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP'; -export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM'; - export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg'; export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg'; export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg'; diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue index 7dda4b2f8a..05245716c2 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.vue +++ b/packages/frontend/src/components/global/MkStickyContainer.vue @@ -23,8 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -47,6 +50,10 @@ withDefaults(defineProps<{ min-height: calc(var(--fukidashi-radius) * 2); padding-top: calc(var(--fukidashi-radius) * .13); + &.accented { + --fukidashi-bg: var(--MI_THEME-accent); + } + &.shadow { filter: drop-shadow(0 4px 32px var(--MI_THEME-shadow)); } @@ -77,7 +84,7 @@ withDefaults(defineProps<{ .content { position: relative; - padding: 8px 12px; + padding: 10px 14px; } .tail { diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index ae15776041..4a1100c324 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -227,7 +227,6 @@ defineExpose({ .container { position: relative; width: 100%; - margin-top: 4px; } .medias { diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index a84bd9b256..f2f36308ca 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.center]: align === 'center', [$style.big]: big, [$style.asDrawer]: asDrawer, + [$style.widthSpecified]: width != null, }" @focusin.passive.stop="() => {}" > @@ -29,15 +30,19 @@ SPDX-License-Identifier: AGPL-3.0-only > + {{ i18n.ts.none }} @@ -438,6 +473,12 @@ onBeforeUnmount(() => { } } + &:not(.widthSpecified) { + > .menu { + max-width: 400px; + } + } + &.big:not(.asDrawer) { > .menu { min-width: 230px; @@ -607,10 +648,19 @@ onBeforeUnmount(() => { .item_content_text { max-width: calc(100vw - 4rem); +} + +.item_content_text_title { text-overflow: ellipsis; overflow: hidden; } +.item_content_text_caption { + text-wrap: auto; + font-size: 85%; + opacity: 0.7; +} + .switchButton { margin-left: -2px; --height: 1.35em; diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue index ab8bda403b..a729619180 100644 --- a/packages/frontend/src/components/MkPagination.vue +++ b/packages/frontend/src/components/MkPagination.vue @@ -24,16 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- +
+
+ {{ i18n.ts.loadMore }}
-
- +
+ {{ i18n.ts.loadMore }} @@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only 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'; +import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js'; import type { ComputedRef } from 'vue'; import type { MisskeyEntity } from '@/types/date-separated-list.js'; import { misskeyApi } from '@/utility/misskey-api.js'; @@ -74,8 +74,6 @@ export type Paging reversed?: boolean; offsetMode?: boolean; - - pageEl?: HTMLElement; }; type MisskeyEntityMap = Map; @@ -141,8 +139,7 @@ const { enableInfiniteScroll, } = prefer.r; -const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value); -const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : window.document.body); +const scrollableElement = computed(() => rootEl.value ? getScrollContainer(rootEl.value) : window.document.body); const visibility = useDocumentVisibility(); @@ -173,13 +170,13 @@ watch(rootEl, () => { }); }); -watch([backed, contentEl], () => { +watch([backed, rootEl], () => { if (!backed.value) { - if (!contentEl.value) return; + if (!rootEl.value) return; scrollRemove.value = props.pagination.reversed - ? onScrollBottom(contentEl.value, executeQueue, TOLERANCE) - : onScrollTop(contentEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE); + ? onScrollBottom(rootEl.value, executeQueue, TOLERANCE) + : onScrollTop(rootEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE); } else { if (scrollRemove.value) scrollRemove.value(); scrollRemove.value = null; @@ -349,7 +346,7 @@ const appearFetchMoreAhead = async (): Promise => { fetchMoreAppearTimeout(); }; -const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE); +const isHead = (): boolean => isBackTop.value || (props.pagination.reversed ? isTailVisible : isHeadVisible)(rootEl.value!, TOLERANCE); watch(visibility, () => { if (visibility.value === 'hidden') { @@ -364,7 +361,7 @@ watch(visibility, () => { timerForSetPause = null; } else { isPausingUpdate = false; - if (isTop()) { + if (isHead()) { executeQueue(); } } @@ -376,16 +373,18 @@ watch(visibility, () => { * ストリーミングから降ってきたアイテムはこれで追加する * @param item アイテム */ -const prepend = (item: MisskeyEntity): void => { +function prepend(item: MisskeyEntity): void { if (items.value.size === 0) { items.value.set(item.id, item); fetching.value = false; return; } - if (isTop() && !isPausingUpdate) unshiftItems([item]); + console.log(isHead(), isPausingUpdate); + + if (isHead() && !isPausingUpdate) unshiftItems([item]); else prependQueue(item); -}; +} /** * 新着アイテムをitemsの先頭に追加し、displayLimitを適用する @@ -447,7 +446,7 @@ onDeactivated(() => { }); function toBottom() { - scrollToBottom(contentEl.value!); + scrollToBottom(rootEl.value!); } onBeforeMount(() => { diff --git a/packages/frontend/src/components/MkPolkadots.vue b/packages/frontend/src/components/MkPolkadots.vue new file mode 100644 index 0000000000..285c4d0b79 --- /dev/null +++ b/packages/frontend/src/components/MkPolkadots.vue @@ -0,0 +1,40 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index f20aee0ce3..20dab6f028 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -246,6 +246,7 @@ onUnmounted(() => { box-shadow: 0 0 0 1px var(--MI_THEME-divider); border-radius: 8px; overflow: clip; + text-align: left; &:hover { text-decoration: none; diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 099339fbee..f6d6bbf0fb 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -28,7 +28,7 @@ export type Keys = ( 'theme' | 'themeId' | 'customCss' | - 'message_drafts' | + 'chatMessageDrafts' | 'scratchpad' | 'debug' | 'preferences' | diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index d478ece641..894df83721 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -4,6 +4,7 @@ */ import { computed, reactive } from 'vue'; +import { ui } from '@@/js/config.js'; import { clearCache } from './utility/clear-cache.js'; import { $i } from '@/i.js'; import { miLocalStorage } from '@/local-storage.js'; @@ -11,7 +12,6 @@ import { openInstanceMenu, openToolsMenu } from '@/ui/_common_/common.js'; import { lookup } from '@/utility/lookup.js'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; -import { ui } from '@@/js/config.js'; import { unisonReload } from '@/utility/unison-reload.js'; export const navbarItemDef = reactive({ @@ -110,6 +110,12 @@ export const navbarItemDef = reactive({ icon: 'ti ti-device-tv', to: '/channels', }, + chat: { + title: i18n.ts.chat, + icon: 'ti ti-message', + to: '/chat', + indicated: computed(() => $i != null && $i.hasUnreadChatMessages), + }, achievements: { title: i18n.ts.achievements, icon: 'ti ti-medal', diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue index 4e9f4edb70..d1e823215a 100644 --- a/packages/frontend/src/pages/admin/roles.editor.vue +++ b/packages/frontend/src/pages/admin/roles.editor.vue @@ -160,6 +160,26 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + +
+ + + + + + + + + +
+
+