Merge branch 'develop' into feat-frontend-expand-profile-links

This commit is contained in:
Souma 2025-09-05 20:48:28 +09:00
commit da86f303f3
No known key found for this signature in database
6 changed files with 65 additions and 41 deletions

View File

@ -4,6 +4,8 @@
- -
### Client ### Client
- Enhance: AiScriptAppウィジェットで構文エラーを検知してもダイアログではなくウィジェット内にエラーを表示するように
- Enhance: /flushページでサイトキャッシュをクリアできるようになりました
- Enhance: プロフィールへのリンクをユーザーポップアップのアバターに追加 - Enhance: プロフィールへのリンクをユーザーポップアップのアバターに追加
- Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加 - Enhance: ユーザーのノート、フォロー、フォロワーページへのリンクをユーザーポップアップに追加
- Fix: RSSティッカーウィジェットが正しく動作しない問題を修正 - Fix: RSSティッカーウィジェットが正しく動作しない問題を修正

View File

@ -201,6 +201,8 @@ export class ClientServerService {
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
const configUrl = new URL(this.config.url);
fastify.register(fastifyView, { fastify.register(fastifyView, {
root: _dirname + '/views', root: _dirname + '/views',
engine: { engine: {
@ -239,7 +241,6 @@ export class ClientServerService {
done(); done();
}); });
} else { } else {
const configUrl = new URL(this.config.url);
const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, ''); const urlOriginWithoutPort = configUrl.origin.replace(/:\d+$/, '');
const port = (process.env.VITE_PORT ?? '5173'); const port = (process.env.VITE_PORT ?? '5173');
@ -887,6 +888,22 @@ export class ClientServerService {
[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/'); [, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');
fastify.get('/flush', async (request, reply) => { fastify.get('/flush', async (request, reply) => {
let sendHeader = true;
if (request.headers['origin']) {
const originURL = new URL(request.headers['origin']);
if (originURL.protocol !== 'https:') { // Clear-Site-Data only supports https
sendHeader = false;
}
if (originURL.host !== configUrl.host) {
sendHeader = false;
}
}
if (sendHeader) {
reply.header('Clear-Site-Data', '"*"');
}
reply.header('Set-Cookie', 'http-flush-failed=1; Path=/flush; Max-Age=60');
return await reply.view('flush'); return await reply.view('flush');
}); });

View File

@ -6,41 +6,45 @@ html
const msg = document.getElementById('msg'); const msg = document.getElementById('msg');
const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`; const successText = `\nSuccess Flush! <a href="/">Back to Misskey</a>\n成功しました。<a href="/">Misskeyを開き直してください。</a>`;
message('Start flushing.'); if (!document.cookie) {
message('Your site data is fully cleared by your browser.');
message(successText);
} else {
message('Your browser does not support Clear-Site-Data header. Start opportunistic flushing.');
(async function() {
try {
localStorage.clear();
message('localStorage cleared.');
(async function() { const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => {
try { const delidb = indexedDB.deleteDatabase(name);
localStorage.clear(); delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
message('localStorage cleared.'); delidb.onerror = e => rej(e)
}));
const idbPromises = ['MisskeyClient', 'keyval-store'].map((name, i, arr) => new Promise((res, rej) => { await Promise.all(idbPromises);
const delidb = indexedDB.deleteDatabase(name);
delidb.onsuccess = () => res(message(`indexedDB "${name}" cleared. (${i + 1}/${arr.length})`));
delidb.onerror = e => rej(e)
}));
await Promise.all(idbPromises); if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage('clear');
await navigator.serviceWorker.getRegistrations()
.then(registrations => {
return Promise.all(registrations.map(registration => registration.unregister()));
})
.catch(e => { throw new Error(e) });
}
if (navigator.serviceWorker.controller) { message(successText);
navigator.serviceWorker.controller.postMessage('clear'); } catch (e) {
await navigator.serviceWorker.getRegistrations() message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
.then(registrations => { message(`\nIf you retry more than 3 times, try manually clearing the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを手動で消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
return Promise.all(registrations.map(registration => registration.unregister()));
}) console.error(e);
.catch(e => { throw new Error(e) }); setTimeout(() => {
location = '/';
}, 10000)
} }
})();
message(successText); }
} catch (e) {
message(`\n${e}\n\nFlush Failed. <a href="/flush">Please retry.</a>\n失敗しました。<a href="/flush">もう一度試してみてください。</a>`);
message(`\nIf you retry more than 3 times, clear the browser cache or contact to instance admin.\n3回以上試しても失敗する場合、ブラウザのキャッシュを消去し、それでもだめならインスタンス管理者に連絡してみてください。\n`)
console.error(e);
setTimeout(() => {
location = '/';
}, 10000)
}
})();
function message(text) { function message(text) {
msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`) msg.insertAdjacentHTML('beforeend', `<p>[${(new Date()).toString()}] ${text.replace(/\n/g,'<br>')}</p>`)

View File

@ -34,7 +34,7 @@ import { instance as meta } from '@/instance.js';
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; right: 0;
width: 80vw; // 100%shape width: 100vw;
height: 100vh; height: 100vh;
} }

View File

@ -7,25 +7,26 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkContainer :showHeader="widgetProps.showHeader" class="mkw-aiscriptApp"> <MkContainer :showHeader="widgetProps.showHeader" class="mkw-aiscriptApp">
<template #header>App</template> <template #header>App</template>
<div :class="$style.root"> <div :class="$style.root">
<MkAsUi v-if="root" :component="root" :components="components" size="small"/> <div v-if="isSyntaxError">Syntax error :(</div>
<MkAsUi v-else-if="root" :component="root" :components="components" size="small"/>
</div> </div>
</MkContainer> </MkContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import type { Ref } from 'vue';
import { Interpreter, Parser } from '@syuilo/aiscript'; import { Interpreter, Parser } from '@syuilo/aiscript';
import { useWidgetPropsManager } from './widget.js'; import { useWidgetPropsManager } from './widget.js';
import type { Ref } from 'vue';
import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js'; import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js'; import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js'; import { aiScriptReadline, createAiScriptEnv } from '@/aiscript/api.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
import MkAsUi from '@/components/MkAsUi.vue'; import MkAsUi from '@/components/MkAsUi.vue';
import MkContainer from '@/components/MkContainer.vue'; import MkContainer from '@/components/MkContainer.vue';
import { registerAsUiLib } from '@/aiscript/ui.js'; import { registerAsUiLib } from '@/aiscript/ui.js';
import type { AsUiComponent, AsUiRoot } from '@/aiscript/ui.js';
const name = 'aiscriptApp'; const name = 'aiscriptApp';
@ -56,8 +57,11 @@ const parser = new Parser();
const root = ref<AsUiRoot>(); const root = ref<AsUiRoot>();
const components = ref<Ref<AsUiComponent>[]>([]); const components = ref<Ref<AsUiComponent>[]>([]);
const isSyntaxError = ref(false);
async function run() { async function run() {
isSyntaxError.value = false;
const aiscript = new Interpreter({ const aiscript = new Interpreter({
...createAiScriptEnv({ ...createAiScriptEnv({
storageKey: 'widget', storageKey: 'widget',
@ -80,10 +84,7 @@ async function run() {
try { try {
ast = parser.parse(widgetProps.script); ast = parser.parse(widgetProps.script);
} catch (err) { } catch (err) {
os.alert({ isSyntaxError.value = true;
type: 'error',
text: 'Syntax error :(',
});
return; return;
} }
try { try {

View File

@ -52,7 +52,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
const parser = new Parser(); const parser = new Parser();
const run = async () => { async function run() {
const aiscript = new Interpreter(createAiScriptEnv({ const aiscript = new Interpreter(createAiScriptEnv({
storageKey: 'widget', storageKey: 'widget',
token: $i?.token, token: $i?.token,
@ -84,7 +84,7 @@ const run = async () => {
text: err instanceof Error ? err.message : String(err), text: err instanceof Error ? err.message : String(err),
}); });
} }
}; }
defineExpose<WidgetComponentExpose>({ defineExpose<WidgetComponentExpose>({
name, name,