Merge tag '2023.9.0' into merge-upstream

This commit is contained in:
riku6460 2023-09-25 12:43:07 +09:00
commit 87223add7b
No known key found for this signature in database
GPG key ID: 27414FA27DB94CF6
1235 changed files with 19016 additions and 13835 deletions

View file

@ -4,45 +4,110 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<MkModal
ref="dialogEl"
:preferType="'dialog'"
:zPriority="'low'"
@click="cancel"
<MkModalWindow
ref="dialog"
:width="500"
:height="550"
@close="cancel"
@closed="emit('closed')"
>
<div :class="$style.root" class="_gaps_m">
<I18n :src="i18n.ts._2fa.step1" tag="div">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
<template #header>{{ i18n.ts.setupOf2fa }}</template>
<div style="overflow-x: clip;">
<Transition
mode="out-in"
:enterActiveClass="$style.transition_x_enterActive"
:leaveActiveClass="$style.transition_x_leaveActive"
:enterFromClass="$style.transition_x_enterFrom"
:leaveToClass="$style.transition_x_leaveTo"
>
<template v-if="page === 0">
<div style="height: 100cqh; overflow: auto; text-align: center;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<I18n :src="i18n.ts._2fa.step1" tag="div">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
</template>
<template #b>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
</template>
</I18n>
<div>{{ i18n.ts._2fa.step2 }}<br>{{ i18n.ts._2fa.step2Click }}</div>
<a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
<MkKeyValue :copy="twoFactorData.url">
<template #key>{{ i18n.ts._2fa.step2Uri }}</template>
<template #value>{{ twoFactorData.url }}</template>
</MkKeyValue>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
<MkButton primary rounded gradate @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
</div>
</MkSpacer>
</div>
</template>
<template #b>
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
<template v-else-if="page === 1">
<div style="height: 100cqh; overflow: auto;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<div>{{ i18n.ts._2fa.step3Title }}</div>
<MkInput v-model="token" autocomplete="one-time-code"></MkInput>
<div>{{ i18n.ts._2fa.step3 }}</div>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton rounded @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
<MkButton primary rounded gradate @click="tokenDone">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
</div>
</MkSpacer>
</div>
</template>
</I18n>
<div>
{{ i18n.ts._2fa.step2 }}<br>
{{ i18n.ts._2fa.step2Click }}
</div>
<a :href="twoFactorData.url"><img :class="$style.qr" :src="twoFactorData.qr"></a>
<MkKeyValue :copy="twoFactorData.url">
<template #key>{{ i18n.ts._2fa.step2Url }}</template>
<template #value>{{ twoFactorData.url }}</template>
</MkKeyValue>
<div class="_buttons">
<MkButton primary @click="ok">{{ i18n.ts.next }}</MkButton>
<MkButton @click="cancel">{{ i18n.ts.cancel }}</MkButton>
</div>
<template v-else-if="page === 2">
<div style="height: 100cqh; overflow: auto;">
<MkSpacer :marginMin="20" :marginMax="28">
<div class="_gaps">
<div style="text-align: center;">{{ i18n.ts._2fa.setupCompleted }}🎉</div>
<div style="text-align: center;">{{ i18n.ts._2fa.step4 }}</div>
<div style="text-align: center; font-weight: bold;">{{ i18n.ts._2fa.checkBackupCodesBeforeCloseThisWizard }}</div>
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts._2fa.backupCodes }}</template>
<div class="_gaps">
<MkInfo warn>{{ i18n.ts._2fa.backupCodesDescription }}</MkInfo>
<div v-for="(code, i) in backupCodes" :key="code" class="_gaps_s">
<MkKeyValue :copy="code">
<template #key>#{{ i + 1 }}</template>
<template #value><code class="_monospace">{{ code }}</code></template>
</MkKeyValue>
</div>
</div>
</MkFolder>
</div>
<div class="_buttonsCenter" style="margin-top: 16px;">
<MkButton primary rounded gradate @click="allDone">{{ i18n.ts.done }}</MkButton>
</div>
</MkSpacer>
</div>
</template>
</Transition>
</div>
</MkModal>
</MkModalWindow>
</template>
<script lang="ts" setup>
import { shallowRef, ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkModal from '@/components/MkModal.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import { i18n } from '@/i18n';
import MkInput from '@/components/MkInput.vue';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import MkFolder from '@/components/MkFolder.vue';
import MkInfo from '@/components/MkInfo.vue';
import { confetti } from '@/scripts/confetti.js';
defineProps<{
twoFactorData: {
@ -52,36 +117,53 @@ defineProps<{
}>();
const emit = defineEmits<{
(ev: 'ok'): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
const cancel = () => {
emit('cancel');
emit('closed');
};
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const page = ref(0);
const token = ref<string | number | null>(null);
const backupCodes = ref<string[]>();
const ok = () => {
emit('ok');
emit('closed');
};
function cancel() {
dialog.value.close();
}
async function tokenDone() {
const res = await os.apiWithDialog('i/2fa/done', {
token: token.value.toString(),
});
backupCodes.value = res.backupCodes;
page.value++;
confetti({
duration: 1000 * 3,
});
}
function allDone() {
dialog.value.close();
}
</script>
<style lang="scss" module>
.root {
position: relative;
margin: auto;
padding: 32px;
min-width: 320px;
max-width: calc(100svw - 64px);
box-sizing: border-box;
background: var(--panel);
border-radius: var(--radius);
.transition_x_enterActive,
.transition_x_leaveActive {
transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
}
.transition_x_enterFrom {
opacity: 0;
transform: translateX(50px);
}
.transition_x_leaveTo {
opacity: 0;
transform: translateX(-50px);
}
.qr {
width: 20em;
max-width: 100%;
width: 200px;
max-width: 100%;
}
</style>

View file

@ -8,40 +8,44 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #label>{{ i18n.ts['2fa'] }}</template>
<div v-if="$i" class="_gaps_s">
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodes === 'partial'" warn class="info">
{{ i18n.ts._2fa.twoFactorBackupSecretWarning }}
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'partial'" warn>
{{ i18n.ts._2fa.backupCodeUsedWarning }}
</MkInfo>
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodes === 'none'" warn class="info">
{{ i18n.ts._2fa.twoFactorBackupSecretExhausted }}
<MkInfo v-if="$i.twoFactorEnabled && $i.twoFactorBackupCodesStock === 'none'" warn>
{{ i18n.ts._2fa.backupCodesExhaustedWarning }}
</MkInfo>
<MkFolder>
<MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-shield-lock"></i></template>
<template #label>{{ i18n.ts.totp }}</template>
<template #caption>{{ i18n.ts.totpDescription }}</template>
<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--success)"></i></template>
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
<template v-if="$i.securityKeysList.length > 0">
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
</template>
<MkButton v-else @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
<MkButton v-else danger @click="unregisterTOTP">{{ i18n.ts.unregister }}</MkButton>
</div>
<MkButton v-else-if="!twoFactorData && !$i.twoFactorEnabled" @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
<MkButton v-else-if="!$i.twoFactorEnabled" primary gradate @click="registerTOTP">{{ i18n.ts._2fa.registerTOTP }}</MkButton>
</MkFolder>
<MkFolder>
<template #icon><i class="ti ti-key"></i></template>
<template #label>{{ i18n.ts.securityKeyAndPasskey }}</template>
<div class="_gaps_s">
<MkInfo>{{ i18n.ts._2fa.securityKeyInfo }}</MkInfo>
<MkInfo>
{{ i18n.ts._2fa.securityKeyInfo }}
</MkInfo>
<MkInfo v-if="!WebAuthnSupported()" warn>
<MkInfo v-if="!webAuthnSupported()" warn>
{{ i18n.ts._2fa.securityKeyNotSupported }}
</MkInfo>
<MkInfo v-else-if="WebAuthnSupported() && !$i.twoFactorEnabled" warn>
<MkInfo v-else-if="webAuthnSupported() && !$i.twoFactorEnabled" warn>
{{ i18n.ts._2fa.registerTOTPBeforeKey }}
</MkInfo>
@ -68,16 +72,16 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { supported as WebAuthnSupported, create as WebAuthnCreate, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
import { ref, defineAsyncComponent } from 'vue';
import { supported as webAuthnSupported, create as webAuthnCreate, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
// : meUpdatedrefreshAccount
@ -88,64 +92,32 @@ withDefaults(defineProps<{
});
const usePasswordLessLogin = $computed(() => $i?.usePasswordLessLogin ?? false);
let twoFactorData = $ref<{ qr: string; url: string; secret: string; label: string; issuer: string } | null>(null);
async function registerTOTP(): Promise<void> {
const password = await os.inputText({
title: i18n.ts._2fa.registerTOTP,
text: i18n.ts._2fa.passwordToTOTP,
type: 'password',
autocomplete: 'current-password',
});
if (password.canceled) return;
const auth = await os.authenticateDialog();
if (auth.canceled) return;
twoFactorData = <{ qr: string; url: string; secret: string; label: string; issuer: string }>
await os.apiWithDialog('i/2fa/register', {
password: password.result,
const twoFactorData = await os.apiWithDialog('i/2fa/register', {
password: auth.result.password,
token: auth.result.token,
});
const qrdialog = await new Promise<boolean>(res => {
os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), {
twoFactorData,
}, {
'ok': () => res(true),
'cancel': () => res(false),
}, 'closed');
});
if (!qrdialog) return;
const token = await os.inputNumber({
title: i18n.ts._2fa.step3Title,
text: i18n.ts._2fa.step3,
autocomplete: 'one-time-code',
});
if (token.canceled) return;
const { backupCodes } = <{ backupCodes: string[] }>
await os.apiWithDialog('i/2fa/done', {
token: token.result.toString(),
});
await os.alert({
type: 'success',
text: i18n.t('_2fa.step4', { codes: backupCodes.map((code, index) => `${String(index + 1).padStart(2, '0')}. ${code}`).join('\n') }),
});
os.popup(defineAsyncComponent(() => import('./2fa.qrdialog.vue')), {
twoFactorData,
}, {}, 'closed');
}
function unregisterTOTP(): void {
os.inputText({
title: i18n.ts.password,
type: 'password',
autocomplete: 'current-password',
}).then(({ canceled, result: password }) => {
if (canceled) return;
os.apiWithDialog('i/2fa/unregister', {
password: password,
}).catch(error => {
os.alert({
type: 'error',
text: error,
});
async function unregisterTOTP(): Promise<void> {
const auth = await os.authenticateDialog();
if (auth.canceled) return;
os.apiWithDialog('i/2fa/unregister', {
password: auth.result.password,
token: auth.result.token,
}).catch(error => {
os.alert({
type: 'error',
text: error,
});
});
}
@ -171,15 +143,12 @@ async function unregisterKey(key): Promise<void> {
});
if (confirm.canceled) return;
const password = await os.inputText({
title: i18n.ts.password,
type: 'password',
autocomplete: 'current-password',
});
if (password.canceled) return;
const auth = await os.authenticateDialog();
if (auth.canceled) return;
await os.apiWithDialog('i/2fa/remove-key', {
password: password.result,
password: auth.result.password,
token: auth.result.token,
credentialId: key.id,
})
.then(() => os.success())
@ -205,17 +174,14 @@ async function renameKey(key): Promise<void> {
});
}
async function addSecurityKey(): Promise<void> {
const password = await os.inputText({
title: i18n.ts.password,
type: 'password',
autocomplete: 'current-password',
});
if (password.canceled) return;
async function addSecurityKey() {
const auth = await os.authenticateDialog();
if (auth.canceled) return;
const registrationOptions = parseCreationOptionsFromJSON({
publicKey: await os.apiWithDialog('i/2fa/register-key', {
password: password.result,
password: auth.result.password,
token: auth.result.token,
}),
});
@ -229,15 +195,19 @@ async function addSecurityKey(): Promise<void> {
if (name.canceled) return;
const credential = await os.promiseDialog(
WebAuthnCreate(registrationOptions),
webAuthnCreate(registrationOptions),
null,
() => {}, // reject
i18n.ts._2fa.tapSecurityKey,
);
if (!credential) return;
const auth2 = await os.authenticateDialog();
if (auth2.canceled) return;
await os.apiWithDialog('i/2fa/key-done', {
password: password.result,
password: auth.result.password,
token: auth.result.token,
name: name.result,
credential: credential.toJSON(),
});

View file

@ -23,10 +23,10 @@ import { defineAsyncComponent, ref } from 'vue';
import type * as Misskey from 'misskey-js';
import FormSuspense from '@/components/form/suspense.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
const storedAccounts = ref<any>(null);

View file

@ -15,9 +15,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { defineAsyncComponent, ref } from 'vue';
import FormLink from '@/components/form/link.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const isDesktop = ref(window.innerWidth >= 1100);

View file

@ -47,12 +47,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import FormPagination from '@/components/MkPagination.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import { infoImageUrl } from '@/instance';
import { infoImageUrl } from '@/instance.js';
const list = ref<any>(null);

View file

@ -17,11 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
import { ref, watch } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue';
import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import * as os from '@/os.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
const localCustomCss = ref(miLocalStorage.getItem('customCss') ?? '');

View file

@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed } from 'vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import { deckStore } from '@/ui/deck/deck-store';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { deckStore } from '@/ui/deck/deck-store.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
const useSimpleUiForNonRootPages = computed(deckStore.makeGetterSetter('useSimpleUiForNonRootPages'));

View file

@ -50,15 +50,15 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import tinycolor from 'tinycolor2';
import * as os from '@/os';
import * as os from '@/os.js';
import MkPagination from '@/components/MkPagination.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import { i18n } from '@/i18n';
import bytes from '@/filters/bytes';
import { dateString } from '@/filters/date';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n.js';
import bytes from '@/filters/bytes.js';
import { dateString } from '@/filters/date.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkSelect from '@/components/MkSelect.vue';
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu';
import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
let sortMode = ref('+size');
const pagination = {

View file

@ -64,13 +64,13 @@ import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import FormSplit from '@/components/form/split.vue';
import * as os from '@/os';
import bytes from '@/filters/bytes';
import { defaultStore } from '@/store';
import * as os from '@/os.js';
import bytes from '@/filters/bytes.js';
import { defaultStore } from '@/store.js';
import MkChart from '@/components/MkChart.vue';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { $i } from '@/account.js';
const fetching = ref(true);
const usage = ref<any>(null);

View file

@ -53,11 +53,11 @@ import FormSection from '@/components/form/section.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkInput from '@/components/MkInput.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { instance } from '@/instance';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { instance } from '@/instance.js';
const emailAddress = ref($i!.email);
@ -67,18 +67,16 @@ const onChangeReceiveAnnouncementEmail = (v) => {
});
};
const saveEmailAddress = () => {
os.inputText({
title: i18n.ts.password,
type: 'password',
}).then(({ canceled, result: password }) => {
if (canceled) return;
os.apiWithDialog('i/update-email', {
password: password,
email: emailAddress.value,
});
async function saveEmailAddress() {
const auth = await os.authenticateDialog();
if (auth.canceled) return;
os.apiWithDialog('i/update-email', {
password: auth.result.password,
token: auth.result.token,
email: emailAddress.value,
});
};
}
const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention'));
const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply'));

View file

@ -30,6 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
<MkSwitch v-model="showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
<MkFolder>
<template #label>{{ i18n.ts.pinnedList }}</template>
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
<MkButton v-if="defaultStore.reactiveState.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
<MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
</MkFolder>
</div>
</FormSection>
@ -40,13 +46,18 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
<MkSwitch v-model="largeNoteReactions">{{ i18n.ts.largeNoteReactions }}</MkSwitch>
<MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch>
<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
<MkRadios v-model="reactionsDisplaySize">
<template #label>{{ i18n.ts.reactionsDisplaySize }}</template>
<option value="small">{{ i18n.ts.small }}</option>
<option value="medium">{{ i18n.ts.medium }}</option>
<option value="large">{{ i18n.ts.large }}</option>
</MkRadios>
</div>
<MkSelect v-model="instanceTicker">
@ -90,6 +101,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
</MkRadios>
<MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
</div>
</FormSection>
@ -102,6 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="useBlurEffect">{{ i18n.ts.useBlurEffect }}</MkSwitch>
<MkSwitch v-model="useBlurEffectForModal">{{ i18n.ts.useBlurEffectForModal }}</MkSwitch>
<MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch>
<MkSwitch v-model="highlightSensitiveMedia">{{ i18n.ts.highlightSensitiveMedia }}</MkSwitch>
<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
@ -135,6 +149,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
</div>
<MkSelect v-model="serverDisconnectedBehavior">
<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@ -169,6 +184,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import * as Misskey from 'misskey-js';
import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkRadios from '@/components/MkRadios.vue';
@ -178,13 +194,15 @@ import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkLink from '@/components/MkLink.vue';
import { langs } from '@/config';
import { defaultStore } from '@/store';
import * as os from '@/os';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { langs } from '@/config.js';
import { defaultStore } from '@/store.js';
import * as os from '@/os.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
import { globalEvents } from '@/events';
import { claimAchievement } from '@/scripts/achievements.js';
const lang = ref(miLocalStorage.getItem('lang'));
const fontSize = ref(miLocalStorage.getItem('fontSize'));
@ -204,7 +222,7 @@ const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDev
const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
const showClipButtonInNoteFooter = computed(defaultStore.makeGetterSetter('showClipButtonInNoteFooter'));
const largeNoteReactions = computed(defaultStore.makeGetterSetter('largeNoteReactions'));
const reactionsDisplaySize = computed(defaultStore.makeGetterSetter('reactionsDisplaySize'));
const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
@ -217,6 +235,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
@ -231,6 +250,7 @@ const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('
const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
const showTimelineReplies = computed(defaultStore.makeGetterSetter('showTimelineReplies'));
const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
@ -264,6 +284,9 @@ watch([
instanceTicker,
overridedDeviceKind,
mediaListWithOneImageAppearance,
reactionsDisplaySize,
highlightSensitiveMedia,
keepScreenOn,
], async () => {
await reloadAsk();
});
@ -296,6 +319,49 @@ function removeEmojiIndex(lang: string) {
os.promiseDialog(main());
}
async function setPinnedList() {
const lists = await os.api('users/lists/list');
const { canceled, result: list } = await os.select({
title: i18n.ts.selectList,
items: lists.map(x => ({
value: x, text: x.name,
})),
});
if (canceled) return;
defaultStore.set('pinnedUserLists', [list]);
}
function removePinnedList() {
defaultStore.set('pinnedUserLists', []);
}
let smashCount = 0;
let smashTimer: number | null = null;
function testNotification(): void {
const notification: Misskey.entities.Notification = {
id: Math.random().toString(),
createdAt: new Date().toUTCString(),
isRead: false,
type: 'test',
};
globalEvents.emit('clientNotification', notification);
//
smashCount++;
if (smashCount >= 10) {
claimAchievement('smashTestNotificationButton');
smashCount = 0;
}
if (smashTimer) {
clearTimeout(smashTimer);
}
smashTimer = window.setTimeout(() => {
smashCount = 0;
}, 300);
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);

View file

@ -113,11 +113,11 @@ import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os';
import { selectFile } from '@/scripts/select-file';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
import * as os from '@/os.js';
import { selectFile } from '@/scripts/select-file.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { $i } from '@/account.js';
const excludeMutingUsers = ref(false);
const excludeInactiveUsers = ref(false);

View file

@ -28,17 +28,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
import { i18n } from '@/i18n';
import { i18n } from '@/i18n.js';
import MkInfo from '@/components/MkInfo.vue';
import MkSuperMenu from '@/components/MkSuperMenu.vue';
import { signout, $i } from '@/account';
import { unisonReload } from '@/scripts/unison-reload';
import { instance } from '@/instance';
import { useRouter } from '@/router';
import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
import * as os from '@/os';
import { miLocalStorage } from '@/local-storage';
import { fetchCustomEmojis } from '@/custom-emojis';
import { signout, $i } from '@/account.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { instance } from '@/instance.js';
import { useRouter } from '@/router.js';
import { definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
import * as os from '@/os.js';
import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
const indexInfo = {
title: i18n.ts.settings,

View file

@ -19,10 +19,10 @@ import { ref, watch } from 'vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const instanceMutes = ref($i!.mutedInstances.join('\n'));
const changed = ref(false);

View file

@ -59,21 +59,20 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref } from 'vue';
import { toString } from 'misskey-js/built/acct';
import { UserDetailed } from 'misskey-js/built/entities';
import * as Misskey from 'misskey-js';
import FormInfo from '@/components/MkInfo.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkUserInfo from '@/components/MkUserInfo.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { $i } from '@/account';
import { unisonReload } from '@/scripts/unison-reload';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { $i } from '@/account.js';
import { unisonReload } from '@/scripts/unison-reload.js';
const moveToAccount = ref('');
const movedTo = ref<UserDetailed>();
const movedTo = ref<Misskey.entities.UserDetailed>();
const accountAliases = ref(['']);
async function init() {
@ -85,7 +84,7 @@ async function init() {
if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) {
const alsoKnownAs = await os.api('users/show', { userIds: $i.alsoKnownAs });
accountAliases.value = (alsoKnownAs && alsoKnownAs.length > 0) ? alsoKnownAs.map(user => `@${toString(user)}`) : [''];
accountAliases.value = (alsoKnownAs && alsoKnownAs.length > 0) ? alsoKnownAs.map(user => `@${Misskey.acct.toString(user)}`) : [''];
} else {
accountAliases.value = [''];
}

View file

@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedRenoteMuteItems.includes(item.id) }]">
<div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.mutee.id}`">
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
<MkUserCardMini :user="item.mutee"/>
</MkA>
<button class="_button" :class="$style.userToggle" @click="toggleRenoteMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<div v-for="item in items" :key="item.mutee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedMuteItems.includes(item.id) }]">
<div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.mutee.id}`">
<MkA :class="$style.userItemMainBody" :to="userPage(item.mutee)">
<MkUserCardMini :user="item.mutee"/>
</MkA>
<button class="_button" :class="$style.userToggle" @click="toggleMuteItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div class="_gaps_s">
<div v-for="item in items" :key="item.blockee.id" :class="[$style.userItem, { [$style.userItemOpend]: expandedBlockItems.includes(item.id) }]">
<div :class="$style.userItemMain">
<MkA :class="$style.userItemMainBody" :to="`/user-info/${item.blockee.id}`">
<MkA :class="$style.userItemMainBody" :to="userPage(item.blockee)">
<MkUserCardMini :user="item.blockee"/>
</MkA>
<button class="_button" :class="$style.userToggle" @click="toggleBlockItem(item)"><i :class="$style.chevron" class="ti ti-chevron-down"></i></button>
@ -107,12 +107,12 @@ import MkPagination from '@/components/MkPagination.vue';
import MkTab from '@/components/MkTab.vue';
import FormInfo from '@/components/MkInfo.vue';
import FormLink from '@/components/form/link.vue';
import { userPage } from '@/filters/user';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { userPage } from '@/filters/user.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
import * as os from '@/os';
import { infoImageUrl } from '@/instance';
import * as os from '@/os.js';
import { infoImageUrl } from '@/instance.js';
let tab = $ref('renoteMute');

View file

@ -51,13 +51,13 @@ import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import FormSlot from '@/components/form/slot.vue';
import MkContainer from '@/components/MkContainer.vue';
import * as os from '@/os';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar';
import { defaultStore } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { deepClone } from '@/scripts/clone';
import { defaultStore } from '@/store.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { deepClone } from '@/scripts/clone.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));

View file

@ -12,6 +12,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormLink @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink>
</div>
</FormSection>
<FormSection>
<div class="_gaps_m">
<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
</div>
</FormSection>
<FormSection>
<template #label>{{ i18n.ts.pushNotification }}</template>
@ -35,10 +40,10 @@ import { defineAsyncComponent } from 'vue';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
import { notificationTypes } from '@/const';
@ -83,6 +88,10 @@ function onChangeSendReadMessage(v: boolean) {
});
}
function testNotification(): void {
os.api('notifications/test-notification');
}
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);

View file

@ -84,12 +84,12 @@ import MkFolder from '@/components/MkFolder.vue';
import FormInfo from '@/components/MkInfo.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { signout, $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { unisonReload } from '@/scripts/unison-reload';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
import { signout, $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import FormSection from '@/components/form/section.vue';
const reportError = computed(defaultStore.makeGetterSetter('reportError'));
@ -113,14 +113,12 @@ async function deleteAccount() {
if (canceled) return;
}
const { canceled, result: password } = await os.inputText({
title: i18n.ts.password,
type: 'password',
});
if (canceled) return;
const auth = await os.authenticateDialog();
if (auth.canceled) return;
await os.apiWithDialog('i/delete-account', {
password: password,
password: auth.result.password,
token: auth.result.token,
});
await os.alert({

View file

@ -19,16 +19,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { defineAsyncComponent, nextTick, ref } from 'vue';
import { compareVersions } from 'compare-versions';
import { Interpreter, Parser, utils } from '@syuilo/aiscript';
import { v4 as uuid } from 'uuid';
import MkTextarea from '@/components/MkTextarea.vue';
import MkButton from '@/components/MkButton.vue';
import FormInfo from '@/components/MkInfo.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { ColdDeviceStorage } from '@/store.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const parser = new Parser();
const code = ref(null);
@ -44,6 +45,14 @@ function installPlugin({ id, meta, src, token }) {
}));
}
function isSupportedAiScriptVersion(version: string): boolean {
try {
return (compareVersions(version, '0.12.0') >= 0);
} catch (err) {
return false;
}
}
async function install() {
if (code.value == null) return;
@ -54,7 +63,7 @@ async function install() {
text: 'No language version annotation found :(',
});
return;
} else if (!(lv.startsWith('0.12.') || lv.startsWith('0.13.'))) {
} else if (!isSupportedAiScriptVersion(lv)) {
os.alert({
type: 'error',
text: `aiscript version '${lv}' is not supported :(`,

View file

@ -10,28 +10,49 @@ SPDX-License-Identifier: AGPL-3.0-only
<FormSection>
<template #label>{{ i18n.ts.manage }}</template>
<div class="_gaps_s">
<div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_s" style="padding: 20px;">
<span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
<div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_m" style="padding: 20px;">
<div class="_gaps_s">
<span style="display: flex; align-items: center;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
<MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
</div>
<MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ plugin.author }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ plugin.description }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.permission }}</template>
<template #value>{{ plugin.permission }}</template>
</MkKeyValue>
<div class="_gaps_s">
<MkKeyValue>
<template #key>{{ i18n.ts.author }}</template>
<template #value>{{ plugin.author }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.description }}</template>
<template #value>{{ plugin.description }}</template>
</MkKeyValue>
<MkKeyValue>
<template #key>{{ i18n.ts.permission }}</template>
<template #value>
<ul style="margin-top: 0; margin-bottom: 0;">
<li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
</ul>
</template>
</MkKeyValue>
</div>
<div class="_buttons">
<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="ti ti-settings"></i> {{ i18n.ts.settings }}</MkButton>
<MkButton inline danger @click="uninstall(plugin)"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
</div>
<MkFolder>
<template #icon><i class="ti ti-code"></i></template>
<template #label>{{ i18n.ts._plugin.viewSource }}</template>
<div class="_gaps_s">
<div class="_buttons">
<MkButton inline @click="copy(plugin)"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
</div>
<MkCode :code="plugin.src ?? ''"/>
</div>
</MkFolder>
</div>
</div>
</FormSection>
@ -44,12 +65,15 @@ import FormLink from '@/components/form/link.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/MkButton.vue';
import MkCode from '@/components/MkCode.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import * as os from '@/os';
import { ColdDeviceStorage } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { ColdDeviceStorage } from '@/store.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const plugins = ref(ColdDeviceStorage.get('plugins'));
@ -61,6 +85,11 @@ function uninstall(plugin) {
});
}
function copy(plugin) {
copyToClipboard(plugin.src ?? '');
os.success();
}
// TODO: storeactionAiScriptAPI
async function config(plugin) {
const config = plugin.config;

View file

@ -42,15 +42,15 @@ import { v4 as uuid } from 'uuid';
import FormSection from '@/components/form/section.vue';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import * as os from '@/os';
import { ColdDeviceStorage, defaultStore } from '@/store';
import { unisonReload } from '@/scripts/unison-reload';
import { useStream } from '@/stream';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { version, host } from '@/config';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import * as os from '@/os.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { unisonReload } from '@/scripts/unison-reload.js';
import { useStream } from '@/stream.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { version, host } from '@/config.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
const { t, ts } = i18n;
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [

View file

@ -71,11 +71,11 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
let isLocked = $ref($i.isLocked);
let autoAcceptFollowed = $ref($i.autoAcceptFollowed);

View file

@ -76,6 +76,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
</Sortable>
<MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
</div>
</MkFolder>
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
@ -111,14 +113,15 @@ import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormSlot from '@/components/form/slot.vue';
import { selectFile } from '@/scripts/select-file';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { langmap } from '@/scripts/langmap';
import { definePageMetadata } from '@/scripts/page-metadata';
import { claimAchievement } from '@/scripts/achievements';
import { defaultStore } from '@/store';
import { selectFile } from '@/scripts/select-file.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { langmap } from '@/scripts/langmap.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { defaultStore } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));

View file

@ -67,11 +67,11 @@ import FromSlot from '@/components/form/slot.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { deepClone } from '@/scripts/clone';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { deepClone } from '@/scripts/clone.js';
let reactions = $ref(deepClone(defaultStore.state.reactions));

View file

@ -33,11 +33,11 @@ import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormSlot from '@/components/form/slot.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { $i } from '@/account';
import { definePageMetadata } from '@/scripts/page-metadata';
import { defaultStore } from '@/store';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { defaultStore } from '@/store.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
function save() {

View file

@ -45,9 +45,9 @@ import FormSection from '@/components/form/section.vue';
import FormSlot from '@/components/form/slot.vue';
import MkButton from '@/components/MkButton.vue';
import MkPagination from '@/components/MkPagination.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const pagination = {
endpoint: 'i/signin-history' as const,
@ -55,13 +55,6 @@ const pagination = {
};
async function change() {
const { canceled: canceled1, result: currentPassword } = await os.inputText({
title: i18n.ts.currentPassword,
type: 'password',
autocomplete: 'current-password',
});
if (canceled1) return;
const { canceled: canceled2, result: newPassword } = await os.inputText({
title: i18n.ts.newPassword,
type: 'password',
@ -84,21 +77,23 @@ async function change() {
return;
}
const auth = await os.authenticateDialog();
if (auth.canceled) return;
os.apiWithDialog('i/change-password', {
currentPassword,
currentPassword: auth.result.password,
token: auth.result.token,
newPassword,
});
}
function regenerateToken() {
os.inputText({
title: i18n.ts.password,
type: 'password',
}).then(({ canceled, result: password }) => {
if (canceled) return;
os.api('i/regenerate-token', {
password: password,
});
async function regenerateToken() {
const auth = await os.authenticateDialog();
if (auth.canceled) return;
os.api('i/regenerate-token', {
password: auth.result.password,
token: auth.result.token,
});
}

View file

@ -25,8 +25,8 @@ import { } from 'vue';
import MkSelect from '@/components/MkSelect.vue';
import MkButton from '@/components/MkButton.vue';
import MkRange from '@/components/MkRange.vue';
import { i18n } from '@/i18n';
import { playFile, soundsTypes } from '@/scripts/sound';
import { i18n } from '@/i18n.js';
import { playFile, soundsTypes } from '@/scripts/sound.js';
const props = defineProps<{
type: string;

View file

@ -32,9 +32,9 @@ import MkRange from '@/components/MkRange.vue';
import MkButton from '@/components/MkButton.vue';
import FormSection from '@/components/form/section.vue';
import MkFolder from '@/components/MkFolder.vue';
import { soundConfigStore } from '@/scripts/sound';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { soundConfigStore } from '@/scripts/sound.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const masterVolume = computed(soundConfigStore.makeGetterSetter('sound_masterVolume'));

View file

@ -93,9 +93,9 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRadios from '@/components/MkRadios.vue';
import MkButton from '@/components/MkButton.vue';
import MkRange from '@/components/MkRange.vue';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { deepClone } from '@/scripts/clone';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { deepClone } from '@/scripts/clone.js';
const props = defineProps<{
_id: string;

View file

@ -20,10 +20,10 @@ import { v4 as uuid } from 'uuid';
import XStatusbar from './statusbar.statusbar.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const statusbars = defaultStore.reactiveState.statusbars;

View file

@ -21,11 +21,11 @@ import { } from 'vue';
import JSON5 from 'json5';
import MkTextarea from '@/components/MkTextarea.vue';
import MkButton from '@/components/MkButton.vue';
import { applyTheme, validateTheme } from '@/scripts/theme';
import * as os from '@/os';
import { applyTheme, validateTheme } from '@/scripts/theme.js';
import * as os from '@/os.js';
import { addTheme, getThemes } from '@/theme-store';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
let installThemeCode = $ref(null);

View file

@ -37,12 +37,12 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkSelect from '@/components/MkSelect.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import { Theme, getBuiltinThemesRef } from '@/scripts/theme';
import copyToClipboard from '@/scripts/copy-to-clipboard';
import * as os from '@/os';
import { Theme, getBuiltinThemesRef } from '@/scripts/theme.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.js';
import { getThemes, removeTheme } from '@/theme-store';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef();

View file

@ -78,16 +78,16 @@ import MkSelect from '@/components/MkSelect.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import MkButton from '@/components/MkButton.vue';
import { getBuiltinThemesRef } from '@/scripts/theme';
import { selectFile } from '@/scripts/select-file';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
import { ColdDeviceStorage, defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { instance } from '@/instance';
import { uniqueBy } from '@/scripts/array';
import { getBuiltinThemesRef } from '@/scripts/theme.js';
import { selectFile } from '@/scripts/select-file.js';
import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
import { ColdDeviceStorage, defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import { uniqueBy } from '@/scripts/array.js';
import { fetchThemes, getThemes } from '@/theme-store';
import { definePageMetadata } from '@/scripts/page-metadata';
import { miLocalStorage } from '@/local-storage';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { miLocalStorage } from '@/local-storage.js';
const installedThemes = ref(getThemes());
const builtinThemes = getBuiltinThemesRef();

View file

@ -47,10 +47,10 @@ import MkInput from '@/components/MkInput.vue';
import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import { useRouter } from '@/router';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { useRouter } from '@/router.js';
const router = useRouter();

View file

@ -44,9 +44,9 @@ import MkInput from '@/components/MkInput.vue';
import FormSection from '@/components/form/section.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
let name = $ref('');
let url = $ref('');

View file

@ -37,8 +37,8 @@ import { } from 'vue';
import MkPagination from '@/components/MkPagination.vue';
import FormSection from '@/components/form/section.vue';
import FormLink from '@/components/form/link.vue';
import { definePageMetadata } from '@/scripts/page-metadata';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
const pagination = {
endpoint: 'i/webhooks/list' as const,

View file

@ -40,12 +40,12 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
import MkButton from '@/components/MkButton.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkTab from '@/components/MkTab.vue';
import * as os from '@/os';
import number from '@/filters/number';
import { defaultStore } from '@/store';
import { $i } from '@/account';
import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
import * as os from '@/os.js';
import number from '@/filters/number.js';
import { defaultStore } from '@/store.js';
import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';
const render = (mutedWords) => mutedWords.map(x => {
if (Array.isArray(x)) {