Merge branch 'develop' into pizzax-indexeddb
This commit is contained in:
5
packages/client/@types/theme.d.ts
vendored
Normal file
5
packages/client/@types/theme.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Theme } from '../src/scripts/theme';
|
||||
|
||||
declare module '@/themes/*.json5' {
|
||||
export = Theme;
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"watch": "webpack --watch",
|
||||
"build": "webpack",
|
||||
"watch": "vite build --watch --mode development",
|
||||
"build": "vite build",
|
||||
"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
|
||||
},
|
||||
"resolutions": {
|
||||
@@ -10,50 +10,38 @@
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordapp/twemoji": "13.1.1",
|
||||
"@discordapp/twemoji": "14.0.2",
|
||||
"@fortawesome/fontawesome-free": "6.1.1",
|
||||
"@syuilo/aiscript": "0.11.1",
|
||||
"@typescript-eslint/parser": "5.20.0",
|
||||
"@vue/compiler-sfc": "3.2.33",
|
||||
"abort-controller": "3.0.0",
|
||||
"autobind-decorator": "2.4.0",
|
||||
"autosize": "5.0.1",
|
||||
"autwh": "0.1.0",
|
||||
"blurhash": "1.1.5",
|
||||
"broadcast-channel": "4.11.0",
|
||||
"broadcast-channel": "4.12.0",
|
||||
"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
|
||||
"chart.js": "3.7.1",
|
||||
"chart.js": "3.8.0",
|
||||
"chartjs-adapter-date-fns": "2.0.0",
|
||||
"chartjs-plugin-gradient": "0.2.2",
|
||||
"chartjs-plugin-gradient": "0.5.0",
|
||||
"chartjs-plugin-zoom": "1.2.1",
|
||||
"compare-versions": "4.1.3",
|
||||
"content-disposition": "0.5.4",
|
||||
"css-loader": "6.7.1",
|
||||
"cssnano": "5.1.7",
|
||||
"date-fns": "2.28.0",
|
||||
"deepcopy": "2.1.0",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eslint": "8.14.0",
|
||||
"eslint-plugin-vue": "8.7.1",
|
||||
"eventemitter3": "4.0.7",
|
||||
"feed": "4.2.2",
|
||||
"glob": "7.2.0",
|
||||
"idb-keyval": "6.1.0",
|
||||
"insert-text-at-cursor": "0.3.0",
|
||||
"json5": "2.2.1",
|
||||
"json5-loader": "4.0.1",
|
||||
"katex": "0.15.3",
|
||||
"katex": "0.15.6",
|
||||
"matter-js": "0.18.0",
|
||||
"mfm-js": "0.21.0",
|
||||
"mfm-js": "0.22.1",
|
||||
"misskey-js": "0.0.14",
|
||||
"mocha": "9.2.2",
|
||||
"mocha": "10.0.0",
|
||||
"ms": "2.1.3",
|
||||
"nested-property": "4.0.0",
|
||||
"parse5": "6.0.1",
|
||||
"photoswipe": "5.2.4",
|
||||
"portscanner": "2.2.0",
|
||||
"postcss": "8.4.12",
|
||||
"postcss-loader": "6.2.1",
|
||||
"photoswipe": "5.2.7",
|
||||
"prismjs": "1.28.0",
|
||||
"private-ip": "2.3.3",
|
||||
"promise-limit": "2.7.0",
|
||||
@@ -65,36 +53,34 @@
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"sass": "1.50.1",
|
||||
"sass-loader": "12.6.0",
|
||||
"sass": "1.52.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"style-loader": "3.3.1",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.139.2",
|
||||
"throttle-debounce": "4.0.1",
|
||||
"three": "0.140.2",
|
||||
"throttle-debounce": "5.0.0",
|
||||
"tinycolor2": "1.4.2",
|
||||
"ts-loader": "9.2.8",
|
||||
"tsc-alias": "1.5.0",
|
||||
"tsconfig-paths": "3.14.1",
|
||||
"tsc-alias": "1.6.7",
|
||||
"tsconfig-paths": "4.0.0",
|
||||
"twemoji-parser": "14.0.0",
|
||||
"typescript": "4.6.3",
|
||||
"uuid": "8.3.2",
|
||||
"v-debounce": "0.1.2",
|
||||
"vanilla-tilt": "1.7.2",
|
||||
"vue": "3.2.33",
|
||||
"vue-loader": "17.0.0",
|
||||
"vue": "3.2.36",
|
||||
"vue-prism-editor": "2.0.0-alpha.2",
|
||||
"vue-router": "4.0.14",
|
||||
"vue-style-loader": "4.1.3",
|
||||
"vue-svg-loader": "0.17.0-beta.2",
|
||||
"vue-router": "4.0.15",
|
||||
"vuedraggable": "4.0.1",
|
||||
"webpack": "5.72.0",
|
||||
"webpack-cli": "4.9.2",
|
||||
"websocket": "1.0.34",
|
||||
"ws": "8.5.0"
|
||||
"@vitejs/plugin-vue": "2.3.3",
|
||||
"@vue/compiler-sfc": "3.2.36",
|
||||
"@rollup/plugin-alias": "3.1.9",
|
||||
"@rollup/plugin-json": "4.1.0",
|
||||
"rollup": "2.74.1",
|
||||
"typescript": "4.7.2",
|
||||
"vite": "2.9.9",
|
||||
"ws": "8.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/escape-regexp": "0.0.1",
|
||||
@@ -106,21 +92,21 @@
|
||||
"@types/matter-js": "0.17.7",
|
||||
"@types/mocha": "9.1.1",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/parse5": "6.0.3",
|
||||
"@types/punycode": "2.1.0",
|
||||
"@types/qrcode": "1.4.2",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/seedrandom": "3.0.2",
|
||||
"@types/throttle-debounce": "4.0.0",
|
||||
"@types/throttle-debounce": "5.0.0",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@types/webpack": "5.28.0",
|
||||
"@types/webpack-stream": "3.2.12",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.20.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.26.0",
|
||||
"@typescript-eslint/parser": "5.26.0",
|
||||
"eslint": "8.16.0",
|
||||
"eslint-plugin-vue": "9.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "9.5.4",
|
||||
"cypress": "9.7.0",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"start-server-and-test": "1.14.0"
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { del, get, set } from '@/scripts/idb-proxy';
|
||||
import { reactive } from 'vue';
|
||||
import { defineAsyncComponent, reactive } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { apiUrl } from '@/config';
|
||||
import { waiting, api, popup, popupMenu, success, alert } from '@/os';
|
||||
@@ -11,10 +11,10 @@ import { i18n } from './i18n';
|
||||
|
||||
type Account = misskey.entities.MeDetailed;
|
||||
|
||||
const data = localStorage.getItem('account');
|
||||
const accountData = localStorage.getItem('account');
|
||||
|
||||
// TODO: 外部からはreadonlyに
|
||||
export const $i = data ? reactive(JSON.parse(data) as Account) : null;
|
||||
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
|
||||
|
||||
export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function signout() {
|
||||
return Promise.all(registrations.map(registration => registration.unregister()));
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (err) {}
|
||||
//#endregion
|
||||
|
||||
document.cookie = `igi=; path=/`;
|
||||
@@ -104,8 +104,8 @@ function fetchAccount(token: string): Promise<Account> {
|
||||
});
|
||||
}
|
||||
|
||||
export function updateAccount(data) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
export function updateAccount(accountData) {
|
||||
for (const [key, value] of Object.entries(accountData)) {
|
||||
$i[key] = value;
|
||||
}
|
||||
localStorage.setItem('account', JSON.stringify($i));
|
||||
@@ -141,7 +141,7 @@ export async function openAccountMenu(opts: {
|
||||
onChoose?: (account: misskey.entities.UserDetailed) => void;
|
||||
}, ev: MouseEvent) {
|
||||
function showSigninDialog() {
|
||||
popup(import('@/components/signin-dialog.vue'), {}, {
|
||||
popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
success();
|
||||
@@ -150,7 +150,7 @@ export async function openAccountMenu(opts: {
|
||||
}
|
||||
|
||||
function createAccount() {
|
||||
popup(import('@/components/signup-dialog.vue'), {}, {
|
||||
popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
|
||||
done: res => {
|
||||
addAccount(res.id, res.i);
|
||||
switchAccountWithToken(res.i);
|
||||
|
@@ -37,7 +37,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const window = ref<InstanceType<typeof XWindow>>();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="bcekxzvu _card _gap">
|
||||
<div class="_content target">
|
||||
<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>
|
||||
<MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId">
|
||||
<MkA v-user-preview="report.targetUserId" class="info" :to="userPage(report.targetUser)">
|
||||
<MkUserName class="name" :user="report.targetUser"/>
|
||||
<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
|
||||
</MkA>
|
||||
@@ -43,20 +43,20 @@ export default defineComponent({
|
||||
MkSwitch,
|
||||
},
|
||||
|
||||
emits: ['resolved'],
|
||||
|
||||
props: {
|
||||
report: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
emits: ['resolved'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
forward: this.report.forwarded,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
acct,
|
||||
|
@@ -42,7 +42,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
thickness: number;
|
||||
|
@@ -48,8 +48,8 @@ async function onClick() {
|
||||
});
|
||||
isFollowing.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
wait.value = false;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void;
|
||||
(ev: 'update:modelValue', v: boolean): void;
|
||||
}>();
|
||||
|
||||
const label = computed(() => {
|
||||
|
@@ -90,8 +90,8 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', v: { canceled: boolean; result: any }): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'done', v: { canceled: boolean; result: any }): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = ref<InstanceType<typeof MkModal>>();
|
||||
@@ -122,14 +122,14 @@ function onBgClick() {
|
||||
if (props.cancelableByBgClick) cancel();
|
||||
}
|
||||
*/
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') cancel();
|
||||
function onKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Escape') cancel();
|
||||
}
|
||||
|
||||
function onInputKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
function onInputKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Enter') {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
ok();
|
||||
}
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ const is = computed(() => {
|
||||
"application/x-tar",
|
||||
"application/gzip",
|
||||
"application/x-7z-compressed"
|
||||
].some(e => e === props.file.type)) return 'archive';
|
||||
].some(archiveType => archiveType === props.file.type)) return 'archive';
|
||||
return 'unknown';
|
||||
});
|
||||
|
||||
|
@@ -33,8 +33,8 @@ withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', r?: Misskey.entities.DriveFile[]): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'done', r?: Misskey.entities.DriveFile[]): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = ref<InstanceType<typeof XModalWindow>>();
|
||||
|
@@ -24,6 +24,6 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
</script>
|
||||
|
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import MkDriveFileThumbnail from './drive-file-thumbnail.vue';
|
||||
@@ -50,9 +50,9 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'chosen', r: Misskey.entities.DriveFile): void;
|
||||
(e: 'dragstart'): void;
|
||||
(e: 'dragend'): void;
|
||||
(ev: 'chosen', r: Misskey.entities.DriveFile): void;
|
||||
(ev: 'dragstart'): void;
|
||||
(ev: 'dragend'): void;
|
||||
}>();
|
||||
|
||||
const isDragging = ref(false);
|
||||
@@ -99,14 +99,14 @@ function onClick(ev: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function onContextmenu(e: MouseEvent) {
|
||||
os.contextMenu(getMenu(), e);
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
os.contextMenu(getMenu(), ev);
|
||||
}
|
||||
|
||||
function onDragstart(e: DragEvent) {
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
|
||||
function onDragstart(ev: DragEvent) {
|
||||
if (ev.dataTransfer) {
|
||||
ev.dataTransfer.effectAllowed = 'move';
|
||||
ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
|
||||
}
|
||||
isDragging.value = true;
|
||||
|
||||
@@ -133,11 +133,11 @@ function rename() {
|
||||
}
|
||||
|
||||
function describe() {
|
||||
os.popup(import('@/components/media-caption.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), {
|
||||
title: i18n.ts.describeFile,
|
||||
input: {
|
||||
placeholder: i18n.ts.inputNewDescription,
|
||||
default: props.file.comment !== null ? props.file.comment : '',
|
||||
default: props.file.comment != null ? props.file.comment : '',
|
||||
},
|
||||
image: props.file
|
||||
}, {
|
||||
@@ -146,7 +146,7 @@ function describe() {
|
||||
let comment = result.result;
|
||||
os.api('drive/files/update', {
|
||||
fileId: props.file.id,
|
||||
comment: comment.length == 0 ? null : comment
|
||||
comment: comment.length === 0 ? null : comment
|
||||
});
|
||||
}
|
||||
}, 'closed');
|
||||
|
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, defineAsyncComponent, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
@@ -84,12 +84,12 @@ function onDragover(ev: DragEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFile = ev.dataTransfer.items[0].kind == 'file';
|
||||
const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
const isFile = ev.dataTransfer.items[0].kind === 'file';
|
||||
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
|
||||
if (isFile || isDriveFile || isDriveFolder) {
|
||||
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
|
||||
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
|
||||
} else {
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
@@ -118,7 +118,7 @@ function onDrop(ev: DragEvent) {
|
||||
|
||||
//#region ドライブのファイル
|
||||
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile != '') {
|
||||
if (driveFile != null && driveFile !== '') {
|
||||
const file = JSON.parse(driveFile);
|
||||
emit('removeFile', file.id);
|
||||
os.api('drive/files/update', {
|
||||
@@ -130,11 +130,11 @@ function onDrop(ev: DragEvent) {
|
||||
|
||||
//#region ドライブのフォルダ
|
||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||
if (driveFolder != null && driveFolder != '') {
|
||||
if (driveFolder != null && driveFolder !== '') {
|
||||
const folder = JSON.parse(driveFolder);
|
||||
|
||||
// 移動先が自分自身ならreject
|
||||
if (folder.id == props.folder.id) return;
|
||||
if (folder.id === props.folder.id) return;
|
||||
|
||||
emit('removeFolder', folder.id);
|
||||
os.api('drive/folders/update', {
|
||||
@@ -230,7 +230,7 @@ function onContextmenu(ev: MouseEvent) {
|
||||
text: i18n.ts.openInWindow,
|
||||
icon: 'fas fa-window-restore',
|
||||
action: () => {
|
||||
os.popup(import('./drive-window.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('./drive-window.vue')), {
|
||||
initialFolder: props.folder
|
||||
}, {
|
||||
}, 'closed');
|
||||
|
@@ -24,10 +24,10 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'move', v?: Misskey.entities.DriveFolder): void;
|
||||
(e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void;
|
||||
(e: 'removeFile', v: Misskey.entities.DriveFile['id']): void;
|
||||
(e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void;
|
||||
(ev: 'move', v?: Misskey.entities.DriveFolder): void;
|
||||
(ev: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void;
|
||||
(ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void;
|
||||
(ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void;
|
||||
}>();
|
||||
|
||||
const hover = ref(false);
|
||||
@@ -45,22 +45,22 @@ function onMouseout() {
|
||||
hover.value = false;
|
||||
}
|
||||
|
||||
function onDragover(e: DragEvent) {
|
||||
if (!e.dataTransfer) return;
|
||||
function onDragover(ev: DragEvent) {
|
||||
if (!ev.dataTransfer) return;
|
||||
|
||||
// このフォルダがルートかつカレントディレクトリならドロップ禁止
|
||||
if (props.folder == null && props.parentFolder == null) {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
||||
const isFile = e.dataTransfer.items[0].kind == 'file';
|
||||
const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
const isFile = ev.dataTransfer.items[0].kind === 'file';
|
||||
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
|
||||
if (isFile || isDriveFile || isDriveFolder) {
|
||||
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
|
||||
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -74,22 +74,22 @@ function onDragleave() {
|
||||
if (props.folder || props.parentFolder) draghover.value = false;
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent) {
|
||||
function onDrop(ev: DragEvent) {
|
||||
draghover.value = false;
|
||||
|
||||
if (!e.dataTransfer) return;
|
||||
if (!ev.dataTransfer) return;
|
||||
|
||||
// ファイルだったら
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
for (const file of Array.from(e.dataTransfer.files)) {
|
||||
if (ev.dataTransfer.files.length > 0) {
|
||||
for (const file of Array.from(ev.dataTransfer.files)) {
|
||||
emit('upload', file, props.folder);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//#region ドライブのファイル
|
||||
const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile != '') {
|
||||
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile !== '') {
|
||||
const file = JSON.parse(driveFile);
|
||||
emit('removeFile', file.id);
|
||||
os.api('drive/files/update', {
|
||||
@@ -100,11 +100,11 @@ function onDrop(e: DragEvent) {
|
||||
//#endregion
|
||||
|
||||
//#region ドライブのフォルダ
|
||||
const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||
if (driveFolder != null && driveFolder != '') {
|
||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||
if (driveFolder != null && driveFolder !== '') {
|
||||
const folder = JSON.parse(driveFolder);
|
||||
// 移動先が自分自身ならreject
|
||||
if (props.folder && folder.id == props.folder.id) return;
|
||||
if (props.folder && folder.id === props.folder.id) return;
|
||||
emit('removeFolder', folder.id);
|
||||
os.api('drive/folders/update', {
|
||||
folderId: folder.id,
|
||||
|
@@ -110,11 +110,11 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
|
||||
(e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
|
||||
(e: 'move-root'): void;
|
||||
(e: 'cd', v: Misskey.entities.DriveFolder | null): void;
|
||||
(e: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
||||
(ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
|
||||
(ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
|
||||
(ev: 'move-root'): void;
|
||||
(ev: 'cd', v: Misskey.entities.DriveFolder | null): void;
|
||||
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
||||
}>();
|
||||
|
||||
const loadMoreFiles = ref<InstanceType<typeof MkButton>>();
|
||||
@@ -153,7 +153,7 @@ function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
|
||||
|
||||
function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) {
|
||||
const current = folder.value ? folder.value.id : null;
|
||||
if (current != file.folderId) {
|
||||
if (current !== file.folderId) {
|
||||
removeFile(file);
|
||||
} else {
|
||||
addFile(file, true);
|
||||
@@ -170,7 +170,7 @@ function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder)
|
||||
|
||||
function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) {
|
||||
const current = folder.value ? folder.value.id : null;
|
||||
if (current != updatedFolder.parentId) {
|
||||
if (current !== updatedFolder.parentId) {
|
||||
removeFolder(updatedFolder);
|
||||
} else {
|
||||
addFolder(updatedFolder, true);
|
||||
@@ -181,23 +181,23 @@ function onStreamDriveFolderDeleted(folderId: string) {
|
||||
removeFolder(folderId);
|
||||
}
|
||||
|
||||
function onDragover(e: DragEvent): any {
|
||||
if (!e.dataTransfer) return;
|
||||
function onDragover(ev: DragEvent): any {
|
||||
if (!ev.dataTransfer) return;
|
||||
|
||||
// ドラッグ元が自分自身の所有するアイテムだったら
|
||||
if (isDragSource.value) {
|
||||
// 自分自身にはドロップさせない
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const isFile = e.dataTransfer.items[0].kind == 'file';
|
||||
const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
const isFile = ev.dataTransfer.items[0].kind === 'file';
|
||||
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
||||
const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
|
||||
if (isFile || isDriveFile || isDriveFolder) {
|
||||
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
|
||||
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
|
||||
} else {
|
||||
e.dataTransfer.dropEffect = 'none';
|
||||
ev.dataTransfer.dropEffect = 'none';
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -211,24 +211,24 @@ function onDragleave() {
|
||||
draghover.value = false;
|
||||
}
|
||||
|
||||
function onDrop(e: DragEvent): any {
|
||||
function onDrop(ev: DragEvent): any {
|
||||
draghover.value = false;
|
||||
|
||||
if (!e.dataTransfer) return;
|
||||
if (!ev.dataTransfer) return;
|
||||
|
||||
// ドロップされてきたものがファイルだったら
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
for (const file of Array.from(e.dataTransfer.files)) {
|
||||
if (ev.dataTransfer.files.length > 0) {
|
||||
for (const file of Array.from(ev.dataTransfer.files)) {
|
||||
upload(file, folder.value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//#region ドライブのファイル
|
||||
const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile != '') {
|
||||
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile !== '') {
|
||||
const file = JSON.parse(driveFile);
|
||||
if (files.value.some(f => f.id == file.id)) return;
|
||||
if (files.value.some(f => f.id === file.id)) return;
|
||||
removeFile(file.id);
|
||||
os.api('drive/files/update', {
|
||||
fileId: file.id,
|
||||
@@ -238,13 +238,13 @@ function onDrop(e: DragEvent): any {
|
||||
//#endregion
|
||||
|
||||
//#region ドライブのフォルダ
|
||||
const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||
if (driveFolder != null && driveFolder != '') {
|
||||
const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
|
||||
if (driveFolder != null && driveFolder !== '') {
|
||||
const droppedFolder = JSON.parse(driveFolder);
|
||||
|
||||
// 移動先が自分自身ならreject
|
||||
if (folder.value && droppedFolder.id == folder.value.id) return false;
|
||||
if (folders.value.some(f => f.id == droppedFolder.id)) return false;
|
||||
if (folder.value && droppedFolder.id === folder.value.id) return false;
|
||||
if (folders.value.some(f => f.id === droppedFolder.id)) return false;
|
||||
removeFolder(droppedFolder.id);
|
||||
os.api('drive/folders/update', {
|
||||
folderId: droppedFolder.id,
|
||||
@@ -357,16 +357,16 @@ function onChangeFileInput() {
|
||||
}
|
||||
|
||||
function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) {
|
||||
uploadFile(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => {
|
||||
uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => {
|
||||
addFile(res, true);
|
||||
});
|
||||
}
|
||||
|
||||
function chooseFile(file: Misskey.entities.DriveFile) {
|
||||
const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id);
|
||||
const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id);
|
||||
if (props.multiple) {
|
||||
if (isAlreadySelected) {
|
||||
selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id);
|
||||
selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
|
||||
} else {
|
||||
selectedFiles.value.push(file);
|
||||
}
|
||||
@@ -382,10 +382,10 @@ function chooseFile(file: Misskey.entities.DriveFile) {
|
||||
}
|
||||
|
||||
function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
|
||||
const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id);
|
||||
const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id);
|
||||
if (props.multiple) {
|
||||
if (isAlreadySelected) {
|
||||
selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id);
|
||||
selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id);
|
||||
} else {
|
||||
selectedFolders.value.push(folderToChoose);
|
||||
}
|
||||
@@ -404,7 +404,7 @@ function move(target?: Misskey.entities.DriveFolder) {
|
||||
if (!target) {
|
||||
goRoot();
|
||||
return;
|
||||
} else if (typeof target == 'object') {
|
||||
} else if (typeof target === 'object') {
|
||||
target = target.id;
|
||||
}
|
||||
|
||||
@@ -430,9 +430,9 @@ function move(target?: Misskey.entities.DriveFolder) {
|
||||
|
||||
function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) {
|
||||
const current = folder.value ? folder.value.id : null;
|
||||
if (current != folderToAdd.parentId) return;
|
||||
if (current !== folderToAdd.parentId) return;
|
||||
|
||||
if (folders.value.some(f => f.id == folderToAdd.id)) {
|
||||
if (folders.value.some(f => f.id === folderToAdd.id)) {
|
||||
const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id);
|
||||
folders.value[exist] = folderToAdd;
|
||||
return;
|
||||
@@ -447,9 +447,9 @@ function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) {
|
||||
|
||||
function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) {
|
||||
const current = folder.value ? folder.value.id : null;
|
||||
if (current != fileToAdd.folderId) return;
|
||||
if (current !== fileToAdd.folderId) return;
|
||||
|
||||
if (files.value.some(f => f.id == fileToAdd.id)) {
|
||||
if (files.value.some(f => f.id === fileToAdd.id)) {
|
||||
const exist = files.value.map(f => f.id).indexOf(fileToAdd.id);
|
||||
files.value[exist] = fileToAdd;
|
||||
return;
|
||||
@@ -464,12 +464,12 @@ function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) {
|
||||
|
||||
function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) {
|
||||
const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove;
|
||||
folders.value = folders.value.filter(f => f.id != folderIdToRemove);
|
||||
folders.value = folders.value.filter(f => f.id !== folderIdToRemove);
|
||||
}
|
||||
|
||||
function removeFile(file: Misskey.entities.DriveFile | string) {
|
||||
const fileId = typeof file === 'object' ? file.id : file;
|
||||
files.value = files.value.filter(f => f.id != fileId);
|
||||
files.value = files.value.filter(f => f.id !== fileId);
|
||||
}
|
||||
|
||||
function appendFile(file: Misskey.entities.DriveFile) {
|
||||
@@ -512,7 +512,7 @@ async function fetch() {
|
||||
folderId: folder.value ? folder.value.id : null,
|
||||
limit: foldersMax + 1
|
||||
}).then(fetchedFolders => {
|
||||
if (fetchedFolders.length == foldersMax + 1) {
|
||||
if (fetchedFolders.length === foldersMax + 1) {
|
||||
moreFolders.value = true;
|
||||
fetchedFolders.pop();
|
||||
}
|
||||
@@ -524,7 +524,7 @@ async function fetch() {
|
||||
type: props.type,
|
||||
limit: filesMax + 1
|
||||
}).then(fetchedFiles => {
|
||||
if (fetchedFiles.length == filesMax + 1) {
|
||||
if (fetchedFiles.length === filesMax + 1) {
|
||||
moreFiles.value = true;
|
||||
fetchedFiles.pop();
|
||||
}
|
||||
@@ -551,7 +551,7 @@ function fetchMoreFiles() {
|
||||
untilId: files.value[files.value.length - 1].id,
|
||||
limit: max + 1
|
||||
}).then(files => {
|
||||
if (files.length == max + 1) {
|
||||
if (files.length === max + 1) {
|
||||
moreFiles.value = true;
|
||||
files.pop();
|
||||
} else {
|
||||
|
@@ -25,8 +25,8 @@ withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'chosen', v: any): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'chosen', v: any): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
function chosen(emoji: any) {
|
||||
|
@@ -24,7 +24,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'chosen', v: string, ev: MouseEvent): void;
|
||||
(ev: 'chosen', v: string, event: MouseEvent): void;
|
||||
}>();
|
||||
|
||||
const shown = ref(!!props.initialShown);
|
||||
|
@@ -61,7 +61,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<header class="_acrylic">{{ i18n.ts.emoji }}</header>
|
||||
<XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
|
||||
<XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs">
|
||||
@@ -97,7 +97,7 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'chosen', v: string): void;
|
||||
(ev: 'chosen', v: string): void;
|
||||
}>();
|
||||
|
||||
const search = ref<HTMLInputElement>();
|
||||
@@ -138,7 +138,7 @@ watch(q, () => {
|
||||
const emojis = customEmojis;
|
||||
const matches = new Set<Misskey.entities.CustomEmoji>();
|
||||
|
||||
const exactMatch = emojis.find(e => e.name === newQ);
|
||||
const exactMatch = emojis.find(emoji => emoji.name === newQ);
|
||||
if (exactMatch) matches.add(exactMatch);
|
||||
|
||||
if (newQ.includes(' ')) { // AND検索
|
||||
@@ -201,7 +201,7 @@ watch(q, () => {
|
||||
const emojis = emojilist;
|
||||
const matches = new Set<UnicodeEmojiDef>();
|
||||
|
||||
const exactMatch = emojis.find(e => e.name === newQ);
|
||||
const exactMatch = emojis.find(emoji => emoji.name === newQ);
|
||||
if (exactMatch) matches.add(exactMatch);
|
||||
|
||||
if (newQ.includes(' ')) { // AND検索
|
||||
@@ -295,7 +295,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
|
||||
// 最近使った絵文字更新
|
||||
if (!pinned.value.includes(key)) {
|
||||
let recents = defaultStore.state.recentlyUsedEmojis;
|
||||
recents = recents.filter((e: any) => e !== key);
|
||||
recents = recents.filter((emoji: any) => emoji !== key);
|
||||
recents.unshift(key);
|
||||
defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
|
||||
}
|
||||
@@ -313,12 +313,12 @@ function done(query?: any): boolean | void {
|
||||
if (query == null || typeof query !== 'string') return;
|
||||
|
||||
const q2 = query.replace(/:/g, '');
|
||||
const exactMatchCustom = customEmojis.find(e => e.name === q2);
|
||||
const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2);
|
||||
if (exactMatchCustom) {
|
||||
chosen(exactMatchCustom);
|
||||
return true;
|
||||
}
|
||||
const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2);
|
||||
const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2);
|
||||
if (exactMatchUnicode) {
|
||||
chosen(exactMatchUnicode);
|
||||
return true;
|
||||
|
@@ -58,7 +58,7 @@ if (props.user.isFollowing == null) {
|
||||
}
|
||||
|
||||
function onFollowChange(user: Misskey.entities.UserDetailed) {
|
||||
if (user.id == props.user.id) {
|
||||
if (user.id === props.user.id) {
|
||||
isFollowing.value = user.isFollowing;
|
||||
hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
|
||||
}
|
||||
@@ -96,8 +96,8 @@ async function onClick() {
|
||||
hasPendingFollowRequestFromYou.value = true;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
} finally {
|
||||
wait.value = false;
|
||||
}
|
||||
|
@@ -41,8 +41,8 @@ import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'done'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let dialog: InstanceType<typeof XModalWindow> = $ref();
|
||||
|
@@ -44,7 +44,7 @@
|
||||
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
|
||||
<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
|
||||
</FormRange>
|
||||
<MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)" class="_formBlock">
|
||||
<MkButton v-else-if="form[item].type === 'button'" class="_formBlock" @click="form[item].action($event, values)">
|
||||
<span v-text="form[item].content || item"></span>
|
||||
</MkButton>
|
||||
</template>
|
||||
|
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { computed, defineAsyncComponent, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -112,7 +112,7 @@ export default defineComponent({
|
||||
ev.preventDefault();
|
||||
|
||||
const tooltipShowing = ref(true);
|
||||
os.popup(import('@/components/ui/tooltip.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
|
||||
showing: tooltipShowing,
|
||||
text: computed(() => {
|
||||
return props.textConverter(finalValue.value);
|
||||
|
@@ -31,7 +31,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void;
|
||||
(ev: 'update:modelValue', v: boolean): void;
|
||||
}>();
|
||||
|
||||
let button = $ref<HTMLElement>();
|
||||
|
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'click', ev: MouseEvent): void;
|
||||
(ev: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
|
||||
const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
|
||||
|
@@ -46,7 +46,7 @@ export default defineComponent({
|
||||
const url = computed(() => {
|
||||
if (char.value) {
|
||||
let codes = Array.from(char.value).map(x => x.codePointAt(0).toString(16));
|
||||
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
|
||||
if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
|
||||
codes = codes.filter(x => x && x.length);
|
||||
return `${twemojiSvgBase}/${codes.join('-')}.svg`;
|
||||
} else {
|
||||
|
@@ -38,7 +38,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { popupMenu } from '@/os';
|
||||
import { url } from '@/config';
|
||||
import { scrollToTop } from '@/scripts/scroll';
|
||||
|
@@ -1,6 +1,17 @@
|
||||
<template>
|
||||
<div class="yxspomdl" :class="{ inline, colored, mini }">
|
||||
<div class="ring"></div>
|
||||
<div class="container">
|
||||
<svg class="spinner bg" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
||||
<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg class="spinner fg" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="matrix(1.125,0,0,1.125,12,12)">
|
||||
<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -19,7 +30,7 @@ const props = withDefaults(defineProps<{
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes ring {
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
@@ -33,7 +44,7 @@ const props = withDefaults(defineProps<{
|
||||
text-align: center;
|
||||
cursor: wait;
|
||||
|
||||
--size: 48px;
|
||||
--size: 40px;
|
||||
|
||||
&.colored {
|
||||
color: var(--accent);
|
||||
@@ -50,32 +61,31 @@ const props = withDefaults(defineProps<{
|
||||
--size: 32px;
|
||||
}
|
||||
|
||||
> .ring {
|
||||
> .container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: 0 auto;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 50%;
|
||||
border: solid 4px;
|
||||
}
|
||||
|
||||
&:before {
|
||||
border-color: currentColor;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&:after {
|
||||
> .spinner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
border-color: currentColor transparent transparent transparent;
|
||||
animation: ring 0.5s linear infinite;
|
||||
left: 0;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
fill-rule: evenodd;
|
||||
clip-rule: evenodd;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-miterlimit: 1.5;
|
||||
}
|
||||
|
||||
> .bg {
|
||||
opacity: 0.275;
|
||||
}
|
||||
|
||||
> .fg {
|
||||
animation: spinner 0.5s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,32 @@ const props = withDefaults(defineProps<{
|
||||
}
|
||||
}
|
||||
|
||||
.mfm-x2 {
|
||||
--mfm-zoom-size: 200%;
|
||||
}
|
||||
|
||||
.mfm-x3 {
|
||||
--mfm-zoom-size: 400%;
|
||||
}
|
||||
|
||||
.mfm-x4 {
|
||||
--mfm-zoom-size: 600%;
|
||||
}
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
font-size: var(--mfm-zoom-size);
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
/* only half effective */
|
||||
font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
|
||||
|
||||
.mfm-x2, .mfm-x3, .mfm-x4 {
|
||||
/* disabled */
|
||||
font-size: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mfm-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
|
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{
|
||||
mode: 'relative',
|
||||
});
|
||||
|
||||
const _time = typeof props.time == 'string' ? new Date(props.time) : props.time;
|
||||
const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
|
||||
const absolute = _time.toLocaleString();
|
||||
|
||||
let now = $ref(new Date());
|
||||
|
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineAsyncComponent, defineComponent, ref } from 'vue';
|
||||
import { toUnicode as decodePunycode } from 'punycode/';
|
||||
import { url as local } from '@/config';
|
||||
import * as os from '@/os';
|
||||
@@ -50,7 +50,7 @@ export default defineComponent({
|
||||
const el = ref();
|
||||
|
||||
useTooltip(el, (showing) => {
|
||||
os.popup(import('@/components/url-preview-popup.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), {
|
||||
showing,
|
||||
url: props.url,
|
||||
source: el.value,
|
||||
|
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const modal = $ref<InstanceType<typeof MkModal>>();
|
||||
|
@@ -39,6 +39,19 @@ const bg = {
|
||||
border-radius: 4px 0 0 4px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
text-shadow: /* .866 ≈ sin(60deg) */
|
||||
1px 0 1px #000,
|
||||
.866px .5px 1px #000,
|
||||
.5px .866px 1px #000,
|
||||
0 1px 1px #000,
|
||||
-.5px .866px 1px #000,
|
||||
-.866px .5px 1px #000,
|
||||
-1px 0 1px #000,
|
||||
-.866px -.5px 1px #000,
|
||||
-.5px -.866px 1px #000,
|
||||
0 -1px 1px #000,
|
||||
.5px -.866px 1px #000,
|
||||
.866px -.5px 1px #000;
|
||||
|
||||
> .icon {
|
||||
height: 100%;
|
||||
|
@@ -8,7 +8,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { url as local } from '@/config';
|
||||
import { useTooltip } from '@/scripts/use-tooltip';
|
||||
import * as os from '@/os';
|
||||
@@ -26,7 +26,7 @@ const target = self ? null : '_blank';
|
||||
const el = $ref();
|
||||
|
||||
useTooltip($$(el), (showing) => {
|
||||
os.popup(import('@/components/url-preview-popup.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), {
|
||||
showing,
|
||||
url: props.url,
|
||||
source: el,
|
||||
|
@@ -77,7 +77,7 @@ export default defineComponent({
|
||||
|
||||
computed: {
|
||||
remainingLength(): number {
|
||||
if (typeof this.inputValue != "string") return 512;
|
||||
if (typeof this.inputValue !== "string") return 512;
|
||||
return 512 - length(this.inputValue);
|
||||
}
|
||||
},
|
||||
@@ -116,17 +116,17 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.which === 27) { // ESC
|
||||
onKeydown(evt) {
|
||||
if (evt.which === 27) { // ESC
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeydown(e) {
|
||||
if (e.which === 13) { // Enter
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onInputKeydown(evt) {
|
||||
if (evt.which === 13) { // Enter
|
||||
if (evt.ctrlKey) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this.ok();
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="ldlomzub" :class="{ isMe }" :to="url" :style="{ background: bg }">
|
||||
<img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<MkA v-if="url.startsWith('/')" v-user-preview="canonical" :class="[$style.root, { isMe }]" :to="url" :style="{ background: bg }">
|
||||
<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>
|
||||
<span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.mainHost">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</MkA>
|
||||
<a v-else class="ldlomzub" :href="url" target="_blank" rel="noopener" :style="{ background: bg }">
|
||||
<a v-else :class="$style.root" :href="url" target="_blank" rel="noopener" :style="{ background: bg }">
|
||||
<span class="main">
|
||||
<span class="username">@{{ username }}</span>
|
||||
<span class="host">@{{ toUnicode(host) }}</span>
|
||||
<span :class="$style.mainHost">@{{ toUnicode(host) }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
import { defineComponent, useCssModule } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { host as localHost } from '@/config';
|
||||
import { $i } from '@/account';
|
||||
@@ -45,6 +45,8 @@ export default defineComponent({
|
||||
const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention'));
|
||||
bg.setAlpha(0.1);
|
||||
|
||||
useCssModule();
|
||||
|
||||
return {
|
||||
localHost,
|
||||
isMe,
|
||||
@@ -57,8 +59,8 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ldlomzub {
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: inline-block;
|
||||
padding: 4px 8px 4px 4px;
|
||||
border-radius: 999px;
|
||||
@@ -67,18 +69,18 @@ export default defineComponent({
|
||||
&.isMe {
|
||||
color: var(--mentionMe);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon {
|
||||
width: 1.5em;
|
||||
margin: 0 0.2em 0 0;
|
||||
vertical-align: bottom;
|
||||
border-radius: 100%;
|
||||
}
|
||||
.icon {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
object-fit: cover;
|
||||
margin: 0 0.2em 0 0;
|
||||
vertical-align: bottom;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
> .main {
|
||||
> .host {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.mainHost {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
@@ -91,7 +91,8 @@ export default defineComponent({
|
||||
let style;
|
||||
switch (token.props.name) {
|
||||
case 'tada': {
|
||||
style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : '');
|
||||
const speed = validTime(token.props.args.speed) || '1s';
|
||||
style = 'font-size: 150%;' + (this.$store.state.animatedMfm ? `animation: tada ${speed} linear infinite both;` : '');
|
||||
break;
|
||||
}
|
||||
case 'jelly': {
|
||||
@@ -123,11 +124,13 @@ export default defineComponent({
|
||||
break;
|
||||
}
|
||||
case 'jump': {
|
||||
style = this.$store.state.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : '';
|
||||
const speed = validTime(token.props.args.speed) || '0.75s';
|
||||
style = this.$store.state.animatedMfm ? `animation: mfm-jump ${speed} linear infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'bounce': {
|
||||
style = this.$store.state.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : '';
|
||||
const speed = validTime(token.props.args.speed) || '0.75s';
|
||||
style = this.$store.state.animatedMfm ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
|
||||
break;
|
||||
}
|
||||
case 'flip': {
|
||||
@@ -139,16 +142,19 @@ export default defineComponent({
|
||||
break;
|
||||
}
|
||||
case 'x2': {
|
||||
style = `font-size: 200%;`;
|
||||
break;
|
||||
return h('span', {
|
||||
class: 'mfm-x2',
|
||||
}, genEl(token.children));
|
||||
}
|
||||
case 'x3': {
|
||||
style = `font-size: 400%;`;
|
||||
break;
|
||||
return h('span', {
|
||||
class: 'mfm-x3',
|
||||
}, genEl(token.children));
|
||||
}
|
||||
case 'x4': {
|
||||
style = `font-size: 600%;`;
|
||||
break;
|
||||
return h('span', {
|
||||
class: 'mfm-x4',
|
||||
}, genEl(token.children));
|
||||
}
|
||||
case 'font': {
|
||||
const family =
|
||||
@@ -168,7 +174,8 @@ export default defineComponent({
|
||||
}, genEl(token.children));
|
||||
}
|
||||
case 'rainbow': {
|
||||
style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
|
||||
const speed = validTime(token.props.args.speed) || '1s';
|
||||
style = this.$store.state.animatedMfm ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
|
||||
break;
|
||||
}
|
||||
case 'sparkle': {
|
||||
|
@@ -2,9 +2,9 @@
|
||||
<div
|
||||
v-if="!muted"
|
||||
v-show="!isDeleted"
|
||||
ref="el"
|
||||
v-hotkey="keymap"
|
||||
v-size="{ max: [500, 450, 350, 300] }"
|
||||
ref="el"
|
||||
class="lxwezrsl _block"
|
||||
:tabindex="!isDeleted ? '-1' : null"
|
||||
:class="{ renote: isRenote }"
|
||||
@@ -197,7 +197,7 @@ const keymap = {
|
||||
'q': () => renoteButton.value.renote(true),
|
||||
'esc': blur,
|
||||
'm|o': () => menu(true),
|
||||
's': () => showContent.value != showContent.value,
|
||||
's': () => showContent.value !== showContent.value,
|
||||
};
|
||||
|
||||
useNoteCapture({
|
||||
|
@@ -185,7 +185,7 @@ const keymap = {
|
||||
'down|j|tab': focusAfter,
|
||||
'esc': blur,
|
||||
'm|o': () => menu(true),
|
||||
's': () => showContent.value != showContent.value,
|
||||
's': () => showContent.value !== showContent.value,
|
||||
};
|
||||
|
||||
useNoteCapture({
|
||||
|
@@ -1,34 +1,22 @@
|
||||
<template>
|
||||
<div class="lzyxtsnt">
|
||||
<img v-if="image" :src="image.url"/>
|
||||
<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import ImgWithBlurhash from '@/components/img-with-blurhash.vue';
|
||||
import * as os from '@/os';
|
||||
import { ImageBlock } from '@/scripts/hpml/block';
|
||||
import { Hpml } from '@/scripts/hpml/evaluator';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
block: {
|
||||
type: Object as PropType<ImageBlock>,
|
||||
required: true
|
||||
},
|
||||
hpml: {
|
||||
type: Object as PropType<Hpml>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props, ctx) {
|
||||
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
|
||||
const props = defineProps<{
|
||||
block: PropType<ImageBlock>,
|
||||
hpml: PropType<Hpml>,
|
||||
}>();
|
||||
|
||||
return {
|
||||
image
|
||||
};
|
||||
}
|
||||
});
|
||||
const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -52,16 +52,16 @@ export default defineComponent({
|
||||
const promise = new Promise((ok) => {
|
||||
const canvas = this.hpml.canvases[this.block.canvasId];
|
||||
canvas.toBlob(blob => {
|
||||
const data = new FormData();
|
||||
data.append('file', blob);
|
||||
data.append('i', this.$i.token);
|
||||
const formData = new FormData();
|
||||
formData.append('file', blob);
|
||||
formData.append('i', this.$i.token);
|
||||
if (this.$store.state.uploadFolder) {
|
||||
data.append('folderId', this.$store.state.uploadFolder);
|
||||
formData.append('folderId', this.$store.state.uploadFolder);
|
||||
}
|
||||
|
||||
fetch(apiUrl + '/drive/files/create', {
|
||||
method: 'POST',
|
||||
body: data
|
||||
body: formData,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(f => {
|
||||
|
@@ -38,8 +38,8 @@ export default defineComponent({
|
||||
let ast;
|
||||
try {
|
||||
ast = parse(props.page.script);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
/*os.alert({
|
||||
type: 'error',
|
||||
text: 'Syntax error :('
|
||||
@@ -48,11 +48,11 @@ export default defineComponent({
|
||||
}
|
||||
hpml.aiscript.exec(ast).then(() => {
|
||||
hpml.eval();
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
/*os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
text: err
|
||||
});*/
|
||||
});
|
||||
} else {
|
||||
|
@@ -104,7 +104,7 @@ function add() {
|
||||
}
|
||||
|
||||
function remove(i) {
|
||||
choices.value = choices.value.filter((_, _i) => _i != i);
|
||||
choices.value = choices.value.filter((_, _i) => _i !== i);
|
||||
}
|
||||
|
||||
function get() {
|
||||
|
@@ -88,7 +88,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
async describe(file) {
|
||||
os.popup(import("@/components/media-caption.vue"), {
|
||||
os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), {
|
||||
title: this.$ts.describeFile,
|
||||
input: {
|
||||
placeholder: this.$ts.inputNewDescription,
|
||||
@@ -98,7 +98,7 @@ export default defineComponent({
|
||||
}, {
|
||||
done: result => {
|
||||
if (!result || result.canceled) return;
|
||||
let comment = result.result.length == 0 ? null : result.result;
|
||||
let comment = result.result.length === 0 ? null : result.result;
|
||||
os.api('drive/files/update', {
|
||||
fileId: file.id,
|
||||
comment: comment,
|
||||
|
@@ -62,7 +62,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, watch, nextTick, onMounted } from 'vue';
|
||||
import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue';
|
||||
import * as mfm from 'mfm-js';
|
||||
import * as misskey from 'misskey-js';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
@@ -107,7 +107,7 @@ const props = withDefaults(defineProps<{
|
||||
fixed?: boolean;
|
||||
autofocus?: boolean;
|
||||
}>(), {
|
||||
initialVisibleUsers: [],
|
||||
initialVisibleUsers: () => [],
|
||||
autofocus: true,
|
||||
});
|
||||
|
||||
@@ -228,7 +228,7 @@ if (props.mention) {
|
||||
text += ' ';
|
||||
}
|
||||
|
||||
if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) {
|
||||
if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) {
|
||||
text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
|
||||
}
|
||||
|
||||
@@ -239,16 +239,15 @@ if (props.reply && props.reply.text != null) {
|
||||
for (const x of extractMentions(ast)) {
|
||||
const mention = x.host ?
|
||||
`@${x.username}@${toASCII(x.host)}` :
|
||||
(otherHost == null || otherHost == host) ?
|
||||
(otherHost == null || otherHost === host) ?
|
||||
`@${x.username}` :
|
||||
`@${x.username}@${toASCII(otherHost)}`;
|
||||
|
||||
// 自分は除外
|
||||
if ($i.username == x.username && x.host == null) continue;
|
||||
if ($i.username == x.username && x.host == host) continue;
|
||||
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
|
||||
|
||||
// 重複は除外
|
||||
if (text.indexOf(`${mention} `) != -1) continue;
|
||||
if (text.includes(`${mention} `)) continue;
|
||||
|
||||
text += `${mention} `;
|
||||
}
|
||||
@@ -303,7 +302,7 @@ function checkMissingMention() {
|
||||
const ast = mfm.parse(text);
|
||||
|
||||
for (const x of extractMentions(ast)) {
|
||||
if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
|
||||
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
|
||||
hasNotSpecifiedMentions = true;
|
||||
return;
|
||||
}
|
||||
@@ -316,7 +315,7 @@ function addMissingMention() {
|
||||
const ast = mfm.parse(text);
|
||||
|
||||
for (const x of extractMentions(ast)) {
|
||||
if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
|
||||
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
|
||||
os.api('users/show', { username: x.username, host: x.host }).then(user => {
|
||||
visibleUsers.push(user);
|
||||
});
|
||||
@@ -357,7 +356,7 @@ function chooseFileFrom(ev) {
|
||||
}
|
||||
|
||||
function detachFile(id) {
|
||||
files = files.filter(x => x.id != id);
|
||||
files = files.filter(x => x.id !== id);
|
||||
}
|
||||
|
||||
function updateFiles(_files) {
|
||||
@@ -384,7 +383,7 @@ function setVisibility() {
|
||||
return;
|
||||
}
|
||||
|
||||
os.popup(import('./visibility-picker.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('./visibility-picker.vue')), {
|
||||
currentVisibility: visibility,
|
||||
currentLocalOnly: localOnly,
|
||||
src: visibilityButton,
|
||||
@@ -427,24 +426,24 @@ function clear() {
|
||||
quoteId = null;
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post();
|
||||
if (e.which === 27) emit('esc');
|
||||
function onKeydown(ev: KeyboardEvent) {
|
||||
if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post();
|
||||
if (ev.which === 27) emit('esc');
|
||||
typing();
|
||||
}
|
||||
|
||||
function onCompositionUpdate(e: CompositionEvent) {
|
||||
imeText = e.data;
|
||||
function onCompositionUpdate(ev: CompositionEvent) {
|
||||
imeText = ev.data;
|
||||
typing();
|
||||
}
|
||||
|
||||
function onCompositionEnd(e: CompositionEvent) {
|
||||
function onCompositionEnd(ev: CompositionEvent) {
|
||||
imeText = '';
|
||||
}
|
||||
|
||||
async function onPaste(e: ClipboardEvent) {
|
||||
for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) {
|
||||
if (item.kind == 'file') {
|
||||
async function onPaste(ev: ClipboardEvent) {
|
||||
for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({item, i}))) {
|
||||
if (item.kind === 'file') {
|
||||
const file = item.getAsFile();
|
||||
const lio = file.name.lastIndexOf('.');
|
||||
const ext = lio >= 0 ? file.name.slice(lio) : '';
|
||||
@@ -453,10 +452,10 @@ async function onPaste(e: ClipboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
const paste = e.clipboardData.getData('text');
|
||||
const paste = ev.clipboardData.getData('text');
|
||||
|
||||
if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
|
||||
e.preventDefault();
|
||||
ev.preventDefault();
|
||||
|
||||
os.confirm({
|
||||
type: 'info',
|
||||
@@ -472,49 +471,49 @@ async function onPaste(e: ClipboardEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function onDragover(e) {
|
||||
if (!e.dataTransfer.items[0]) return;
|
||||
const isFile = e.dataTransfer.items[0].kind == 'file';
|
||||
const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
|
||||
function onDragover(ev) {
|
||||
if (!ev.dataTransfer.items[0]) return;
|
||||
const isFile = ev.dataTransfer.items[0].kind === 'file';
|
||||
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
|
||||
if (isFile || isDriveFile) {
|
||||
e.preventDefault();
|
||||
ev.preventDefault();
|
||||
draghover = true;
|
||||
e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
|
||||
ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
|
||||
}
|
||||
}
|
||||
|
||||
function onDragenter(e) {
|
||||
function onDragenter(ev) {
|
||||
draghover = true;
|
||||
}
|
||||
|
||||
function onDragleave(e) {
|
||||
function onDragleave(ev) {
|
||||
draghover = false;
|
||||
}
|
||||
|
||||
function onDrop(e): void {
|
||||
function onDrop(ev): void {
|
||||
draghover = false;
|
||||
|
||||
// ファイルだったら
|
||||
if (e.dataTransfer.files.length > 0) {
|
||||
e.preventDefault();
|
||||
for (const x of Array.from(e.dataTransfer.files)) upload(x);
|
||||
if (ev.dataTransfer.files.length > 0) {
|
||||
ev.preventDefault();
|
||||
for (const x of Array.from(ev.dataTransfer.files)) upload(x);
|
||||
return;
|
||||
}
|
||||
|
||||
//#region ドライブのファイル
|
||||
const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile != '') {
|
||||
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
|
||||
if (driveFile != null && driveFile !== '') {
|
||||
const file = JSON.parse(driveFile);
|
||||
files.push(file);
|
||||
e.preventDefault();
|
||||
ev.preventDefault();
|
||||
}
|
||||
//#endregion
|
||||
}
|
||||
|
||||
function saveDraft() {
|
||||
const data = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||
|
||||
data[draftKey] = {
|
||||
draftData[draftKey] = {
|
||||
updatedAt: new Date(),
|
||||
data: {
|
||||
text: text,
|
||||
@@ -527,20 +526,20 @@ function saveDraft() {
|
||||
}
|
||||
};
|
||||
|
||||
localStorage.setItem('drafts', JSON.stringify(data));
|
||||
localStorage.setItem('drafts', JSON.stringify(draftData));
|
||||
}
|
||||
|
||||
function deleteDraft() {
|
||||
const data = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||
const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||
|
||||
delete data[draftKey];
|
||||
delete draftData[draftKey];
|
||||
|
||||
localStorage.setItem('drafts', JSON.stringify(data));
|
||||
localStorage.setItem('drafts', JSON.stringify(draftData));
|
||||
}
|
||||
|
||||
async function post() {
|
||||
let data = {
|
||||
text: text == '' ? undefined : text,
|
||||
let postData = {
|
||||
text: text === '' ? undefined : text,
|
||||
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
|
||||
replyId: props.reply ? props.reply.id : undefined,
|
||||
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
|
||||
@@ -549,18 +548,18 @@ async function post() {
|
||||
cw: useCw ? cw || '' : undefined,
|
||||
localOnly: localOnly,
|
||||
visibility: visibility,
|
||||
visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined,
|
||||
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
|
||||
};
|
||||
|
||||
if (withHashtags && hashtags && hashtags.trim() !== '') {
|
||||
const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
|
||||
data.text = data.text ? `${data.text} ${hashtags_}` : hashtags_;
|
||||
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
|
||||
}
|
||||
|
||||
// plugin
|
||||
if (notePostInterruptors.length > 0) {
|
||||
for (const interruptor of notePostInterruptors) {
|
||||
data = await interruptor.handler(JSON.parse(JSON.stringify(data)));
|
||||
postData = await interruptor.handler(JSON.parse(JSON.stringify(postData)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,13 +571,13 @@ async function post() {
|
||||
}
|
||||
|
||||
posting = true;
|
||||
os.api('notes/create', data, token).then(() => {
|
||||
os.api('notes/create', postData, token).then(() => {
|
||||
clear();
|
||||
nextTick(() => {
|
||||
deleteDraft();
|
||||
emit('posted');
|
||||
if (data.text && data.text != '') {
|
||||
const hashtags_ = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
|
||||
if (postData.text && postData.text !== '') {
|
||||
const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
|
||||
const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
|
||||
localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
|
||||
}
|
||||
@@ -662,7 +661,7 @@ onMounted(() => {
|
||||
cw = draft.data.cw;
|
||||
visibility = draft.data.visibility;
|
||||
localOnly = draft.data.localOnly;
|
||||
files = (draft.data.files || []).filter(e => e);
|
||||
files = (draft.data.files || []).filter(draftFile => draftFile);
|
||||
if (draft.data.poll) {
|
||||
poll = draft.data.poll;
|
||||
}
|
||||
|
@@ -7,8 +7,8 @@
|
||||
:class="{ reacted: note.myReaction == reaction, canToggle }"
|
||||
@click="toggleReaction()"
|
||||
>
|
||||
<XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/>
|
||||
<span>{{ count }}</span>
|
||||
<XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/>
|
||||
<span class="count">{{ count }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
@@ -141,12 +141,16 @@ export default defineComponent({
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
> span {
|
||||
> .count {
|
||||
color: var(--fgOnAccent);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
> .count {
|
||||
font-size: 0.9em;
|
||||
line-height: 32px;
|
||||
margin: 0 0 0 4px;
|
||||
|
@@ -2,12 +2,12 @@
|
||||
<XModalWindow ref="dialog"
|
||||
:width="370"
|
||||
:height="400"
|
||||
@close="dialog.close()"
|
||||
@close="onClose"
|
||||
@closed="emit('closed')"
|
||||
>
|
||||
<template #header>{{ $ts.login }}</template>
|
||||
|
||||
<MkSignin :auto-set="autoSet" @login="onLogin"/>
|
||||
<MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
@@ -18,17 +18,25 @@ import MkSignin from './signin.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
autoSet?: boolean;
|
||||
message?: string,
|
||||
}>(), {
|
||||
autoSet: false,
|
||||
message: ''
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'done'): void;
|
||||
(ev: 'closed'): void;
|
||||
(ev: 'cancelled'): void;
|
||||
}>();
|
||||
|
||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||
|
||||
function onClose() {
|
||||
emit('cancelled');
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
function onLogin(res) {
|
||||
emit('done', res);
|
||||
dialog.close();
|
||||
|
@@ -1,39 +1,44 @@
|
||||
<template>
|
||||
<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
|
||||
<div class="auth _section _formRoot">
|
||||
<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }"></div>
|
||||
<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
|
||||
<MkInfo v-if="message">
|
||||
{{ message }}
|
||||
</MkInfo>
|
||||
<div v-if="!totpLogin" class="normal-signin">
|
||||
<MkInput v-model="username" class="_formBlock" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
||||
<MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
|
||||
<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
|
||||
<template #prefix><i class="fas fa-lock"></i></template>
|
||||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
|
||||
<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
|
||||
</MkInput>
|
||||
<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
|
||||
<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
|
||||
<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||
</div>
|
||||
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
|
||||
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
|
||||
<p>{{ $ts.tapSecurityKey }}</p>
|
||||
<p>{{ i18n.ts.tapSecurityKey }}</p>
|
||||
<MkButton v-if="!queryingKey" @click="queryKey">
|
||||
{{ $ts.retry }}
|
||||
{{ i18n.ts.retry }}
|
||||
</MkButton>
|
||||
</div>
|
||||
<div v-if="user && user.securityKeys" class="or-hr">
|
||||
<p class="or-msg">{{ $ts.or }}</p>
|
||||
<p class="or-msg">{{ i18n.ts.or }}</p>
|
||||
</div>
|
||||
<div class="twofa-group totp-group">
|
||||
<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
|
||||
<p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
|
||||
<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required>
|
||||
<template #label>{{ $ts.password }}</template>
|
||||
<template #label>{{ i18n.ts.password }}</template>
|
||||
<template #prefix><i class="fas fa-lock"></i></template>
|
||||
</MkInput>
|
||||
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
|
||||
<template #label>{{ $ts.token }}</template>
|
||||
<template #label>{{ i18n.ts.token }}</template>
|
||||
<template #prefix><i class="fas fa-gavel"></i></template>
|
||||
</MkInput>
|
||||
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
|
||||
<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,190 +50,192 @@
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { toUnicode } from 'punycode/';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import { apiUrl, host } from '@/config';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
import { apiUrl, host as configHost } from '@/config';
|
||||
import { byteify, hexify } from '@/scripts/2fa';
|
||||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
const MkCaptcha = defineAsyncComponent(() => import('./captcha.vue'));
|
||||
|
||||
let signing = $ref(false);
|
||||
let user = $ref(null);
|
||||
let username = $ref('');
|
||||
let password = $ref('');
|
||||
let token = $ref('');
|
||||
let host = $ref(toUnicode(configHost));
|
||||
let totpLogin = $ref(false);
|
||||
let credential = $ref(null);
|
||||
let challengeData = $ref(null);
|
||||
let queryingKey = $ref(false);
|
||||
let hCaptchaResponse = $ref(null);
|
||||
let reCaptchaResponse = $ref(null);
|
||||
|
||||
const meta = $computed(() => instance);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'login', v: any): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps({
|
||||
withAvatar: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
|
||||
props: {
|
||||
withAvatar: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
autoSet: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
autoSet: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
|
||||
emits: ['login'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
signing: false,
|
||||
user: null,
|
||||
username: '',
|
||||
password: '',
|
||||
token: '',
|
||||
apiUrl,
|
||||
host: toUnicode(host),
|
||||
totpLogin: false,
|
||||
credential: null,
|
||||
challengeData: null,
|
||||
queryingKey: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
meta() {
|
||||
return this.$instance;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onUsernameChange() {
|
||||
os.api('users/show', {
|
||||
username: this.username
|
||||
}).then(user => {
|
||||
this.user = user;
|
||||
}, () => {
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
|
||||
onLogin(res) {
|
||||
if (this.autoSet) {
|
||||
return login(res.i);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
queryKey() {
|
||||
this.queryingKey = true;
|
||||
return navigator.credentials.get({
|
||||
publicKey: {
|
||||
challenge: byteify(this.challengeData.challenge, 'base64'),
|
||||
allowCredentials: this.challengeData.securityKeys.map(key => ({
|
||||
id: byteify(key.id, 'hex'),
|
||||
type: 'public-key',
|
||||
transports: ['usb', 'nfc', 'ble', 'internal']
|
||||
})),
|
||||
timeout: 60 * 1000
|
||||
}
|
||||
}).catch(() => {
|
||||
this.queryingKey = false;
|
||||
return Promise.reject(null);
|
||||
}).then(credential => {
|
||||
this.queryingKey = false;
|
||||
this.signing = true;
|
||||
return os.api('signin', {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
signature: hexify(credential.response.signature),
|
||||
authenticatorData: hexify(credential.response.authenticatorData),
|
||||
clientDataJSON: hexify(credential.response.clientDataJSON),
|
||||
credentialId: credential.id,
|
||||
challengeId: this.challengeData.challengeId
|
||||
});
|
||||
}).then(res => {
|
||||
this.$emit('login', res);
|
||||
return this.onLogin(res);
|
||||
}).catch(err => {
|
||||
if (err === null) return;
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: this.$ts.signinFailed
|
||||
});
|
||||
this.signing = false;
|
||||
});
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
this.signing = true;
|
||||
if (!this.totpLogin && this.user && this.user.twoFactorEnabled) {
|
||||
if (window.PublicKeyCredential && this.user.securityKeys) {
|
||||
os.api('signin', {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}).then(res => {
|
||||
this.totpLogin = true;
|
||||
this.signing = false;
|
||||
this.challengeData = res;
|
||||
return this.queryKey();
|
||||
}).catch(this.loginFailed);
|
||||
} else {
|
||||
this.totpLogin = true;
|
||||
this.signing = false;
|
||||
}
|
||||
} else {
|
||||
os.api('signin', {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
|
||||
}).then(res => {
|
||||
this.$emit('login', res);
|
||||
this.onLogin(res);
|
||||
}).catch(this.loginFailed);
|
||||
}
|
||||
},
|
||||
|
||||
loginFailed(err) {
|
||||
switch (err.id) {
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: this.$ts.loginFailed,
|
||||
text: this.$ts.noSuchUser
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: this.$ts.loginFailed,
|
||||
text: this.$ts.incorrectPassword,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
|
||||
showSuspendedDialog();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: this.$ts.loginFailed,
|
||||
text: JSON.stringify(err)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.challengeData = null;
|
||||
this.totpLogin = false;
|
||||
this.signing = false;
|
||||
},
|
||||
|
||||
resetPassword() {
|
||||
os.popup(import('@/components/forgot-password.vue'), {}, {
|
||||
}, 'closed');
|
||||
}
|
||||
message: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
function onUsernameChange() {
|
||||
os.api('users/show', {
|
||||
username: username
|
||||
}).then(userResponse => {
|
||||
user = userResponse;
|
||||
}, () => {
|
||||
user = null;
|
||||
});
|
||||
}
|
||||
|
||||
function onLogin(res) {
|
||||
if (props.autoSet) {
|
||||
return login(res.i);
|
||||
}
|
||||
}
|
||||
|
||||
function queryKey() {
|
||||
queryingKey = true;
|
||||
return navigator.credentials.get({
|
||||
publicKey: {
|
||||
challenge: byteify(challengeData.challenge, 'base64'),
|
||||
allowCredentials: challengeData.securityKeys.map(key => ({
|
||||
id: byteify(key.id, 'hex'),
|
||||
type: 'public-key',
|
||||
transports: ['usb', 'nfc', 'ble', 'internal']
|
||||
})),
|
||||
timeout: 60 * 1000
|
||||
}
|
||||
}).catch(() => {
|
||||
queryingKey = false;
|
||||
return Promise.reject(null);
|
||||
}).then(credential => {
|
||||
queryingKey = false;
|
||||
signing = true;
|
||||
return os.api('signin', {
|
||||
username,
|
||||
password,
|
||||
signature: hexify(credential.response.signature),
|
||||
authenticatorData: hexify(credential.response.authenticatorData),
|
||||
clientDataJSON: hexify(credential.response.clientDataJSON),
|
||||
credentialId: credential.id,
|
||||
challengeId: challengeData.challengeId,
|
||||
'hcaptcha-response': hCaptchaResponse,
|
||||
'g-recaptcha-response': reCaptchaResponse,
|
||||
});
|
||||
}).then(res => {
|
||||
emit('login', res);
|
||||
return onLogin(res);
|
||||
}).catch(err => {
|
||||
if (err === null) return;
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.signinFailed
|
||||
});
|
||||
signing = false;
|
||||
});
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
signing = true;
|
||||
console.log('submit')
|
||||
if (!totpLogin && user && user.twoFactorEnabled) {
|
||||
if (window.PublicKeyCredential && user.securityKeys) {
|
||||
os.api('signin', {
|
||||
username,
|
||||
password,
|
||||
'hcaptcha-response': hCaptchaResponse,
|
||||
'g-recaptcha-response': reCaptchaResponse,
|
||||
}).then(res => {
|
||||
totpLogin = true;
|
||||
signing = false;
|
||||
challengeData = res;
|
||||
return queryKey();
|
||||
}).catch(loginFailed);
|
||||
} else {
|
||||
totpLogin = true;
|
||||
signing = false;
|
||||
}
|
||||
} else {
|
||||
os.api('signin', {
|
||||
username,
|
||||
password,
|
||||
'hcaptcha-response': hCaptchaResponse,
|
||||
'g-recaptcha-response': reCaptchaResponse,
|
||||
token: user && user.twoFactorEnabled ? token : undefined
|
||||
}).then(res => {
|
||||
emit('login', res);
|
||||
onLogin(res);
|
||||
}).catch(loginFailed);
|
||||
}
|
||||
}
|
||||
|
||||
function loginFailed(err) {
|
||||
switch (err.id) {
|
||||
case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.noSuchUser
|
||||
});
|
||||
break;
|
||||
}
|
||||
case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: i18n.ts.incorrectPassword,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
|
||||
showSuspendedDialog();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.log(err)
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.loginFailed,
|
||||
text: JSON.stringify(err)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
challengeData = null;
|
||||
totpLogin = false;
|
||||
signing = false;
|
||||
}
|
||||
|
||||
function resetPassword() {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
|
||||
}, 'closed');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -27,8 +27,8 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'done'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const dialog = $ref<InstanceType<typeof XModalWindow>>();
|
||||
|
@@ -58,8 +58,8 @@
|
||||
</template>
|
||||
</I18n>
|
||||
</MkSwitch>
|
||||
<captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
|
||||
<captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
|
||||
<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
|
||||
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton>
|
||||
</template>
|
||||
</form>
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||
const getPasswordStrength = require('syuilo-password-strength');
|
||||
const getPasswordStrength = await import('syuilo-password-strength');
|
||||
import { toUnicode } from 'punycode/';
|
||||
import { host, url } from '@/config';
|
||||
import MkButton from './ui/button.vue';
|
||||
@@ -81,7 +81,7 @@ export default defineComponent({
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSwitch,
|
||||
captcha: defineAsyncComponent(() => import('./captcha.vue')),
|
||||
MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -124,20 +124,20 @@ export default defineComponent({
|
||||
this.meta.tosUrl && !this.ToSAgreement ||
|
||||
this.meta.enableHcaptcha && !this.hCaptchaResponse ||
|
||||
this.meta.enableRecaptcha && !this.reCaptchaResponse ||
|
||||
this.passwordRetypeState == 'not-match';
|
||||
this.passwordRetypeState === 'not-match';
|
||||
},
|
||||
|
||||
shouldShowProfileUrl(): boolean {
|
||||
return (this.username != '' &&
|
||||
this.usernameState != 'invalid-format' &&
|
||||
this.usernameState != 'min-range' &&
|
||||
this.usernameState != 'max-range');
|
||||
return (this.username !== '' &&
|
||||
this.usernameState !== 'invalid-format' &&
|
||||
this.usernameState !== 'min-range' &&
|
||||
this.usernameState !== 'max-range');
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onChangeUsername() {
|
||||
if (this.username == '') {
|
||||
if (this.username === '') {
|
||||
this.usernameState = null;
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onChangeEmail() {
|
||||
if (this.email == '') {
|
||||
if (this.email === '') {
|
||||
this.emailState = null;
|
||||
return;
|
||||
}
|
||||
@@ -188,7 +188,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onChangePassword() {
|
||||
if (this.password == '') {
|
||||
if (this.password === '') {
|
||||
this.passwordStrength = '';
|
||||
return;
|
||||
}
|
||||
@@ -198,12 +198,12 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
onChangePasswordRetype() {
|
||||
if (this.retypedPassword == '') {
|
||||
if (this.retypedPassword === '') {
|
||||
this.passwordRetypeState = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match';
|
||||
this.passwordRetypeState = this.password === this.retypedPassword ? 'match' : 'not-match';
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
|
@@ -19,8 +19,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'note'): void;
|
||||
(e: 'queue', count: number): void;
|
||||
(ev: 'note'): void;
|
||||
(ev: 'queue', count: number): void;
|
||||
}>();
|
||||
|
||||
provide('inChannel', computed(() => props.src === 'channel'));
|
||||
@@ -95,7 +95,7 @@ if (props.src === 'antenna') {
|
||||
visibility: 'specified'
|
||||
};
|
||||
const onNote = note => {
|
||||
if (note.visibility == 'specified') {
|
||||
if (note.visibility === 'specified') {
|
||||
prepend(note);
|
||||
}
|
||||
};
|
||||
|
@@ -19,7 +19,7 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const zIndex = os.claimZIndex('high');
|
||||
|
@@ -90,7 +90,7 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMousedown(e: MouseEvent) {
|
||||
onMousedown(evt: MouseEvent) {
|
||||
function distance(p, q) {
|
||||
return Math.hypot(p.x - q.x, p.y - q.y);
|
||||
}
|
||||
@@ -104,18 +104,18 @@ export default defineComponent({
|
||||
return Math.max(dist1, dist2, dist3, dist4) * 2;
|
||||
}
|
||||
|
||||
const rect = e.target.getBoundingClientRect();
|
||||
const rect = evt.target.getBoundingClientRect();
|
||||
|
||||
const ripple = document.createElement('div');
|
||||
ripple.style.top = (e.clientY - rect.top - 1).toString() + 'px';
|
||||
ripple.style.left = (e.clientX - rect.left - 1).toString() + 'px';
|
||||
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
|
||||
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
|
||||
|
||||
this.$refs.ripples.appendChild(ripple);
|
||||
|
||||
const circleCenterX = e.clientX - rect.left;
|
||||
const circleCenterY = e.clientY - rect.top;
|
||||
const circleCenterX = evt.clientX - rect.left;
|
||||
const circleCenterY = evt.clientY - rect.top;
|
||||
|
||||
const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY);
|
||||
const scale = calcCircleScale(evt.target.clientWidth, evt.target.clientHeight, circleCenterX, circleCenterY);
|
||||
|
||||
window.setTimeout(() => {
|
||||
ripple.style.transform = 'scale(' + (scale / 2) + ')';
|
||||
|
@@ -19,7 +19,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let rootEl = $ref<HTMLDivElement>();
|
||||
@@ -63,8 +63,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
});
|
||||
|
||||
function onMousedown(e: Event) {
|
||||
if (!contains(rootEl, e.target) && (rootEl != e.target)) emit('closed');
|
||||
function onMousedown(evt: Event) {
|
||||
if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -23,7 +23,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as tinycolor from 'tinycolor2';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
const localStoragePrefix = 'ui:folder:';
|
||||
|
||||
|
@@ -60,7 +60,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(ev: 'close'): void;
|
||||
}>();
|
||||
|
||||
let itemsEl = $ref<HTMLDivElement>();
|
||||
|
@@ -79,10 +79,10 @@ export default defineComponent({
|
||||
this.$refs.modal.close();
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.which === 27) { // Esc
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onKeydown(evt) {
|
||||
if (evt.which === 27) { // Esc
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
|
@@ -14,8 +14,14 @@
|
||||
</div>
|
||||
|
||||
<div v-else ref="rootEl">
|
||||
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
||||
<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
|
||||
{{ $ts.loadMore }}
|
||||
</MkButton>
|
||||
<MkLoading v-else class="loading"/>
|
||||
</div>
|
||||
<slot :items="items"></slot>
|
||||
<div v-show="more" key="_more_" class="cxiknjgy _gap">
|
||||
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
||||
<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
|
||||
{{ $ts.loadMore }}
|
||||
</MkButton>
|
||||
@@ -62,7 +68,7 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'queue', count: number): void;
|
||||
(ev: 'queue', count: number): void;
|
||||
}>();
|
||||
|
||||
type Item = { id: string; [another: string]: unknown; };
|
||||
@@ -106,7 +112,7 @@ const init = async (): Promise<void> => {
|
||||
offset.value = res.length;
|
||||
error.value = false;
|
||||
fetching.value = false;
|
||||
}, e => {
|
||||
}, err => {
|
||||
error.value = true;
|
||||
fetching.value = false;
|
||||
});
|
||||
@@ -149,7 +155,7 @@ const fetchMore = async (): Promise<void> => {
|
||||
}
|
||||
offset.value += res.length;
|
||||
moreFetching.value = false;
|
||||
}, e => {
|
||||
}, err => {
|
||||
moreFetching.value = false;
|
||||
});
|
||||
};
|
||||
@@ -177,7 +183,7 @@ const fetchMoreAhead = async (): Promise<void> => {
|
||||
}
|
||||
offset.value += res.length;
|
||||
moreFetching.value = false;
|
||||
}, e => {
|
||||
}, err => {
|
||||
moreFetching.value = false;
|
||||
});
|
||||
};
|
||||
@@ -244,6 +250,11 @@ const append = (item: Item): void => {
|
||||
items.value.push(item);
|
||||
};
|
||||
|
||||
const removeItem = (finder: (item: Item) => boolean) => {
|
||||
const i = items.value.findIndex(finder);
|
||||
items.value.splice(i, 1);
|
||||
};
|
||||
|
||||
const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
|
||||
const i = items.value.findIndex(item => item.id === id);
|
||||
items.value[i] = replacer(items.value[i]);
|
||||
@@ -273,9 +284,9 @@ defineExpose({
|
||||
queue,
|
||||
backed,
|
||||
reload,
|
||||
fetchMoreAhead,
|
||||
prepend,
|
||||
append,
|
||||
removeItem,
|
||||
updateItem,
|
||||
});
|
||||
</script>
|
||||
|
@@ -19,7 +19,7 @@ defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closed'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let modal = $ref<InstanceType<typeof MkModal>>();
|
||||
|
@@ -139,10 +139,10 @@ export default defineComponent({
|
||||
this.showing = false;
|
||||
},
|
||||
|
||||
onKeydown(e) {
|
||||
if (e.which === 27) { // Esc
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onKeydown(evt) {
|
||||
if (evt.which === 27) { // Esc
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
this.close();
|
||||
}
|
||||
},
|
||||
@@ -162,15 +162,15 @@ export default defineComponent({
|
||||
this.top();
|
||||
},
|
||||
|
||||
onHeaderMousedown(e) {
|
||||
onHeaderMousedown(evt) {
|
||||
const main = this.$el as any;
|
||||
|
||||
if (!contains(main, document.activeElement)) main.focus();
|
||||
|
||||
const position = main.getBoundingClientRect();
|
||||
|
||||
const clickX = e.touches && e.touches.length > 0 ? e.touches[0].clientX : e.clientX;
|
||||
const clickY = e.touches && e.touches.length > 0 ? e.touches[0].clientY : e.clientY;
|
||||
const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
|
||||
const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
|
||||
const moveBaseX = clickX - position.left;
|
||||
const moveBaseY = clickY - position.top;
|
||||
const browserWidth = window.innerWidth;
|
||||
@@ -204,10 +204,10 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// 上ハンドル掴み時
|
||||
onTopHandleMousedown(e) {
|
||||
onTopHandleMousedown(evt) {
|
||||
const main = this.$el as any;
|
||||
|
||||
const base = e.clientY;
|
||||
const base = evt.clientY;
|
||||
const height = parseInt(getComputedStyle(main, '').height, 10);
|
||||
const top = parseInt(getComputedStyle(main, '').top, 10);
|
||||
|
||||
@@ -230,10 +230,10 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// 右ハンドル掴み時
|
||||
onRightHandleMousedown(e) {
|
||||
onRightHandleMousedown(evt) {
|
||||
const main = this.$el as any;
|
||||
|
||||
const base = e.clientX;
|
||||
const base = evt.clientX;
|
||||
const width = parseInt(getComputedStyle(main, '').width, 10);
|
||||
const left = parseInt(getComputedStyle(main, '').left, 10);
|
||||
const browserWidth = window.innerWidth;
|
||||
@@ -254,10 +254,10 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// 下ハンドル掴み時
|
||||
onBottomHandleMousedown(e) {
|
||||
onBottomHandleMousedown(evt) {
|
||||
const main = this.$el as any;
|
||||
|
||||
const base = e.clientY;
|
||||
const base = evt.clientY;
|
||||
const height = parseInt(getComputedStyle(main, '').height, 10);
|
||||
const top = parseInt(getComputedStyle(main, '').top, 10);
|
||||
const browserHeight = window.innerHeight;
|
||||
@@ -278,10 +278,10 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// 左ハンドル掴み時
|
||||
onLeftHandleMousedown(e) {
|
||||
onLeftHandleMousedown(evt) {
|
||||
const main = this.$el as any;
|
||||
|
||||
const base = e.clientX;
|
||||
const base = evt.clientX;
|
||||
const width = parseInt(getComputedStyle(main, '').width, 10);
|
||||
const left = parseInt(getComputedStyle(main, '').left, 10);
|
||||
|
||||
@@ -304,27 +304,27 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// 左上ハンドル掴み時
|
||||
onTopLeftHandleMousedown(e) {
|
||||
this.onTopHandleMousedown(e);
|
||||
this.onLeftHandleMousedown(e);
|
||||
onTopLeftHandleMousedown(evt) {
|
||||
this.onTopHandleMousedown(evt);
|
||||
this.onLeftHandleMousedown(evt);
|
||||
},
|
||||
|
||||
// 右上ハンドル掴み時
|
||||
onTopRightHandleMousedown(e) {
|
||||
this.onTopHandleMousedown(e);
|
||||
this.onRightHandleMousedown(e);
|
||||
onTopRightHandleMousedown(evt) {
|
||||
this.onTopHandleMousedown(evt);
|
||||
this.onRightHandleMousedown(evt);
|
||||
},
|
||||
|
||||
// 右下ハンドル掴み時
|
||||
onBottomRightHandleMousedown(e) {
|
||||
this.onBottomHandleMousedown(e);
|
||||
this.onRightHandleMousedown(e);
|
||||
onBottomRightHandleMousedown(evt) {
|
||||
this.onBottomHandleMousedown(evt);
|
||||
this.onRightHandleMousedown(evt);
|
||||
},
|
||||
|
||||
// 左下ハンドル掴み時
|
||||
onBottomLeftHandleMousedown(e) {
|
||||
this.onBottomHandleMousedown(e);
|
||||
this.onLeftHandleMousedown(e);
|
||||
onBottomLeftHandleMousedown(evt) {
|
||||
this.onBottomHandleMousedown(evt);
|
||||
this.onLeftHandleMousedown(evt);
|
||||
},
|
||||
|
||||
// 高さを適用
|
||||
|
@@ -70,7 +70,7 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (typeof this.q == 'object') {
|
||||
if (typeof this.q === 'object') {
|
||||
this.user = this.q;
|
||||
this.fetched = true;
|
||||
} else {
|
||||
|
@@ -60,9 +60,9 @@ import * as os from '@/os';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'ok', selected: misskey.entities.UserDetailed): void;
|
||||
(e: 'cancel'): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'ok', selected: misskey.entities.UserDetailed): void;
|
||||
(ev: 'cancel'): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let username = $ref('');
|
||||
|
@@ -57,9 +57,9 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void;
|
||||
(e: 'changeLocalOnly', v: boolean): void;
|
||||
(e: 'closed'): void;
|
||||
(ev: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void;
|
||||
(ev: 'changeLocalOnly', v: boolean): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
let v = $ref(props.currentVisibility);
|
||||
|
@@ -21,8 +21,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done');
|
||||
(e: 'closed');
|
||||
(ev: 'done');
|
||||
(ev: 'closed');
|
||||
}>();
|
||||
|
||||
function done() {
|
||||
|
@@ -19,7 +19,7 @@
|
||||
<div class="customize-container">
|
||||
<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button>
|
||||
<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button>
|
||||
<component class="handle" :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
|
||||
<component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="handle" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
|
||||
</div>
|
||||
</template>
|
||||
</XDraggable>
|
||||
@@ -37,7 +37,7 @@ import { widgets as widgetDefs } from '@/widgets';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
|
||||
XDraggable: defineAsyncComponent(() => import('vuedraggable')),
|
||||
MkSelect,
|
||||
MkButton,
|
||||
},
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// TODO: useTooltip関数使うようにしたい
|
||||
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
|
||||
|
||||
import { Directive, ref } from 'vue';
|
||||
import { defineAsyncComponent, Directive, ref } from 'vue';
|
||||
import { isTouchUsing } from '@/scripts/touch';
|
||||
import { popup, alert } from '@/os';
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
if (self.text == null) return;
|
||||
|
||||
const showing = ref(true);
|
||||
popup(import('@/components/ui/tooltip.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
|
||||
showing,
|
||||
text: self.text,
|
||||
targetElement: el,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Directive, ref } from 'vue';
|
||||
import { defineAsyncComponent, Directive, ref } from 'vue';
|
||||
import autobind from 'autobind-decorator';
|
||||
import { popup } from '@/os';
|
||||
|
||||
@@ -24,7 +24,7 @@ export class UserPreview {
|
||||
|
||||
const showing = ref(true);
|
||||
|
||||
popup(import('@/components/user-preview.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/user-preview.vue')), {
|
||||
showing,
|
||||
q: this.user,
|
||||
source: this.el
|
||||
|
@@ -96,6 +96,13 @@
|
||||
{ "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] },
|
||||
{ "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] },
|
||||
{ "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] },
|
||||
{ "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] },
|
||||
{ "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] },
|
||||
{ "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] },
|
||||
{ "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] },
|
||||
{ "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] },
|
||||
@@ -149,11 +156,19 @@
|
||||
{ "category": "people", "char": "🤞", "name": "crossed_fingers", "keywords": ["good", "lucky"] },
|
||||
{ "category": "people", "char": "🖖", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] },
|
||||
{ "category": "people", "char": "✍", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] },
|
||||
{ "category": "people", "char": "🤏", "name": "pinching_hand", "keywords": ["hand", "fingers"] },
|
||||
{ "category": "people", "char": "🤌", "name": "pinched_fingers", "keywords": ["hand", "fingers"] },
|
||||
{ "category": "people", "char": "🤳", "name": "selfie", "keywords": ["camera", "phone"] },
|
||||
{ "category": "people", "char": "💅", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] },
|
||||
{ "category": "people", "char": "👄", "name": "lips", "keywords": ["mouth", "kiss"] },
|
||||
{ "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] },
|
||||
{ "category": "people", "char": "🦷", "name": "tooth", "keywords": ["teeth", "dentist"] },
|
||||
{ "category": "people", "char": "👅", "name": "tongue", "keywords": ["mouth", "playful"] },
|
||||
{ "category": "people", "char": "👂", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] },
|
||||
@@ -275,7 +290,11 @@
|
||||
{ "category": "people", "char": "🧚♀️", "name": "woman_fairy", "keywords": ["woman", "female"] },
|
||||
{ "category": "people", "char": "🧚♂️", "name": "man_fairy", "keywords": ["man", "male"] },
|
||||
{ "category": "people", "char": "👼", "name": "angel", "keywords": ["heaven", "wings", "halo"] },
|
||||
{ "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] },
|
||||
{ "category": "people", "char": "🤰", "name": "pregnant_woman", "keywords": ["baby"] },
|
||||
{ "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] },
|
||||
{ "category": "people", "char": "🤱", "name": "breastfeeding", "keywords": ["nursing", "baby"] },
|
||||
{ "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] },
|
||||
{ "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] },
|
||||
@@ -459,7 +478,7 @@
|
||||
{ "category": "animals_and_nature", "char": "🐛", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] },
|
||||
{ "category": "animals_and_nature", "char": "🦋", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] },
|
||||
{ "category": "animals_and_nature", "char": "🐌", "name": "snail", "keywords": ["slow", "animal", "shell"] },
|
||||
{ "category": "animals_and_nature", "char": "🐞", "name": "beetle", "keywords": ["animal", "insect", "nature", "ladybug"] },
|
||||
{ "category": "animals_and_nature", "char": "🐞", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] },
|
||||
{ "category": "animals_and_nature", "char": "🐜", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] },
|
||||
{ "category": "animals_and_nature", "char": "🦗", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] },
|
||||
{ "category": "animals_and_nature", "char": "🕷", "name": "spider", "keywords": ["animal", "arachnid"] },
|
||||
@@ -615,6 +634,10 @@
|
||||
{ "category": "animals_and_nature", "char": "💧", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] },
|
||||
{ "category": "animals_and_nature", "char": "💦", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] },
|
||||
{ "category": "animals_and_nature", "char": "🌊", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] },
|
||||
{ "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] },
|
||||
{ "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] },
|
||||
{ "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] },
|
||||
{ "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] },
|
||||
{ "category": "food_and_drink", "char": "🍏", "name": "green_apple", "keywords": ["fruit", "nature"] },
|
||||
{ "category": "food_and_drink", "char": "🍎", "name": "apple", "keywords": ["fruit", "mac", "school"] },
|
||||
{ "category": "food_and_drink", "char": "🍐", "name": "pear", "keywords": ["fruit", "nature", "food"] },
|
||||
@@ -737,6 +760,9 @@
|
||||
{ "category": "food_and_drink", "char": "🥣", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] },
|
||||
{ "category": "food_and_drink", "char": "🥡", "name": "takeout_box", "keywords": ["food", "leftovers"] },
|
||||
{ "category": "food_and_drink", "char": "🥢", "name": "chopsticks", "keywords": ["food"] },
|
||||
{ "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] },
|
||||
{ "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] },
|
||||
{ "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] },
|
||||
{ "category": "activity", "char": "⚽", "name": "soccer", "keywords": ["sports", "football"] },
|
||||
{ "category": "activity", "char": "🏀", "name": "basketball", "keywords": ["sports", "balls", "NBA"] },
|
||||
{ "category": "activity", "char": "🏈", "name": "football", "keywords": ["sports", "balls", "NFL"] },
|
||||
@@ -844,6 +870,8 @@
|
||||
{ "category": "activity", "char": "🪄", "name": "magic_wand", "keywords": [] },
|
||||
{ "category": "activity", "char": "🪅", "name": "pinata", "keywords": [] },
|
||||
{ "category": "activity", "char": "🪆", "name": "nesting_dolls", "keywords": [] },
|
||||
{ "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] },
|
||||
{ "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] },
|
||||
{ "category": "travel_and_places", "char": "🚗", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] },
|
||||
{ "category": "travel_and_places", "char": "🚕", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] },
|
||||
{ "category": "travel_and_places", "char": "🚙", "name": "blue_car", "keywords": ["transportation", "vehicle"] },
|
||||
@@ -971,11 +999,12 @@
|
||||
{ "category": "travel_and_places", "char": "🕋", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] },
|
||||
{ "category": "travel_and_places", "char": "⛩", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] },
|
||||
{ "category": "travel_and_places", "char": "🛕", "name": "hindu_temple", "keywords": ["temple"] },
|
||||
|
||||
{ "category": "travel_and_places", "char": "🪨", "name": "rock", "keywords": [] },
|
||||
{ "category": "travel_and_places", "char": "🪵", "name": "wood", "keywords": [] },
|
||||
{ "category": "travel_and_places", "char": "🛖", "name": "hut", "keywords": [] },
|
||||
|
||||
{ "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] },
|
||||
{ "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] },
|
||||
{ "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] },
|
||||
{ "category": "objects", "char": "⌚", "name": "watch", "keywords": ["time", "accessories"] },
|
||||
{ "category": "objects", "char": "📱", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] },
|
||||
{ "category": "objects", "char": "📲", "name": "calling", "keywords": ["iphone", "incoming"] },
|
||||
@@ -1016,6 +1045,7 @@
|
||||
{ "category": "objects", "char": "⌛", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] },
|
||||
{ "category": "objects", "char": "📡", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] },
|
||||
{ "category": "objects", "char": "🔋", "name": "battery", "keywords": ["power", "energy", "sustain"] },
|
||||
{ "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] },
|
||||
{ "category": "objects", "char": "🔌", "name": "electric_plug", "keywords": ["charger", "power"] },
|
||||
{ "category": "objects", "char": "💡", "name": "bulb", "keywords": ["light", "electricity", "idea"] },
|
||||
{ "category": "objects", "char": "🔦", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] },
|
||||
@@ -1031,6 +1061,7 @@
|
||||
{ "category": "objects", "char": "💰", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] },
|
||||
{ "category": "objects", "char": "🪙", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] },
|
||||
{ "category": "objects", "char": "💳", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] },
|
||||
{ "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] },
|
||||
{ "category": "objects", "char": "💎", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] },
|
||||
{ "category": "objects", "char": "⚖", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] },
|
||||
{ "category": "objects", "char": "🧰", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] },
|
||||
@@ -1077,6 +1108,8 @@
|
||||
{ "category": "objects", "char": "🩹", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
|
||||
{ "category": "objects", "char": "🩺", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
|
||||
{ "category": "objects", "char": "🪒", "name": "razor", "keywords": ["health"] },
|
||||
{ "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] },
|
||||
{ "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] },
|
||||
{ "category": "objects", "char": "🧬", "name": "dna", "keywords": ["biologist", "genetics", "life"] },
|
||||
{ "category": "objects", "char": "🧫", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] },
|
||||
{ "category": "objects", "char": "🧪", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] },
|
||||
@@ -1111,6 +1144,7 @@
|
||||
{ "category": "objects", "char": "🪤", "name": "mouse_trap", "keywords": ["household"] },
|
||||
{ "category": "objects", "char": "🪣", "name": "bucket", "keywords": ["household"] },
|
||||
{ "category": "objects", "char": "🪥", "name": "toothbrush", "keywords": ["household"] },
|
||||
{ "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] },
|
||||
{ "category": "objects", "char": "⛱", "name": "parasol_on_ground", "keywords": ["weather", "summer"] },
|
||||
{ "category": "objects", "char": "🗿", "name": "moyai", "keywords": ["rock", "easter island", "moai"] },
|
||||
{ "category": "objects", "char": "🛍", "name": "shopping", "keywords": ["mall", "buy", "purchase"] },
|
||||
@@ -1404,6 +1438,7 @@
|
||||
{ "category": "symbols", "char": "➖", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] },
|
||||
{ "category": "symbols", "char": "➗", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] },
|
||||
{ "category": "symbols", "char": "✖️", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] },
|
||||
{ "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] },
|
||||
{ "category": "symbols", "char": "♾", "name": "infinity", "keywords": ["forever"] },
|
||||
{ "category": "symbols", "char": "💲", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] },
|
||||
{ "category": "symbols", "char": "💱", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] },
|
||||
@@ -1747,3 +1782,4 @@
|
||||
{ "category": "flags", "char": "🇺🇳", "name": "united_nations", "keywords": ["un", "flag", "banner"] },
|
||||
{ "category": "flags", "char": "🏴☠️", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] }
|
||||
]
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
export default (v, digits = 0) => {
|
||||
if (v == null) return '?';
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (v == 0) return '0';
|
||||
if (v === 0) return '0';
|
||||
const isMinus = v < 0;
|
||||
if (isMinus) v = -v;
|
||||
const i = Math.floor(Math.log(v) / Math.log(1024));
|
||||
|
@@ -13,9 +13,9 @@ if (localStorage.getItem('accounts') != null) {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
|
||||
import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
|
||||
import compareVersions from 'compare-versions';
|
||||
import * as JSON5 from 'json5';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
import widgets from '@/widgets';
|
||||
import directives from '@/directives';
|
||||
@@ -149,7 +149,7 @@ if ($i && $i.token) {
|
||||
try {
|
||||
document.body.innerHTML = '<div>Please wait...</div>';
|
||||
await login(i);
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
// Render the error screen
|
||||
// TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな)
|
||||
document.body.innerHTML = '<div id="err">Oops!</div>';
|
||||
@@ -171,14 +171,14 @@ fetchInstanceMetaPromise.then(() => {
|
||||
initializeSw();
|
||||
});
|
||||
|
||||
const app = createApp(await (
|
||||
window.location.search === '?zen' ? import('@/ui/zen.vue') :
|
||||
!$i ? import('@/ui/visitor.vue') :
|
||||
ui === 'deck' ? import('@/ui/deck.vue') :
|
||||
ui === 'desktop' ? import('@/ui/desktop.vue') :
|
||||
ui === 'classic' ? import('@/ui/classic.vue') :
|
||||
import('@/ui/universal.vue')
|
||||
).then(x => x.default));
|
||||
const app = createApp(
|
||||
window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
|
||||
!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
|
||||
ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
|
||||
ui === 'desktop' ? defineAsyncComponent(() => import('@/ui/desktop.vue')) :
|
||||
ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
|
||||
defineAsyncComponent(() => import('@/ui/universal.vue'))
|
||||
);
|
||||
|
||||
if (_DEV_) {
|
||||
app.config.performance = true;
|
||||
@@ -206,11 +206,26 @@ if (splash) splash.addEventListener('transitionend', () => {
|
||||
splash.remove();
|
||||
});
|
||||
|
||||
const rootEl = document.createElement('div');
|
||||
document.body.appendChild(rootEl);
|
||||
|
||||
if (ui === 'deck') await deckStore.ready;
|
||||
|
||||
// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
|
||||
// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する
|
||||
const rootEl = (() => {
|
||||
const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
|
||||
|
||||
const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID);
|
||||
|
||||
if (currentEl) {
|
||||
console.warn('multiple import detected');
|
||||
return currentEl;
|
||||
}
|
||||
|
||||
const rootEl = document.createElement('div');
|
||||
rootEl.id = MISSKEY_MOUNT_DIV_ID;
|
||||
document.body.appendChild(rootEl);
|
||||
return rootEl;
|
||||
})();
|
||||
|
||||
app.mount(rootEl);
|
||||
|
||||
// boot.jsのやつを解除
|
||||
@@ -236,10 +251,10 @@ if (lastVersion !== version) {
|
||||
if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
|
||||
// ログインしてる場合だけ
|
||||
if ($i) {
|
||||
popup(import('@/components/updated.vue'), {}, {}, 'closed');
|
||||
popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,7 +339,7 @@ stream.on('_disconnected_', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('emojiAdded', data => {
|
||||
stream.on('emojiAdded', emojiData => {
|
||||
// TODO
|
||||
//store.commit('instance/set', );
|
||||
});
|
||||
|
@@ -4,11 +4,11 @@ import { api } from './os';
|
||||
|
||||
// TODO: 他のタブと永続化されたstateを同期
|
||||
|
||||
const data = localStorage.getItem('instance');
|
||||
const instanceData = localStorage.getItem('instance');
|
||||
|
||||
// TODO: instanceをリアクティブにするかは再考の余地あり
|
||||
|
||||
export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : {
|
||||
export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : {
|
||||
// TODO: set default values
|
||||
});
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
|
||||
|
||||
import { Component, markRaw, Ref, ref } from 'vue';
|
||||
import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import insertTextAtCursor from 'insert-text-at-cursor';
|
||||
import * as Misskey from 'misskey-js';
|
||||
@@ -59,10 +59,10 @@ export const apiWithDialog = ((
|
||||
token?: string | null | undefined,
|
||||
) => {
|
||||
const promise = api(endpoint, data, token);
|
||||
promiseDialog(promise, null, (e) => {
|
||||
promiseDialog(promise, null, (err) => {
|
||||
alert({
|
||||
type: 'error',
|
||||
text: e.message + '\n' + (e as any).id,
|
||||
text: err.message + '\n' + (err as any).id,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export const apiWithDialog = ((
|
||||
export function promiseDialog<T extends Promise<any>>(
|
||||
promise: T,
|
||||
onSuccess?: ((res: any) => void) | null,
|
||||
onFailure?: ((e: Error) => void) | null,
|
||||
onFailure?: ((err: Error) => void) | null,
|
||||
text?: string,
|
||||
): T {
|
||||
const showing = ref(true);
|
||||
@@ -88,14 +88,14 @@ export function promiseDialog<T extends Promise<any>>(
|
||||
showing.value = false;
|
||||
}, 1000);
|
||||
}
|
||||
}).catch(e => {
|
||||
}).catch(err => {
|
||||
showing.value = false;
|
||||
if (onFailure) {
|
||||
onFailure(e);
|
||||
onFailure(err);
|
||||
} else {
|
||||
alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
text: err,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -110,10 +110,6 @@ export function promiseDialog<T extends Promise<any>>(
|
||||
return promise;
|
||||
}
|
||||
|
||||
function isModule(x: any): x is typeof import('*.vue') {
|
||||
return x.default != null;
|
||||
}
|
||||
|
||||
let popupIdCount = 0;
|
||||
export const popups = ref([]) as Ref<{
|
||||
id: any;
|
||||
@@ -131,10 +127,7 @@ export function claimZIndex(priority: 'low' | 'middle' | 'high' = 'low'): number
|
||||
return zIndexes[priority];
|
||||
}
|
||||
|
||||
export async function popup(component: Component | typeof import('*.vue') | Promise<Component | typeof import('*.vue')>, props: Record<string, any>, events = {}, disposeEvent?: string) {
|
||||
if (component.then) component = await component;
|
||||
|
||||
if (isModule(component)) component = component.default;
|
||||
export async function popup(component: Component, props: Record<string, any>, events = {}, disposeEvent?: string) {
|
||||
markRaw(component);
|
||||
|
||||
const id = ++popupIdCount;
|
||||
@@ -163,7 +156,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom
|
||||
|
||||
export function pageWindow(path: string) {
|
||||
const { component, props } = resolve(path);
|
||||
popup(import('@/components/page-window.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/page-window.vue')), {
|
||||
initialPath: path,
|
||||
initialComponent: markRaw(component),
|
||||
initialProps: props,
|
||||
@@ -172,7 +165,7 @@ export function pageWindow(path: string) {
|
||||
|
||||
export function modalPageWindow(path: string) {
|
||||
const { component, props } = resolve(path);
|
||||
popup(import('@/components/modal-page-window.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/modal-page-window.vue')), {
|
||||
initialPath: path,
|
||||
initialComponent: markRaw(component),
|
||||
initialProps: props,
|
||||
@@ -180,7 +173,7 @@ export function modalPageWindow(path: string) {
|
||||
}
|
||||
|
||||
export function toast(message: string) {
|
||||
popup(import('@/components/toast.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
|
||||
message
|
||||
}, {}, 'closed');
|
||||
}
|
||||
@@ -191,7 +184,7 @@ export function alert(props: {
|
||||
text?: string | null;
|
||||
}): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), props, {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, {
|
||||
done: result => {
|
||||
resolve();
|
||||
},
|
||||
@@ -205,7 +198,7 @@ export function confirm(props: {
|
||||
text?: string | null;
|
||||
}): Promise<{ canceled: boolean }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
|
||||
...props,
|
||||
showCancelButton: true,
|
||||
}, {
|
||||
@@ -226,7 +219,7 @@ export function inputText(props: {
|
||||
canceled: false; result: string;
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
input: {
|
||||
@@ -251,7 +244,7 @@ export function inputNumber(props: {
|
||||
canceled: false; result: number;
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
input: {
|
||||
@@ -276,7 +269,7 @@ export function inputDate(props: {
|
||||
canceled: false; result: Date;
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
input: {
|
||||
@@ -313,7 +306,7 @@ export function select<C extends any = any>(props: {
|
||||
canceled: false; result: C;
|
||||
}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
|
||||
title: props.title,
|
||||
text: props.text,
|
||||
select: {
|
||||
@@ -335,7 +328,7 @@ export function success() {
|
||||
window.setTimeout(() => {
|
||||
showing.value = false;
|
||||
}, 1000);
|
||||
popup(import('@/components/waiting-dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
||||
success: true,
|
||||
showing: showing
|
||||
}, {
|
||||
@@ -347,7 +340,7 @@ export function success() {
|
||||
export function waiting() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const showing = ref(true);
|
||||
popup(import('@/components/waiting-dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
|
||||
success: false,
|
||||
showing: showing
|
||||
}, {
|
||||
@@ -358,7 +351,7 @@ export function waiting() {
|
||||
|
||||
export function form(title, form) {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/form-dialog.vue'), { title, form }, {
|
||||
popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, {
|
||||
done: result => {
|
||||
resolve(result);
|
||||
},
|
||||
@@ -368,7 +361,7 @@ export function form(title, form) {
|
||||
|
||||
export async function selectUser() {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/user-select-dialog.vue'), {}, {
|
||||
popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, {
|
||||
ok: user => {
|
||||
resolve(user);
|
||||
},
|
||||
@@ -378,7 +371,7 @@ export async function selectUser() {
|
||||
|
||||
export async function selectDriveFile(multiple: boolean) {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/drive-select-dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
||||
type: 'file',
|
||||
multiple
|
||||
}, {
|
||||
@@ -393,7 +386,7 @@ export async function selectDriveFile(multiple: boolean) {
|
||||
|
||||
export async function selectDriveFolder(multiple: boolean) {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/drive-select-dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
|
||||
type: 'folder',
|
||||
multiple
|
||||
}, {
|
||||
@@ -408,7 +401,7 @@ export async function selectDriveFolder(multiple: boolean) {
|
||||
|
||||
export async function pickEmoji(src: HTMLElement | null, opts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
popup(import('@/components/emoji-picker-dialog.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
|
||||
src,
|
||||
...opts
|
||||
}, {
|
||||
@@ -458,7 +451,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
|
||||
characterData: false,
|
||||
});
|
||||
|
||||
openingEmojiPicker = await popup(import('@/components/emoji-picker-window.vue'), {
|
||||
openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), {
|
||||
src,
|
||||
...opts
|
||||
}, {
|
||||
@@ -480,7 +473,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let dispose;
|
||||
popup(import('@/components/ui/popup-menu.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/ui/popup-menu.vue')), {
|
||||
items,
|
||||
src,
|
||||
width: options?.width,
|
||||
@@ -501,7 +494,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent)
|
||||
ev.preventDefault();
|
||||
return new Promise((resolve, reject) => {
|
||||
let dispose;
|
||||
popup(import('@/components/ui/context-menu.vue'), {
|
||||
popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), {
|
||||
items,
|
||||
ev,
|
||||
}, {
|
||||
|
@@ -150,6 +150,7 @@ const patrons = [
|
||||
'Weeble',
|
||||
'蝉暮せせせ',
|
||||
'ThatOneCalculator',
|
||||
'pixeldesu',
|
||||
];
|
||||
|
||||
let easterEggReady = false;
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<template #label>URL</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="ad.imageUrl" class="_formBlock">
|
||||
<template #label>{{ $ts.imageUrl }}</template>
|
||||
<template #label>{{ i18n.ts.imageUrl }}</template>
|
||||
</MkInput>
|
||||
<FormRadios v-model="ad.place" class="_formBlock">
|
||||
<template #label>Form</template>
|
||||
@@ -17,34 +17,34 @@
|
||||
</FormRadios>
|
||||
<!--
|
||||
<div style="margin: 32px 0;">
|
||||
{{ $ts.priority }}
|
||||
<MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio>
|
||||
<MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio>
|
||||
<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
|
||||
{{ i18n.ts.priority }}
|
||||
<MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
|
||||
<MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
|
||||
<MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
|
||||
</div>
|
||||
-->
|
||||
<FormSplit>
|
||||
<MkInput v-model="ad.ratio" type="number">
|
||||
<template #label>{{ $ts.ratio }}</template>
|
||||
<template #label>{{ i18n.ts.ratio }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="ad.expiresAt" type="date">
|
||||
<template #label>{{ $ts.expiration }}</template>
|
||||
<template #label>{{ i18n.ts.expiration }}</template>
|
||||
</MkInput>
|
||||
</FormSplit>
|
||||
<MkTextarea v-model="ad.memo" class="_formBlock">
|
||||
<template #label>{{ $ts.memo }}</template>
|
||||
<template #label>{{ i18n.ts.memo }}</template>
|
||||
</MkTextarea>
|
||||
<div class="buttons _formBlock">
|
||||
<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
|
||||
<MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
|
||||
<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
@@ -52,81 +52,65 @@ import FormRadios from '@/components/form/radios.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkTextarea,
|
||||
FormRadios,
|
||||
FormSplit,
|
||||
},
|
||||
let ads: any[] = $ref([]);
|
||||
|
||||
emits: ['info'],
|
||||
os.api('admin/ad/list').then(adsResponse => {
|
||||
ads = adsResponse;
|
||||
});
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.ads,
|
||||
icon: 'fas fa-audio-description',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: this.$ts.add,
|
||||
handler: this.add,
|
||||
}],
|
||||
},
|
||||
ads: [],
|
||||
}
|
||||
},
|
||||
function add() {
|
||||
ads.unshift({
|
||||
id: null,
|
||||
memo: '',
|
||||
place: 'square',
|
||||
priority: 'middle',
|
||||
ratio: 1,
|
||||
url: '',
|
||||
imageUrl: null,
|
||||
expiresAt: null,
|
||||
});
|
||||
}
|
||||
|
||||
created() {
|
||||
os.api('admin/ad/list').then(ads => {
|
||||
this.ads = ads;
|
||||
function remove(ad) {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: ad.url }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
ads = ads.filter(x => x !== ad);
|
||||
os.apiWithDialog('admin/ad/delete', {
|
||||
id: ad.id
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.ads.unshift({
|
||||
id: null,
|
||||
memo: '',
|
||||
place: 'square',
|
||||
priority: 'middle',
|
||||
ratio: 1,
|
||||
url: '',
|
||||
imageUrl: null,
|
||||
expiresAt: null,
|
||||
});
|
||||
},
|
||||
function save(ad) {
|
||||
if (ad.id == null) {
|
||||
os.apiWithDialog('admin/ad/create', {
|
||||
...ad,
|
||||
expiresAt: new Date(ad.expiresAt).getTime()
|
||||
});
|
||||
} else {
|
||||
os.apiWithDialog('admin/ad/update', {
|
||||
...ad,
|
||||
expiresAt: new Date(ad.expiresAt).getTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
remove(ad) {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: ad.url }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
this.ads = this.ads.filter(x => x != ad);
|
||||
os.apiWithDialog('admin/ad/delete', {
|
||||
id: ad.id
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
save(ad) {
|
||||
if (ad.id == null) {
|
||||
os.apiWithDialog('admin/ad/create', {
|
||||
...ad,
|
||||
expiresAt: new Date(ad.expiresAt).getTime()
|
||||
});
|
||||
} else {
|
||||
os.apiWithDialog('admin/ad/update', {
|
||||
...ad,
|
||||
expiresAt: new Date(ad.expiresAt).getTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.ads,
|
||||
icon: 'fas fa-audio-description',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: i18n.ts.add,
|
||||
handler: add,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -3,112 +3,98 @@
|
||||
<section v-for="announcement in announcements" class="_card _gap announcements">
|
||||
<div class="_content announcement">
|
||||
<MkInput v-model="announcement.title">
|
||||
<template #label>{{ $ts.title }}</template>
|
||||
<template #label>{{ i18n.ts.title }}</template>
|
||||
</MkInput>
|
||||
<MkTextarea v-model="announcement.text">
|
||||
<template #label>{{ $ts.text }}</template>
|
||||
<template #label>{{ i18n.ts.text }}</template>
|
||||
</MkTextarea>
|
||||
<MkInput v-model="announcement.imageUrl">
|
||||
<template #label>{{ $ts.imageUrl }}</template>
|
||||
<template #label>{{ i18n.ts.imageUrl }}</template>
|
||||
</MkInput>
|
||||
<p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p>
|
||||
<p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p>
|
||||
<div class="buttons">
|
||||
<MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
|
||||
<MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
|
||||
<MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkTextarea,
|
||||
},
|
||||
let announcements: any[] = $ref([]);
|
||||
|
||||
emits: ['info'],
|
||||
os.api('admin/announcements/list').then(announcementResponse => {
|
||||
announcements = announcementResponse;
|
||||
});
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.announcements,
|
||||
icon: 'fas fa-broadcast-tower',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: this.$ts.add,
|
||||
handler: this.add,
|
||||
}],
|
||||
},
|
||||
announcements: [],
|
||||
}
|
||||
},
|
||||
function add() {
|
||||
announcements.unshift({
|
||||
id: null,
|
||||
title: '',
|
||||
text: '',
|
||||
imageUrl: null
|
||||
});
|
||||
}
|
||||
|
||||
created() {
|
||||
os.api('admin/announcements/list').then(announcements => {
|
||||
this.announcements = announcements;
|
||||
function remove(announcement) {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: announcement.title }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
announcements = announcements.filter(x => x !== announcement);
|
||||
os.api('admin/announcements/delete', announcement);
|
||||
});
|
||||
}
|
||||
|
||||
function save(announcement) {
|
||||
if (announcement.id == null) {
|
||||
os.api('admin/announcements/create', announcement).then(() => {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
text: i18n.ts.saved
|
||||
});
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.announcements.unshift({
|
||||
id: null,
|
||||
title: '',
|
||||
text: '',
|
||||
imageUrl: null
|
||||
} else {
|
||||
os.api('admin/announcements/update', announcement).then(() => {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
text: i18n.ts.saved
|
||||
});
|
||||
},
|
||||
|
||||
remove(announcement) {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: announcement.title }),
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
this.announcements = this.announcements.filter(x => x != announcement);
|
||||
os.api('admin/announcements/delete', announcement);
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
save(announcement) {
|
||||
if (announcement.id == null) {
|
||||
os.api('admin/announcements/create', announcement).then(() => {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
text: this.$ts.saved
|
||||
});
|
||||
}).catch(e => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
} else {
|
||||
os.api('admin/announcements/update', announcement).then(() => {
|
||||
os.alert({
|
||||
type: 'success',
|
||||
text: this.$ts.saved
|
||||
});
|
||||
}).catch(e => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.announcements,
|
||||
icon: 'fas fa-broadcast-tower',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: i18n.ts.add,
|
||||
handler: add,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -43,8 +43,8 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import FormRadios from '@/components/form/radios.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
@@ -54,64 +54,39 @@ import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormRadios,
|
||||
FormInput,
|
||||
FormButton,
|
||||
FormSuspense,
|
||||
FormSlot,
|
||||
MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')),
|
||||
},
|
||||
const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue'));
|
||||
|
||||
emits: ['info'],
|
||||
let provider = $ref(null);
|
||||
let hcaptchaSiteKey: string | null = $ref(null);
|
||||
let hcaptchaSecretKey: string | null = $ref(null);
|
||||
let recaptchaSiteKey: string | null = $ref(null);
|
||||
let recaptchaSecretKey: string | null = $ref(null);
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.botProtection,
|
||||
icon: 'fas fa-shield-alt'
|
||||
},
|
||||
provider: null,
|
||||
enableHcaptcha: false,
|
||||
hcaptchaSiteKey: null,
|
||||
hcaptchaSecretKey: null,
|
||||
enableRecaptcha: false,
|
||||
recaptchaSiteKey: null,
|
||||
recaptchaSecretKey: null,
|
||||
}
|
||||
},
|
||||
const enableHcaptcha = $computed(() => provider === 'hcaptcha');
|
||||
const enableRecaptcha = $computed(() => provider === 'recaptcha');
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableHcaptcha = meta.enableHcaptcha;
|
||||
this.hcaptchaSiteKey = meta.hcaptchaSiteKey;
|
||||
this.hcaptchaSecretKey = meta.hcaptchaSecretKey;
|
||||
this.enableRecaptcha = meta.enableRecaptcha;
|
||||
this.recaptchaSiteKey = meta.recaptchaSiteKey;
|
||||
this.recaptchaSecretKey = meta.recaptchaSecretKey;
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
enableHcaptcha = meta.enableHcaptcha;
|
||||
hcaptchaSiteKey = meta.hcaptchaSiteKey;
|
||||
hcaptchaSecretKey = meta.hcaptchaSecretKey;
|
||||
enableRecaptcha = meta.enableRecaptcha;
|
||||
recaptchaSiteKey = meta.recaptchaSiteKey;
|
||||
recaptchaSecretKey = meta.recaptchaSecretKey;
|
||||
|
||||
this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null;
|
||||
provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null;
|
||||
}
|
||||
|
||||
this.$watch(() => this.provider, () => {
|
||||
this.enableHcaptcha = this.provider === 'hcaptcha';
|
||||
this.enableRecaptcha = this.provider === 'recaptcha';
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableHcaptcha: this.enableHcaptcha,
|
||||
hcaptchaSiteKey: this.hcaptchaSiteKey,
|
||||
hcaptchaSecretKey: this.hcaptchaSecretKey,
|
||||
enableRecaptcha: this.enableRecaptcha,
|
||||
recaptchaSiteKey: this.recaptchaSiteKey,
|
||||
recaptchaSecretKey: this.recaptchaSecretKey,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableHcaptcha,
|
||||
hcaptchaSiteKey,
|
||||
hcaptchaSecretKey,
|
||||
enableRecaptcha,
|
||||
recaptchaSiteKey,
|
||||
recaptchaSecretKey,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@@ -9,36 +9,23 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import bytes from '@/filters/bytes';
|
||||
import number from '@/filters/number';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSuspense,
|
||||
MkKeyValue,
|
||||
},
|
||||
const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
|
||||
|
||||
emits: ['info'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.database,
|
||||
icon: 'fas fa-database',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)),
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
bytes, number,
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.database,
|
||||
icon: 'fas fa-database',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -3,37 +3,37 @@
|
||||
<FormSuspense :p="init">
|
||||
<div class="_formRoot">
|
||||
<FormSwitch v-model="enableEmail" class="_formBlock">
|
||||
<template #label>{{ $ts.enableEmail }}</template>
|
||||
<template #caption>{{ $ts.emailConfigInfo }}</template>
|
||||
<template #label>{{ i18n.ts.enableEmail }}</template>
|
||||
<template #caption>{{ i18n.ts.emailConfigInfo }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<template v-if="enableEmail">
|
||||
<FormInput v-model="email" type="email" class="_formBlock">
|
||||
<template #label>{{ $ts.emailAddress }}</template>
|
||||
<template #label>{{ i18n.ts.emailAddress }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.smtpConfig }}</template>
|
||||
<template #label>{{ i18n.ts.smtpConfig }}</template>
|
||||
<FormSplit :min-width="280">
|
||||
<FormInput v-model="smtpHost" class="_formBlock">
|
||||
<template #label>{{ $ts.smtpHost }}</template>
|
||||
<template #label>{{ i18n.ts.smtpHost }}</template>
|
||||
</FormInput>
|
||||
<FormInput v-model="smtpPort" type="number" class="_formBlock">
|
||||
<template #label>{{ $ts.smtpPort }}</template>
|
||||
<template #label>{{ i18n.ts.smtpPort }}</template>
|
||||
</FormInput>
|
||||
</FormSplit>
|
||||
<FormSplit :min-width="280">
|
||||
<FormInput v-model="smtpUser" class="_formBlock">
|
||||
<template #label>{{ $ts.smtpUser }}</template>
|
||||
<template #label>{{ i18n.ts.smtpUser }}</template>
|
||||
</FormInput>
|
||||
<FormInput v-model="smtpPass" type="password" class="_formBlock">
|
||||
<template #label>{{ $ts.smtpPass }}</template>
|
||||
<template #label>{{ i18n.ts.smtpPass }}</template>
|
||||
</FormInput>
|
||||
</FormSplit>
|
||||
<FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
|
||||
<FormInfo class="_formBlock">{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
|
||||
<FormSwitch v-model="smtpSecure" class="_formBlock">
|
||||
<template #label>{{ $ts.smtpSecure }}</template>
|
||||
<template #caption>{{ $ts.smtpSecureInfo }}</template>
|
||||
<template #label>{{ i18n.ts.smtpSecure }}</template>
|
||||
<template #caption>{{ i18n.ts.smtpSecureInfo }}</template>
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
</template>
|
||||
@@ -42,8 +42,8 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
@@ -52,86 +52,71 @@ import FormSplit from '@/components/form/split.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { fetchInstance, instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormSplit,
|
||||
FormSection,
|
||||
FormInfo,
|
||||
FormSuspense,
|
||||
},
|
||||
let enableEmail: boolean = $ref(false);
|
||||
let email: any = $ref(null);
|
||||
let smtpSecure: boolean = $ref(false);
|
||||
let smtpHost: string = $ref('');
|
||||
let smtpPort: number = $ref(0);
|
||||
let smtpUser: string = $ref('');
|
||||
let smtpPass: string = $ref('');
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
enableEmail = meta.enableEmail;
|
||||
email = meta.email;
|
||||
smtpSecure = meta.smtpSecure;
|
||||
smtpHost = meta.smtpHost;
|
||||
smtpPort = meta.smtpPort;
|
||||
smtpUser = meta.smtpUser;
|
||||
smtpPass = meta.smtpPass;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.emailServer,
|
||||
icon: 'fas fa-envelope',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
text: this.$ts.testEmail,
|
||||
handler: this.testEmail,
|
||||
}, {
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: this.$ts.save,
|
||||
handler: this.save,
|
||||
}],
|
||||
},
|
||||
enableEmail: false,
|
||||
email: null,
|
||||
smtpSecure: false,
|
||||
smtpHost: '',
|
||||
smtpPort: 0,
|
||||
smtpUser: '',
|
||||
smtpPass: '',
|
||||
}
|
||||
},
|
||||
async function testEmail() {
|
||||
const { canceled, result: destination } = await os.inputText({
|
||||
title: i18n.ts.destination,
|
||||
type: 'email',
|
||||
placeholder: instance.maintainerEmail
|
||||
});
|
||||
if (canceled) return;
|
||||
os.apiWithDialog('admin/send-email', {
|
||||
to: destination,
|
||||
subject: 'Test email',
|
||||
text: 'Yo'
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableEmail = meta.enableEmail;
|
||||
this.email = meta.email;
|
||||
this.smtpSecure = meta.smtpSecure;
|
||||
this.smtpHost = meta.smtpHost;
|
||||
this.smtpPort = meta.smtpPort;
|
||||
this.smtpUser = meta.smtpUser;
|
||||
this.smtpPass = meta.smtpPass;
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableEmail,
|
||||
email,
|
||||
smtpSecure,
|
||||
smtpHost,
|
||||
smtpPort,
|
||||
smtpUser,
|
||||
smtpPass,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
async testEmail() {
|
||||
const { canceled, result: destination } = await os.inputText({
|
||||
title: this.$ts.destination,
|
||||
type: 'email',
|
||||
placeholder: this.$instance.maintainerEmail
|
||||
});
|
||||
if (canceled) return;
|
||||
os.apiWithDialog('admin/send-email', {
|
||||
to: destination,
|
||||
subject: 'Test email',
|
||||
text: 'Yo'
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableEmail: this.enableEmail,
|
||||
email: this.email,
|
||||
smtpSecure: this.smtpSecure,
|
||||
smtpHost: this.smtpHost,
|
||||
smtpPort: this.smtpPort,
|
||||
smtpUser: this.smtpUser,
|
||||
smtpPass: this.smtpPass,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.emailServer,
|
||||
icon: 'fas fa-envelope',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
text: i18n.ts.testEmail,
|
||||
handler: testEmail,
|
||||
}, {
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: i18n.ts.save,
|
||||
handler: save,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -27,85 +27,71 @@
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import XModalWindow from '@/components/ui/modal-window.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import * as os from '@/os';
|
||||
import { unique } from '@/scripts/array';
|
||||
import { i18n } from '@/i18n';
|
||||
import { emojiCategories } from '@/instance';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
XModalWindow,
|
||||
MkButton,
|
||||
MkInput,
|
||||
},
|
||||
const props = defineProps<{
|
||||
emoji: any,
|
||||
}>();
|
||||
|
||||
props: {
|
||||
emoji: {
|
||||
required: true,
|
||||
let dialog = $ref(null);
|
||||
let name: string = $ref(props.emoji.name);
|
||||
let category: string = $ref(props.emoji.category);
|
||||
let aliases: string = $ref(props.emoji.aliases.join(' '));
|
||||
let categories: string[] = $ref(emojiCategories);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
function ok() {
|
||||
update();
|
||||
}
|
||||
|
||||
async function update() {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: props.emoji.id,
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
});
|
||||
|
||||
emit('done', {
|
||||
updated: {
|
||||
id: props.emoji.id,
|
||||
name,
|
||||
category,
|
||||
aliases: aliases.split(' '),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
emits: ['done', 'closed'],
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
name: this.emoji.name,
|
||||
category: this.emoji.category,
|
||||
aliases: this.emoji.aliases?.join(' '),
|
||||
categories: [],
|
||||
}
|
||||
},
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
created() {
|
||||
os.api('meta', { detail: false }).then(({ emojis }) => {
|
||||
this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
|
||||
os.api('admin/emoji/delete', {
|
||||
id: props.emoji.id
|
||||
}).then(() => {
|
||||
emit('done', {
|
||||
deleted: true
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
ok() {
|
||||
this.update();
|
||||
},
|
||||
|
||||
async update() {
|
||||
await os.apiWithDialog('admin/emoji/update', {
|
||||
id: this.emoji.id,
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
aliases: this.aliases.split(' '),
|
||||
});
|
||||
|
||||
this.$emit('done', {
|
||||
updated: {
|
||||
name: this.name,
|
||||
category: this.category,
|
||||
aliases: this.aliases.split(' '),
|
||||
}
|
||||
});
|
||||
this.$refs.dialog.close();
|
||||
},
|
||||
|
||||
async del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: this.emoji.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.api('admin/emoji/delete', {
|
||||
id: this.emoji.id
|
||||
}).then(() => {
|
||||
this.$emit('done', {
|
||||
deleted: true
|
||||
});
|
||||
this.$refs.dialog.close();
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -63,7 +63,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineComponent, ref, toRef } from 'vue';
|
||||
import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkPagination from '@/components/ui/pagination.vue';
|
||||
@@ -130,17 +130,17 @@ const add = async (ev: MouseEvent) => {
|
||||
};
|
||||
|
||||
const edit = (emoji) => {
|
||||
os.popup(import('./emoji-edit-dialog.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), {
|
||||
emoji: emoji
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.updated) {
|
||||
emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, {
|
||||
...emoji,
|
||||
emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
|
||||
...oldEmoji,
|
||||
...result.updated
|
||||
});
|
||||
}));
|
||||
} else if (result.deleted) {
|
||||
emojisPaginationComponent.value.removeItem(item => item.id === emoji.id);
|
||||
emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id);
|
||||
}
|
||||
},
|
||||
}, 'closed');
|
||||
@@ -175,10 +175,10 @@ const menu = (ev: MouseEvent) => {
|
||||
type: 'info',
|
||||
text: i18n.ts.exportRequested,
|
||||
});
|
||||
}).catch((e) => {
|
||||
}).catch((err) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e.message,
|
||||
text: err.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -195,10 +195,10 @@ const menu = (ev: MouseEvent) => {
|
||||
type: 'info',
|
||||
text: i18n.ts.importRequested,
|
||||
});
|
||||
}).catch((e) => {
|
||||
}).catch((err) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e.message,
|
||||
text: err.message,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@@ -34,74 +34,52 @@
|
||||
</XModalWindow>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSwitch from '@/components/form/switch.vue';
|
||||
import XModalWindow from '@/components/ui/modal-window.vue';
|
||||
import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
|
||||
import bytes from '@/filters/bytes';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
XModalWindow,
|
||||
MkDriveFileThumbnail,
|
||||
},
|
||||
let file: any = $ref(null);
|
||||
let info: any = $ref(null);
|
||||
let isSensitive: boolean = $ref(false);
|
||||
|
||||
props: {
|
||||
fileId: {
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
const props = defineProps<{
|
||||
fileId: string,
|
||||
}>();
|
||||
|
||||
emits: ['closed'],
|
||||
async function fetch() {
|
||||
file = await os.api('drive/files/show', { fileId: props.fileId });
|
||||
info = await os.api('admin/drive/show-file', { fileId: props.fileId });
|
||||
isSensitive = file.isSensitive;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
file: null,
|
||||
info: null,
|
||||
isSensitive: false,
|
||||
};
|
||||
},
|
||||
fetch();
|
||||
|
||||
created() {
|
||||
this.fetch();
|
||||
},
|
||||
function showUser() {
|
||||
os.pageWindow(`/user-info/${file.userId}`);
|
||||
}
|
||||
|
||||
methods: {
|
||||
async fetch() {
|
||||
this.file = await os.api('drive/files/show', { fileId: this.fileId });
|
||||
this.info = await os.api('admin/drive/show-file', { fileId: this.fileId });
|
||||
this.isSensitive = this.file.isSensitive;
|
||||
},
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.t('removeAreYouSure', { x: file.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
showUser() {
|
||||
os.pageWindow(`/user-info/${this.file.userId}`);
|
||||
},
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: file.id
|
||||
});
|
||||
}
|
||||
|
||||
async del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: this.$t('removeAreYouSure', { x: this.file.name }),
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('drive/files/delete', {
|
||||
fileId: this.file.id
|
||||
});
|
||||
},
|
||||
|
||||
async toggleIsSensitive(v) {
|
||||
await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
|
||||
this.isSensitive = v;
|
||||
},
|
||||
|
||||
bytes
|
||||
}
|
||||
});
|
||||
async function toggleIsSensitive(v) {
|
||||
await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
|
||||
isSensitive = v;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
@@ -93,7 +93,7 @@ function clear() {
|
||||
}
|
||||
|
||||
function show(file) {
|
||||
os.popup(import('./file-dialog.vue'), {
|
||||
os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), {
|
||||
fileId: file.id
|
||||
}, {}, 'closed');
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
|
||||
<div v-if="!narrow || page == null" class="nav">
|
||||
<div v-if="!narrow || initialPage == null" class="nav">
|
||||
<MkHeader :info="header"></MkHeader>
|
||||
|
||||
<MkSpacer :content-max="700" :margin-min="16">
|
||||
@@ -12,21 +12,21 @@
|
||||
<MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo>
|
||||
<MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo>
|
||||
|
||||
<MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
|
||||
<MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div v-if="!(narrow && initialPage == null)" class="main">
|
||||
<MkStickyContainer>
|
||||
<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template>
|
||||
<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
|
||||
<component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/>
|
||||
</MkStickyContainer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, nextTick, onMounted, onUnmounted, provide, watch } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import MkSuperMenu from '@/components/ui/super-menu.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
@@ -35,292 +35,277 @@ import { instance } from '@/instance';
|
||||
import * as symbols from '@/symbols';
|
||||
import * as os from '@/os';
|
||||
import { lookupUser } from '@/scripts/lookup-user';
|
||||
import { MisskeyNavigator } from '@/scripts/navigate';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkSuperMenu,
|
||||
MkInfo,
|
||||
},
|
||||
const isEmpty = (x: string | null) => x == null || x === '';
|
||||
|
||||
provide: {
|
||||
shouldOmitHeaderTitle: false,
|
||||
},
|
||||
const nav = new MisskeyNavigator();
|
||||
|
||||
props: {
|
||||
initialPage: {
|
||||
type: String,
|
||||
required: false
|
||||
const indexInfo = {
|
||||
title: i18n.ts.controlPanel,
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
hideHeader: true,
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
initialPage?: string,
|
||||
}>();
|
||||
|
||||
provide('shouldOmitHeaderTitle', false);
|
||||
|
||||
let INFO = $ref(indexInfo);
|
||||
let childInfo = $ref(null);
|
||||
let page = $ref(props.initialPage);
|
||||
let narrow = $ref(false);
|
||||
let view = $ref(null);
|
||||
let el = $ref(null);
|
||||
let pageProps = $ref({});
|
||||
let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
|
||||
let noBotProtection = !instance.enableHcaptcha && !instance.enableRecaptcha;
|
||||
|
||||
const NARROW_THRESHOLD = 600;
|
||||
const ro = new ResizeObserver((entries, observer) => {
|
||||
if (entries.length === 0) return;
|
||||
narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
|
||||
});
|
||||
|
||||
const menuDef = $computed(() => [{
|
||||
title: i18n.ts.quickAction,
|
||||
items: [{
|
||||
type: 'button',
|
||||
icon: 'fas fa-search',
|
||||
text: i18n.ts.lookup,
|
||||
action: lookup,
|
||||
}, ...(instance.disableRegistration ? [{
|
||||
type: 'button',
|
||||
icon: 'fas fa-user',
|
||||
text: i18n.ts.invite,
|
||||
action: invite,
|
||||
}] : [])],
|
||||
}, {
|
||||
title: i18n.ts.administration,
|
||||
items: [{
|
||||
icon: 'fas fa-tachometer-alt',
|
||||
text: i18n.ts.dashboard,
|
||||
to: '/admin/overview',
|
||||
active: props.initialPage === 'overview',
|
||||
}, {
|
||||
icon: 'fas fa-users',
|
||||
text: i18n.ts.users,
|
||||
to: '/admin/users',
|
||||
active: props.initialPage === 'users',
|
||||
}, {
|
||||
icon: 'fas fa-laugh',
|
||||
text: i18n.ts.customEmojis,
|
||||
to: '/admin/emojis',
|
||||
active: props.initialPage === 'emojis',
|
||||
}, {
|
||||
icon: 'fas fa-globe',
|
||||
text: i18n.ts.federation,
|
||||
to: '/admin/federation',
|
||||
active: props.initialPage === 'federation',
|
||||
}, {
|
||||
icon: 'fas fa-clipboard-list',
|
||||
text: i18n.ts.jobQueue,
|
||||
to: '/admin/queue',
|
||||
active: props.initialPage === 'queue',
|
||||
}, {
|
||||
icon: 'fas fa-cloud',
|
||||
text: i18n.ts.files,
|
||||
to: '/admin/files',
|
||||
active: props.initialPage === 'files',
|
||||
}, {
|
||||
icon: 'fas fa-broadcast-tower',
|
||||
text: i18n.ts.announcements,
|
||||
to: '/admin/announcements',
|
||||
active: props.initialPage === 'announcements',
|
||||
}, {
|
||||
icon: 'fas fa-audio-description',
|
||||
text: i18n.ts.ads,
|
||||
to: '/admin/ads',
|
||||
active: props.initialPage === 'ads',
|
||||
}, {
|
||||
icon: 'fas fa-exclamation-circle',
|
||||
text: i18n.ts.abuseReports,
|
||||
to: '/admin/abuses',
|
||||
active: props.initialPage === 'abuses',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.settings,
|
||||
items: [{
|
||||
icon: 'fas fa-cog',
|
||||
text: i18n.ts.general,
|
||||
to: '/admin/settings',
|
||||
active: props.initialPage === 'settings',
|
||||
}, {
|
||||
icon: 'fas fa-envelope',
|
||||
text: i18n.ts.emailServer,
|
||||
to: '/admin/email-settings',
|
||||
active: props.initialPage === 'email-settings',
|
||||
}, {
|
||||
icon: 'fas fa-cloud',
|
||||
text: i18n.ts.objectStorage,
|
||||
to: '/admin/object-storage',
|
||||
active: props.initialPage === 'object-storage',
|
||||
}, {
|
||||
icon: 'fas fa-lock',
|
||||
text: i18n.ts.security,
|
||||
to: '/admin/security',
|
||||
active: props.initialPage === 'security',
|
||||
}, {
|
||||
icon: 'fas fa-globe',
|
||||
text: i18n.ts.relays,
|
||||
to: '/admin/relays',
|
||||
active: props.initialPage === 'relays',
|
||||
}, {
|
||||
icon: 'fas fa-share-alt',
|
||||
text: i18n.ts.integration,
|
||||
to: '/admin/integrations',
|
||||
active: props.initialPage === 'integrations',
|
||||
}, {
|
||||
icon: 'fas fa-ban',
|
||||
text: i18n.ts.instanceBlocking,
|
||||
to: '/admin/instance-block',
|
||||
active: props.initialPage === 'instance-block',
|
||||
}, {
|
||||
icon: 'fas fa-ghost',
|
||||
text: i18n.ts.proxyAccount,
|
||||
to: '/admin/proxy-account',
|
||||
active: props.initialPage === 'proxy-account',
|
||||
}, {
|
||||
icon: 'fas fa-cogs',
|
||||
text: i18n.ts.other,
|
||||
to: '/admin/other-settings',
|
||||
active: props.initialPage === 'other-settings',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.info,
|
||||
items: [{
|
||||
icon: 'fas fa-database',
|
||||
text: i18n.ts.database,
|
||||
to: '/admin/database',
|
||||
active: props.initialPage === 'database',
|
||||
}],
|
||||
}]);
|
||||
|
||||
const component = $computed(() => {
|
||||
if (props.initialPage == null) return null;
|
||||
switch (props.initialPage) {
|
||||
case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
|
||||
case 'users': return defineAsyncComponent(() => import('./users.vue'));
|
||||
case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
|
||||
case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
|
||||
case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
|
||||
case 'files': return defineAsyncComponent(() => import('./files.vue'));
|
||||
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
|
||||
case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
|
||||
case 'database': return defineAsyncComponent(() => import('./database.vue'));
|
||||
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
|
||||
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
|
||||
case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
|
||||
case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
|
||||
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
||||
case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
|
||||
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
|
||||
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
|
||||
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
|
||||
case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
|
||||
}
|
||||
});
|
||||
|
||||
watch(component, () => {
|
||||
pageProps = {};
|
||||
|
||||
nextTick(() => {
|
||||
scroll(el, { top: 0 });
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.initialPage, () => {
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
} else {
|
||||
if (props.initialPage == null) {
|
||||
INFO = indexInfo;
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
setup(props, context) {
|
||||
const indexInfo = {
|
||||
title: i18n.ts.controlPanel,
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
hideHeader: true,
|
||||
};
|
||||
const INFO = ref(indexInfo);
|
||||
const childInfo = ref(null);
|
||||
const page = ref(props.initialPage);
|
||||
const narrow = ref(false);
|
||||
const view = ref(null);
|
||||
const el = ref(null);
|
||||
const pageChanged = (page) => {
|
||||
if (page == null) return;
|
||||
const viewInfo = page[symbols.PAGE_INFO];
|
||||
if (isRef(viewInfo)) {
|
||||
watch(viewInfo, () => {
|
||||
childInfo.value = viewInfo.value;
|
||||
}, { immediate: true });
|
||||
} else {
|
||||
childInfo.value = viewInfo;
|
||||
}
|
||||
};
|
||||
const pageProps = ref({});
|
||||
watch(narrow, () => {
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
}
|
||||
});
|
||||
|
||||
const isEmpty = (x: any) => x == null || x == '';
|
||||
onMounted(() => {
|
||||
ro.observe(el);
|
||||
|
||||
const noMaintainerInformation = ref(false);
|
||||
const noBotProtection = ref(false);
|
||||
narrow = el.offsetWidth < NARROW_THRESHOLD;
|
||||
if (props.initialPage == null && !narrow) {
|
||||
nav.push('/admin/overview');
|
||||
}
|
||||
});
|
||||
|
||||
os.api('meta', { detail: true }).then(meta => {
|
||||
// TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する
|
||||
noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
|
||||
noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
|
||||
onUnmounted(() => {
|
||||
ro.disconnect();
|
||||
});
|
||||
|
||||
const pageChanged = (page) => {
|
||||
if (page == null) {
|
||||
childInfo = null;
|
||||
} else {
|
||||
childInfo = page[symbols.PAGE_INFO];
|
||||
}
|
||||
};
|
||||
|
||||
const invite = () => {
|
||||
os.api('admin/invite').then(x => {
|
||||
os.alert({
|
||||
type: 'info',
|
||||
text: x.code
|
||||
});
|
||||
|
||||
const menuDef = computed(() => [{
|
||||
title: i18n.ts.quickAction,
|
||||
items: [{
|
||||
type: 'button',
|
||||
icon: 'fas fa-search',
|
||||
text: i18n.ts.lookup,
|
||||
action: lookup,
|
||||
}, ...(instance.disableRegistration ? [{
|
||||
type: 'button',
|
||||
icon: 'fas fa-user',
|
||||
text: i18n.ts.invite,
|
||||
action: invite,
|
||||
}] : [])],
|
||||
}, {
|
||||
title: i18n.ts.administration,
|
||||
items: [{
|
||||
icon: 'fas fa-tachometer-alt',
|
||||
text: i18n.ts.dashboard,
|
||||
to: '/admin/overview',
|
||||
active: page.value === 'overview',
|
||||
}, {
|
||||
icon: 'fas fa-users',
|
||||
text: i18n.ts.users,
|
||||
to: '/admin/users',
|
||||
active: page.value === 'users',
|
||||
}, {
|
||||
icon: 'fas fa-laugh',
|
||||
text: i18n.ts.customEmojis,
|
||||
to: '/admin/emojis',
|
||||
active: page.value === 'emojis',
|
||||
}, {
|
||||
icon: 'fas fa-globe',
|
||||
text: i18n.ts.federation,
|
||||
to: '/admin/federation',
|
||||
active: page.value === 'federation',
|
||||
}, {
|
||||
icon: 'fas fa-clipboard-list',
|
||||
text: i18n.ts.jobQueue,
|
||||
to: '/admin/queue',
|
||||
active: page.value === 'queue',
|
||||
}, {
|
||||
icon: 'fas fa-cloud',
|
||||
text: i18n.ts.files,
|
||||
to: '/admin/files',
|
||||
active: page.value === 'files',
|
||||
}, {
|
||||
icon: 'fas fa-broadcast-tower',
|
||||
text: i18n.ts.announcements,
|
||||
to: '/admin/announcements',
|
||||
active: page.value === 'announcements',
|
||||
}, {
|
||||
icon: 'fas fa-audio-description',
|
||||
text: i18n.ts.ads,
|
||||
to: '/admin/ads',
|
||||
active: page.value === 'ads',
|
||||
}, {
|
||||
icon: 'fas fa-exclamation-circle',
|
||||
text: i18n.ts.abuseReports,
|
||||
to: '/admin/abuses',
|
||||
active: page.value === 'abuses',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.settings,
|
||||
items: [{
|
||||
icon: 'fas fa-cog',
|
||||
text: i18n.ts.general,
|
||||
to: '/admin/settings',
|
||||
active: page.value === 'settings',
|
||||
}, {
|
||||
icon: 'fas fa-envelope',
|
||||
text: i18n.ts.emailServer,
|
||||
to: '/admin/email-settings',
|
||||
active: page.value === 'email-settings',
|
||||
}, {
|
||||
icon: 'fas fa-cloud',
|
||||
text: i18n.ts.objectStorage,
|
||||
to: '/admin/object-storage',
|
||||
active: page.value === 'object-storage',
|
||||
}, {
|
||||
icon: 'fas fa-lock',
|
||||
text: i18n.ts.security,
|
||||
to: '/admin/security',
|
||||
active: page.value === 'security',
|
||||
}, {
|
||||
icon: 'fas fa-globe',
|
||||
text: i18n.ts.relays,
|
||||
to: '/admin/relays',
|
||||
active: page.value === 'relays',
|
||||
}, {
|
||||
icon: 'fas fa-share-alt',
|
||||
text: i18n.ts.integration,
|
||||
to: '/admin/integrations',
|
||||
active: page.value === 'integrations',
|
||||
}, {
|
||||
icon: 'fas fa-ban',
|
||||
text: i18n.ts.instanceBlocking,
|
||||
to: '/admin/instance-block',
|
||||
active: page.value === 'instance-block',
|
||||
}, {
|
||||
icon: 'fas fa-ghost',
|
||||
text: i18n.ts.proxyAccount,
|
||||
to: '/admin/proxy-account',
|
||||
active: page.value === 'proxy-account',
|
||||
}, {
|
||||
icon: 'fas fa-cogs',
|
||||
text: i18n.ts.other,
|
||||
to: '/admin/other-settings',
|
||||
active: page.value === 'other-settings',
|
||||
}],
|
||||
}, {
|
||||
title: i18n.ts.info,
|
||||
items: [{
|
||||
icon: 'fas fa-database',
|
||||
text: i18n.ts.database,
|
||||
to: '/admin/database',
|
||||
active: page.value === 'database',
|
||||
}],
|
||||
}]);
|
||||
const component = computed(() => {
|
||||
if (page.value == null) return null;
|
||||
switch (page.value) {
|
||||
case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
|
||||
case 'users': return defineAsyncComponent(() => import('./users.vue'));
|
||||
case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
|
||||
case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
|
||||
case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
|
||||
case 'files': return defineAsyncComponent(() => import('./files.vue'));
|
||||
case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
|
||||
case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
|
||||
case 'database': return defineAsyncComponent(() => import('./database.vue'));
|
||||
case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
|
||||
case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
|
||||
case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
|
||||
case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
|
||||
case 'security': return defineAsyncComponent(() => import('./security.vue'));
|
||||
case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
|
||||
case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
|
||||
case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
|
||||
case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
|
||||
case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
|
||||
}
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(component, () => {
|
||||
pageProps.value = {};
|
||||
const lookup = (ev) => {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.user,
|
||||
icon: 'fas fa-user',
|
||||
action: () => {
|
||||
lookupUser();
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.note,
|
||||
icon: 'fas fa-pencil-alt',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.file,
|
||||
icon: 'fas fa-cloud',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.instance,
|
||||
icon: 'fas fa-globe',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
};
|
||||
|
||||
nextTick(() => {
|
||||
scroll(el.value, { top: 0 });
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.initialPage, () => {
|
||||
if (props.initialPage == null && !narrow.value) {
|
||||
page.value = 'overview';
|
||||
} else {
|
||||
page.value = props.initialPage;
|
||||
if (props.initialPage == null) {
|
||||
INFO.value = indexInfo;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
narrow.value = el.value.offsetWidth < 800;
|
||||
if (!narrow.value) {
|
||||
page.value = 'overview';
|
||||
}
|
||||
});
|
||||
|
||||
const invite = () => {
|
||||
os.api('admin/invite').then(x => {
|
||||
os.alert({
|
||||
type: 'info',
|
||||
text: x.code
|
||||
});
|
||||
}).catch(e => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const lookup = (ev) => {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.user,
|
||||
icon: 'fas fa-user',
|
||||
action: () => {
|
||||
lookupUser();
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.note,
|
||||
icon: 'fas fa-pencil-alt',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.file,
|
||||
icon: 'fas fa-cloud',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}, {
|
||||
text: i18n.ts.instance,
|
||||
icon: 'fas fa-globe',
|
||||
action: () => {
|
||||
alert('TODO');
|
||||
}
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
};
|
||||
|
||||
return {
|
||||
[symbols.PAGE_INFO]: INFO,
|
||||
menuDef,
|
||||
header: {
|
||||
title: i18n.ts.controlPanel,
|
||||
},
|
||||
noMaintainerInformation,
|
||||
noBotProtection,
|
||||
page,
|
||||
narrow,
|
||||
view,
|
||||
el,
|
||||
pageChanged,
|
||||
childInfo,
|
||||
pageProps,
|
||||
component,
|
||||
invite,
|
||||
lookup,
|
||||
};
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: INFO,
|
||||
header: {
|
||||
title: i18n.ts.controlPanel,
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@@ -2,57 +2,45 @@
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<FormSuspense :p="init">
|
||||
<FormTextarea v-model="blockedHosts" class="_formBlock">
|
||||
<span>{{ $ts.blockedInstances }}</span>
|
||||
<template #caption>{{ $ts.blockedInstancesDescription }}</template>
|
||||
<span>{{ i18n.ts.blockedInstances }}</span>
|
||||
<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
|
||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormButton,
|
||||
FormTextarea,
|
||||
FormSuspense,
|
||||
},
|
||||
let blockedHosts: string = $ref('');
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
blockedHosts = meta.blockedHosts.join('\n');
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.instanceBlocking,
|
||||
icon: 'fas fa-ban',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
blockedHosts: '',
|
||||
}
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
blockedHosts: blockedHosts.split('\n') || [],
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.blockedHosts = meta.blockedHosts.join('\n');
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
blockedHosts: this.blockedHosts.split('\n') || [],
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.instanceBlocking,
|
||||
icon: 'fas fa-ban',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -24,57 +24,36 @@
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormInfo,
|
||||
FormButton,
|
||||
FormSuspense,
|
||||
},
|
||||
let uri: string = $ref('');
|
||||
let enableDiscordIntegration: boolean = $ref(false);
|
||||
let discordClientId: string | null = $ref(null);
|
||||
let discordClientSecret: string | null = $ref(null);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
discordClientId = meta.discordClientId;
|
||||
discordClientSecret = meta.discordClientSecret;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: 'Discord',
|
||||
icon: 'fab fa-discord'
|
||||
},
|
||||
enableDiscordIntegration: false,
|
||||
discordClientId: null,
|
||||
discordClientSecret: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
this.discordClientId = meta.discordClientId;
|
||||
this.discordClientSecret = meta.discordClientSecret;
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableDiscordIntegration: this.enableDiscordIntegration,
|
||||
discordClientId: this.discordClientId,
|
||||
discordClientSecret: this.discordClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableDiscordIntegration,
|
||||
discordClientId,
|
||||
discordClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@@ -24,57 +24,36 @@
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormInfo,
|
||||
FormButton,
|
||||
FormSuspense,
|
||||
},
|
||||
let uri: string = $ref('');
|
||||
let enableGithubIntegration: boolean = $ref(false);
|
||||
let githubClientId: string | null = $ref(null);
|
||||
let githubClientSecret: string | null = $ref(null);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableGithubIntegration = meta.enableGithubIntegration;
|
||||
githubClientId = meta.githubClientId;
|
||||
githubClientSecret = meta.githubClientSecret;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: 'GitHub',
|
||||
icon: 'fab fa-github'
|
||||
},
|
||||
enableGithubIntegration: false,
|
||||
githubClientId: null,
|
||||
githubClientSecret: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
this.githubClientId = meta.githubClientId;
|
||||
this.githubClientSecret = meta.githubClientSecret;
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableGithubIntegration: this.enableGithubIntegration,
|
||||
githubClientId: this.githubClientId,
|
||||
githubClientSecret: this.githubClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableGithubIntegration,
|
||||
githubClientId,
|
||||
githubClientSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@@ -24,7 +24,7 @@
|
||||
</FormSuspense>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
@@ -32,49 +32,28 @@ import FormButton from '@/components/ui/button.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormInfo,
|
||||
FormButton,
|
||||
FormSuspense,
|
||||
},
|
||||
let uri: string = $ref('');
|
||||
let enableTwitterIntegration: boolean = $ref(false);
|
||||
let twitterConsumerKey: string | null = $ref(null);
|
||||
let twitterConsumerSecret: string | null = $ref(null);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
uri = meta.uri;
|
||||
enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
twitterConsumerKey = meta.twitterConsumerKey;
|
||||
twitterConsumerSecret = meta.twitterConsumerSecret;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: 'Twitter',
|
||||
icon: 'fab fa-twitter'
|
||||
},
|
||||
enableTwitterIntegration: false,
|
||||
twitterConsumerKey: null,
|
||||
twitterConsumerSecret: null,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.uri = meta.uri;
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.twitterConsumerKey = meta.twitterConsumerKey;
|
||||
this.twitterConsumerSecret = meta.twitterConsumerSecret;
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableTwitterIntegration: this.enableTwitterIntegration,
|
||||
twitterConsumerKey: this.twitterConsumerKey,
|
||||
twitterConsumerSecret: this.twitterConsumerSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableTwitterIntegration,
|
||||
twitterConsumerKey,
|
||||
twitterConsumerSecret,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
@@ -4,69 +4,52 @@
|
||||
<FormFolder class="_formBlock">
|
||||
<template #icon><i class="fab fa-twitter"></i></template>
|
||||
<template #label>Twitter</template>
|
||||
<template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
|
||||
<template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XTwitter/>
|
||||
</FormFolder>
|
||||
<FormFolder to="/admin/integrations/github" class="_formBlock">
|
||||
<FormFolder class="_formBlock">
|
||||
<template #icon><i class="fab fa-github"></i></template>
|
||||
<template #label>GitHub</template>
|
||||
<template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
|
||||
<template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XGithub/>
|
||||
</FormFolder>
|
||||
<FormFolder to="/admin/integrations/discord" class="_formBlock">
|
||||
<FormFolder class="_formBlock">
|
||||
<template #icon><i class="fab fa-discord"></i></template>
|
||||
<template #label>Discord</template>
|
||||
<template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
|
||||
<template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
|
||||
<XDiscord/>
|
||||
</FormFolder>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import FormSecion from '@/components/form/section.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import XTwitter from './integrations.twitter.vue';
|
||||
import XGithub from './integrations.github.vue';
|
||||
import XDiscord from './integrations.discord.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormFolder,
|
||||
FormSecion,
|
||||
FormSuspense,
|
||||
XTwitter,
|
||||
XGithub,
|
||||
XDiscord,
|
||||
},
|
||||
let enableTwitterIntegration: boolean = $ref(false);
|
||||
let enableGithubIntegration: boolean = $ref(false);
|
||||
let enableDiscordIntegration: boolean = $ref(false);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
enableGithubIntegration = meta.enableGithubIntegration;
|
||||
enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.integration,
|
||||
icon: 'fas fa-share-alt',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
enableTwitterIntegration: false,
|
||||
enableGithubIntegration: false,
|
||||
enableDiscordIntegration: false,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.enableTwitterIntegration = meta.enableTwitterIntegration;
|
||||
this.enableGithubIntegration = meta.enableGithubIntegration;
|
||||
this.enableDiscordIntegration = meta.enableDiscordIntegration;
|
||||
},
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.integration,
|
||||
icon: 'fas fa-share-alt',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -2,32 +2,32 @@
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<FormSuspense :p="init">
|
||||
<div class="_formRoot">
|
||||
<FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch>
|
||||
<FormSwitch v-model="useObjectStorage" class="_formBlock">{{ i18n.ts.useObjectStorage }}</FormSwitch>
|
||||
|
||||
<template v-if="useObjectStorage">
|
||||
<FormInput v-model="objectStorageBaseUrl" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageBaseUrl }}</template>
|
||||
<template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="objectStorageBucket" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageBucket }}</template>
|
||||
<template #caption>{{ $ts.objectStorageBucketDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageBucket }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="objectStoragePrefix" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStoragePrefix }}</template>
|
||||
<template #caption>{{ $ts.objectStoragePrefixDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="objectStorageEndpoint" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageEndpoint }}</template>
|
||||
<template #caption>{{ $ts.objectStorageEndpointDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="objectStorageRegion" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageRegion }}</template>
|
||||
<template #caption>{{ $ts.objectStorageRegionDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageRegion }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormSplit :min-width="280">
|
||||
@@ -43,17 +43,17 @@
|
||||
</FormSplit>
|
||||
|
||||
<FormSwitch v-model="objectStorageUseSSL" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageUseSSL }}</template>
|
||||
<template #caption>{{ $ts.objectStorageUseSSLDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageUseSSL }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="objectStorageUseProxy" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageUseProxy }}</template>
|
||||
<template #caption>{{ $ts.objectStorageUseProxyDesc }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageUseProxy }}</template>
|
||||
<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock">
|
||||
<template #label>{{ $ts.objectStorageSetPublicRead }}</template>
|
||||
<template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock">
|
||||
@@ -65,8 +65,8 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormGroup from '@/components/form/group.vue';
|
||||
@@ -76,84 +76,70 @@ import FormSection from '@/components/form/section.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormGroup,
|
||||
FormSuspense,
|
||||
FormSplit,
|
||||
FormSection,
|
||||
},
|
||||
let useObjectStorage: boolean = $ref(false);
|
||||
let objectStorageBaseUrl: string | null = $ref(null);
|
||||
let objectStorageBucket: string | null = $ref(null);
|
||||
let objectStoragePrefix: string | null = $ref(null);
|
||||
let objectStorageEndpoint: string | null = $ref(null);
|
||||
let objectStorageRegion: string | null = $ref(null);
|
||||
let objectStoragePort: number | null = $ref(null);
|
||||
let objectStorageAccessKey: string | null = $ref(null);
|
||||
let objectStorageSecretKey: string | null = $ref(null);
|
||||
let objectStorageUseSSL: boolean = $ref(false);
|
||||
let objectStorageUseProxy: boolean = $ref(false);
|
||||
let objectStorageSetPublicRead: boolean = $ref(false);
|
||||
let objectStorageS3ForcePathStyle: boolean = $ref(true);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
useObjectStorage = meta.useObjectStorage;
|
||||
objectStorageBaseUrl = meta.objectStorageBaseUrl;
|
||||
objectStorageBucket = meta.objectStorageBucket;
|
||||
objectStoragePrefix = meta.objectStoragePrefix;
|
||||
objectStorageEndpoint = meta.objectStorageEndpoint;
|
||||
objectStorageRegion = meta.objectStorageRegion;
|
||||
objectStoragePort = meta.objectStoragePort;
|
||||
objectStorageAccessKey = meta.objectStorageAccessKey;
|
||||
objectStorageSecretKey = meta.objectStorageSecretKey;
|
||||
objectStorageUseSSL = meta.objectStorageUseSSL;
|
||||
objectStorageUseProxy = meta.objectStorageUseProxy;
|
||||
objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
|
||||
objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.objectStorage,
|
||||
icon: 'fas fa-cloud',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: this.$ts.save,
|
||||
handler: this.save,
|
||||
}],
|
||||
},
|
||||
useObjectStorage: false,
|
||||
objectStorageBaseUrl: null,
|
||||
objectStorageBucket: null,
|
||||
objectStoragePrefix: null,
|
||||
objectStorageEndpoint: null,
|
||||
objectStorageRegion: null,
|
||||
objectStoragePort: null,
|
||||
objectStorageAccessKey: null,
|
||||
objectStorageSecretKey: null,
|
||||
objectStorageUseSSL: false,
|
||||
objectStorageUseProxy: false,
|
||||
objectStorageSetPublicRead: false,
|
||||
objectStorageS3ForcePathStyle: true,
|
||||
}
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
useObjectStorage,
|
||||
objectStorageBaseUrl,
|
||||
objectStorageBucket,
|
||||
objectStoragePrefix,
|
||||
objectStorageEndpoint,
|
||||
objectStorageRegion,
|
||||
objectStoragePort,
|
||||
objectStorageAccessKey,
|
||||
objectStorageSecretKey,
|
||||
objectStorageUseSSL,
|
||||
objectStorageUseProxy,
|
||||
objectStorageSetPublicRead,
|
||||
objectStorageS3ForcePathStyle,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.useObjectStorage = meta.useObjectStorage;
|
||||
this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
|
||||
this.objectStorageBucket = meta.objectStorageBucket;
|
||||
this.objectStoragePrefix = meta.objectStoragePrefix;
|
||||
this.objectStorageEndpoint = meta.objectStorageEndpoint;
|
||||
this.objectStorageRegion = meta.objectStorageRegion;
|
||||
this.objectStoragePort = meta.objectStoragePort;
|
||||
this.objectStorageAccessKey = meta.objectStorageAccessKey;
|
||||
this.objectStorageSecretKey = meta.objectStorageSecretKey;
|
||||
this.objectStorageUseSSL = meta.objectStorageUseSSL;
|
||||
this.objectStorageUseProxy = meta.objectStorageUseProxy;
|
||||
this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
|
||||
this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
useObjectStorage: this.useObjectStorage,
|
||||
objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
|
||||
objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
|
||||
objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
|
||||
objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
|
||||
objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
|
||||
objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
|
||||
objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
|
||||
objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
|
||||
objectStorageUseSSL: this.objectStorageUseSSL,
|
||||
objectStorageUseProxy: this.objectStorageUseProxy,
|
||||
objectStorageSetPublicRead: this.objectStorageSetPublicRead,
|
||||
objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.objectStorage,
|
||||
icon: 'fas fa-cloud',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: i18n.ts.save,
|
||||
handler: save,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -6,52 +6,35 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormSection,
|
||||
FormSuspense,
|
||||
},
|
||||
async function init() {
|
||||
await os.api('admin/meta');
|
||||
}
|
||||
|
||||
emits: ['info'],
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta').then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.other,
|
||||
icon: 'fas fa-cogs',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: this.$ts.save,
|
||||
handler: this.save,
|
||||
}],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
},
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.other,
|
||||
icon: 'fas fa-cogs',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: i18n.ts.save,
|
||||
handler: save,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -5,20 +5,20 @@
|
||||
<div class="label">Users</div>
|
||||
<div class="value _monospace">
|
||||
{{ number(stats.originalUsersCount) }}
|
||||
<MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
|
||||
<MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
|
||||
</div>
|
||||
</div>
|
||||
<div class="number _panel">
|
||||
<div class="label">Notes</div>
|
||||
<div class="value _monospace">
|
||||
{{ number(stats.originalNotesCount) }}
|
||||
<MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
|
||||
<MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkContainer :foldable="true" class="charts">
|
||||
<template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
|
||||
<template #header><i class="fas fa-chart-bar"></i>{{ i18n.ts.charts }}</template>
|
||||
<div style="padding: 12px;">
|
||||
<MkInstanceStats :chart-limit="500" :detailed="true"/>
|
||||
</div>
|
||||
@@ -38,7 +38,7 @@
|
||||
<!--<XMetrics/>-->
|
||||
|
||||
<MkFolder style="margin: var(--margin)">
|
||||
<template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template>
|
||||
<template #header><i class="fas fa-info-circle"></i> {{ i18n.ts.info }}</template>
|
||||
<div class="cfcdecdf">
|
||||
<div class="number _panel">
|
||||
<div class="label">Misskey</div>
|
||||
@@ -65,103 +65,61 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, markRaw, version as vueVersion } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import MkInstanceStats from '@/components/instance-stats.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
import MkNumberDiff from '@/components/number-diff.vue';
|
||||
import MkContainer from '@/components/ui/container.vue';
|
||||
import MkFolder from '@/components/ui/folder.vue';
|
||||
import MkQueueChart from '@/components/queue-chart.vue';
|
||||
import { version, url } from '@/config';
|
||||
import bytes from '@/filters/bytes';
|
||||
import number from '@/filters/number';
|
||||
import XMetrics from './metrics.vue';
|
||||
import * as os from '@/os';
|
||||
import { stream } from '@/stream';
|
||||
import * as symbols from '@/symbols';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkNumberDiff,
|
||||
MkInstanceStats,
|
||||
MkContainer,
|
||||
MkFolder,
|
||||
MkQueueChart,
|
||||
XMetrics,
|
||||
},
|
||||
let stats: any = $ref(null);
|
||||
let serverInfo: any = $ref(null);
|
||||
let usersComparedToThePrevDay: any = $ref(null);
|
||||
let notesComparedToThePrevDay: any = $ref(null);
|
||||
const queueStatsConnection = markRaw(stream.useChannel('queueStats'));
|
||||
|
||||
emits: ['info'],
|
||||
onMounted(async () => {
|
||||
os.api('stats', {}).then(statsResponse => {
|
||||
stats = statsResponse;
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.dashboard,
|
||||
icon: 'fas fa-tachometer-alt',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
version,
|
||||
vueVersion,
|
||||
url,
|
||||
stats: null,
|
||||
meta: null,
|
||||
serverInfo: null,
|
||||
usersComparedToThePrevDay: null,
|
||||
notesComparedToThePrevDay: null,
|
||||
fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
|
||||
fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
|
||||
queueStatsConnection: markRaw(stream.useChannel('queueStats')),
|
||||
}
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
os.api('meta', { detail: true }).then(meta => {
|
||||
this.meta = meta;
|
||||
});
|
||||
|
||||
os.api('stats', {}).then(stats => {
|
||||
this.stats = stats;
|
||||
|
||||
os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
|
||||
this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1];
|
||||
});
|
||||
|
||||
os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
|
||||
this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1];
|
||||
});
|
||||
os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
|
||||
usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
|
||||
});
|
||||
|
||||
os.api('admin/server-info', {}).then(serverInfo => {
|
||||
this.serverInfo = serverInfo;
|
||||
os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
|
||||
notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
|
||||
});
|
||||
});
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.queueStatsConnection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
os.api('admin/server-info').then(serverInfoResponse => {
|
||||
serverInfo = serverInfoResponse;
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
queueStatsConnection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
beforeUnmount() {
|
||||
this.queueStatsConnection.dispose();
|
||||
},
|
||||
onBeforeUnmount(() => {
|
||||
queueStatsConnection.dispose();
|
||||
});
|
||||
|
||||
methods: {
|
||||
async showInstanceInfo(q) {
|
||||
let instance = q;
|
||||
if (typeof q === 'string') {
|
||||
instance = await os.api('federation/show-instance', {
|
||||
host: q
|
||||
});
|
||||
}
|
||||
// TODO
|
||||
},
|
||||
|
||||
bytes,
|
||||
|
||||
number,
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.dashboard,
|
||||
icon: 'fas fa-tachometer-alt',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
|
||||
<FormSuspense :p="init">
|
||||
<MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo>
|
||||
<MkInfo class="_formBlock">{{ i18n.ts.proxyAccountDescription }}</MkInfo>
|
||||
<MkKeyValue class="_formBlock">
|
||||
<template #key>{{ $ts.proxyAccount }}</template>
|
||||
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
|
||||
<template #key>{{ i18n.ts.proxyAccount }}</template>
|
||||
<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
|
||||
</MkKeyValue>
|
||||
|
||||
<FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
|
||||
<FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</FormButton>
|
||||
</FormSuspense>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import MkInfo from '@/components/ui/info.vue';
|
||||
@@ -21,53 +21,40 @@ import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkKeyValue,
|
||||
FormButton,
|
||||
MkInfo,
|
||||
FormSuspense,
|
||||
},
|
||||
let proxyAccount: any = $ref(null);
|
||||
let proxyAccountId: any = $ref(null);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
proxyAccountId = meta.proxyAccountId;
|
||||
if (proxyAccountId) {
|
||||
proxyAccount = await os.api('users/show', { userId: proxyAccountId });
|
||||
}
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.proxyAccount,
|
||||
icon: 'fas fa-ghost',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
proxyAccount: null,
|
||||
proxyAccountId: null,
|
||||
}
|
||||
},
|
||||
function chooseProxyAccount() {
|
||||
os.selectUser().then(user => {
|
||||
proxyAccount = user;
|
||||
proxyAccountId = user.id;
|
||||
save();
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.proxyAccountId = meta.proxyAccountId;
|
||||
if (this.proxyAccountId) {
|
||||
this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId });
|
||||
}
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
proxyAccountId: proxyAccountId,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
chooseProxyAccount() {
|
||||
os.selectUser().then(user => {
|
||||
this.proxyAccount = user;
|
||||
this.proxyAccountId = user.id;
|
||||
this.save();
|
||||
});
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
proxyAccountId: this.proxyAccountId,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.proxyAccount,
|
||||
icon: 'fas fa-ghost',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -26,62 +26,40 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import number from '@/filters/number';
|
||||
import MkQueueChart from '@/components/queue-chart.vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkQueueChart
|
||||
},
|
||||
const activeSincePrevTick = ref(0);
|
||||
const active = ref(0);
|
||||
const waiting = ref(0);
|
||||
const delayed = ref(0);
|
||||
const jobs = ref([]);
|
||||
|
||||
props: {
|
||||
domain: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
connection: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
const props = defineProps<{
|
||||
domain: string,
|
||||
connection: any,
|
||||
}>();
|
||||
|
||||
setup(props) {
|
||||
const activeSincePrevTick = ref(0);
|
||||
const active = ref(0);
|
||||
const waiting = ref(0);
|
||||
const delayed = ref(0);
|
||||
const jobs = ref([]);
|
||||
onMounted(() => {
|
||||
os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => {
|
||||
jobs.value = result;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => {
|
||||
jobs.value = result;
|
||||
});
|
||||
const onStats = (stats) => {
|
||||
activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
|
||||
active.value = stats[props.domain].active;
|
||||
waiting.value = stats[props.domain].waiting;
|
||||
delayed.value = stats[props.domain].delayed;
|
||||
};
|
||||
|
||||
const onStats = (stats) => {
|
||||
activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
|
||||
active.value = stats[props.domain].active;
|
||||
waiting.value = stats[props.domain].waiting;
|
||||
delayed.value = stats[props.domain].delayed;
|
||||
};
|
||||
props.connection.on('stats', onStats);
|
||||
|
||||
props.connection.on('stats', onStats);
|
||||
|
||||
onUnmounted(() => {
|
||||
props.connection.off('stats', onStats);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
jobs,
|
||||
activeSincePrevTick,
|
||||
active,
|
||||
waiting,
|
||||
delayed,
|
||||
number,
|
||||
};
|
||||
},
|
||||
onUnmounted(() => {
|
||||
props.connection.off('stats', onStats);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@@ -6,71 +6,60 @@
|
||||
<XQueue :connection="connection" domain="deliver">
|
||||
<template #title>Out</template>
|
||||
</XQueue>
|
||||
<MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton>
|
||||
<MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.clearQueue }}</MkButton>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, markRaw } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import XQueue from './queue.chart.vue';
|
||||
import * as os from '@/os';
|
||||
import { stream } from '@/stream';
|
||||
import * as symbols from '@/symbols';
|
||||
import * as config from '@/config';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
XQueue,
|
||||
},
|
||||
const connection = markRaw(stream.useChannel('queueStats'))
|
||||
|
||||
emits: ['info'],
|
||||
function clear() {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
title: i18n.ts.clearQueueConfirmTitle,
|
||||
text: i18n.ts.clearQueueConfirmText,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.jobQueue,
|
||||
icon: 'fas fa-clipboard-list',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-up-right-from-square',
|
||||
text: this.$ts.dashboard,
|
||||
handler: () => {
|
||||
window.open(config.url + '/queue', '_blank');
|
||||
},
|
||||
}],
|
||||
},
|
||||
connection: markRaw(stream.useChannel('queueStats')),
|
||||
}
|
||||
},
|
||||
os.apiWithDialog('admin/queue/clear');
|
||||
});
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.connection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
connection.send('requestLog', {
|
||||
id: Math.random().toString().substr(2, 8),
|
||||
length: 200
|
||||
});
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
beforeUnmount() {
|
||||
this.connection.dispose();
|
||||
},
|
||||
onBeforeUnmount(() => {
|
||||
connection.dispose();
|
||||
});
|
||||
|
||||
methods: {
|
||||
clear() {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
title: this.$ts.clearQueueConfirmTitle,
|
||||
text: this.$ts.clearQueueConfirmText,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('admin/queue/clear', {});
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.jobQueue,
|
||||
icon: 'fas fa-clipboard-list',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-up-right-from-square',
|
||||
text: i18n.ts.dashboard,
|
||||
handler: () => {
|
||||
window.open(config.url + '/queue', '_blank');
|
||||
},
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -8,84 +8,71 @@
|
||||
<i v-else class="fas fa-clock icon requesting"></i>
|
||||
<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
|
||||
</div>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
|
||||
<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
},
|
||||
let relays: any[] = $ref([]);
|
||||
|
||||
emits: ['info'],
|
||||
async function addRelay() {
|
||||
const { canceled, result: inbox } = await os.inputText({
|
||||
title: i18n.ts.addRelay,
|
||||
type: 'url',
|
||||
placeholder: i18n.ts.inboxUrl
|
||||
});
|
||||
if (canceled) return;
|
||||
os.api('admin/relays/add', {
|
||||
inbox
|
||||
}).then((relay: any) => {
|
||||
refresh();
|
||||
}).catch((err: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.message || err
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.relays,
|
||||
icon: 'fas fa-globe',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: this.$ts.addRelay,
|
||||
handler: this.addRelay,
|
||||
}],
|
||||
},
|
||||
relays: [],
|
||||
inbox: '',
|
||||
}
|
||||
},
|
||||
function remove(inbox: string) {
|
||||
os.api('admin/relays/remove', {
|
||||
inbox
|
||||
}).then(() => {
|
||||
refresh();
|
||||
}).catch((err: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.message || err
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
},
|
||||
function refresh() {
|
||||
os.api('admin/relays/list').then((relayList: any) => {
|
||||
relays = relayList;
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async addRelay() {
|
||||
const { canceled, result: inbox } = await os.inputText({
|
||||
title: this.$ts.addRelay,
|
||||
type: 'url',
|
||||
placeholder: this.$ts.inboxUrl
|
||||
});
|
||||
if (canceled) return;
|
||||
os.api('admin/relays/add', {
|
||||
inbox
|
||||
}).then((relay: any) => {
|
||||
this.refresh();
|
||||
}).catch((e: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e.message || e
|
||||
});
|
||||
});
|
||||
},
|
||||
refresh();
|
||||
|
||||
remove(inbox: string) {
|
||||
os.api('admin/relays/remove', {
|
||||
inbox
|
||||
}).then(() => {
|
||||
this.refresh();
|
||||
}).catch((e: any) => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: e.message || e
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
refresh() {
|
||||
os.api('admin/relays/list').then((relays: any) => {
|
||||
this.relays = relays;
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.relays,
|
||||
icon: 'fas fa-globe',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-plus',
|
||||
text: i18n.ts.addRelay,
|
||||
handler: addRelay,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -4,10 +4,10 @@
|
||||
<div class="_formRoot">
|
||||
<FormFolder class="_formBlock">
|
||||
<template #icon><i class="fas fa-shield-alt"></i></template>
|
||||
<template #label>{{ $ts.botProtection }}</template>
|
||||
<template #label>{{ i18n.ts.botProtection }}</template>
|
||||
<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
|
||||
<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
|
||||
<template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
|
||||
<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
|
||||
|
||||
<XBotProtection/>
|
||||
</FormFolder>
|
||||
@@ -21,7 +21,7 @@
|
||||
<template #label>Summaly Proxy URL</template>
|
||||
</FormInput>
|
||||
|
||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
|
||||
<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
|
||||
</div>
|
||||
</FormFolder>
|
||||
</div>
|
||||
@@ -29,8 +29,8 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInfo from '@/components/ui/info.vue';
|
||||
@@ -42,49 +42,32 @@ import XBotProtection from './bot-protection.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormFolder,
|
||||
FormSwitch,
|
||||
FormInfo,
|
||||
FormSection,
|
||||
FormSuspense,
|
||||
FormButton,
|
||||
FormInput,
|
||||
XBotProtection,
|
||||
},
|
||||
let summalyProxy: string = $ref('');
|
||||
let enableHcaptcha: boolean = $ref(false);
|
||||
let enableRecaptcha: boolean = $ref(false);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
summalyProxy = meta.summalyProxy;
|
||||
enableHcaptcha = meta.enableHcaptcha;
|
||||
enableRecaptcha = meta.enableRecaptcha;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.security,
|
||||
icon: 'fas fa-lock',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
summalyProxy: '',
|
||||
enableHcaptcha: false,
|
||||
enableRecaptcha: false,
|
||||
}
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
summalyProxy,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.summalyProxy = meta.summalyProxy;
|
||||
this.enableHcaptcha = meta.enableHcaptcha;
|
||||
this.enableRecaptcha = meta.enableRecaptcha;
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
summalyProxy: this.summalyProxy,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.security,
|
||||
icon: 'fas fa-lock',
|
||||
bg: 'var(--bg)',
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -3,104 +3,104 @@
|
||||
<FormSuspense :p="init">
|
||||
<div class="_formRoot">
|
||||
<FormInput v-model="name" class="_formBlock">
|
||||
<template #label>{{ $ts.instanceName }}</template>
|
||||
<template #label>{{ i18n.ts.instanceName }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormTextarea v-model="description" class="_formBlock">
|
||||
<template #label>{{ $ts.instanceDescription }}</template>
|
||||
<template #label>{{ i18n.ts.instanceDescription }}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormInput v-model="tosUrl" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-link"></i></template>
|
||||
<template #label>{{ $ts.tosUrl }}</template>
|
||||
<template #label>{{ i18n.ts.tosUrl }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormSplit :min-width="300">
|
||||
<FormInput v-model="maintainerName" class="_formBlock">
|
||||
<template #label>{{ $ts.maintainerName }}</template>
|
||||
<template #label>{{ i18n.ts.maintainerName }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="maintainerEmail" type="email" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-envelope"></i></template>
|
||||
<template #label>{{ $ts.maintainerEmail }}</template>
|
||||
<template #label>{{ i18n.ts.maintainerEmail }}</template>
|
||||
</FormInput>
|
||||
</FormSplit>
|
||||
|
||||
<FormTextarea v-model="pinnedUsers" class="_formBlock">
|
||||
<template #label>{{ $ts.pinnedUsers }}</template>
|
||||
<template #caption>{{ $ts.pinnedUsersDescription }}</template>
|
||||
<template #label>{{ i18n.ts.pinnedUsers }}</template>
|
||||
<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormSection>
|
||||
<FormSwitch v-model="enableRegistration" class="_formBlock">
|
||||
<template #label>{{ $ts.enableRegistration }}</template>
|
||||
<template #label>{{ i18n.ts.enableRegistration }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSwitch v-model="emailRequiredForSignup" class="_formBlock">
|
||||
<template #label>{{ $ts.emailRequiredForSignup }}</template>
|
||||
<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
|
||||
</FormSwitch>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch>
|
||||
<FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch>
|
||||
<FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo>
|
||||
<FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ i18n.ts.enableLocalTimeline }}</FormSwitch>
|
||||
<FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ i18n.ts.enableGlobalTimeline }}</FormSwitch>
|
||||
<FormInfo class="_formBlock">{{ i18n.ts.disablingTimelinesInfo }}</FormInfo>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.theme }}</template>
|
||||
<template #label>{{ i18n.ts.theme }}</template>
|
||||
|
||||
<FormInput v-model="iconUrl" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-link"></i></template>
|
||||
<template #label>{{ $ts.iconUrl }}</template>
|
||||
<template #label>{{ i18n.ts.iconUrl }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="bannerUrl" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-link"></i></template>
|
||||
<template #label>{{ $ts.bannerUrl }}</template>
|
||||
<template #label>{{ i18n.ts.bannerUrl }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="backgroundImageUrl" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-link"></i></template>
|
||||
<template #label>{{ $ts.backgroundImageUrl }}</template>
|
||||
<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="themeColor" class="_formBlock">
|
||||
<template #prefix><i class="fas fa-palette"></i></template>
|
||||
<template #label>{{ $ts.themeColor }}</template>
|
||||
<template #label>{{ i18n.ts.themeColor }}</template>
|
||||
<template #caption>#RRGGBB</template>
|
||||
</FormInput>
|
||||
|
||||
<FormTextarea v-model="defaultLightTheme" class="_formBlock">
|
||||
<template #label>{{ $ts.instanceDefaultLightTheme }}</template>
|
||||
<template #caption>{{ $ts.instanceDefaultThemeDescription }}</template>
|
||||
<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</FormTextarea>
|
||||
|
||||
<FormTextarea v-model="defaultDarkTheme" class="_formBlock">
|
||||
<template #label>{{ $ts.instanceDefaultDarkTheme }}</template>
|
||||
<template #caption>{{ $ts.instanceDefaultThemeDescription }}</template>
|
||||
<template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
|
||||
<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
|
||||
</FormTextarea>
|
||||
</FormSection>
|
||||
|
||||
<FormSection>
|
||||
<template #label>{{ $ts.files }}</template>
|
||||
<template #label>{{ i18n.ts.files }}</template>
|
||||
|
||||
<FormSwitch v-model="cacheRemoteFiles" class="_formBlock">
|
||||
<template #label>{{ $ts.cacheRemoteFiles }}</template>
|
||||
<template #caption>{{ $ts.cacheRemoteFilesDescription }}</template>
|
||||
<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
|
||||
<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<FormSplit :min-width="280">
|
||||
<FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock">
|
||||
<template #label>{{ $ts.driveCapacityPerLocalAccount }}</template>
|
||||
<template #label>{{ i18n.ts.driveCapacityPerLocalAccount }}</template>
|
||||
<template #suffix>MB</template>
|
||||
<template #caption>{{ $ts.inMb }}</template>
|
||||
<template #caption>{{ i18n.ts.inMb }}</template>
|
||||
</FormInput>
|
||||
|
||||
<FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock">
|
||||
<template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template>
|
||||
<template #label>{{ i18n.ts.driveCapacityPerRemoteAccount }}</template>
|
||||
<template #suffix>MB</template>
|
||||
<template #caption>{{ $ts.inMb }}</template>
|
||||
<template #caption>{{ i18n.ts.inMb }}</template>
|
||||
</FormInput>
|
||||
</FormSplit>
|
||||
</FormSection>
|
||||
@@ -109,8 +109,8 @@
|
||||
<template #label>ServiceWorker</template>
|
||||
|
||||
<FormSwitch v-model="enableServiceWorker" class="_formBlock">
|
||||
<template #label>{{ $ts.enableServiceworker }}</template>
|
||||
<template #caption>{{ $ts.serviceworkerInfo }}</template>
|
||||
<template #label>{{ i18n.ts.enableServiceworker }}</template>
|
||||
<template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
|
||||
</FormSwitch>
|
||||
|
||||
<template v-if="enableServiceWorker">
|
||||
@@ -142,8 +142,8 @@
|
||||
</MkSpacer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import FormSwitch from '@/components/form/switch.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
@@ -154,119 +154,103 @@ import FormSuspense from '@/components/form/suspense.vue';
|
||||
import * as os from '@/os';
|
||||
import * as symbols from '@/symbols';
|
||||
import { fetchInstance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
FormSwitch,
|
||||
FormInput,
|
||||
FormSuspense,
|
||||
FormTextarea,
|
||||
FormInfo,
|
||||
FormSection,
|
||||
FormSplit,
|
||||
},
|
||||
let name: string | null = $ref(null);
|
||||
let description: string | null = $ref(null);
|
||||
let tosUrl: string | null = $ref(null);
|
||||
let maintainerName: string | null = $ref(null);
|
||||
let maintainerEmail: string | null = $ref(null);
|
||||
let iconUrl: string | null = $ref(null);
|
||||
let bannerUrl: string | null = $ref(null);
|
||||
let backgroundImageUrl: string | null = $ref(null);
|
||||
let themeColor: any = $ref(null);
|
||||
let defaultLightTheme: any = $ref(null);
|
||||
let defaultDarkTheme: any = $ref(null);
|
||||
let enableLocalTimeline: boolean = $ref(false);
|
||||
let enableGlobalTimeline: boolean = $ref(false);
|
||||
let pinnedUsers: string = $ref('');
|
||||
let cacheRemoteFiles: boolean = $ref(false);
|
||||
let localDriveCapacityMb: any = $ref(0);
|
||||
let remoteDriveCapacityMb: any = $ref(0);
|
||||
let enableRegistration: boolean = $ref(false);
|
||||
let emailRequiredForSignup: boolean = $ref(false);
|
||||
let enableServiceWorker: boolean = $ref(false);
|
||||
let swPublicKey: any = $ref(null);
|
||||
let swPrivateKey: any = $ref(null);
|
||||
let deeplAuthKey: string = $ref('');
|
||||
let deeplIsPro: boolean = $ref(false);
|
||||
|
||||
emits: ['info'],
|
||||
async function init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
name = meta.name;
|
||||
description = meta.description;
|
||||
tosUrl = meta.tosUrl;
|
||||
iconUrl = meta.iconUrl;
|
||||
bannerUrl = meta.bannerUrl;
|
||||
backgroundImageUrl = meta.backgroundImageUrl;
|
||||
themeColor = meta.themeColor;
|
||||
defaultLightTheme = meta.defaultLightTheme;
|
||||
defaultDarkTheme = meta.defaultDarkTheme;
|
||||
maintainerName = meta.maintainerName;
|
||||
maintainerEmail = meta.maintainerEmail;
|
||||
enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
enableGlobalTimeline = !meta.disableGlobalTimeline;
|
||||
pinnedUsers = meta.pinnedUsers.join('\n');
|
||||
cacheRemoteFiles = meta.cacheRemoteFiles;
|
||||
localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
|
||||
remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
|
||||
enableRegistration = !meta.disableRegistration;
|
||||
emailRequiredForSignup = meta.emailRequiredForSignup;
|
||||
enableServiceWorker = meta.enableServiceWorker;
|
||||
swPublicKey = meta.swPublickey;
|
||||
swPrivateKey = meta.swPrivateKey;
|
||||
deeplAuthKey = meta.deeplAuthKey;
|
||||
deeplIsPro = meta.deeplIsPro;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.general,
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: this.$ts.save,
|
||||
handler: this.save,
|
||||
}],
|
||||
},
|
||||
name: null,
|
||||
description: null,
|
||||
tosUrl: null as string | null,
|
||||
maintainerName: null,
|
||||
maintainerEmail: null,
|
||||
iconUrl: null,
|
||||
bannerUrl: null,
|
||||
backgroundImageUrl: null,
|
||||
themeColor: null,
|
||||
defaultLightTheme: null,
|
||||
defaultDarkTheme: null,
|
||||
enableLocalTimeline: false,
|
||||
enableGlobalTimeline: false,
|
||||
pinnedUsers: '',
|
||||
cacheRemoteFiles: false,
|
||||
localDriveCapacityMb: 0,
|
||||
remoteDriveCapacityMb: 0,
|
||||
enableRegistration: false,
|
||||
emailRequiredForSignup: false,
|
||||
enableServiceWorker: false,
|
||||
swPublicKey: null,
|
||||
swPrivateKey: null,
|
||||
deeplAuthKey: '',
|
||||
deeplIsPro: false,
|
||||
}
|
||||
},
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
name,
|
||||
description,
|
||||
tosUrl,
|
||||
iconUrl,
|
||||
bannerUrl,
|
||||
backgroundImageUrl,
|
||||
themeColor: themeColor === '' ? null : themeColor,
|
||||
defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
|
||||
defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
|
||||
maintainerName,
|
||||
maintainerEmail,
|
||||
disableLocalTimeline: !enableLocalTimeline,
|
||||
disableGlobalTimeline: !enableGlobalTimeline,
|
||||
pinnedUsers: pinnedUsers.split('\n'),
|
||||
cacheRemoteFiles,
|
||||
localDriveCapacityMb: parseInt(localDriveCapacityMb, 10),
|
||||
remoteDriveCapacityMb: parseInt(remoteDriveCapacityMb, 10),
|
||||
disableRegistration: !enableRegistration,
|
||||
emailRequiredForSignup,
|
||||
enableServiceWorker,
|
||||
swPublicKey,
|
||||
swPrivateKey,
|
||||
deeplAuthKey,
|
||||
deeplIsPro,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
|
||||
methods: {
|
||||
async init() {
|
||||
const meta = await os.api('admin/meta');
|
||||
this.name = meta.name;
|
||||
this.description = meta.description;
|
||||
this.tosUrl = meta.tosUrl;
|
||||
this.iconUrl = meta.iconUrl;
|
||||
this.bannerUrl = meta.bannerUrl;
|
||||
this.backgroundImageUrl = meta.backgroundImageUrl;
|
||||
this.themeColor = meta.themeColor;
|
||||
this.defaultLightTheme = meta.defaultLightTheme;
|
||||
this.defaultDarkTheme = meta.defaultDarkTheme;
|
||||
this.maintainerName = meta.maintainerName;
|
||||
this.maintainerEmail = meta.maintainerEmail;
|
||||
this.enableLocalTimeline = !meta.disableLocalTimeline;
|
||||
this.enableGlobalTimeline = !meta.disableGlobalTimeline;
|
||||
this.pinnedUsers = meta.pinnedUsers.join('\n');
|
||||
this.cacheRemoteFiles = meta.cacheRemoteFiles;
|
||||
this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
|
||||
this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
|
||||
this.enableRegistration = !meta.disableRegistration;
|
||||
this.emailRequiredForSignup = meta.emailRequiredForSignup;
|
||||
this.enableServiceWorker = meta.enableServiceWorker;
|
||||
this.swPublicKey = meta.swPublickey;
|
||||
this.swPrivateKey = meta.swPrivateKey;
|
||||
this.deeplAuthKey = meta.deeplAuthKey;
|
||||
this.deeplIsPro = meta.deeplIsPro;
|
||||
},
|
||||
|
||||
save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
name: this.name,
|
||||
description: this.description,
|
||||
tosUrl: this.tosUrl,
|
||||
iconUrl: this.iconUrl,
|
||||
bannerUrl: this.bannerUrl,
|
||||
backgroundImageUrl: this.backgroundImageUrl,
|
||||
themeColor: this.themeColor === '' ? null : this.themeColor,
|
||||
defaultLightTheme: this.defaultLightTheme === '' ? null : this.defaultLightTheme,
|
||||
defaultDarkTheme: this.defaultDarkTheme === '' ? null : this.defaultDarkTheme,
|
||||
maintainerName: this.maintainerName,
|
||||
maintainerEmail: this.maintainerEmail,
|
||||
disableLocalTimeline: !this.enableLocalTimeline,
|
||||
disableGlobalTimeline: !this.enableGlobalTimeline,
|
||||
pinnedUsers: this.pinnedUsers.split('\n'),
|
||||
cacheRemoteFiles: this.cacheRemoteFiles,
|
||||
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
|
||||
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
|
||||
disableRegistration: !this.enableRegistration,
|
||||
emailRequiredForSignup: this.emailRequiredForSignup,
|
||||
enableServiceWorker: this.enableServiceWorker,
|
||||
swPublicKey: this.swPublicKey,
|
||||
swPrivateKey: this.swPrivateKey,
|
||||
deeplAuthKey: this.deeplAuthKey,
|
||||
deeplIsPro: this.deeplIsPro,
|
||||
}).then(() => {
|
||||
fetchInstance();
|
||||
});
|
||||
}
|
||||
defineExpose({
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: i18n.ts.general,
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
asFullButton: true,
|
||||
icon: 'fas fa-check',
|
||||
text: i18n.ts.save,
|
||||
handler: save,
|
||||
}],
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -26,8 +26,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineExpose, ref } from 'vue';
|
||||
import * as JSON5 from 'json5';
|
||||
import { ref } from 'vue';
|
||||
import JSON5 from 'json5';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
|
@@ -111,8 +111,8 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
setBannerImage(e) {
|
||||
selectFile(e.currentTarget ?? e.target, null).then(file => {
|
||||
setBannerImage(evt) {
|
||||
selectFile(evt.currentTarget ?? evt.target, null).then(file => {
|
||||
this.bannerId = file.id;
|
||||
});
|
||||
},
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user