Compare commits
10 Commits
591b35604e
...
2c6fa13a2c
| Author | SHA1 | Date |
|---|---|---|
|
|
2c6fa13a2c | |
|
|
cea2817ce4 | |
|
|
6e061763a5 | |
|
|
44e3d2907a | |
|
|
dd54ec4186 | |
|
|
cec02966ad | |
|
|
f0f2864b33 | |
|
|
0dd3cac8d9 | |
|
|
246e48656e | |
|
|
50430e310a |
|
|
@ -15,10 +15,14 @@
|
|||
## 2023.x.x (unreleased)
|
||||
|
||||
### General
|
||||
-
|
||||
- Feat: プラグインのスクリプトとデータを端末間で同期をできるようになりました
|
||||
- 既存のプラグインはローカルに保存されています。
|
||||
- ローカルのプラグインはプラグインの管理にあるボタンからアカウントに移動できます。
|
||||
- `Mk:load`と`Mk:save`に引数`toAccount?: bool`が追加され、`true`にするとアカウントでデータを共有できます。(デフォルトは`false`)
|
||||
|
||||
### Client
|
||||
-
|
||||
- Fix: アイコンデコレーションが複数の場所で見切れている問題を修正
|
||||
― Fix: 「フォロー中の人全員の返信を含める/含めないようにする」のボタンを押下した際の確認が機能していない問題を修正
|
||||
|
||||
### Server
|
||||
- Fix: トークンのないプラグインをアンインストールするときにエラーが出ないように
|
||||
|
|
|
|||
|
|
@ -1167,6 +1167,9 @@ export interface Locale {
|
|||
"overrideSourceCodeOnly": string;
|
||||
"syncSetting": string;
|
||||
"syncing": string;
|
||||
"movePluginToAccount": string;
|
||||
"movePluginToAccountConfirm": string;
|
||||
"overridePluginConfirm": string;
|
||||
"_announcement": {
|
||||
"forExistingUsers": string;
|
||||
"forExistingUsersDescription": string;
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,9 @@ duplicateSyncedPlugin: "このプラグインは同期されているプラグ
|
|||
overrideSourceCodeOnly: "コードのみを上書きする"
|
||||
syncSetting: "同期設定"
|
||||
syncing: "他端末と同期"
|
||||
movePluginToAccount: "アカウントに移行"
|
||||
movePluginToAccountConfirm: "プラグインをアカウントに移行して他端末と同期しますか?プラグインとプラグイン設定以外は引き継がれません。"
|
||||
overridePluginConfirm: "すでにプラグインがアカウントに存在します。上書きしますか?アカウントにあるプラグインデータは削除されます。"
|
||||
|
||||
_announcement:
|
||||
forExistingUsers: "既存ユーザーのみ"
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ export class InstanceEntityService {
|
|||
faviconUrl: instance.faviconUrl,
|
||||
themeColor: instance.themeColor,
|
||||
infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
|
||||
latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -103,5 +103,10 @@ export const packedFederationInstanceSchema = {
|
|||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
latestRequestReceivedAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export async function mainBoot() {
|
|||
}
|
||||
});
|
||||
|
||||
let plugins = ColdDeviceStorage.get('plugins').filter(p => p.active);
|
||||
const plugins = ColdDeviceStorage.get('plugins').filter(p => p.active);
|
||||
const accountPlugins = Object.values(await getPluginList()).filter(p => p.active);
|
||||
plugins.push(...accountPlugins);
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ export async function mainBoot() {
|
|||
|
||||
// プラグインに変更が入ったら自動でリロードする
|
||||
stream.useChannel('main').on('registryUpdated', ({ scope, key }: { scope: string[], key: string, value: any }) => {
|
||||
if (scope[0] === 'client' && key === 'plugins') {
|
||||
if (scope.length === 1 && scope[0] === 'client' && key === 'plugins') {
|
||||
unisonReload();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1072,6 +1072,7 @@ defineExpose({
|
|||
|
||||
.preview {
|
||||
padding: 16px 20px 0 20px;
|
||||
min-height: 75px;
|
||||
max-height: 150px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,6 @@ function getReactionName(reaction: string): string {
|
|||
}
|
||||
|
||||
.users {
|
||||
contain: content;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: -4px 14px 0 10px;
|
||||
|
|
@ -85,7 +84,7 @@ function getReactionName(reaction: string): string {
|
|||
line-height: 24px;
|
||||
padding-top: 4px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -148,12 +148,13 @@ async function reloadAsk() {
|
|||
}
|
||||
|
||||
async function updateRepliesAll(withReplies: boolean) {
|
||||
const { canceled } = os.confirm({
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: withReplies ? i18n.ts.confirmShowRepliesAll : i18n.ts.confirmHideRepliesAll,
|
||||
});
|
||||
if (canceled) return;
|
||||
await os.api('following/update-all', { withReplies });
|
||||
|
||||
os.api('following/update-all', { withReplies });
|
||||
}
|
||||
|
||||
watch([
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div class="_buttons">
|
||||
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
|
||||
<MkButton v-if="!plugin.fromAccount" inline @click="moveToAccount(plugin)"><i class="ti ti-link"></i> {{ i18n.ts.movePluginToAccount }}</MkButton>
|
||||
<MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
|
||||
</div>
|
||||
|
||||
|
|
@ -75,6 +76,8 @@ import { unisonReload } from '@/scripts/unison-reload.js';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||
import { getPluginList } from '@/plugin.js';
|
||||
import { toHash } from '@/scripts/xxhash.js';
|
||||
import { savePluginToAccount } from '@/scripts/install-plugin.js';
|
||||
|
||||
let plugins = Object.values(await getPluginList());
|
||||
plugins.push(...ColdDeviceStorage.get('plugins'));
|
||||
|
|
@ -87,7 +90,8 @@ async function uninstall(plugin) {
|
|||
await os.api('i/registry/remove-all-keys-in-scope', { scope: ['client', 'aiscript', 'plugins', plugin.id] });
|
||||
await os.api('i/registry/set', { scope: ['client'], key: 'plugins', value: plugins });
|
||||
} else {
|
||||
ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
|
||||
const coldPlugins = ColdDeviceStorage.get('plugins');
|
||||
ColdDeviceStorage.set('plugins', coldPlugins.filter(x => x.id !== plugin.id));
|
||||
}
|
||||
await os.apiWithDialog('i/revoke-token', {
|
||||
token: plugin.token,
|
||||
|
|
@ -97,6 +101,37 @@ async function uninstall(plugin) {
|
|||
});
|
||||
}
|
||||
|
||||
async function moveToAccount(plugin) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.movePluginToAccountConfirm,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
const hash = await toHash(plugin.name, plugin.author);
|
||||
const plugins = await getPluginList();
|
||||
if (Object.keys(plugins).some(v => v === hash)) {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.ts.overridePluginConfirm,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
await os.api('i/registry/remove-all-keys-in-scope', { scope: ['client', 'aiscript', 'plugins', hash] });
|
||||
}
|
||||
|
||||
const coldPlugins = ColdDeviceStorage.get('plugins');
|
||||
ColdDeviceStorage.set('plugins', coldPlugins.filter(x => x.id !== plugin.id));
|
||||
|
||||
plugin.id = hash;
|
||||
plugin.fromAccount = true;
|
||||
plugins[hash] = plugin;
|
||||
|
||||
await os.api('i/registry/set', { scope: ['client'], key: 'plugins', value: plugins });
|
||||
}
|
||||
|
||||
function copy(plugin) {
|
||||
copyToClipboard(plugin.src ?? '');
|
||||
os.success();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ export async function loadScriptStorage(toAccount: boolean, scriptData: ScriptDa
|
|||
value = await api('i/registry/get', { scope: ['client', 'aiscript', scriptData.type, scriptData.id!], key: key });
|
||||
}
|
||||
} else {
|
||||
value = miLocalStorage.getItem(`aiscript:${scriptData.type}:${key}`);
|
||||
if (scriptData.type === 'widget') {
|
||||
value = miLocalStorage.getItem(`aiscript:${scriptData.type}:${key}`);
|
||||
} else {
|
||||
value = miLocalStorage.getItem(`aiscript:${scriptData.type}:${scriptData.id!}:${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (value === null) return null;
|
||||
|
|
@ -30,6 +34,10 @@ export async function saveScriptStorage(toAccount: boolean, scriptData: ScriptDa
|
|||
await api('i/registry/set', { scope: ['client', 'aiscript', scriptData.type, scriptData.id!], key: key, value: jsonValue });
|
||||
}
|
||||
} else {
|
||||
miLocalStorage.setItem(`aiscript:${scriptData.type}:${key}`, jsonValue);
|
||||
if (scriptData.type === 'widget') {
|
||||
miLocalStorage.setItem(`aiscript:${scriptData.type}:${key}`, jsonValue);
|
||||
} else {
|
||||
miLocalStorage.setItem(`aiscript:${scriptData.type}:${scriptData.id!}:${key}`, jsonValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@
|
|||
import { defineAsyncComponent } from 'vue';
|
||||
import { compareVersions } from 'compare-versions';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import xxhash from 'xxhash-wasm';
|
||||
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
|
||||
import type { Plugin } from '@/store.js';
|
||||
import { ColdDeviceStorage } from '@/store.js';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { getPluginList } from '@/plugin.js';
|
||||
import { toHash } from './xxhash.js';
|
||||
|
||||
export type AiScriptPluginMeta = {
|
||||
name: string;
|
||||
|
|
@ -25,11 +25,6 @@ export type AiScriptPluginMeta = {
|
|||
|
||||
const parser = new Parser();
|
||||
|
||||
async function toHash(name: string, author: string) {
|
||||
const { h32ToString } = await xxhash();
|
||||
return h32ToString(author + name);
|
||||
}
|
||||
|
||||
export function savePlugin({ id, meta, src, token }: {
|
||||
id: string;
|
||||
meta: AiScriptPluginMeta;
|
||||
|
|
@ -53,7 +48,7 @@ async function savePluginToAccount(pluginOnlyOverride: boolean, { hash, meta, sr
|
|||
src: string;
|
||||
token: string;
|
||||
}) {
|
||||
let plugins = await getPluginList();
|
||||
const plugins = await getPluginList();
|
||||
// pluginOnlyOverrideがtrueになっているということはすでに重複していることが確定している
|
||||
const configData = pluginOnlyOverride ? plugins[hash].configData : {};
|
||||
const pluginToken = pluginOnlyOverride ? plugins[hash].token : token;
|
||||
|
|
@ -169,15 +164,13 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
|
|||
});
|
||||
|
||||
if (isLocal) {
|
||||
|
||||
savePlugin({
|
||||
id: uuid(),
|
||||
meta: realMeta,
|
||||
token,
|
||||
src: code,
|
||||
});
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
await savePluginToAccount(pluginOnlyOverride, {
|
||||
hash: pluginHash,
|
||||
meta: realMeta,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
import xxhash from 'xxhash-wasm';
|
||||
|
||||
export async function toHash(name: string, author: string) {
|
||||
const { h32ToString } = await xxhash();
|
||||
return h32ToString(author + name);
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ function more(ev: MouseEvent) {
|
|||
.bottom {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 20px 0;
|
||||
padding-top: 20px;
|
||||
background: var(--X14);
|
||||
-webkit-backdrop-filter: var(--blur, blur(8px));
|
||||
backdrop-filter: var(--blur, blur(8px));
|
||||
|
|
@ -228,11 +228,10 @@ function more(ev: MouseEvent) {
|
|||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 30px;
|
||||
padding: 20px 0 20px 30px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
margin-top: 16px;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +362,7 @@ function more(ev: MouseEvent) {
|
|||
.bottom {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
padding: 20px 0;
|
||||
padding-top: 20px;
|
||||
background: var(--X14);
|
||||
-webkit-backdrop-filter: var(--blur, blur(8px));
|
||||
backdrop-filter: var(--blur, blur(8px));
|
||||
|
|
@ -374,7 +373,6 @@ function more(ev: MouseEvent) {
|
|||
position: relative;
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
margin-bottom: 16px;
|
||||
text-align: center;
|
||||
|
||||
&:before {
|
||||
|
|
@ -411,6 +409,7 @@ function more(ev: MouseEvent) {
|
|||
.account {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
width: 100%;
|
||||
overflow: clip;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue