parent
d8318c02a1
commit
bd81a6c8ad
|
|
@ -64,6 +64,7 @@ import {
|
||||||
packedMetaDetailedOnlySchema,
|
packedMetaDetailedOnlySchema,
|
||||||
packedMetaDetailedSchema,
|
packedMetaDetailedSchema,
|
||||||
packedMetaLiteSchema,
|
packedMetaLiteSchema,
|
||||||
|
packedMetaClientOptionsSchema,
|
||||||
} from '@/models/json-schema/meta.js';
|
} from '@/models/json-schema/meta.js';
|
||||||
import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js';
|
import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js';
|
||||||
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
|
||||||
|
|
@ -135,6 +136,7 @@ export const refs = {
|
||||||
MetaLite: packedMetaLiteSchema,
|
MetaLite: packedMetaLiteSchema,
|
||||||
MetaDetailedOnly: packedMetaDetailedOnlySchema,
|
MetaDetailedOnly: packedMetaDetailedOnlySchema,
|
||||||
MetaDetailed: packedMetaDetailedSchema,
|
MetaDetailed: packedMetaDetailedSchema,
|
||||||
|
MetaClientOptions: packedMetaClientOptionsSchema,
|
||||||
UserWebhook: packedUserWebhookSchema,
|
UserWebhook: packedUserWebhookSchema,
|
||||||
SystemWebhook: packedSystemWebhookSchema,
|
SystemWebhook: packedSystemWebhookSchema,
|
||||||
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
|
||||||
|
|
|
||||||
|
|
@ -725,7 +725,11 @@ export class MiMeta {
|
||||||
@Column('jsonb', {
|
@Column('jsonb', {
|
||||||
default: { },
|
default: { },
|
||||||
})
|
})
|
||||||
public clientOptions: Record<string, any>;
|
public clientOptions: {
|
||||||
|
entrancePageStyle: 'classic' | 'simple';
|
||||||
|
showTimelineForVisitor: boolean;
|
||||||
|
showActivitiesForVisitor: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SoftwareSuspension = {
|
export type SoftwareSuspension = {
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,7 @@ export const packedMetaLiteSchema = {
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
clientOptions: {
|
clientOptions: {
|
||||||
type: 'object',
|
ref: 'MetaClientOptions',
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
},
|
||||||
disableRegistration: {
|
disableRegistration: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|
@ -397,3 +396,23 @@ export const packedMetaDetailedSchema = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const packedMetaClientOptionsSchema = {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
entrancePageStyle: {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['classic', 'simple'],
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
showTimelineForVisitor: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
showActivitiesForVisitor: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
|
||||||
|
|
@ -428,8 +428,7 @@ export const meta = {
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
},
|
},
|
||||||
clientOptions: {
|
clientOptions: {
|
||||||
type: 'object',
|
ref: 'MetaClientOptions',
|
||||||
optional: false, nullable: false,
|
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Inject } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { MiMeta } from '@/models/Meta.js';
|
import type { MiMeta } from '@/models/Meta.js';
|
||||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
|
@ -67,7 +68,14 @@ export const paramDef = {
|
||||||
description: { type: 'string', nullable: true },
|
description: { type: 'string', nullable: true },
|
||||||
defaultLightTheme: { type: 'string', nullable: true },
|
defaultLightTheme: { type: 'string', nullable: true },
|
||||||
defaultDarkTheme: { type: 'string', nullable: true },
|
defaultDarkTheme: { type: 'string', nullable: true },
|
||||||
clientOptions: { type: 'object', nullable: false },
|
clientOptions: {
|
||||||
|
type: 'object', nullable: false,
|
||||||
|
properties: {
|
||||||
|
entrancePageStyle: { type: 'string', nullable: false, enum: ['classic', 'simple'] },
|
||||||
|
showTimelineForVisitor: { type: 'boolean', nullable: false },
|
||||||
|
showActivitiesForVisitor: { type: 'boolean', nullable: false },
|
||||||
|
},
|
||||||
|
},
|
||||||
cacheRemoteFiles: { type: 'boolean' },
|
cacheRemoteFiles: { type: 'boolean' },
|
||||||
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
cacheRemoteSensitiveFiles: { type: 'boolean' },
|
||||||
emailRequiredForSignup: { type: 'boolean' },
|
emailRequiredForSignup: { type: 'boolean' },
|
||||||
|
|
@ -217,6 +225,9 @@ export const paramDef = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.meta)
|
||||||
|
private serverSettings: MiMeta,
|
||||||
|
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
private moderationLogService: ModerationLogService,
|
private moderationLogService: ModerationLogService,
|
||||||
) {
|
) {
|
||||||
|
|
@ -329,7 +340,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.clientOptions !== undefined) {
|
if (ps.clientOptions !== undefined) {
|
||||||
set.clientOptions = ps.clientOptions;
|
set.clientOptions = {
|
||||||
|
...serverSettings.clientOptions,
|
||||||
|
...ps.clientOptions,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ps.cacheRemoteFiles !== undefined) {
|
if (ps.cacheRemoteFiles !== undefined) {
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.body">
|
<div :class="$style.body">
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<span :class="$style.title">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].title }}</span>
|
<span :class="$style.title">{{ i18n.ts._achievements._types[`_${achievement.name}`].title }}</span>
|
||||||
<span :class="$style.time">
|
<span :class="$style.time">
|
||||||
<time v-tooltip="new Date(achievement.unlockedAt).toLocaleString()">{{ new Date(achievement.unlockedAt).getFullYear() }}/{{ new Date(achievement.unlockedAt).getMonth() + 1 }}/{{ new Date(achievement.unlockedAt).getDate() }}</time>
|
<time v-tooltip="new Date(achievement.unlockedAt).toLocaleString()">{{ new Date(achievement.unlockedAt).getFullYear() }}/{{ new Date(achievement.unlockedAt).getMonth() + 1 }}/{{ new Date(achievement.unlockedAt).getDate() }}</time>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.description">{{ withDescription ? (i18n.ts._achievements._types as any)['_' + achievement.name].description : '???' }}</div>
|
<div :class="$style.description">{{ withDescription ? i18n.ts._achievements._types[`_${achievement.name}`].description : '???' }}</div>
|
||||||
<div v-if="(i18n.ts._achievements._types as any)['_' + achievement.name].flavor && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].flavor }}</div>
|
<div v-if="'flavor' in i18n.ts._achievements._types[`_${achievement.name}`] && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types[`_${achievement.name}`] as { flavor: string; }).flavor }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="withLocked">
|
<template v-if="withLocked">
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
|
import { emojilist, getEmojiName } from '@@/js/emojilist.js';
|
||||||
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
|
import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
|
||||||
|
|
@ -63,7 +64,7 @@ import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
export type CompleteInfo = {
|
export type CompleteInfo = {
|
||||||
user: {
|
user: {
|
||||||
payload: any;
|
payload: Misskey.entities.User;
|
||||||
query: string | null;
|
query: string | null;
|
||||||
},
|
},
|
||||||
hashtag: {
|
hashtag: {
|
||||||
|
|
@ -185,9 +186,9 @@ const suggests = ref<Element>();
|
||||||
const rootEl = useTemplateRef('rootEl');
|
const rootEl = useTemplateRef('rootEl');
|
||||||
|
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
const users = ref<any[]>([]);
|
const users = ref<Misskey.entities.User[]>([]);
|
||||||
const hashtags = ref<any[]>([]);
|
const hashtags = ref<string[]>([]);
|
||||||
const emojis = ref<(EmojiDef)[]>([]);
|
const emojis = ref<EmojiDef[]>([]);
|
||||||
const items = ref<Element[] | HTMLCollection>([]);
|
const items = ref<Element[] | HTMLCollection>([]);
|
||||||
const mfmTags = ref<string[]>([]);
|
const mfmTags = ref<string[]>([]);
|
||||||
const mfmParams = ref<string[]>([]);
|
const mfmParams = ref<string[]>([]);
|
||||||
|
|
@ -204,8 +205,8 @@ function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T][
|
||||||
emit('closed');
|
emit('closed');
|
||||||
if (type === 'emoji' || type === 'emojiComplete') {
|
if (type === 'emoji' || type === 'emojiComplete') {
|
||||||
let recents = store.s.recentlyUsedEmojis;
|
let recents = store.s.recentlyUsedEmojis;
|
||||||
recents = recents.filter((emoji: any) => emoji !== value);
|
recents = recents.filter((emoji) => emoji !== value);
|
||||||
recents.unshift(value);
|
recents.unshift(value as string);
|
||||||
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
store.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,7 +255,7 @@ function exec() {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
detail: false,
|
detail: false,
|
||||||
}).then(searchedUsers => {
|
}).then(searchedUsers => {
|
||||||
users.value = searchedUsers as any[];
|
users.value = searchedUsers;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
// キャッシュ
|
// キャッシュ
|
||||||
sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers));
|
sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers));
|
||||||
|
|
@ -276,7 +277,7 @@ function exec() {
|
||||||
query: props.q,
|
query: props.q,
|
||||||
limit: 30,
|
limit: 30,
|
||||||
}).then(searchedHashtags => {
|
}).then(searchedHashtags => {
|
||||||
hashtags.value = searchedHashtags as any[];
|
hashtags.value = searchedHashtags;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
// キャッシュ
|
// キャッシュ
|
||||||
sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags));
|
sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags));
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>{{ i18n.ts.permission }}</template>
|
<template #key>{{ i18n.ts.permission }}</template>
|
||||||
<template #value>
|
<template #value>
|
||||||
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
|
<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
|
||||||
<li v-for="permission in extension.meta.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li>
|
<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<template v-else>{{ i18n.ts.none }}</template>
|
<template v-else>{{ i18n.ts.none }}</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<slot name="additionalInfo"/>
|
<slot name="additionalInfo"></slot>
|
||||||
|
|
||||||
<div class="_buttonsCenter">
|
<div class="_buttonsCenter">
|
||||||
<MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
|
<MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
|
||||||
|
|
@ -101,6 +101,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
|
||||||
export type Extension = {
|
export type Extension = {
|
||||||
type: 'plugin';
|
type: 'plugin';
|
||||||
raw: string;
|
raw: string;
|
||||||
|
|
@ -109,7 +111,7 @@ export type Extension = {
|
||||||
version: string;
|
version: string;
|
||||||
author: string;
|
author: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
permissions?: string[];
|
permissions?: (typeof Misskey.permissions)[number][];
|
||||||
config?: Record<string, unknown>;
|
config?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
} | {
|
} | {
|
||||||
|
|
@ -125,7 +127,6 @@ export type Extension = {
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
|
||||||
import FormSplit from '@/components/form/split.vue';
|
import FormSplit from '@/components/form/split.vue';
|
||||||
import MkCode from '@/components/MkCode.vue';
|
import MkCode from '@/components/MkCode.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ notification.invitation.room.name }}
|
{{ notification.invitation.room.name }}
|
||||||
</div>
|
</div>
|
||||||
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
|
<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
|
||||||
{{ (i18n.ts._achievements._types as any)['_' + notification.achievement].title }}
|
{{ i18n.ts._achievements._types[`_${notification.achievement}`].title }}
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
|
<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
|
||||||
{{ i18n.ts.showFile }}
|
{{ i18n.ts.showFile }}
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ async function unsubscribe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function encode(buffer: ArrayBuffer | null) {
|
function encode(buffer: ArrayBuffer | null) {
|
||||||
return btoa(String.fromCharCode.apply(null, buffer ? new Uint8Array(buffer) as any : []));
|
return btoa(String.fromCharCode(...(buffer != null ? new Uint8Array(buffer) : [])));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, useTemplateRef } from 'vue';
|
import { onMounted, useTemplateRef } from 'vue';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
|
import type { ScatterDataPoint } from 'chart.js';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { useChartTooltip } from '@/composables/use-chart-tooltip.js';
|
import { useChartTooltip } from '@/composables/use-chart-tooltip.js';
|
||||||
|
|
@ -18,6 +19,12 @@ import { alpha } from '@/utility/color.js';
|
||||||
import { initChart } from '@/utility/init-chart.js';
|
import { initChart } from '@/utility/init-chart.js';
|
||||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||||
|
|
||||||
|
interface RetentionPoint extends ScatterDataPoint {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
d: string;
|
||||||
|
}
|
||||||
|
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
const chartEl = useTemplateRef('chartEl');
|
const chartEl = useTemplateRef('chartEl');
|
||||||
|
|
@ -62,14 +69,14 @@ onMounted(async () => {
|
||||||
fill: false,
|
fill: false,
|
||||||
tension: 0.4,
|
tension: 0.4,
|
||||||
data: [{
|
data: [{
|
||||||
x: '0',
|
x: 0,
|
||||||
y: 100,
|
y: 100,
|
||||||
d: getYYYYMMDD(new Date(record.createdAt)),
|
d: getYYYYMMDD(new Date(record.createdAt)),
|
||||||
}, ...Object.entries(record.data).sort((a, b) => getDate(a[0]) > getDate(b[0]) ? 1 : -1).map(([k, v], i) => ({
|
}, ...Object.entries(record.data).sort((a, b) => getDate(a[0]) > getDate(b[0]) ? 1 : -1).map(([k, v], i) => ({
|
||||||
x: (i + 1).toString(),
|
x: i + 1,
|
||||||
y: (v / record.users) * 100,
|
y: (v / record.users) * 100,
|
||||||
d: getYYYYMMDD(new Date(record.createdAt)),
|
d: getYYYYMMDD(new Date(record.createdAt)),
|
||||||
}))] as any,
|
}))],
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
|
@ -111,11 +118,11 @@ onMounted(async () => {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
title(context) {
|
title(context) {
|
||||||
const v = context[0].dataset.data[context[0].dataIndex] as unknown as { x: string, y: number, d: string };
|
const v = context[0].dataset.data[context[0].dataIndex] as RetentionPoint;
|
||||||
return `${v.x} days later`;
|
return `${v.x} days later`;
|
||||||
},
|
},
|
||||||
label(context) {
|
label(context) {
|
||||||
const v = context.dataset.data[context.dataIndex] as unknown as { x: string, y: number, d: string };
|
const v = context.dataset.data[context.dataIndex] as RetentionPoint;
|
||||||
const p = Math.round(v.y) + '%';
|
const p = Math.round(v.y) + '%';
|
||||||
return `${v.d} ${p}`;
|
return `${v.d} ${p}`;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ const serverSettings = computed<Misskey.entities.AdminUpdateMetaRequest>(() => {
|
||||||
enableReactionsBuffering,
|
enableReactionsBuffering,
|
||||||
clientOptions: {
|
clientOptions: {
|
||||||
entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple',
|
entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple',
|
||||||
} as any,
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
@ok="save()"
|
@ok="save()"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header><i class="ti ti-icons"></i> {{ (i18n.ts._widgets as any)[widgetName] ?? widgetName }}</template>
|
<template #header><i class="ti ti-icons"></i> {{ i18n.ts._widgets[widgetName] ?? widgetName }}</template>
|
||||||
|
|
||||||
<MkPreviewWithControls>
|
<MkPreviewWithControls>
|
||||||
<template #preview>
|
<template #preview>
|
||||||
|
|
@ -50,13 +50,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
import { useTemplateRef, ref, computed, onBeforeUnmount, onMounted } from 'vue';
|
import { useTemplateRef, ref, computed, onBeforeUnmount, onMounted } from 'vue';
|
||||||
import MkPreviewWithControls from './MkPreviewWithControls.vue';
|
import MkPreviewWithControls from './MkPreviewWithControls.vue';
|
||||||
import type { Form } from '@/utility/form.js';
|
import type { Form } from '@/utility/form.js';
|
||||||
|
import type { WidgetName } from '@/widgets/index.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import MkForm from '@/components/MkForm.vue';
|
import MkForm from '@/components/MkForm.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
widgetName: string;
|
widgetName: WidgetName;
|
||||||
form: Form;
|
form: Form;
|
||||||
currentSettings: Record<string, any>;
|
currentSettings: Record<string, any>;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<span :class="$style.container">
|
<span :class="$style.container">
|
||||||
<span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }">
|
<span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }">
|
||||||
<slot/>
|
<slot></slot>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,3 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientOptions = {
|
|
||||||
entrancePageStyle: 'classic' | 'simple';
|
|
||||||
showTimelineForVisitor: boolean;
|
|
||||||
showActivitiesForVisitor: boolean;
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import JSON5 from 'json5';
|
import JSON5 from 'json5';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { host } from '@@/js/config.js';
|
import { host } from '@@/js/config.js';
|
||||||
import type { ClientOptions } from '@/instance.js';
|
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
|
@ -172,11 +172,11 @@ import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
const meta = await misskeyApi('admin/meta');
|
const meta = await misskeyApi('admin/meta');
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
|
const entrancePageStyle = ref<Misskey.entities.MetaClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
|
const showTimelineForVisitor = ref<Misskey.entities.MetaClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
|
const showActivitiesForVisitor = ref<Misskey.entities.MetaClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
|
||||||
|
|
||||||
const iconUrl = ref(meta.iconUrl);
|
const iconUrl = ref(meta.iconUrl);
|
||||||
const app192IconUrl = ref(meta.app192IconUrl);
|
const app192IconUrl = ref(meta.app192IconUrl);
|
||||||
|
|
@ -195,11 +195,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
os.apiWithDialog('admin/update-meta', {
|
os.apiWithDialog('admin/update-meta', {
|
||||||
clientOptions: ({
|
clientOptions: {
|
||||||
entrancePageStyle: entrancePageStyle.value,
|
entrancePageStyle: entrancePageStyle.value,
|
||||||
showTimelineForVisitor: showTimelineForVisitor.value,
|
showTimelineForVisitor: showTimelineForVisitor.value,
|
||||||
showActivitiesForVisitor: showActivitiesForVisitor.value,
|
showActivitiesForVisitor: showActivitiesForVisitor.value,
|
||||||
} as ClientOptions) as any,
|
},
|
||||||
iconUrl: iconUrl.value,
|
iconUrl: iconUrl.value,
|
||||||
app192IconUrl: app192IconUrl.value,
|
app192IconUrl: app192IconUrl.value,
|
||||||
app512IconUrl: app512IconUrl.value,
|
app512IconUrl: app512IconUrl.value,
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section>
|
||||||
<div v-if="app.permission.length > 0">
|
<div v-if="permissions.length > 0">
|
||||||
<p>{{ i18n.tsx._auth.permission({ name }) }}</p>
|
<p>{{ i18n.tsx._auth.permission({ name }) }}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="p in app.permission" :key="p">{{ (i18n.ts._permissions as any)[p] ?? p }}</li>
|
<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] ?? p }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div>
|
<div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div>
|
||||||
|
|
@ -37,6 +37,10 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
const app = computed(() => props.session.app);
|
const app = computed(() => props.session.app);
|
||||||
|
|
||||||
|
const permissions = computed(() => {
|
||||||
|
return props.session.app.permission.filter((p): p is typeof Misskey.permissions[number] => typeof p === 'string');
|
||||||
|
});
|
||||||
|
|
||||||
const name = computed(() => {
|
const name = computed(() => {
|
||||||
const el = window.document.createElement('div');
|
const el = window.document.createElement('div');
|
||||||
el.textContent = app.value.name;
|
el.textContent = app.value.name;
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ async function run() {
|
||||||
const version = utils.getLangVersion(flash.value.script);
|
const version = utils.getLangVersion(flash.value.script);
|
||||||
const isLegacy = getIsLegacy(version);
|
const isLegacy = getIsLegacy(version);
|
||||||
|
|
||||||
const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
|
const { Interpreter, Parser, values } = (isLegacy ? (await import('@syuilo/aiscript-0-19-0')) : await import('@syuilo/aiscript')) as typeof import('@syuilo/aiscript');
|
||||||
|
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #key>{{ i18n.ts.permission }}</template>
|
<template #key>{{ i18n.ts.permission }}</template>
|
||||||
<template #value>
|
<template #value>
|
||||||
<ul style="margin-top: 0; margin-bottom: 0;">
|
<ul style="margin-top: 0; margin-bottom: 0;">
|
||||||
<li v-for="permission in plugin.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li>
|
<li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li>
|
||||||
<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
|
<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1083,7 +1083,7 @@ function removePinnedList() {
|
||||||
function enableAllDataSaver() {
|
function enableAllDataSaver() {
|
||||||
const g = { ...prefer.s.dataSaver };
|
const g = { ...prefer.s.dataSaver };
|
||||||
|
|
||||||
Object.keys(g).forEach((key) => { (g as any)[key] = true; });
|
(Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = true; });
|
||||||
|
|
||||||
dataSaver.value = g;
|
dataSaver.value = g;
|
||||||
}
|
}
|
||||||
|
|
@ -1091,7 +1091,7 @@ function enableAllDataSaver() {
|
||||||
function disableAllDataSaver() {
|
function disableAllDataSaver() {
|
||||||
const g = { ...prefer.s.dataSaver };
|
const g = { ...prefer.s.dataSaver };
|
||||||
|
|
||||||
Object.keys(g).forEach((key) => { (g as any)[key] = false; });
|
(Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = false; });
|
||||||
|
|
||||||
dataSaver.value = g;
|
dataSaver.value = g;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export type Plugin = {
|
||||||
version: string;
|
version: string;
|
||||||
author?: string;
|
author?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
permissions?: string[];
|
permissions?: (typeof Misskey.permissions)[number][];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AiScriptPluginMeta = {
|
export type AiScriptPluginMeta = {
|
||||||
|
|
@ -34,7 +34,7 @@ export type AiScriptPluginMeta = {
|
||||||
version: string;
|
version: string;
|
||||||
author: string;
|
author: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
permissions?: string[];
|
permissions?: (typeof Misskey.permissions)[number][];
|
||||||
config?: Record<string, any>;
|
config?: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
|
||||||
version: version as string,
|
version: version as string,
|
||||||
author: author as string,
|
author: author as string,
|
||||||
description: description as string | undefined,
|
description: description as string | undefined,
|
||||||
permissions: permissions as string[] | undefined,
|
permissions: permissions as (typeof Misskey.permissions)[number][] | undefined,
|
||||||
config: config as Record<string, any> | undefined,
|
config: config as Record<string, any> | undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import { deckStore } from '@/ui/deck/deck-store.js';
|
||||||
import { unisonReload } from '@/utility/unison-reload.js';
|
import { unisonReload } from '@/utility/unison-reload.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import type { SoundStore } from '@/preferences/def.js';
|
||||||
|
|
||||||
// TODO: そのうち消す
|
// TODO: そのうち消す
|
||||||
export function migrateOldSettings() {
|
export function migrateOldSettings() {
|
||||||
|
|
@ -126,10 +127,10 @@ export function migrateOldSettings() {
|
||||||
prefer.commit('sound.masterVolume', store.s.sound_masterVolume);
|
prefer.commit('sound.masterVolume', store.s.sound_masterVolume);
|
||||||
prefer.commit('sound.notUseSound', store.s.sound_notUseSound);
|
prefer.commit('sound.notUseSound', store.s.sound_notUseSound);
|
||||||
prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
|
prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
|
||||||
prefer.commit('sound.on.note', store.s.sound_note as any);
|
prefer.commit('sound.on.note', store.s.sound_note as SoundStore);
|
||||||
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
|
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as SoundStore);
|
||||||
prefer.commit('sound.on.notification', store.s.sound_notification as any);
|
prefer.commit('sound.on.notification', store.s.sound_notification as SoundStore);
|
||||||
prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
|
prefer.commit('sound.on.reaction', store.s.sound_reaction as SoundStore);
|
||||||
prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
|
prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
|
||||||
prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
|
prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,12 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { markRaw, ref } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import lightTheme from '@@/themes/l-light.json5';
|
|
||||||
import darkTheme from '@@/themes/d-green-lime.json5';
|
|
||||||
import { prefersReducedMotion } from '@@/js/config.js';
|
import { prefersReducedMotion } from '@@/js/config.js';
|
||||||
import { hemisphere } from '@@/js/intl-const.js';
|
import { hemisphere } from '@@/js/intl-const.js';
|
||||||
import type { DeviceKind } from '@/utility/device-kind.js';
|
import type { DeviceKind } from '@/utility/device-kind.js';
|
||||||
import type { Plugin } from '@/plugin.js';
|
|
||||||
import type { TIPS } from '@/tips.js';
|
import type { TIPS } from '@/tips.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
|
||||||
import { Pizzax } from '@/lib/pizzax.js';
|
import { Pizzax } from '@/lib/pizzax.js';
|
||||||
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,15 @@ import { popup } from '@/os.js';
|
||||||
|
|
||||||
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
|
export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
|
||||||
|
|
||||||
|
type CompleteProps<T extends keyof CompleteInfo> = {
|
||||||
|
type: T;
|
||||||
|
value: CompleteInfo[T]['payload'];
|
||||||
|
};
|
||||||
|
|
||||||
|
function isCompleteType<T extends keyof CompleteInfo>(expectedType: T, props: CompleteProps<keyof CompleteInfo>): props is CompleteProps<T> {
|
||||||
|
return props.type === expectedType;
|
||||||
|
}
|
||||||
|
|
||||||
export class Autocomplete {
|
export class Autocomplete {
|
||||||
private suggestion: {
|
private suggestion: {
|
||||||
x: Ref<number>;
|
x: Ref<number>;
|
||||||
|
|
@ -253,19 +262,19 @@ export class Autocomplete {
|
||||||
/**
|
/**
|
||||||
* オートコンプリートする
|
* オートコンプリートする
|
||||||
*/
|
*/
|
||||||
private complete<T extends keyof CompleteInfo>({ type, value }: { type: T; value: CompleteInfo[T]['payload'] }) {
|
private complete<T extends keyof CompleteInfo>(props: CompleteProps<T>) {
|
||||||
this.close();
|
this.close();
|
||||||
|
|
||||||
const caret = Number(this.textarea.selectionStart);
|
const caret = Number(this.textarea.selectionStart);
|
||||||
|
|
||||||
if (type === 'user') {
|
if (isCompleteType('user', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`;
|
const acct = props.value.host === null ? props.value.username : `${props.value.username}@${toASCII(props.value.host)}`;
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = `${trimmedBefore}@${acct} ${after}`;
|
this.text = `${trimmedBefore}@${acct} ${after}`;
|
||||||
|
|
@ -276,7 +285,7 @@ export class Autocomplete {
|
||||||
const pos = trimmedBefore.length + (acct.length + 2);
|
const pos = trimmedBefore.length + (acct.length + 2);
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
} else if (type === 'hashtag') {
|
} else if (isCompleteType('hashtag', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
|
|
@ -284,15 +293,15 @@ export class Autocomplete {
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = `${trimmedBefore}#${value} ${after}`;
|
this.text = `${trimmedBefore}#${props.value} ${after}`;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
const pos = trimmedBefore.length + (value.length + 2);
|
const pos = trimmedBefore.length + (props.value.length + 2);
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
} else if (type === 'emoji') {
|
} else if (isCompleteType('emoji', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
|
|
@ -300,15 +309,15 @@ export class Autocomplete {
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = trimmedBefore + value + after;
|
this.text = trimmedBefore + props.value + after;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
const pos = trimmedBefore.length + value.length;
|
const pos = trimmedBefore.length + props.value.length;
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
} else if (type === 'emojiComplete') {
|
} else if (isCompleteType('emojiComplete', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
|
|
@ -316,15 +325,15 @@ export class Autocomplete {
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = trimmedBefore + value + after;
|
this.text = trimmedBefore + props.value + after;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
const pos = trimmedBefore.length + value.length;
|
const pos = trimmedBefore.length + props.value.length;
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
} else if (type === 'mfmTag') {
|
} else if (isCompleteType('mfmTag', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
|
|
@ -332,15 +341,15 @@ export class Autocomplete {
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = `${trimmedBefore}$[${value} ]${after}`;
|
this.text = `${trimmedBefore}$[${props.value} ]${after}`;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
const pos = trimmedBefore.length + (value.length + 3);
|
const pos = trimmedBefore.length + (props.value.length + 3);
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
} else if (type === 'mfmParam') {
|
} else if (isCompleteType('mfmParam', props)) {
|
||||||
const source = this.text;
|
const source = this.text;
|
||||||
|
|
||||||
const before = source.substring(0, caret);
|
const before = source.substring(0, caret);
|
||||||
|
|
@ -348,12 +357,12 @@ export class Autocomplete {
|
||||||
const after = source.substring(caret);
|
const after = source.substring(caret);
|
||||||
|
|
||||||
// 挿入
|
// 挿入
|
||||||
this.text = `${trimmedBefore}.${value}${after}`;
|
this.text = `${trimmedBefore}.${props.value}${after}`;
|
||||||
|
|
||||||
// キャレットを戻す
|
// キャレットを戻す
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
this.textarea.focus();
|
this.textarea.focus();
|
||||||
const pos = trimmedBefore.length + (value.length + 1);
|
const pos = trimmedBefore.length + (props.value.length + 1);
|
||||||
this.textarea.setSelectionRange(pos, pos);
|
this.textarea.setSelectionRange(pos, pos);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { i18n } from '@/i18n.js';
|
||||||
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
|
||||||
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
|
|
||||||
const name = 'ai';
|
const name = 'aichan';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
transparent: {
|
transparent: {
|
||||||
|
|
|
||||||
|
|
@ -93,12 +93,12 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
|
||||||
const menuOpened = ref(false);
|
const menuOpened = ref(false);
|
||||||
|
|
||||||
const headerTitle = computed<string>(() => {
|
const headerTitle = computed<string>(() => {
|
||||||
if (widgetProps.src === 'list' && widgetProps.list != null) {
|
if (widgetProps.src === 'list') {
|
||||||
return widgetProps.list.name;
|
return widgetProps.list != null ? widgetProps.list.name : '?';
|
||||||
} else if (widgetProps.src === 'antenna' && widgetProps.antenna != null) {
|
} else if (widgetProps.src === 'antenna') {
|
||||||
return widgetProps.antenna.name;
|
return widgetProps.antenna != null ? widgetProps.antenna.name : '?';
|
||||||
} else {
|
} else {
|
||||||
return (i18n.ts._timelines as any)[widgetProps.src] ?? '?';
|
return i18n.ts._timelines[widgetProps.src] ?? '?';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
const name = 'hashtags';
|
const name = 'trends';
|
||||||
|
|
||||||
const widgetPropsDef = {
|
const widgetPropsDef = {
|
||||||
showHeader: {
|
showHeader: {
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,5 @@ export const widgets = [
|
||||||
|
|
||||||
...federationWidgets,
|
...federationWidgets,
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
export type WidgetName = typeof widgets[number];
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
|
||||||
import { getDefaultFormValues } from '@/utility/form.js';
|
import { getDefaultFormValues } from '@/utility/form.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { deepClone } from '@/utility/clone.js';
|
import { deepClone } from '@/utility/clone.js';
|
||||||
|
import type { WidgetName } from './index.js';
|
||||||
|
|
||||||
export type Widget<P extends Record<string, unknown>> = {
|
export type Widget<P extends Record<string, unknown>> = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -31,7 +32,7 @@ export type WidgetComponentExpose = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useWidgetPropsManager = <F extends FormWithDefault>(
|
export const useWidgetPropsManager = <F extends FormWithDefault>(
|
||||||
name: string,
|
name: WidgetName,
|
||||||
propsDef: F,
|
propsDef: F,
|
||||||
props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
|
props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
|
||||||
emit: WidgetComponentEmits<GetFormResultType<F>>,
|
emit: WidgetComponentEmits<GetFormResultType<F>>,
|
||||||
|
|
|
||||||
|
|
@ -2230,6 +2230,7 @@ declare namespace entities {
|
||||||
MetaLite,
|
MetaLite,
|
||||||
MetaDetailedOnly,
|
MetaDetailedOnly,
|
||||||
MetaDetailed,
|
MetaDetailed,
|
||||||
|
MetaClientOptions,
|
||||||
UserWebhook,
|
UserWebhook,
|
||||||
SystemWebhook,
|
SystemWebhook,
|
||||||
AbuseReportNotificationRecipient,
|
AbuseReportNotificationRecipient,
|
||||||
|
|
@ -2820,6 +2821,9 @@ type MeDetailed = components['schemas']['MeDetailed'];
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type MeDetailedOnly = components['schemas']['MeDetailedOnly'];
|
type MeDetailedOnly = components['schemas']['MeDetailedOnly'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type MetaClientOptions = components['schemas']['MetaClientOptions'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type MetaDetailed = components['schemas']['MetaDetailed'];
|
type MetaDetailed = components['schemas']['MetaDetailed'];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
|
||||||
export type MetaLite = components['schemas']['MetaLite'];
|
export type MetaLite = components['schemas']['MetaLite'];
|
||||||
export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
|
export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
|
||||||
export type MetaDetailed = components['schemas']['MetaDetailed'];
|
export type MetaDetailed = components['schemas']['MetaDetailed'];
|
||||||
|
export type MetaClientOptions = components['schemas']['MetaClientOptions'];
|
||||||
export type UserWebhook = components['schemas']['UserWebhook'];
|
export type UserWebhook = components['schemas']['UserWebhook'];
|
||||||
export type SystemWebhook = components['schemas']['SystemWebhook'];
|
export type SystemWebhook = components['schemas']['SystemWebhook'];
|
||||||
export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
|
export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
|
||||||
|
|
|
||||||
|
|
@ -5441,7 +5441,7 @@ export type components = {
|
||||||
feedbackUrl: string | null;
|
feedbackUrl: string | null;
|
||||||
defaultDarkTheme: string | null;
|
defaultDarkTheme: string | null;
|
||||||
defaultLightTheme: string | null;
|
defaultLightTheme: string | null;
|
||||||
clientOptions: Record<string, never>;
|
clientOptions: components['schemas']['MetaClientOptions'];
|
||||||
disableRegistration: boolean;
|
disableRegistration: boolean;
|
||||||
emailRequiredForSignup: boolean;
|
emailRequiredForSignup: boolean;
|
||||||
enableHcaptcha: boolean;
|
enableHcaptcha: boolean;
|
||||||
|
|
@ -5540,6 +5540,12 @@ export type components = {
|
||||||
cacheRemoteSensitiveFiles: boolean;
|
cacheRemoteSensitiveFiles: boolean;
|
||||||
};
|
};
|
||||||
MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly'];
|
MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly'];
|
||||||
|
MetaClientOptions: {
|
||||||
|
/** @enum {string} */
|
||||||
|
entrancePageStyle: 'classic' | 'simple';
|
||||||
|
showTimelineForVisitor: boolean;
|
||||||
|
showActivitiesForVisitor: boolean;
|
||||||
|
};
|
||||||
UserWebhook: {
|
UserWebhook: {
|
||||||
/** Format: id */
|
/** Format: id */
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -9468,7 +9474,7 @@ export interface operations {
|
||||||
deeplIsPro: boolean;
|
deeplIsPro: boolean;
|
||||||
defaultDarkTheme: string | null;
|
defaultDarkTheme: string | null;
|
||||||
defaultLightTheme: string | null;
|
defaultLightTheme: string | null;
|
||||||
clientOptions: Record<string, never>;
|
clientOptions: components['schemas']['MetaClientOptions'];
|
||||||
description: string | null;
|
description: string | null;
|
||||||
disableRegistration: boolean;
|
disableRegistration: boolean;
|
||||||
impressumUrl: string | null;
|
impressumUrl: string | null;
|
||||||
|
|
@ -12724,7 +12730,12 @@ export interface operations {
|
||||||
description?: string | null;
|
description?: string | null;
|
||||||
defaultLightTheme?: string | null;
|
defaultLightTheme?: string | null;
|
||||||
defaultDarkTheme?: string | null;
|
defaultDarkTheme?: string | null;
|
||||||
clientOptions?: Record<string, never>;
|
clientOptions?: {
|
||||||
|
/** @enum {string} */
|
||||||
|
entrancePageStyle?: 'classic' | 'simple';
|
||||||
|
showTimelineForVisitor?: boolean;
|
||||||
|
showActivitiesForVisitor?: boolean;
|
||||||
|
};
|
||||||
cacheRemoteFiles?: boolean;
|
cacheRemoteFiles?: boolean;
|
||||||
cacheRemoteSensitiveFiles?: boolean;
|
cacheRemoteSensitiveFiles?: boolean;
|
||||||
emailRequiredForSignup?: boolean;
|
emailRequiredForSignup?: boolean;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue