Compare commits
25 Commits
room
...
l10n_devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ede30b94fa | ||
|
|
a7ea5757e0 | ||
|
|
e0d3281aee | ||
|
|
be20c18830 | ||
|
|
3985e109fc | ||
|
|
543ee41714 | ||
|
|
d32b163290 | ||
|
|
0b47ffdeca | ||
|
|
cd3ceeeca9 | ||
|
|
595df11cef | ||
|
|
4f984fa1a8 | ||
|
|
4f5b3a8f33 | ||
|
|
c952d16c76 | ||
|
|
1d8d5327cd | ||
|
|
49c205b3fa | ||
|
|
cbc1fabf5f | ||
|
|
b0175c49e7 | ||
|
|
983841c3fe | ||
|
|
93f869fb59 | ||
|
|
d98c6fcf72 | ||
|
|
599b1592ef | ||
|
|
003afb6177 | ||
|
|
e74bd3b7b7 | ||
|
|
7f745f7767 | ||
|
|
ca6e99e940 |
3
.gitignore
vendored
@@ -81,6 +81,3 @@ vite.config.local-dev.ts.timestamp-*
|
||||
|
||||
# VSCode addon
|
||||
.favorites.json
|
||||
|
||||
# Affinity
|
||||
*.af~lock~
|
||||
|
||||
16
CHANGELOG.md
@@ -1,15 +1,3 @@
|
||||
## Unreleased
|
||||
|
||||
### General
|
||||
-
|
||||
|
||||
### Client
|
||||
-
|
||||
|
||||
### Server
|
||||
-
|
||||
|
||||
|
||||
## 2026.5.1
|
||||
|
||||
### General
|
||||
@@ -19,10 +7,6 @@
|
||||
### Client
|
||||
- Enhance: ノートの詳細表示での公開範囲の表示を改善
|
||||
(Cherry-picked from https://github.com/kokonect-link/cherrypick/commit/ecc75563f4e428b66adccc379bf317b5b21ed8e6)
|
||||
- Enhance: テーマのプレビュー時、リロードせずにもとのテーマに戻せるように
|
||||
- Fix: テーマエディター使用時に、最初の変更のみ適用される問題を修正
|
||||
- Fix: テーマのプレビュー時、既存のテーマとIDが被っている場合にプレビューできない問題を修正
|
||||
- Fix: テーマのインストールエラーの表示を改善
|
||||
- Fix: ロール設定画面でロールをアサイン/アサイン解除した際、リロードしなくても画面に反映されるよう修正
|
||||
|
||||
### Server
|
||||
|
||||
@@ -1012,6 +1012,7 @@ inMinutes: "د"
|
||||
inDays: "ي"
|
||||
widgets: "التطبيقات المُصغّرة"
|
||||
presets: "إعدادات مسبقة"
|
||||
previewingThemeRestore: "استرجاع"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "اسم الملف"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "Predefinit"
|
||||
zeroPadding: "Sense omplir"
|
||||
nothingToConfigure: "No hi ha res a configurar"
|
||||
viewRenotedChannel: "Mirar el canal d'impulsos "
|
||||
previewingThemeRestore: "Restaurar"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Títol de l'arxiu"
|
||||
|
||||
@@ -1134,6 +1134,7 @@ inMinutes: "Minut"
|
||||
inDays: "Dnů"
|
||||
widgets: "Widgety"
|
||||
presets: "Předvolba"
|
||||
previewingThemeRestore: "Obnovit"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Název souboru"
|
||||
|
||||
@@ -1408,6 +1408,7 @@ frame: "Rahmen"
|
||||
presets: "Vorlage"
|
||||
zeroPadding: "Nullauffüllung"
|
||||
nothingToConfigure: "Es sind keine Einstellungen verfügbar"
|
||||
previewingThemeRestore: "Wiederherstellen"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Dateibeschriftung"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "Preset"
|
||||
zeroPadding: "Zero padding"
|
||||
nothingToConfigure: "No configurable options available"
|
||||
viewRenotedChannel: "Show renoted channel"
|
||||
previewingThemeRestore: "Restore"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "File caption"
|
||||
|
||||
@@ -1238,7 +1238,7 @@ sourceCodeIsNotYetProvided: "El código fuente aún no está disponible. Contact
|
||||
repositoryUrl: "URL del repositorio"
|
||||
repositoryUrlDescription: "Si estás usando Misskey tal cual (sin cambios en el código fuente), entra en https://github.com/misskey-dev/misskey"
|
||||
repositoryUrlOrTarballRequired: "Si no has publicado un repositorio aún, deberás publicar un tarball en su lugar. Mira el archivo .config/example.yml para más información."
|
||||
feedback: "Comentarios"
|
||||
feedback: "Enviar sugerencias (Feedback)"
|
||||
feedbackUrl: "URL de comentarios"
|
||||
impressum: "Impressum"
|
||||
impressumUrl: "Impressum URL"
|
||||
@@ -1409,6 +1409,7 @@ presets: "Predefinido"
|
||||
zeroPadding: "Relleno cero"
|
||||
nothingToConfigure: "No hay nada que configurar"
|
||||
viewRenotedChannel: "Ver el canal al que te has suscrito"
|
||||
previewingThemeRestore: "Regresar"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Título del archivo"
|
||||
@@ -2098,6 +2099,7 @@ _role:
|
||||
canSearchNotes: "Uso de la búsqueda de notas"
|
||||
canSearchUsers: "Uso de la búsqueda de usuarios"
|
||||
canUseTranslator: "Uso de traductor"
|
||||
canCreateChannel: "Puede crear canales"
|
||||
avatarDecorationLimit: "Número máximo de decoraciones de avatar"
|
||||
canImportAntennas: "Permitir la importación de antenas"
|
||||
canImportBlocking: "Permitir la importación de bloqueos"
|
||||
@@ -2215,7 +2217,7 @@ _registry:
|
||||
domain: "Dominio"
|
||||
createKey: "Crear una clave"
|
||||
_aboutMisskey:
|
||||
about: "Misskey es un software de código abierto, desarrollado por syuilo desde el 2014"
|
||||
about: "Misskey es un software de código abierto, desarrollado por syuilo desde 2014"
|
||||
contributors: "Principales colaboradores"
|
||||
allContributors: "Todos los colaboradores"
|
||||
source: "Código fuente"
|
||||
|
||||
@@ -1285,6 +1285,7 @@ inMinutes: "min"
|
||||
inDays: "j"
|
||||
widgets: "Widgets"
|
||||
presets: "Préréglage"
|
||||
previewingThemeRestore: "Restaurer"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Nom du fichier"
|
||||
|
||||
@@ -1291,6 +1291,7 @@ inMinutes: "menit"
|
||||
inDays: "hari"
|
||||
widgets: "Widget"
|
||||
presets: "Prasetel"
|
||||
previewingThemeRestore: "Kembalikan"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Nama berkas"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "Preimpostato"
|
||||
zeroPadding: "Al vivo"
|
||||
nothingToConfigure: "Niente da configurare"
|
||||
viewRenotedChannel: "Visualizza il canale del Rinota"
|
||||
previewingThemeRestore: "Ripristina"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Didascalia dell'immagine"
|
||||
|
||||
@@ -1409,8 +1409,6 @@ presets: "プリセット"
|
||||
zeroPadding: "ゼロ埋め"
|
||||
nothingToConfigure: "設定項目はありません"
|
||||
viewRenotedChannel: "リノート先のチャンネルを見る"
|
||||
previewingTheme: "テーマのプレビュー中"
|
||||
previewingThemeRestore: "元に戻す"
|
||||
|
||||
_imageEditing:
|
||||
_vars:
|
||||
@@ -3556,17 +3554,3 @@ _qr:
|
||||
scanFile: "端末の画像をスキャン"
|
||||
raw: "テキスト"
|
||||
mfm: "MFM"
|
||||
|
||||
_room:
|
||||
snapToGrid: "グリッドにスナップ"
|
||||
gridScale: "グリッドサイズ"
|
||||
thereAreUnsavedChanges: "未保存の変更があります"
|
||||
revertAllChangesConfirmation: "全ての変更を取り消し、部屋を最後に保存した状態まで戻しますか?"
|
||||
graphicsQuality: "グラフィックの品質"
|
||||
frameRate: "フレームレート"
|
||||
resolution: "解像度"
|
||||
yourDeviceNotSupported_title: "お使いのデバイスはMisskeyRoomをサポートしていません。"
|
||||
yourDeviceNotSupported_description: "MisskeyRoomを動作させるには、WebGPUをサポートするデバイスが必要です。"
|
||||
failedToInitialize: "初期化に失敗しました"
|
||||
crushed_description: "バグ、またはデバイスのリソース不足の可能性が考えられます。"
|
||||
antialiasing: "アンチエイリアス"
|
||||
|
||||
@@ -1355,6 +1355,7 @@ widgets: "ウィジェット"
|
||||
deviceInfoDescription: "なんか技術的なことで分からんこと聞くときは、下の情報も一緒に書いてもらえると、こっちも分かりやすいし、はよ直ると思います。"
|
||||
youAreAdmin: "あんた、管理者やで"
|
||||
presets: "プリセット"
|
||||
previewingThemeRestore: "元に戻す"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "ファイル名"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "프리셋"
|
||||
zeroPadding: "0으로 채우기"
|
||||
nothingToConfigure: "설정 항목이 없습니다."
|
||||
viewRenotedChannel: "리노트된 채널 보기"
|
||||
previewingThemeRestore: "복구"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "파일 설명"
|
||||
|
||||
@@ -970,6 +970,7 @@ renotes: "Herdelen"
|
||||
followingOrFollower: "Gevolgd of volger"
|
||||
confirmShowRepliesAll: "Dit is een onomkeerbare operatie. Weet je zeker dat reacties op anderen van iedereen die je volgt, wil weergeven in je tijdlijn?"
|
||||
information: "Over"
|
||||
previewingThemeRestore: "Herstellen"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Bestandsnaam"
|
||||
|
||||
@@ -1044,6 +1044,7 @@ inMinutes: "minuta"
|
||||
inDays: "dzień"
|
||||
widgets: "Widżety"
|
||||
presets: "Konfiguracja"
|
||||
previewingThemeRestore: "Przywróć"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Nazwa pliku"
|
||||
|
||||
@@ -1391,6 +1391,7 @@ schedule: "Agendar"
|
||||
scheduled: "Agendado"
|
||||
widgets: "Widgets"
|
||||
presets: "Predefinições"
|
||||
previewingThemeRestore: "Restaurar"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Nome do Ficheiro"
|
||||
|
||||
@@ -1216,6 +1216,7 @@ surrender: "Anulează"
|
||||
copyPreferenceId: "Copiază ID-ul preferințelor"
|
||||
information: "Despre"
|
||||
presets: "Presetate"
|
||||
previewingThemeRestore: "Restabilește"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Nume fișier"
|
||||
|
||||
@@ -1350,6 +1350,7 @@ frame: "Рамки"
|
||||
presets: "Шаблоны"
|
||||
zeroPadding: "Без отступов"
|
||||
nothingToConfigure: "Нечего менять"
|
||||
previewingThemeRestore: "Восстановить"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Описание файла"
|
||||
|
||||
@@ -916,6 +916,7 @@ information: "Informácie"
|
||||
inMinutes: "min"
|
||||
inDays: "dní"
|
||||
widgets: "Widgety"
|
||||
previewingThemeRestore: "Obnoviť"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Názov súboru"
|
||||
|
||||
@@ -559,6 +559,7 @@ tryAgain: "Försök igen senare"
|
||||
signinWithPasskey: "Logga in med nyckel"
|
||||
unknownWebAuthnKey: "Okänd nyckel"
|
||||
information: "Om"
|
||||
previewingThemeRestore: "Återställ"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Filnamn"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "พรีเซ็ต"
|
||||
zeroPadding: "ห่างเป็น 0"
|
||||
nothingToConfigure: "ไม่มีอะไรให้ต้ังค่า"
|
||||
viewRenotedChannel: "แสดงช่องที่ถูกรีโน้ต"
|
||||
previewingThemeRestore: "เลิกทำ"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "แคปชั่นของไฟล์"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "Ön ayar"
|
||||
zeroPadding: "Sıfır doldurma"
|
||||
nothingToConfigure: "Ayarlar seçeneği bulunmamaktadır."
|
||||
viewRenotedChannel: "Show renoted channel"
|
||||
previewingThemeRestore: "Geri yükle"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "Dosya başlığı"
|
||||
|
||||
@@ -922,6 +922,7 @@ information: "Інформація"
|
||||
inMinutes: "х"
|
||||
inDays: "д"
|
||||
widgets: "Віджети"
|
||||
previewingThemeRestore: "Відновити"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Ім'я файлу"
|
||||
|
||||
@@ -1226,6 +1226,7 @@ inMinutes: "phút"
|
||||
inDays: "ngày"
|
||||
widgets: "Tiện ích"
|
||||
presets: "Mẫu thiết lập"
|
||||
previewingThemeRestore: "Khôi phục"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
filename: "Tên tập tin"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "预设值"
|
||||
zeroPadding: "填充 0"
|
||||
nothingToConfigure: "没有项目"
|
||||
viewRenotedChannel: "查看转帖所属频道"
|
||||
previewingThemeRestore: "还原"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "文件标题"
|
||||
|
||||
@@ -1409,6 +1409,7 @@ presets: "預設值"
|
||||
zeroPadding: "補零"
|
||||
nothingToConfigure: "無可設定的項目"
|
||||
viewRenotedChannel: "顯示轉發貼文者的頻道"
|
||||
previewingThemeRestore: "復原"
|
||||
_imageEditing:
|
||||
_vars:
|
||||
caption: "檔案標題"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2026.5.1",
|
||||
"version": "2026.5.1-beta.0",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -28,7 +28,7 @@ import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
|
||||
import { serverContext } from '@/server-context.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
import type { Theme } from '@@/js/theme.js';
|
||||
import type { Theme } from '@/theme.js';
|
||||
|
||||
console.log('Misskey Embed');
|
||||
|
||||
|
||||
@@ -5,10 +5,26 @@
|
||||
|
||||
// TODO: (可能な部分を)sharedに抽出して frontend と共通化
|
||||
|
||||
import tinycolor from 'tinycolor2';
|
||||
import lightTheme from '@@/themes/_light.json5';
|
||||
import darkTheme from '@@/themes/_dark.json5';
|
||||
import { compile } from '@@/js/theme.js';
|
||||
import type { Theme } from '@@/js/theme.js';
|
||||
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;
|
||||
|
||||
@@ -16,7 +32,7 @@ export function assertIsTheme(theme: Record<string, unknown>): theme is Theme {
|
||||
return typeof theme === 'object' && theme !== null && 'id' in theme && 'name' in theme && 'author' in theme && 'props' in theme;
|
||||
}
|
||||
|
||||
export function applyTheme(theme: Theme) {
|
||||
export function applyTheme(theme: Theme, persist = true) {
|
||||
if (timeout) window.clearTimeout(timeout);
|
||||
|
||||
window.document.documentElement.classList.add('_themeChanging_');
|
||||
@@ -52,3 +68,48 @@ export function applyTheme(theme: Theme) {
|
||||
|
||||
// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
|
||||
}
|
||||
|
||||
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 funcTxt = parts.shift();
|
||||
const argTxt = parts.shift();
|
||||
|
||||
if (funcTxt && argTxt) {
|
||||
const func = funcTxt.substring(1);
|
||||
const arg = parseFloat(argTxt);
|
||||
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();
|
||||
}
|
||||
|
||||
13
packages/frontend-shared/@types/theme.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
declare module '@@/themes/*.json5' {
|
||||
import { Theme } from '@@/js/theme.js';
|
||||
|
||||
const theme: Theme;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default theme;
|
||||
}
|
||||
109
packages/frontend-shared/build.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import fs from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname } from 'node:path';
|
||||
import * as esbuild from 'esbuild';
|
||||
import { build } from 'esbuild';
|
||||
import { execa } from 'execa';
|
||||
|
||||
const _filename = fileURLToPath(import.meta.url);
|
||||
const _dirname = dirname(_filename);
|
||||
const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
|
||||
|
||||
const entryPoints = fs.globSync('./js/**/**.{ts,tsx}');
|
||||
|
||||
/** @type {import('esbuild').BuildOptions} */
|
||||
const options = {
|
||||
entryPoints,
|
||||
minify: process.env.NODE_ENV === 'production',
|
||||
outdir: './js-built',
|
||||
target: 'es2022',
|
||||
platform: 'browser',
|
||||
format: 'esm',
|
||||
sourcemap: 'linked',
|
||||
};
|
||||
|
||||
const args = process.argv.slice(2).map(arg => arg.toLowerCase());
|
||||
|
||||
// js-built配下をすべて削除する
|
||||
if (!args.includes('--no-clean')) {
|
||||
fs.rmSync('./js-built', { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (args.includes('--watch')) {
|
||||
await watchSrc();
|
||||
} else {
|
||||
await buildSrc();
|
||||
}
|
||||
|
||||
async function buildSrc() {
|
||||
console.log(`[${_package.name}] start building...`);
|
||||
|
||||
await build(options)
|
||||
.then(() => {
|
||||
console.log(`[${_package.name}] build succeeded.`);
|
||||
})
|
||||
.catch((err) => {
|
||||
process.stderr.write(err.stderr);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
|
||||
} else {
|
||||
await buildDts();
|
||||
}
|
||||
|
||||
fs.copyFileSync('./js/emojilist.json', './js-built/emojilist.json');
|
||||
|
||||
console.log(`[${_package.name}] finish building.`);
|
||||
}
|
||||
|
||||
function buildDts() {
|
||||
return execa(
|
||||
'tsgo',
|
||||
[
|
||||
'--project', 'tsconfig.json',
|
||||
'--outDir', 'js-built',
|
||||
'--declaration', 'true',
|
||||
'--emitDeclarationOnly', 'true',
|
||||
],
|
||||
{
|
||||
stdout: process.stdout,
|
||||
stderr: process.stderr,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function watchSrc() {
|
||||
const plugins = [{
|
||||
name: 'gen-dts',
|
||||
setup(build) {
|
||||
build.onStart(() => {
|
||||
console.log(`[${_package.name}] detect changed...`);
|
||||
});
|
||||
build.onEnd(async result => {
|
||||
if (result.errors.length > 0) {
|
||||
console.error(`[${_package.name}] watch build failed:`, result);
|
||||
return;
|
||||
}
|
||||
await buildDts();
|
||||
});
|
||||
},
|
||||
}];
|
||||
|
||||
console.log(`[${_package.name}] start watching...`);
|
||||
|
||||
const context = await esbuild.context({ ...options, plugins });
|
||||
await context.watch();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
process.on('SIGHUP', resolve);
|
||||
process.on('SIGINT', resolve);
|
||||
process.on('SIGTERM', resolve);
|
||||
process.on('uncaughtException', reject);
|
||||
process.on('exit', resolve);
|
||||
}).finally(async () => {
|
||||
await context.dispose();
|
||||
console.log(`[${_package.name}] finish watching.`);
|
||||
});
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import tinycolor from 'tinycolor2';
|
||||
import JSON5 from 'json5';
|
||||
import lightTheme from '@@/themes/_light.json5';
|
||||
import type { BundledTheme } from 'shiki/themes';
|
||||
|
||||
export type Theme = {
|
||||
id: string;
|
||||
name: string;
|
||||
author: string;
|
||||
desc?: string;
|
||||
base?: 'dark' | 'light';
|
||||
kind?: 'dark' | 'light'; // legacy
|
||||
props: Record<string, string>;
|
||||
codeHighlighter?: {
|
||||
base: BundledTheme;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
overrides?: Record<string, any>;
|
||||
} | {
|
||||
base: '_none_';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
overrides: Record<string, any>;
|
||||
};
|
||||
};
|
||||
|
||||
export type CompiledTheme = Record<string, string>;
|
||||
|
||||
export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
|
||||
|
||||
export const getBuiltinThemes = () => Promise.all(
|
||||
[
|
||||
'l-light',
|
||||
'l-coffee',
|
||||
'l-apricot',
|
||||
'l-rainy',
|
||||
'l-botanical',
|
||||
'l-vivid',
|
||||
'l-cherry',
|
||||
'l-sushi',
|
||||
'l-u0',
|
||||
|
||||
'd-dark',
|
||||
'd-persimmon',
|
||||
'd-astro',
|
||||
'd-future',
|
||||
'd-botanical',
|
||||
'd-green-lime',
|
||||
'd-green-orange',
|
||||
'd-cherry',
|
||||
'd-ice',
|
||||
'd-u0',
|
||||
].map(name => import(`@@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
|
||||
);
|
||||
|
||||
export function compile(theme: Theme): CompiledTheme {
|
||||
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 funcTxt = parts.shift();
|
||||
const argTxt = parts.shift();
|
||||
|
||||
if (funcTxt && argTxt) {
|
||||
const func = funcTxt.substring(1);
|
||||
const arg = parseFloat(argTxt);
|
||||
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 = {} as CompiledTheme;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function validateTheme(theme: Record<string, any>): boolean {
|
||||
if (theme.id == null || typeof theme.id !== 'string') return false;
|
||||
if (theme.name == null || typeof theme.name !== 'string') return false;
|
||||
if (theme.base == null || !['light', 'dark'].includes(theme.base)) return false;
|
||||
if (theme.props == null || typeof theme.props !== 'object') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function parseThemeCode(code: string): Theme {
|
||||
let theme;
|
||||
|
||||
try {
|
||||
theme = JSON5.parse(code);
|
||||
} catch (_) {
|
||||
throw new Error('Failed to parse theme json');
|
||||
}
|
||||
if (!validateTheme(theme)) {
|
||||
throw new Error('This theme is invaild');
|
||||
}
|
||||
|
||||
return theme;
|
||||
}
|
||||
@@ -1,18 +1,32 @@
|
||||
{
|
||||
"name": "frontend-shared",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"main": "./js-built/index.js",
|
||||
"types": "./js-built/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./js-built/index.js",
|
||||
"types": "./js-built/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./js-built/*",
|
||||
"types": "./js-built/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node ./build.js",
|
||||
"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
|
||||
"eslint": "eslint './**/*.{js,jsx,ts,tsx}'",
|
||||
"typecheck": "tsgo --noEmit",
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@typescript-eslint/eslint-plugin": "8.59.0",
|
||||
"@typescript-eslint/parser": "8.59.0",
|
||||
"esbuild": "0.28.0",
|
||||
"eslint-plugin-vue": "10.9.0",
|
||||
"nodemon": "3.1.14",
|
||||
"vue-eslint-parser": "10.4.0"
|
||||
},
|
||||
"files": [
|
||||
@@ -20,10 +34,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"i18n": "workspace:*",
|
||||
"json5": "2.2.3",
|
||||
"misskey-js": "workspace:*",
|
||||
"shiki": "4.0.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"vue": "3.5.33"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ let moduleInitialized = false;
|
||||
let unobserve = () => {};
|
||||
let misskeyOS = null;
|
||||
|
||||
function loadTheme(themeMaganer: typeof import('../src/theme')['themeManager']) {
|
||||
function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
|
||||
unobserve();
|
||||
const theme = themes[window.document.documentElement.dataset.misskeyTheme];
|
||||
if (theme) {
|
||||
themeMaganer.updateTheme(themes[window.document.documentElement.dataset.misskeyTheme]);
|
||||
applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]);
|
||||
} else {
|
||||
themeMaganer.updateTheme(themes['l-light']);
|
||||
applyTheme(themes['l-light']);
|
||||
}
|
||||
const observer = new MutationObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
@@ -34,7 +34,7 @@ function loadTheme(themeMaganer: typeof import('../src/theme')['themeManager'])
|
||||
const target = entry.target as HTMLElement;
|
||||
const theme = themes[target.dataset.misskeyTheme];
|
||||
if (theme) {
|
||||
themeMaganer.updateTheme(themes[target.dataset.misskeyTheme]);
|
||||
applyTheme(themes[target.dataset.misskeyTheme]);
|
||||
} else {
|
||||
target.removeAttribute('style');
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 879 KiB |
|
Before Width: | Height: | Size: 626 KiB |
@@ -1 +0,0 @@
|
||||
これらのサムネイルはdev buildでRoomのカタログダイアログを表示し家具を選択した状態でブラウザのコンソールで`takeScreenshot();`を叩くと生成・ダウンロードできます
|
||||
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 27 KiB |