wip
This commit is contained in:
		
							parent
							
								
									296b8ce24b
								
							
						
					
					
						commit
						dc76f14457
					
				|  | @ -3,17 +3,6 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  * BOOT LOADER | ||||
|  * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。 | ||||
|  * - 翻訳ファイルをフェッチする。 | ||||
|  * - バージョンに基づいて適切なメインスクリプトを読み込む。 | ||||
|  * - キャッシュされたコンパイル済みテーマを適用する。 | ||||
|  * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。 | ||||
|  * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。 | ||||
|  * 注: webpackは介さないため、このファイルではrequireやimportは使えません。 | ||||
|  */ | ||||
| 
 | ||||
| 'use strict'; | ||||
| 
 | ||||
| // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
 | ||||
|  | @ -320,6 +309,6 @@ | |||
| 			#errorInfo { | ||||
| 				width: 50%; | ||||
| 			} | ||||
| 		}`)
 | ||||
| 		}`);
 | ||||
| 	} | ||||
| })(); | ||||
|  |  | |||
|  | @ -0,0 +1,102 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import tinycolor from 'tinycolor2'; | ||||
| import lightTheme from '@@/themes/_light.json5'; | ||||
| import darkTheme from '@@/themes/_dark.json5'; | ||||
| import type { BundledTheme } from 'shiki/themes'; | ||||
| 
 | ||||
| export type Theme = { | ||||
| 	id: string; | ||||
| 	name: string; | ||||
| 	author: string; | ||||
| 	desc?: string; | ||||
| 	base?: 'dark' | 'light'; | ||||
| 	props: Record<string, string>; | ||||
| 	codeHighlighter?: { | ||||
| 		base: BundledTheme; | ||||
| 		overrides?: Record<string, any>; | ||||
| 	} | { | ||||
| 		base: '_none_'; | ||||
| 		overrides: Record<string, any>; | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| let timeout: number | null = null; | ||||
| 
 | ||||
| export function applyTheme(theme: Theme, persist = true) { | ||||
| 	if (timeout) window.clearTimeout(timeout); | ||||
| 
 | ||||
| 	document.documentElement.classList.add('_themeChanging_'); | ||||
| 
 | ||||
| 	timeout = window.setTimeout(() => { | ||||
| 		document.documentElement.classList.remove('_themeChanging_'); | ||||
| 	}, 1000); | ||||
| 
 | ||||
| 	const colorScheme = theme.base === 'dark' ? 'dark' : 'light'; | ||||
| 
 | ||||
| 	// Deep copy
 | ||||
| 	const _theme = JSON.parse(JSON.stringify(theme)); | ||||
| 
 | ||||
| 	if (_theme.base) { | ||||
| 		const base = [lightTheme, darkTheme].find(x => x.id === _theme.base); | ||||
| 		if (base) _theme.props = Object.assign({}, base.props, _theme.props); | ||||
| 	} | ||||
| 
 | ||||
| 	const props = compile(_theme); | ||||
| 
 | ||||
| 	for (const tag of document.head.children) { | ||||
| 		if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') { | ||||
| 			tag.setAttribute('content', props['htmlThemeColor']); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	for (const [k, v] of Object.entries(props)) { | ||||
| 		document.documentElement.style.setProperty(`--${k}`, v.toString()); | ||||
| 	} | ||||
| 
 | ||||
| 	document.documentElement.style.setProperty('color-scheme', colorScheme); | ||||
| } | ||||
| 
 | ||||
| function compile(theme: Theme): Record<string, string> { | ||||
| 	function getColor(val: string): tinycolor.Instance { | ||||
| 		if (val[0] === '@') { // ref (prop)
 | ||||
| 			return getColor(theme.props[val.substring(1)]); | ||||
| 		} else if (val[0] === '$') { // ref (const)
 | ||||
| 			return getColor(theme.props[val]); | ||||
| 		} else if (val[0] === ':') { // func
 | ||||
| 			const parts = val.split('<'); | ||||
| 			const func = parts.shift().substring(1); | ||||
| 			const arg = parseFloat(parts.shift()); | ||||
| 			const color = getColor(parts.join('<')); | ||||
| 
 | ||||
| 			switch (func) { | ||||
| 				case 'darken': return color.darken(arg); | ||||
| 				case 'lighten': return color.lighten(arg); | ||||
| 				case 'alpha': return color.setAlpha(arg); | ||||
| 				case 'hue': return color.spin(arg); | ||||
| 				case 'saturate': return color.saturate(arg); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		// other case
 | ||||
| 		return tinycolor(val); | ||||
| 	} | ||||
| 
 | ||||
| 	const props = {}; | ||||
| 
 | ||||
| 	for (const [k, v] of Object.entries(theme.props)) { | ||||
| 		if (k.startsWith('$')) continue; // ignore const
 | ||||
| 
 | ||||
| 		props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v)); | ||||
| 	} | ||||
| 
 | ||||
| 	return props; | ||||
| } | ||||
| 
 | ||||
| function genValue(c: tinycolor.Instance): string { | ||||
| 	return c.toRgbString(); | ||||
| } | ||||
|  | @ -23,7 +23,8 @@ | |||
| 		"useDefineForClassFields": true, | ||||
| 		"baseUrl": ".", | ||||
| 		"paths": { | ||||
| 			"@/*": ["./src/*"] | ||||
| 			"@/*": ["./src/*"], | ||||
| 			"@@/*": ["../frontend-shared/*"] | ||||
| 		}, | ||||
| 		"typeRoots": [ | ||||
| 			"./@types", | ||||
|  |  | |||
|  | @ -72,6 +72,7 @@ export function getConfig(): UserConfig { | |||
| 			extensions, | ||||
| 			alias: { | ||||
| 				'@/': __dirname + '/src/', | ||||
| 				'@@/': __dirname + '/../frontend-shared/', | ||||
| 				'/client-assets/': __dirname + '/assets/', | ||||
| 				'/static-assets/': __dirname + '/../backend/assets/' | ||||
| 			}, | ||||
|  |  | |||
|  | @ -79,6 +79,8 @@ import tinycolor from 'tinycolor2'; | |||
| import { v4 as uuid } from 'uuid'; | ||||
| import JSON5 from 'json5'; | ||||
| 
 | ||||
| import lightTheme from '@@/themes/_light.json5'; | ||||
| import darkTheme from '@@/themes/_dark.json5'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
| import MkCodeEditor from '@/components/MkCodeEditor.vue'; | ||||
| import MkTextarea from '@/components/MkTextarea.vue'; | ||||
|  | @ -86,8 +88,6 @@ import MkFolder from '@/components/MkFolder.vue'; | |||
| 
 | ||||
| import { $i } from '@/account.js'; | ||||
| import { Theme, applyTheme } from '@/scripts/theme.js'; | ||||
| import lightTheme from '@/themes/_light.json5'; | ||||
| import darkTheme from '@/themes/_dark.json5'; | ||||
| import { host } from '@/config.js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { ColdDeviceStorage, defaultStore } from '@/store.js'; | ||||
|  |  | |||
|  | @ -7,13 +7,13 @@ import { getHighlighterCore, loadWasm } from 'shiki/core'; | |||
| import darkPlus from 'shiki/themes/dark-plus.mjs'; | ||||
| import { bundledThemesInfo } from 'shiki/themes'; | ||||
| import { bundledLanguagesInfo } from 'shiki/langs'; | ||||
| import lightTheme from '@@/themes/_light.json5'; | ||||
| import darkTheme from '@@/themes/_dark.json5'; | ||||
| import { unique } from './array.js'; | ||||
| import { deepClone } from './clone.js'; | ||||
| import { deepMerge } from './merge.js'; | ||||
| import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core'; | ||||
| import { ColdDeviceStorage } from '@/store.js'; | ||||
| import lightTheme from '@/themes/_light.json5'; | ||||
| import darkTheme from '@/themes/_dark.json5'; | ||||
| 
 | ||||
| let _highlighter: HighlighterCore | null = null; | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,11 +5,11 @@ | |||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| import tinycolor from 'tinycolor2'; | ||||
| import lightTheme from '@@/themes/_light.json5'; | ||||
| import darkTheme from '@@/themes/_dark.json5'; | ||||
| import { deepClone } from './clone.js'; | ||||
| import type { BundledTheme } from 'shiki/themes'; | ||||
| import { globalEvents } from '@/events.js'; | ||||
| import lightTheme from '@/themes/_light.json5'; | ||||
| import darkTheme from '@/themes/_dark.json5'; | ||||
| import { miLocalStorage } from '@/local-storage.js'; | ||||
| 
 | ||||
| export type Theme = { | ||||
|  |  | |||
|  | @ -458,10 +458,10 @@ export const defaultStore = markRaw(new Storage('base', { | |||
| 		where: 'device', | ||||
| 		default: false, | ||||
| 	}, | ||||
|   contextMenu: { | ||||
| 	contextMenu: { | ||||
| 		where: 'device', | ||||
| 		default: 'app' as 'app' | 'appWithShift' | 'native', | ||||
|   }, | ||||
| 	}, | ||||
| 
 | ||||
| 	sound_masterVolume: { | ||||
| 		where: 'device', | ||||
|  | @ -520,8 +520,8 @@ interface Watcher { | |||
| /** | ||||
|  * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ) | ||||
|  */ | ||||
| import lightTheme from '@/themes/l-light.json5'; | ||||
| import darkTheme from '@/themes/d-green-lime.json5'; | ||||
| import lightTheme from '@@/themes/l-light.json5'; | ||||
| import darkTheme from '@@/themes/d-green-lime.json5'; | ||||
| 
 | ||||
| export class ColdDeviceStorage { | ||||
| 	public static default = { | ||||
|  | @ -558,7 +558,7 @@ export class ColdDeviceStorage { | |||
| 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void { | ||||
| 		// 呼び出し側のバグ等で undefined が来ることがある
 | ||||
| 		// undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
 | ||||
| 		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | ||||
| 		  | ||||
| 		if (value === undefined) { | ||||
| 			console.error(`attempt to store undefined value for key '${key}'`); | ||||
| 			return; | ||||
|  |  | |||
|  | @ -23,7 +23,8 @@ | |||
| 		"useDefineForClassFields": true, | ||||
| 		"baseUrl": ".", | ||||
| 		"paths": { | ||||
| 			"@/*": ["./src/*"] | ||||
| 			"@/*": ["./src/*"], | ||||
| 			"@@/*": ["../frontend-shared/*"] | ||||
| 		}, | ||||
| 		"typeRoots": [ | ||||
| 			"./@types", | ||||
|  |  | |||
|  | @ -87,6 +87,7 @@ export function getConfig(): UserConfig { | |||
| 			extensions, | ||||
| 			alias: { | ||||
| 				'@/': __dirname + '/src/', | ||||
| 				'@@/': __dirname + '/../frontend-shared/', | ||||
| 				'/client-assets/': __dirname + '/assets/', | ||||
| 				'/static-assets/': __dirname + '/../backend/assets/', | ||||
| 				'/fluent-emojis/': __dirname + '/../../fluent-emojis/dist/', | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue