Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: カスタムCSSのバックアップ・同期機能 #161

Open
wants to merge 15 commits into
base: taiyme
Choose a base branch
from
Open
109 changes: 109 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10683,6 +10683,115 @@ export interface Locale extends ILocale {
*/
readonly "caption": string;
};
readonly "_backupAndSyncingCustomCss": {
readonly "_backup": {
/**
* カスタムCSSのバックアップ
*/
readonly "title": string;
/**
* 現在のカスタムCSSをバックアップとしてサーバーに保存することが可能です。
*/
readonly "description": string;
/**
* バックアップがありません。「新規作成」で現在適用されているカスタムCSSのバックアップを作成できます。
*/
readonly "notfound": string;
/**
* 作成日時: {datetime}
*/
readonly "createdAt": ParameterizedString<"datetime">;
/**
* バックアップの読み込みに失敗しました。
*/
readonly "cannnotLoad": string;
/**
* バックアップファイルはjson形式である必要があります。
*/
readonly "invalidFile": string;
/**
* バックアップ名が不正です。
*/
readonly "invalidName": string;
/**
* バックアップのカスタムCSSが不正です。
*/
readonly "invalidCustomCss": string;
/**
* カスタムCSSが設定されていません
*/
readonly "noCustomCss": string;
/**
* カスタムCSSを設定されていない状態ではバックアップを作成できません。
*/
readonly "noCustomCssDescription": string;
/**
* バックアップ名を入力
*/
readonly "inputBackupName": string;
/**
* カスタムCSSのバックアップを適用
*/
readonly "applyBackup": string;
/**
* カスタムCSSのバックアップ「{name}」を適用しますか?
*/
readonly "applyBackupDescription": ParameterizedString<"name">;
/**
* カスタムCSSを設定されていない状態では上書き保存できません。
* カスタムCSSのバックアップを削除する場合はメニューから「削除」を選択してください。
*/
readonly "cannotOverrideEmpty": string;
/**
* カスタムCSSのバックアップを上書き保存
*/
readonly "overrideBackup": string;
/**
* カスタムCSSのバックアップ「{name}」を上書き保存しますか?
* この操作は取り消せません。
*/
readonly "overrideBackupDescription": ParameterizedString<"name">;
/**
* カスタムCSSのバックアップを削除
*/
readonly "deleteBackup": string;
/**
* カスタムCSSのバックアップ「{name}」を削除しますか?
* この操作は取り消せません。
*/
readonly "deleteBackupDescription": ParameterizedString<"name">;
/**
* 上書き保存
*/
readonly "override": string;
/**
* バックアップ内容を見る
*/
readonly "preview": string;
};
readonly "_syncing": {
/**
* カスタムCSSの同期
*/
readonly "title": string;
/**
* カスタムCSSの同期を有効化すると、カスタムCSSの変更がリアルタイムに同期されます。
*/
readonly "description": string;
/**
* カスタムCSSの同期を有効化
*/
readonly "enable": string;
/**
* 同期するカスタムCSSを選択
*/
readonly "select": string;
/**
* 任意のカスタムCSSのバックアップを他のデバイスとの間で同期することができます。
*/
readonly "selectDescription": string;
};
};
};
readonly "_admin": {
/**
Expand Down
28 changes: 28 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2848,6 +2848,34 @@ _tms:
_preventLongPressContextMenu:
label: "長押しによるコンテキストメニューイベントの発行を防ぐ"
caption: "長押しを含む操作が中断される問題を解消します。"
_backupAndSyncingCustomCss:
_backup:
title: "カスタムCSSのバックアップ"
description: "現在のカスタムCSSをバックアップとしてサーバーに保存することが可能です。"
notfound: "バックアップがありません。「新規作成」で現在適用されているカスタムCSSのバックアップを作成できます。"
createdAt: "作成日時: {datetime}"
cannnotLoad: "バックアップの読み込みに失敗しました。"
invalidFile: "バックアップファイルはjson形式である必要があります。"
invalidName: "バックアップ名が不正です。"
invalidCustomCss: "バックアップのカスタムCSSが不正です。"
noCustomCss: "カスタムCSSが設定されていません"
noCustomCssDescription: "カスタムCSSを設定されていない状態ではバックアップを作成できません。"
inputBackupName: "バックアップ名を入力"
applyBackup: "カスタムCSSのバックアップを適用"
applyBackupDescription: "カスタムCSSのバックアップ「{name}」を適用しますか?"
cannotOverrideEmpty: "カスタムCSSを設定されていない状態では上書き保存できません。\nカスタムCSSのバックアップを削除する場合はメニューから「削除」を選択してください。"
overrideBackup: "カスタムCSSのバックアップを上書き保存"
overrideBackupDescription: "カスタムCSSのバックアップ「{name}」を上書き保存しますか?\nこの操作は取り消せません。"
deleteBackup: "カスタムCSSのバックアップを削除"
deleteBackupDescription: "カスタムCSSのバックアップ「{name}」を削除しますか?\nこの操作は取り消せません。"
override: "上書き保存"
preview: "バックアップ内容を見る"
_syncing:
title: "カスタムCSSの同期"
description: "カスタムCSSの同期を有効化すると、カスタムCSSの変更がリアルタイムに同期されます。"
enable: "カスタムCSSの同期を有効化"
select: "同期するカスタムCSSを選択"
selectDescription: "任意のカスタムCSSのバックアップを他のデバイスとの間で同期することができます。"
_admin:
repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。taiymeを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/taiyme/misskey と記入します。"
_search:
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/server/web/boot.embed.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@
}
//#endregion

async function addStyle(styleText) {
let css = document.createElement('style');
css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css);
function addStyle(styleText) {
const styleTag = document.createElement('style');
styleTag.textContent = styleText;
document.head.appendChild(styleTag);
}

async function renderError(code) {
Expand Down
15 changes: 8 additions & 7 deletions packages/backend/src/server/web/boot.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,16 @@

const customCss = localStorage.getItem('customCss');
if (customCss && customCss.length > 0) {
const style = document.createElement('style');
style.innerHTML = customCss;
document.head.appendChild(style);
const styleTag = document.createElement('style');
styleTag.textContent = customCss;
styleTag.id = 'custom_css';
document.head.appendChild(styleTag);
}

async function addStyle(styleText) {
let css = document.createElement('style');
css.appendChild(document.createTextNode(styleText));
document.head.appendChild(css);
function addStyle(styleText) {
const styleTag = document.createElement('style');
styleTag.textContent = styleText;
document.head.appendChild(styleTag);
}

async function renderError(code, details) {
Expand Down
7 changes: 4 additions & 3 deletions packages/frontend/src/_dev_boot_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ async function main() {

const customCss = localStorage.getItem('customCss');
if (customCss && customCss.length > 0) {
const style = document.createElement('style');
style.innerHTML = customCss;
document.head.appendChild(style);
const styleTag = document.createElement('style');
styleTag.textContent = customCss;
styleTag.id = 'custom_css';
document.head.appendChild(styleTag);
}
}

Expand Down
35 changes: 31 additions & 4 deletions packages/frontend/src/boot/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { computed, watch, version as vueVersion, App } from 'vue';
import { compareVersions } from 'compare-versions';
import { version, lang, updateLocale, locale, commitHash } from '@@/js/config.js';
import type { CustomCSSBackup } from '@/pages/tms/backup-and-syncing-custom-css/backup.vue';
import widgets from '@/widgets/index.js';
import directives from '@/directives/index.js';
import components from '@/components/index.js';
Expand All @@ -26,6 +27,7 @@ import { setupRouter } from '@/router/main.js';
import { createMainRouter } from '@/router/definition.js';
import { tmsFlaskStore } from '@/tms/flask-store.js';
import { tmsStore } from '@/tms/store.js';
import { useStream } from '@/stream.js';
import { preventLongPressContextMenu } from '@/scripts/tms/prevent-longpress-contextmenu.js';

export async function common(createVue: () => App<Element>) {
Expand Down Expand Up @@ -120,10 +122,10 @@ export async function common(createVue: () => App<Element>) {
html.setAttribute('lang', lang);
//#endregion

await defaultStore.ready;
await deckStore.ready;
await tmsStore.ready;
await tmsFlaskStore.ready;
await defaultStore.loaded;
await deckStore.loaded;
await tmsStore.loaded;
await tmsFlaskStore.loaded;

if (tmsFlaskStore.state.preventLongPressContextMenu) {
preventLongPressContextMenu();
Expand Down Expand Up @@ -213,6 +215,31 @@ export async function common(createVue: () => App<Element>) {
}
}, { immediate: true });

// tms custom css syncing
const enabledCustomCssSyncing = tmsFlaskStore.state.enabledCustomCssSyncing;
if (enabledCustomCssSyncing) {
const syncingCustomCssId = tmsFlaskStore.state.syncingCustomCssId;
const connection = useStream().useChannel('main');
const scope = ['tms', 'customCssBackups'] as const satisfies string[];

connection.on('registryUpdated', ({ scope: recievedScope, key, value }) => {
if (scope.join('/') !== recievedScope?.join('/')) return;
if (key !== syncingCustomCssId) return;

const { customCss } = value as unknown as CustomCSSBackup;
miLocalStorage.setItem('customCss', customCss);

let styleTag = document.getElementById('custom_css');
if (styleTag == null) {
styleTag = document.createElement('style');
styleTag.id = 'custom_css';
document.head.appendChild(styleTag);
}

styleTag.textContent = customCss;
});
}

// Keep screen on
const onVisibilityChange = () => document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
Expand Down
81 changes: 81 additions & 0 deletions packages/frontend/src/components/TmsCodePreviewDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<!--
SPDX-FileCopyrightText: syuilo and other misskey contributors
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkModalWindow
ref="modal"
:height="height"
:width="width"
@close="close()"
@closed="emit('closed')"
>
<template #header><i class="ti ti-file-text"></i> {{ name }}</template>
<MkCode :lang="lang" :code="code" :class="[$style.codeBlock, 'codeBlock']"/>
</MkModalWindow>
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkCode from '@/components/MkCode.vue';

defineProps<{
name: string;
lang?: string;
code: string;
}>();

const emit = defineEmits<{
closed: [];
}>();

const modal = shallowRef<InstanceType<typeof MkModalWindow>>();
const height = ref(window.innerHeight);
const width = ref(window.innerWidth);

const close = () => {
modal.value?.close();
};

const resizeModal = () => {
const h = window.innerHeight;
const w = window.innerWidth;

if (w < 600) {
height.value = h;
width.value = w;
} else {
height.value = h * 0.8;
width.value = w * 0.8;
}
};

onMounted(() => {
resizeModal();
window.addEventListener('resize', resizeModal);
});

onUnmounted(() => {
window.removeEventListener('resize', resizeModal);
});
</script>

<style lang="scss" module>
.codeBlock {
height: 100%;

> div {
height: 100%;
}
}
</style>

<style lang="scss" scoped>
.codeBlock :deep(pre.shiki) {
margin: 0;
height: 100%;
border-radius: 0;
}
</style>
Loading
Loading