+
{{ i18n.ts.showNoteActionsOnlyHover }}
{{ i18n.ts.collapseRenotes }}
{{ i18n.ts.enableAdvancedMfm }}
{{ i18n.ts.enableAnimatedMfm }}
@@ -140,6 +141,7 @@ async function reloadAsk() {
const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
+const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 0512a8d0c..e3cf1aefb 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -59,6 +59,8 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'tl',
'overridedDeviceKind',
'serverDisconnectedBehavior',
+ 'collapseRenotes',
+ 'showNoteActionsOnlyHover',
'nsfw',
'animation',
'animatedMfm',
@@ -420,7 +422,6 @@ onUnmounted(() => {
definePageMetadata(computed(() => ({
title: ts.preferencesBackups,
icon: 'ti ti-device-floppy',
- bg: 'var(--bg)',
})));
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index cb46858c5..f5a090a63 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -47,6 +47,5 @@ const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts.statusbar,
icon: 'ti ti-list',
- bg: 'var(--bg)',
});
diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue
index 13a06286f..373af193d 100644
--- a/packages/frontend/src/pages/user-info.vue
+++ b/packages/frontend/src/pages/user-info.vue
@@ -337,7 +337,31 @@ async function assignRole() {
});
if (canceled) return;
- await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id });
+ const { canceled: canceled2, result: period } = await os.select({
+ title: i18n.ts.period,
+ items: [{
+ value: 'indefinitely', text: i18n.ts.indefinitely,
+ }, {
+ value: 'oneHour', text: i18n.ts.oneHour,
+ }, {
+ value: 'oneDay', text: i18n.ts.oneDay,
+ }, {
+ value: 'oneWeek', text: i18n.ts.oneWeek,
+ }, {
+ value: 'oneMonth', text: i18n.ts.oneMonth,
+ }],
+ default: 'indefinitely',
+ });
+ if (canceled2) return;
+
+ const expiresAt = period === 'indefinitely' ? null
+ : period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
+ : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
+ : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
+ : period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
+ : null;
+
+ await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id, expiresAt });
refreshUser();
}
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index f62a6461c..b6f9b3eb2 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -26,6 +26,9 @@
{{ i18n.ts.joinThisServer }}
{{ i18n.ts.exploreOtherServers }}
@@ -62,6 +65,7 @@ import XSignupDialog from '@/components/MkSignupDialog.vue';
import MkButton from '@/components/MkButton.vue';
import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
import MkTimeline from '@/components/MkTimeline.vue';
+import MkInfo from '@/components/MkInfo.vue';
import { instanceName } from '@/config';
import * as os from '@/os';
import { i18n } from '@/i18n';
@@ -249,6 +253,10 @@ function exploreOtherServers() {
padding: 0 32px;
}
+ > .warn {
+ padding: 32px 32px 0 32px;
+ }
+
> .action {
padding: 32px;
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 17eb99be2..a1a36480f 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -1,12 +1,12 @@
import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
import { createAiScriptEnv } from '@/scripts/aiscript/api';
import { inputText } from '@/os';
-import { noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions } from '@/store';
+import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions } from '@/store';
const parser = new Parser();
const pluginContexts = new Map
();
-export function install(plugin) {
+export function install(plugin: Plugin): void {
// 後方互換性のため
if (plugin.src == null) return;
console.info('Plugin installed:', plugin.name, 'v' + plugin.version);
@@ -15,7 +15,7 @@ export function install(plugin) {
plugin: plugin,
storageKey: 'plugins:' + plugin.id,
}), {
- in: (q) => {
+ in: (q): Promise => {
return new Promise(ok => {
inputText({
title: q,
@@ -28,10 +28,10 @@ export function install(plugin) {
});
});
},
- out: (value) => {
+ out: (value): void => {
console.log(value);
},
- log: (type, params) => {
+ log: (): void => {
},
});
@@ -40,9 +40,9 @@ export function install(plugin) {
aiscript.exec(parser.parse(plugin.src));
}
-function createPluginEnv(opts) {
- const config = new Map();
- for (const [k, v] of Object.entries(opts.plugin.config || {})) {
+function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record {
+ const config = new Map();
+ for (const [k, v] of Object.entries(opts.plugin.config ?? {})) {
config.set(k, utils.jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default));
}
@@ -50,22 +50,28 @@ function createPluginEnv(opts) {
...createAiScriptEnv({ ...opts, token: opts.plugin.token }),
//#region Deprecated
'Mk:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerPostFormAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
'Mk:register_user_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerUserAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
'Mk:register_note_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerNoteAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
//#endregion
'Plugin:register_post_form_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerPostFormAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
'Plugin:register_user_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerUserAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
'Plugin:register_note_action': values.FN_NATIVE(([title, handler]) => {
+ utils.assertString(title);
registerNoteAction({ pluginId: opts.plugin.id, title: title.value, handler });
}),
'Plugin:register_note_view_interruptor': values.FN_NATIVE(([handler]) => {
@@ -75,54 +81,78 @@ function createPluginEnv(opts) {
registerNotePostInterruptor({ pluginId: opts.plugin.id, handler });
}),
'Plugin:open_url': values.FN_NATIVE(([url]) => {
+ utils.assertString(url);
window.open(url.value, '_blank');
}),
'Plugin:config': values.OBJ(config),
};
}
-function initPlugin({ plugin, aiscript }) {
+function initPlugin({ plugin, aiscript }): void {
pluginContexts.set(plugin.id, aiscript);
}
-function registerPostFormAction({ pluginId, title, handler }) {
+function registerPostFormAction({ pluginId, title, handler }): void {
postFormActions.push({
title, handler: (form, update) => {
- pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
- update(key.value, value.value);
+ const pluginContext = pluginContexts.get(pluginId);
+ if (!pluginContext) {
+ return;
+ }
+ pluginContext.execFn(handler, [utils.jsToVal(form), values.FN_NATIVE(([key, value]) => {
+ if (!key || !value) {
+ return;
+ }
+ update(utils.valToJs(key), utils.valToJs(value));
})]);
},
});
}
-function registerUserAction({ pluginId, title, handler }) {
+function registerUserAction({ pluginId, title, handler }): void {
userActions.push({
title, handler: (user) => {
- pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(user)]);
+ const pluginContext = pluginContexts.get(pluginId);
+ if (!pluginContext) {
+ return;
+ }
+ pluginContext.execFn(handler, [utils.jsToVal(user)]);
},
});
}
-function registerNoteAction({ pluginId, title, handler }) {
+function registerNoteAction({ pluginId, title, handler }): void {
noteActions.push({
title, handler: (note) => {
- pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]);
+ const pluginContext = pluginContexts.get(pluginId);
+ if (!pluginContext) {
+ return;
+ }
+ pluginContext.execFn(handler, [utils.jsToVal(note)]);
},
});
}
-function registerNoteViewInterruptor({ pluginId, handler }) {
+function registerNoteViewInterruptor({ pluginId, handler }): void {
noteViewInterruptors.push({
handler: async (note) => {
- return utils.valToJs(await pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]));
+ const pluginContext = pluginContexts.get(pluginId);
+ if (!pluginContext) {
+ return;
+ }
+ return utils.valToJs(await pluginContext.execFn(handler, [utils.jsToVal(note)]));
},
});
}
-function registerNotePostInterruptor({ pluginId, handler }) {
+function registerNotePostInterruptor({ pluginId, handler }): void {
notePostInterruptors.push({
handler: async (note) => {
- return utils.valToJs(await pluginContexts.get(pluginId).execFn(handler, [utils.jsToVal(note)]));
+ const pluginContext = pluginContexts.get(pluginId);
+ if (!pluginContext) {
+ return;
+ }
+ return utils.valToJs(await pluginContext.execFn(handler, [utils.jsToVal(note)]));
},
});
}
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 6c6baf826..5170ca4c8 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -143,8 +143,32 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
return roles.filter(r => r.target === 'manual').map(r => ({
text: r.name,
- action: () => {
- os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id });
+ action: async () => {
+ const { canceled, result: period } = await os.select({
+ title: i18n.ts.period,
+ items: [{
+ value: 'indefinitely', text: i18n.ts.indefinitely,
+ }, {
+ value: 'oneHour', text: i18n.ts.oneHour,
+ }, {
+ value: 'oneDay', text: i18n.ts.oneDay,
+ }, {
+ value: 'oneWeek', text: i18n.ts.oneWeek,
+ }, {
+ value: 'oneMonth', text: i18n.ts.oneMonth,
+ }],
+ default: 'indefinitely',
+ });
+ if (canceled) return;
+
+ const expiresAt = period === 'indefinitely' ? null
+ : period === 'oneHour' ? Date.now() + (1000 * 60 * 60)
+ : period === 'oneDay' ? Date.now() + (1000 * 60 * 60 * 24)
+ : period === 'oneWeek' ? Date.now() + (1000 * 60 * 60 * 24 * 7)
+ : period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
+ : null;
+
+ os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt });
},
}));
},
diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts
index 4a0ded637..b7238016c 100644
--- a/packages/frontend/src/scripts/hotkey.ts
+++ b/packages/frontend/src/scripts/hotkey.ts
@@ -53,10 +53,10 @@ const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, c
return result;
});
-const ignoreElemens = ['input', 'textarea'];
+const ignoreElements = ['input', 'textarea'];
function match(ev: KeyboardEvent, patterns: Action['patterns']): boolean {
- const key = ev.code.toLowerCase();
+ const key = ev.key.toLowerCase();
return patterns.some(pattern => pattern.which.includes(key) &&
pattern.ctrl === ev.ctrlKey &&
pattern.shift === ev.shiftKey &&
@@ -70,7 +70,7 @@ export const makeHotkey = (keymap: Keymap) => {
return (ev: KeyboardEvent) => {
if (document.activeElement) {
- if (ignoreElemens.some(el => document.activeElement!.matches(el))) return;
+ if (ignoreElements.some(el => document.activeElement!.matches(el))) return;
if (document.activeElement.attributes['contenteditable']) return;
}
diff --git a/packages/frontend/src/scripts/keycode.ts b/packages/frontend/src/scripts/keycode.ts
index 69f6a8280..35813edbd 100644
--- a/packages/frontend/src/scripts/keycode.ts
+++ b/packages/frontend/src/scripts/keycode.ts
@@ -16,18 +16,3 @@ export const aliases = {
'right': 'ArrowRight',
'plus': ['NumpadAdd', 'Semicolon'],
};
-
-/*!
-* Programmatically add the following
-*/
-
-// lower case chars
-for (let i = 97; i < 123; i++) {
- const char = String.fromCharCode(i);
- aliases[char] = `Key${char.toUpperCase()}`;
-}
-
-// numbers
-for (let i = 0; i < 10; i++) {
- aliases[i] = [`Numpad${i}`, `Digit${i}`];
-}
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts
index 274e96e0a..2fe5bdcf8 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/scripts/media-proxy.ts
@@ -1,20 +1,20 @@
-import { query, appendQuery } from '@/scripts/url';
+import { query } from '@/scripts/url';
import { url } from '@/config';
import { instance } from '@/instance';
-export function getProxiedImageUrl(imageUrl: string, type?: 'preview'): string {
- if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/')) {
- // もう既にproxyっぽそうだったらsearchParams付けるだけ
- return appendQuery(imageUrl, query({
- fallback: '1',
- ...(type ? { [type]: '1' } : {}),
- }));
+export function getProxiedImageUrl(imageUrl: string, type?: 'preview', mustOrigin: boolean = false): string {
+ const localProxy = `${url}/proxy`;
+
+ if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
+ // もう既にproxyっぽそうだったらurlを取り出す
+ imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
}
- return `${instance.mediaProxy}/image.webp?${query({
+ return `${mustOrigin ? localProxy : instance.mediaProxy}/image.webp?${query({
url: imageUrl,
fallback: '1',
...(type ? { [type]: '1' } : {}),
+ ...(mustOrigin ? { origin: '1' } : {}),
})}`;
}
diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/scripts/page-metadata.ts
index 0db8369f9..8810e2696 100644
--- a/packages/frontend/src/scripts/page-metadata.ts
+++ b/packages/frontend/src/scripts/page-metadata.ts
@@ -10,7 +10,6 @@ export type PageMetadata = {
icon?: string | null;
avatar?: misskey.entities.User | null;
userName?: misskey.entities.User | null;
- bg?: string;
};
export function definePageMetadata(metadata: PageMetadata | null | Ref | ComputedRef): void {
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index 9d1f60323..b08982fac 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -4,6 +4,27 @@ const cache = new Map();
export const soundsTypes = [
null,
+ 'syuilo/n-aec',
+ 'syuilo/n-aec-4va',
+ 'syuilo/n-aec-4vb',
+ 'syuilo/n-aec-8va',
+ 'syuilo/n-aec-8vb',
+ 'syuilo/n-cea',
+ 'syuilo/n-cea-4va',
+ 'syuilo/n-cea-4vb',
+ 'syuilo/n-cea-8va',
+ 'syuilo/n-cea-8vb',
+ 'syuilo/n-eca',
+ 'syuilo/n-eca-4va',
+ 'syuilo/n-eca-4vb',
+ 'syuilo/n-eca-8va',
+ 'syuilo/n-eca-8vb',
+ 'syuilo/n-ea',
+ 'syuilo/n-ea-4va',
+ 'syuilo/n-ea-4vb',
+ 'syuilo/n-ea-8va',
+ 'syuilo/n-ea-8vb',
+ 'syuilo/n-ea-harmony',
'syuilo/up',
'syuilo/down',
'syuilo/pope1',
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 54c159ed6..a6ad1774f 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -273,6 +273,10 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'device',
default: 5,
},
+ showNoteActionsOnlyHover: {
+ where: 'device',
+ default: false,
+ },
aiChanMode: {
where: 'device',
default: false,
@@ -283,12 +287,15 @@ export const defaultStore = markRaw(new Storage('base', {
const PREFIX = 'miux:' as const;
-type Plugin = {
+export type Plugin = {
id: string;
name: string;
active: boolean;
+ config?: Record;
configData: Record;
token: string;
+ src: string | null;
+ version: string;
ast: any[];
};
@@ -312,14 +319,14 @@ export class ColdDeviceStorage {
syncDeviceDarkMode: true,
plugins: [] as Plugin[],
mediaVolume: 0.5,
- sound_masterVolume: 0.3,
- sound_note: { type: 'syuilo/down', volume: 1 },
- sound_noteMy: { type: 'syuilo/up', volume: 1 },
- sound_notification: { type: 'syuilo/pope2', volume: 1 },
- sound_chat: { type: 'syuilo/pope1', volume: 1 },
- sound_chatBg: { type: 'syuilo/waon', volume: 1 },
- sound_antenna: { type: 'syuilo/triple', volume: 1 },
- sound_channel: { type: 'syuilo/square-pico', volume: 1 },
+ sound_masterVolume: 0.5,
+ sound_note: { type: 'syuilo/n-aec', volume: 0.5 },
+ sound_noteMy: { type: 'syuilo/n-cea', volume: 0.5 },
+ sound_notification: { type: 'syuilo/n-ea', volume: 0.5 },
+ sound_chat: { type: 'syuilo/pope1', volume: 0.5 },
+ sound_chatBg: { type: 'syuilo/waon', volume: 0.5 },
+ sound_antenna: { type: 'syuilo/triple', volume: 0.5 },
+ sound_channel: { type: 'syuilo/square-pico', volume: 0.5 },
};
public static watchers: Watcher[] = [];
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 38ee37de2..e895847bd 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -22,7 +22,7 @@
-
+
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index beae799f5..11d1c85e3 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -2,7 +2,7 @@