Merge branch 'misskey-dev:develop' into error-style
This commit is contained in:
commit
2cfb3a1d36
67 changed files with 281 additions and 157 deletions
|
@ -35,6 +35,7 @@ You should also include the user name that made the change.
|
|||
- Server: Supports IPv6 on Redis transport. @mei23
|
||||
IPv4/IPv6 is used by default. You can tune this behavior via `redis.family`.
|
||||
- Server: Add possibility to log IP addresses of users @syuilo
|
||||
- Add additional drive capacity change support @CyberRex0
|
||||
|
||||
### Bugfixes
|
||||
- Server: Fix GenerateVideoThumbnail failed @mei23
|
||||
|
|
|
@ -203,6 +203,7 @@ done: "完了"
|
|||
processing: "処理中"
|
||||
preview: "プレビュー"
|
||||
default: "デフォルト"
|
||||
defaultValueIs: "デフォルト: {value}"
|
||||
noCustomEmojis: "絵文字はありません"
|
||||
noJobs: "ジョブはありません"
|
||||
federating: "連合中"
|
||||
|
@ -542,7 +543,7 @@ relays: "リレー"
|
|||
addRelay: "リレーの追加"
|
||||
inboxUrl: "inboxのURL"
|
||||
addedRelays: "追加済みのリレー"
|
||||
serviceworkerInfo: "プッシュ通知を行うには有効する必要があります。"
|
||||
serviceworkerInfo: "プッシュ通知を行うには有効にする必要があります。"
|
||||
deletedNote: "削除された投稿"
|
||||
invisibleNote: "非公開の投稿"
|
||||
enableInfiniteScroll: "自動でもっと見る"
|
||||
|
@ -855,6 +856,8 @@ noEmailServerWarning: "メールサーバーの設定がされていません。
|
|||
thereIsUnresolvedAbuseReportWarning: "未対応の通報があります。"
|
||||
recommended: "推奨"
|
||||
check: "チェック"
|
||||
driveCapOverrideLabel: "このユーザーのドライブ容量上限を変更"
|
||||
driveCapOverrideCaption: "0以下を指定すると解除されます。"
|
||||
requireAdminForView: "閲覧するには管理者アカウントでログインしている必要があります。"
|
||||
isSystemAccount: "システムにより自動で作成・管理されているアカウントです。"
|
||||
typeToConfirm: "この操作を行うには {x} と入力してください"
|
||||
|
@ -1731,6 +1734,7 @@ _deck:
|
|||
profile: "プロファイル"
|
||||
introduction: "カラムを組み合わせて自分だけのインターフェイスを作りましょう!"
|
||||
introduction2: "画面の右にある + を押して、いつでもカラムを追加できます。"
|
||||
widgetsIntroduction: "カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください"
|
||||
|
||||
_columns:
|
||||
main: "メイン"
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
export class driveCapacityOverrideMb1655813815729 {
|
||||
name = 'driveCapacityOverrideMb1655813815729'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "driveCapacityOverrideMb" integer`);
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`COMMENT ON COLUMN "user"."driveCapacityOverrideMb" IS 'Overrides user drive capacity limit'`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "driveCapacityOverrideMb"`);
|
||||
}
|
||||
}
|
|
@ -218,6 +218,12 @@ export class User {
|
|||
})
|
||||
public token: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
comment: 'Overrides user drive capacity limit',
|
||||
})
|
||||
public driveCapacityOverrideMb: number | null;
|
||||
|
||||
constructor(data: Partial<User>) {
|
||||
if (data == null) return;
|
||||
|
||||
|
|
|
@ -315,6 +315,7 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
} : undefined) : undefined,
|
||||
emojis: populateEmojis(user.emojis, user.host),
|
||||
onlineStatus: this.getOnlineStatus(user),
|
||||
driveCapacityOverrideMb: user.driveCapacityOverrideMb,
|
||||
|
||||
...(opts.detail ? {
|
||||
url: profile!.url,
|
||||
|
|
|
@ -314,6 +314,7 @@ import * as ep___users_search from './endpoints/users/search.js';
|
|||
import * as ep___users_show from './endpoints/users/show.js';
|
||||
import * as ep___users_stats from './endpoints/users/stats.js';
|
||||
import * as ep___fetchRss from './endpoints/fetch-rss.js';
|
||||
import * as ep___admin_driveCapOverride from './endpoints/admin/drive-capacity-override.js';
|
||||
|
||||
const eps = [
|
||||
['admin/meta', ep___admin_meta],
|
||||
|
@ -629,6 +630,7 @@ const eps = [
|
|||
['users/search', ep___users_search],
|
||||
['users/show', ep___users_show],
|
||||
['users/stats', ep___users_stats],
|
||||
['admin/drive-capacity-override', ep___admin_driveCapOverride],
|
||||
['fetch-rss', ep___fetchRss],
|
||||
];
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import define from '../../define.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { insertModerationLog } from '@/services/insert-moderation-log.js';
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
overrideMb: { type: 'number', nullable: true },
|
||||
},
|
||||
required: ['userId', 'overrideMb'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const user = await Users.findOneBy({ id: ps.userId });
|
||||
|
||||
if (user == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
if (!Users.isLocalUser(user)) {
|
||||
throw new Error('user is not local user');
|
||||
}
|
||||
|
||||
/*if (user.isAdmin) {
|
||||
throw new Error('cannot suspend admin');
|
||||
}
|
||||
if (user.isModerator) {
|
||||
throw new Error('cannot suspend moderator');
|
||||
}*/
|
||||
|
||||
await Users.update(user.id, {
|
||||
driveCapacityOverrideMb: ps.overrideMb,
|
||||
});
|
||||
|
||||
insertModerationLog(me, 'change-drive-capacity-override', {
|
||||
targetId: user.id,
|
||||
});
|
||||
});
|
|
@ -39,7 +39,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
const usage = await DriveFiles.calcDriveUsageOf(user.id);
|
||||
|
||||
return {
|
||||
capacity: 1024 * 1024 * instance.localDriveCapacityMb,
|
||||
capacity: 1024 * 1024 * (user.driveCapacityOverrideMb || instance.localDriveCapacityMb),
|
||||
usage: usage,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -307,7 +307,7 @@ async function deleteOldFile(user: IRemoteUser) {
|
|||
|
||||
type AddFileArgs = {
|
||||
/** User who wish to add file */
|
||||
user: { id: User['id']; host: User['host'] } | null;
|
||||
user: { id: User['id']; host: User['host']; driveCapacityOverrideMb: User['driveCapacityOverrideMb'] } | null;
|
||||
/** File path */
|
||||
path: string;
|
||||
/** Name */
|
||||
|
@ -371,9 +371,16 @@ export async function addFile({
|
|||
//#region Check drive usage
|
||||
if (user && !isLink) {
|
||||
const usage = await DriveFiles.calcDriveUsageOf(user);
|
||||
const u = await Users.findOneBy({ id: user.id });
|
||||
|
||||
const instance = await fetchMeta();
|
||||
const driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
|
||||
let driveCapacity = 1024 * 1024 * (Users.isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
|
||||
|
||||
if (Users.isLocalUser(user) && u?.driveCapacityOverrideMb != null) {
|
||||
driveCapacity = 1024 * 1024 * u.driveCapacityOverrideMb;
|
||||
logger.debug('drive capacity override applied');
|
||||
logger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
|
||||
}
|
||||
|
||||
logger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
"uuid": "8.3.2",
|
||||
"v-debounce": "0.1.2",
|
||||
"vanilla-tilt": "1.7.2",
|
||||
"vite": "3.0.0-beta.5",
|
||||
"vite": "3.0.0-beta.6",
|
||||
"vue": "3.2.37",
|
||||
"vue-prism-editor": "2.0.0-alpha.2",
|
||||
"vuedraggable": "4.0.1",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<XWindow ref="window" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
|
||||
<XWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
|
||||
<template #header>
|
||||
<i class="fas fa-exclamation-circle" style="margin-right: 0.5em;"></i>
|
||||
<I18n :src="i18n.ts.reportAbuseOf" tag="span">
|
||||
|
@ -40,7 +40,7 @@ const emit = defineEmits<{
|
|||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
const window = ref<InstanceType<typeof XWindow>>();
|
||||
const uiWindow = ref<InstanceType<typeof XWindow>>();
|
||||
const comment = ref(props.initialComment || '');
|
||||
|
||||
function send() {
|
||||
|
@ -52,7 +52,7 @@ function send() {
|
|||
type: 'success',
|
||||
text: i18n.ts.abuseReported
|
||||
});
|
||||
window.value?.close();
|
||||
uiWindow.value?.close();
|
||||
emit('closed');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<span v-if="emoji.isCustomEmoji" class="emoji"><img :src="defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
|
||||
<span v-else-if="!defaultStore.state.useOsNativeEmojis" class="emoji"><img :src="emoji.url" :alt="emoji.emoji"/></span>
|
||||
<span v-else class="emoji">{{ emoji.emoji }}</span>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
|
||||
<span v-if="emoji.aliasOf" class="alias">({{ emoji.aliasOf }})</span>
|
||||
</li>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<code v-if="inline" :class="`language-${prismLang}`" v-html="html"></code>
|
||||
<pre v-else :class="`language-${prismLang}`"><code :class="`language-${prismLang}`" v-html="html"></code></pre>
|
||||
|
@ -5,7 +6,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import 'prismjs';
|
||||
import { Prism } from 'prismjs';
|
||||
import 'prismjs/themes/prism-okaidia.css';
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
|
||||
<form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit">
|
||||
<div class="main _formRoot">
|
||||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required>
|
||||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
|
||||
<template #label>{{ i18n.ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
</MkInput>
|
||||
|
||||
<MkInput v-model="email" class="_formBlock" type="email" spellcheck="false" required>
|
||||
<MkInput v-model="email" class="_formBlock" type="email" :spellcheck="false" required>
|
||||
<template #label>{{ i18n.ts.emailAddress }}</template>
|
||||
<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
|
||||
</MkInput>
|
||||
|
|
|
@ -98,7 +98,7 @@ export default defineComponent({
|
|||
|
||||
created() {
|
||||
for (const item in this.form) {
|
||||
this.values[item] = this.form[item].hasOwnProperty('default') ? this.form[item].default : null;
|
||||
this.values[item] = this.form[item].default ?? null;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div v-if="block" v-html="compiledFormula"></div>
|
||||
<span v-else v-html="compiledFormula"></span>
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, defineAsyncComponent } from 'vue';import * as os from '@/os';
|
||||
import { defineComponent, defineAsyncComponent } from 'vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
|
|
@ -13,9 +13,6 @@ const props = defineProps<{
|
|||
router?: Router;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
const router = props.router ?? inject('router');
|
||||
|
||||
if (router == null) {
|
||||
|
|
|
@ -77,10 +77,16 @@ export default {
|
|||
<style lang="scss" module>
|
||||
.wrap {
|
||||
overflow: hidden; overflow: clip;
|
||||
animation-play-state: running;
|
||||
|
||||
&:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
animation-play-state: inherit;
|
||||
}
|
||||
.text {
|
||||
display: inline-block;
|
||||
|
@ -88,6 +94,7 @@ export default {
|
|||
animation-timing-function: linear;
|
||||
animation-iteration-count: infinite;
|
||||
animation-duration: inherit;
|
||||
animation-play-state: inherit;
|
||||
}
|
||||
.paused .text {
|
||||
animation-play-state: paused;
|
||||
|
|
|
@ -24,7 +24,6 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
setup(props, ctx) {
|
||||
|
||||
const hpml = new Hpml(props.page, {
|
||||
randomSeed: Math.random(),
|
||||
visitor: $i,
|
||||
|
|
|
@ -116,8 +116,11 @@ function get() {
|
|||
let base = parseInt(after.value);
|
||||
switch (unit.value) {
|
||||
case 'day': base *= 24;
|
||||
// fallthrough
|
||||
case 'hour': base *= 60;
|
||||
// fallthrough
|
||||
case 'minute': base *= 60;
|
||||
// fallthrough
|
||||
case 'second': return base *= 1000;
|
||||
default: return null;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
{{ message }}
|
||||
</MkInfo>
|
||||
<div v-if="!totpLogin" class="normal-signin">
|
||||
<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">
|
||||
<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>
|
||||
|
@ -32,7 +32,7 @@
|
|||
<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>
|
||||
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false" required>
|
||||
<template #label>{{ i18n.ts.token }}</template>
|
||||
<template #prefix><i class="fas fa-gavel"></i></template>
|
||||
</MkInput>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
|
||||
<template v-if="meta">
|
||||
<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" spellcheck="false" required>
|
||||
<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :spellcheck="false" required>
|
||||
<template #label>{{ $ts.invitationCode }}</template>
|
||||
<template #prefix><i class="fas fa-key"></i></template>
|
||||
</MkInput>
|
||||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
|
||||
<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
|
||||
<template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
|
||||
</template>
|
||||
</MkInput>
|
||||
<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
|
||||
<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
|
||||
<template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
|
||||
<template #prefix><i class="fas fa-envelope"></i></template>
|
||||
<template #caption>
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
import { onMounted, ref, watch, PropType, onBeforeUnmount } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
|
||||
const props = defineProps<{}>();
|
||||
|
||||
const loaded = !!window.TagCanvas;
|
||||
const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
|
||||
const computedStyle = getComputedStyle(document.documentElement);
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';import * as os from '@/os';
|
||||
import { defineComponent } from 'vue';
|
||||
import * as os from '@/os';
|
||||
|
||||
export default defineComponent({});
|
||||
</script>
|
||||
|
|
|
@ -99,12 +99,12 @@ export default defineComponent({
|
|||
buttonsLeft: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: [],
|
||||
default: () => [],
|
||||
},
|
||||
buttonsRight: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: [],
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@ function calc(src: Element) {
|
|||
|
||||
export default {
|
||||
mounted(src, binding, vn) {
|
||||
|
||||
const resize = new ResizeObserver((entries, observer) => {
|
||||
calc(src);
|
||||
});
|
||||
|
|
|
@ -49,6 +49,7 @@ export default {
|
|||
showing,
|
||||
text: self.text,
|
||||
asMfm: binding.modifiers.mfm,
|
||||
direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top',
|
||||
targetElement: el,
|
||||
}, {}, 'closed');
|
||||
|
||||
|
|
|
@ -75,7 +75,6 @@ const hasTabs = computed(() => {
|
|||
|
||||
const showTabsPopup = (ev: MouseEvent) => {
|
||||
if (!hasTabs.value) return;
|
||||
if (!narrow.value) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const menu = props.tabs.map(tab => ({
|
||||
|
|
|
@ -27,10 +27,10 @@
|
|||
</div>
|
||||
<!-- TODO
|
||||
<div class="inputs" style="display: flex; padding-top: 1.2em;">
|
||||
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false">
|
||||
<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" :spellcheck="false">
|
||||
<span>{{ $ts.username }}</span>
|
||||
</MkInput>
|
||||
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'">
|
||||
<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params().origin === 'local'">
|
||||
<span>{{ $ts.host }}</span>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
|
|
@ -61,27 +61,22 @@ let hcaptchaSecretKey: string | null = $ref(null);
|
|||
let recaptchaSiteKey: string | null = $ref(null);
|
||||
let recaptchaSecretKey: string | null = $ref(null);
|
||||
|
||||
const enableHcaptcha = $computed(() => provider === 'hcaptcha');
|
||||
const enableRecaptcha = $computed(() => provider === 'recaptcha');
|
||||
|
||||
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;
|
||||
|
||||
provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null;
|
||||
provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : null;
|
||||
}
|
||||
|
||||
function save() {
|
||||
os.apiWithDialog('admin/update-meta', {
|
||||
enableHcaptcha,
|
||||
enableHcaptcha: provider === 'hcaptcha',
|
||||
hcaptchaSiteKey,
|
||||
hcaptchaSecretKey,
|
||||
enableRecaptcha,
|
||||
enableRecaptcha: provider === 'recaptcha',
|
||||
recaptchaSiteKey,
|
||||
recaptchaSecretKey,
|
||||
}).then(() => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<MkStickyContainer>
|
||||
<template #header><XHeader :actions="headerActions" :tabs="headerTabs" v-model:tab="tab"/></template>
|
||||
<template #header><XHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="900">
|
||||
<div class="ogwlenmc">
|
||||
<div v-if="tab === 'local'" class="local">
|
||||
|
|
|
@ -19,7 +19,7 @@ const props = defineProps<{
|
|||
user: misskey.entities.User;
|
||||
}>();
|
||||
|
||||
const chart = $ref(null);
|
||||
let chart = $ref(null);
|
||||
|
||||
os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16, span: 'day' }).then(res => {
|
||||
chart = res;
|
||||
|
|
|
@ -30,11 +30,11 @@
|
|||
</MkSelect>
|
||||
</div>
|
||||
<div class="inputs">
|
||||
<MkInput v-model="searchUsername" style="flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.users.reload()">
|
||||
<MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:modelValue="$refs.users.reload()">
|
||||
<template #prefix>@</template>
|
||||
<template #label>{{ $ts.username }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="searchHost" style="flex: 1;" type="text" spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()">
|
||||
<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()">
|
||||
<template #prefix>@</template>
|
||||
<template #label>{{ $ts.host }}</template>
|
||||
</MkInput>
|
||||
|
|
|
@ -74,8 +74,8 @@ const props = defineProps<{
|
|||
postId: string;
|
||||
}>();
|
||||
|
||||
const post = $ref(null);
|
||||
const error = $ref(null);
|
||||
let post = $ref(null);
|
||||
let error = $ref(null);
|
||||
const otherPostsPagination = {
|
||||
endpoint: 'users/gallery/posts' as const,
|
||||
limit: 6,
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { watch } from 'vue';
|
||||
import * as Acct from 'misskey-js/built/acct';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkInput from '@/components/form/input.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
|
|
|
@ -41,6 +41,7 @@ import MkButton from '@/components/ui/button.vue';
|
|||
import * as os from '@/os';
|
||||
import { mainRouter } from '@/router';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
listId: string;
|
||||
|
|
|
@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
|
|||
let values: string = $ref(props.value.values.join('\n'));
|
||||
|
||||
watch(values, () => {
|
||||
props.value.values = values.split('\n')
|
||||
props.value.values = values.split('\n');
|
||||
}, {
|
||||
deep: true
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" v-model:tab="tab"/></template>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer :content-max="700">
|
||||
<div class="jqqmcavi">
|
||||
<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="fas fa-external-link-square-alt"></i> {{ $ts._pages.viewPage }}</MkButton>
|
||||
|
@ -82,7 +82,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineComponent, defineAsyncComponent, computed, provide, watch } from 'vue';
|
||||
import { defineAsyncComponent, computed, provide, watch } from 'vue';
|
||||
import 'prismjs';
|
||||
import { highlight, languages } from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
|
@ -93,7 +93,6 @@ import { v4 as uuid } from 'uuid';
|
|||
import XVariable from './page-editor.script-block.vue';
|
||||
import XBlocks from './page-editor.blocks.vue';
|
||||
import MkTextarea from '@/components/form/textarea.vue';
|
||||
import MkContainer from '@/components/ui/container.vue';
|
||||
import MkButton from '@/components/ui/button.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
import MkSwitch from '@/components/form/switch.vue';
|
||||
|
@ -168,15 +167,15 @@ function save() {
|
|||
const options = getSaveOptions();
|
||||
|
||||
const onError = err => {
|
||||
if (err.id == '3d81ceae-475f-4600-b2a8-2bc116157532') {
|
||||
if (err.info.param == 'name') {
|
||||
if (err.id === '3d81ceae-475f-4600-b2a8-2bc116157532') {
|
||||
if (err.info.param === 'name') {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
title: i18n.ts._pages.invalidNameTitle,
|
||||
text: i18n.ts._pages.invalidNameText,
|
||||
});
|
||||
}
|
||||
} else if (err.code == 'NAME_ALREADY_EXISTS') {
|
||||
} else if (err.code === 'NAME_ALREADY_EXISTS') {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts._pages.nameAlreadyExists,
|
||||
|
@ -310,7 +309,7 @@ function getPageBlockList() {
|
|||
function getScriptBlockList(type: string = null) {
|
||||
const list = [];
|
||||
|
||||
const blocks = blockDefs.filter(block => type === null || block.out === null || block.out === type || typeof block.out === 'number');
|
||||
const blocks = blockDefs.filter(block => type == null || block.out == null || block.out === type || typeof block.out === 'number');
|
||||
|
||||
for (const block of blocks) {
|
||||
const category = list.find(x => x.category === block.category);
|
||||
|
@ -345,8 +344,8 @@ function getScriptBlockList(type: string = null) {
|
|||
return list;
|
||||
}
|
||||
|
||||
function setEyeCatchingImage(e) {
|
||||
selectFile(e.currentTarget ?? e.target, null).then(file => {
|
||||
function setEyeCatchingImage(img) {
|
||||
selectFile(img.currentTarget ?? img.target, null).then(file => {
|
||||
eyeCatchingImageId = file.id;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li>
|
||||
<li>
|
||||
{{ i18n.ts._2fa.step3 }}<br>
|
||||
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput>
|
||||
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" :spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput>
|
||||
<MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton>
|
||||
</li>
|
||||
</ol>
|
||||
|
|
|
@ -31,16 +31,6 @@ const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMain
|
|||
const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
|
||||
const profile = computed(deckStore.makeGetterSetter('profile'));
|
||||
|
||||
watch(navWindow, async () => {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'info',
|
||||
text: i18n.ts.reloadToApplySetting,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
unisonReload();
|
||||
});
|
||||
|
||||
async function setProfile() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts._deck.profile,
|
||||
|
|
|
@ -78,6 +78,7 @@ import FormButton from '@/components/ui/button.vue';
|
|||
import FormTextarea from '@/components/form/textarea.vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
|
||||
import { $i } from '@/account';
|
||||
import { Theme, applyTheme } from '@/scripts/theme';
|
||||
import lightTheme from '@/themes/_light.json5';
|
||||
import darkTheme from '@/themes/_dark.json5';
|
||||
|
@ -118,7 +119,7 @@ const fgColors = [
|
|||
{ color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
|
||||
];
|
||||
|
||||
const theme = $ref<Partial<Theme>>({
|
||||
let theme = $ref<Partial<Theme>>({
|
||||
base: 'light',
|
||||
props: lightTheme.props,
|
||||
});
|
||||
|
|
|
@ -112,6 +112,17 @@
|
|||
|
||||
<MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/>
|
||||
</FormFolder>
|
||||
<FormSection>
|
||||
<template #label>Drive Capacity Override</template>
|
||||
|
||||
<FormInput v-if="user.host == null" v-model="driveCapacityOverrideMb" inline :manual-save="true" type="number" :placeholder="i18n.t('defaultValueIs', { value: instance.driveCapacityPerLocalUserMb })" @update:model-value="applyDriveCapacityOverride">
|
||||
<template #label>{{ i18n.ts.driveCapOverrideLabel }}</template>
|
||||
<template #suffix>MB</template>
|
||||
<template #caption>
|
||||
{{ i18n.ts.driveCapOverrideCaption }}
|
||||
</template>
|
||||
</FormInput>
|
||||
</FormSection>
|
||||
</div>
|
||||
<div v-else-if="tab === 'chart'" class="_formRoot">
|
||||
<div class="cmhjzshm">
|
||||
|
@ -141,7 +152,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, defineComponent, watch } from 'vue';
|
||||
import { computed, watch } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import MkChart from '@/components/chart.vue';
|
||||
import MkObjectView from '@/components/object-view.vue';
|
||||
|
@ -150,6 +161,8 @@ import FormSwitch from '@/components/form/switch.vue';
|
|||
import FormLink from '@/components/form/link.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import FormButton from '@/components/ui/button.vue';
|
||||
import FormInput from '@/components/form/input.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import FormFolder from '@/components/form/folder.vue';
|
||||
import MkKeyValue from '@/components/key-value.vue';
|
||||
import MkSelect from '@/components/form/select.vue';
|
||||
|
@ -164,6 +177,7 @@ import { userPage, acct } from '@/filters/user';
|
|||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import { i18n } from '@/i18n';
|
||||
import { iAmAdmin, iAmModerator } from '@/account';
|
||||
import { instance } from '@/instance';
|
||||
|
||||
const props = defineProps<{
|
||||
userId: string;
|
||||
|
@ -172,13 +186,14 @@ const props = defineProps<{
|
|||
let tab = $ref('overview');
|
||||
let chartSrc = $ref('per-user-notes');
|
||||
let user = $ref<null | misskey.entities.UserDetailed>();
|
||||
let init = $ref();
|
||||
let init = $ref<ReturnType<typeof createFetcher>>();
|
||||
let info = $ref();
|
||||
let ips = $ref(null);
|
||||
let ap = $ref(null);
|
||||
let moderator = $ref(false);
|
||||
let silenced = $ref(false);
|
||||
let suspended = $ref(false);
|
||||
let driveCapacityOverrideMb: number | null = $ref(0);
|
||||
let moderationNote = $ref('');
|
||||
const filesPagination = {
|
||||
endpoint: 'admin/drive/files' as const,
|
||||
|
@ -203,6 +218,7 @@ function createFetcher() {
|
|||
moderator = info.isModerator;
|
||||
silenced = info.isSilenced;
|
||||
suspended = info.isSuspended;
|
||||
driveCapacityOverrideMb = user.driveCapacityOverrideMb;
|
||||
moderationNote = info.moderationNote;
|
||||
|
||||
watch($$(moderationNote), async () => {
|
||||
|
@ -289,6 +305,22 @@ async function deleteAllFiles() {
|
|||
await refreshUser();
|
||||
}
|
||||
|
||||
async function applyDriveCapacityOverride() {
|
||||
let driveCapOrMb = driveCapacityOverrideMb;
|
||||
if (driveCapacityOverrideMb && driveCapacityOverrideMb < 0) {
|
||||
driveCapOrMb = null;
|
||||
}
|
||||
try {
|
||||
await os.apiWithDialog('admin/drive-capacity-override', { userId: user.id, overrideMb: driveCapOrMb });
|
||||
await refreshUser();
|
||||
} catch (err) {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: err.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const confirm = await os.confirm({
|
||||
type: 'warning',
|
||||
|
@ -319,7 +351,7 @@ watch(() => props.userId, () => {
|
|||
immediate: true,
|
||||
});
|
||||
|
||||
watch(() => user, () => {
|
||||
watch($$(user), () => {
|
||||
os.api('ap/get', {
|
||||
uri: user.uri ?? `${url}/users/${user.id}`,
|
||||
}).then(res => {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<span class="text">{{ instanceName }}</span>
|
||||
</h1>
|
||||
<div class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
|
||||
</h1>
|
||||
<div class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
|
||||
</h1>
|
||||
<div class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || $ts.headlineMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<h1>Welcome to Misskey!</h1>
|
||||
<div class="_formRoot">
|
||||
<p>{{ $ts.intro }}</p>
|
||||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-admin-username class="_formBlock">
|
||||
<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username class="_formBlock">
|
||||
<template #label>{{ $ts.username }}</template>
|
||||
<template #prefix>@</template>
|
||||
<template #suffix>@{{ host }}</template>
|
||||
|
|
|
@ -38,7 +38,7 @@ export function install(plugin) {
|
|||
function createPluginEnv(opts) {
|
||||
const config = new Map();
|
||||
for (const [k, v] of Object.entries(opts.plugin.config || {})) {
|
||||
config.set(k, jsToVal(opts.plugin.configData.hasOwnProperty(k) ? opts.plugin.configData[k] : v.default));
|
||||
config.set(k, jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default));
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -98,7 +98,7 @@ export function groupOn<T, S>(f: (x: T) => S, xs: T[]): T[][] {
|
|||
export function groupByX<T>(collections: T[], keySelector: (x: T) => string) {
|
||||
return collections.reduce((obj: Record<string, T[]>, item: T) => {
|
||||
const key = keySelector(item);
|
||||
if (!obj.hasOwnProperty(key)) {
|
||||
if (typeof obj[key] === 'undefined') {
|
||||
obj[key] = [];
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export class Autocomplete {
|
|||
x: Ref<number>;
|
||||
y: Ref<number>;
|
||||
q: Ref<string | null>;
|
||||
close: Function;
|
||||
close: () => void;
|
||||
} | null;
|
||||
private textarea: HTMLInputElement | HTMLTextAreaElement;
|
||||
private currentType: string;
|
||||
|
|
|
@ -21,7 +21,6 @@ export async function genSearchQuery(v: any, q: string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import keyCode from './keycode';
|
||||
|
||||
type Keymap = Record<string, Function>;
|
||||
type Callback = (ev: KeyboardEvent) => void;
|
||||
|
||||
type Keymap = Record<string, Callback>;
|
||||
|
||||
type Pattern = {
|
||||
which: string[];
|
||||
|
@ -11,14 +13,14 @@ type Pattern = {
|
|||
|
||||
type Action = {
|
||||
patterns: Pattern[];
|
||||
callback: Function;
|
||||
callback: Callback;
|
||||
allowRepeat: boolean;
|
||||
};
|
||||
|
||||
const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, callback]): Action => {
|
||||
const result = {
|
||||
patterns: [],
|
||||
callback: callback,
|
||||
callback,
|
||||
allowRepeat: true
|
||||
} as Action;
|
||||
|
||||
|
|
|
@ -159,7 +159,6 @@ export class Hpml {
|
|||
|
||||
@autobind
|
||||
private evaluate(expr: Expr, scope: HpmlScope): any {
|
||||
|
||||
if (isLiteralValue(expr)) {
|
||||
if (expr.type === null) {
|
||||
return null;
|
||||
|
|
|
@ -14,13 +14,13 @@ export type Fn = {
|
|||
export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
|
||||
|
||||
export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
|
||||
text: { out: 'string', category: 'value', icon: 'fas fa-quote-right', },
|
||||
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left', },
|
||||
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list', },
|
||||
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up', },
|
||||
ref: { out: null, category: 'value', icon: 'fas fa-magic', },
|
||||
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic', },
|
||||
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt', },
|
||||
text: { out: 'string', category: 'value', icon: 'fas fa-quote-right', },
|
||||
multiLineText: { out: 'string', category: 'value', icon: 'fas fa-align-left', },
|
||||
textList: { out: 'stringArray', category: 'value', icon: 'fas fa-list', },
|
||||
number: { out: 'number', category: 'value', icon: 'fas fa-sort-numeric-up', },
|
||||
ref: { out: null, category: 'value', icon: 'fas fa-magic', },
|
||||
aiScriptVar: { out: null, category: 'value', icon: 'fas fa-magic', },
|
||||
fn: { out: 'function', category: 'value', icon: 'fas fa-square-root-alt', },
|
||||
};
|
||||
|
||||
export const blockDefs = [
|
||||
|
|
|
@ -125,55 +125,56 @@ export function initAiLib(hpml: Hpml) {
|
|||
}
|
||||
});
|
||||
*/
|
||||
})
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
|
||||
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt', },
|
||||
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle', },
|
||||
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
|
||||
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
|
||||
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag', },
|
||||
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus', },
|
||||
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus', },
|
||||
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times', },
|
||||
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide', },
|
||||
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide', },
|
||||
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator', },
|
||||
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals', },
|
||||
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal', },
|
||||
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than', },
|
||||
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than', },
|
||||
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal', },
|
||||
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal', },
|
||||
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right', },
|
||||
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
|
||||
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
|
||||
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
|
||||
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right', },
|
||||
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt', },
|
||||
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt', },
|
||||
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt', },
|
||||
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent', },
|
||||
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent', },
|
||||
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
|
||||
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
|
||||
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice', },
|
||||
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
|
||||
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
|
||||
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice', },
|
||||
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice', },
|
||||
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice', },
|
||||
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice', },
|
||||
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice', }, // dailyRandomPickWithProbabilityMapping
|
||||
if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'fas fa-share-alt' },
|
||||
for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'fas fa-recycle' },
|
||||
not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'fas fa-flag' },
|
||||
add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-plus' },
|
||||
subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-minus' },
|
||||
multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-times' },
|
||||
divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
|
||||
mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'fas fa-divide' },
|
||||
round: { in: ['number'], out: 'number', category: 'operation', icon: 'fas fa-calculator' },
|
||||
eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-equals' },
|
||||
notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'fas fa-not-equal' },
|
||||
gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than' },
|
||||
lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than' },
|
||||
gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-greater-than-equal' },
|
||||
ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'fas fa-less-than-equal' },
|
||||
strLen: { in: ['string'], out: 'number', category: 'text', icon: 'fas fa-quote-right' },
|
||||
strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
|
||||
strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
|
||||
strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
|
||||
join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'fas fa-quote-right' },
|
||||
stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'fas fa-exchange-alt' },
|
||||
pick: { in: [null, 'number'], out: null, category: 'list', icon: 'fas fa-indent' },
|
||||
listLen: { in: [null], out: 'number', category: 'list', icon: 'fas fa-indent' },
|
||||
rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'fas fa-dice' },
|
||||
random: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'fas fa-dice' },
|
||||
randomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'fas fa-dice' },
|
||||
DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'fas fa-dice' }, // dailyRandomPickWithProbabilityMapping
|
||||
};
|
||||
|
||||
export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {
|
||||
|
||||
const date = new Date();
|
||||
const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
|
||||
|
||||
// SHOULD be fine to ignore since it's intended + function shape isn't defined
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const funcs: Record<string, Function> = {
|
||||
not: (a: boolean) => !a,
|
||||
or: (a: boolean, b: boolean) => a || b,
|
||||
|
@ -189,7 +190,7 @@ export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, vi
|
|||
const result: any[] = [];
|
||||
for (let i = 0; i < times; i++) {
|
||||
result.push(fn.exec({
|
||||
[fn.slots[0]]: i + 1
|
||||
[fn.slots[0]]: i + 1,
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export function query(obj: {}): string {
|
||||
export function query(obj: Record<string, any>): string {
|
||||
const params = Object.entries(obj)
|
||||
.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
|
||||
.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
|
||||
|
|
|
@ -72,7 +72,6 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||
'drive',
|
||||
'followRequests',
|
||||
'-',
|
||||
'featured',
|
||||
'explore',
|
||||
'announcements',
|
||||
'search',
|
||||
|
|
|
@ -39,14 +39,6 @@ html {
|
|||
scrollbar-color: var(--scrollbarHandle) inherit;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&:hover {
|
||||
scrollbar-color: var(--scrollbarHandleHover) inherit;
|
||||
}
|
||||
|
||||
&:active {
|
||||
scrollbar-color: var(--accent) inherit;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
|
|
|
@ -60,8 +60,8 @@ const DESKTOP_THRESHOLD = 1100;
|
|||
let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
|
||||
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
||||
const widgetsShowing = $ref(false);
|
||||
const fullView = $ref(false);
|
||||
let widgetsShowing = $ref(false);
|
||||
let fullView = $ref(false);
|
||||
let globalHeaderHeight = $ref(0);
|
||||
const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
const showMenuOnTop = $computed(() => defaultStore.state.menuDisplay === 'top');
|
||||
|
|
|
@ -33,7 +33,8 @@
|
|||
<div>{{ i18n.ts._deck.introduction2 }}</div>
|
||||
</div>
|
||||
<div class="sideMenu">
|
||||
<button v-tooltip="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button>
|
||||
<button v-tooltip.left="i18n.ts._deck.addColumn" class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button>
|
||||
<button v-tooltip.left="i18n.ts.settings" class="_button button settings" @click="showSettings"><i class="fas fa-cog"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -79,12 +80,14 @@ import { i18n } from '@/i18n';
|
|||
import { mainRouter } from '@/router';
|
||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||
|
||||
if (deckStore.state.navWindow) {
|
||||
mainRouter.navHook = (path) => {
|
||||
mainRouter.navHook = (path): boolean => {
|
||||
const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main');
|
||||
if (deckStore.state.navWindow || noMainColumn) {
|
||||
os.pageWindow(path);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isMobile = ref(window.innerWidth <= 500);
|
||||
window.addEventListener('resize', () => {
|
||||
|
@ -108,6 +111,10 @@ const menuIndicated = computed(() => {
|
|||
return false;
|
||||
});
|
||||
|
||||
function showSettings() {
|
||||
os.pageWindow('/settings/deck');
|
||||
}
|
||||
|
||||
let columnsEl = $ref<HTMLElement>();
|
||||
|
||||
const addColumn = async (ev) => {
|
||||
|
|
|
@ -13,7 +13,7 @@ type ColumnWidget = {
|
|||
|
||||
export type Column = {
|
||||
id: string;
|
||||
type: string;
|
||||
type: 'main' | 'widgets' | 'notifications' | 'tl' | 'antenna' | 'list' | 'mentions' | 'direct';
|
||||
name: string | null;
|
||||
width: number;
|
||||
widgets?: ColumnWidget[];
|
||||
|
|
|
@ -53,7 +53,7 @@ function onContextmenu(ev: MouseEvent) {
|
|||
if (isLink(ev.target as HTMLElement)) return;
|
||||
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return;
|
||||
if (window.getSelection()?.toString() !== '') return;
|
||||
const path = router.currentRoute.value.path;
|
||||
const path = mainRouter.currentRoute.value.path;
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: path,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template>
|
||||
|
||||
<div class="wtdtxvec">
|
||||
<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" class="intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
|
||||
<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
|
||||
</div>
|
||||
</XColumn>
|
||||
|
@ -13,6 +14,7 @@ import { } from 'vue';
|
|||
import XColumn from './column.vue';
|
||||
import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store';
|
||||
import XWidgets from '@/components/widgets.vue';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
column: Column;
|
||||
|
@ -52,5 +54,10 @@ function func() {
|
|||
--panelBorder: none;
|
||||
|
||||
padding: 0 var(--margin);
|
||||
|
||||
> .intro {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<div>
|
||||
<h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1>
|
||||
<div v-if="meta" class="about">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div class="desc" v-html="meta.description || $ts.introMisskey"></div>
|
||||
</div>
|
||||
<div class="action">
|
||||
|
@ -101,13 +102,18 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
setParallax(el) {
|
||||
//new simpleParallax(el);
|
||||
},
|
||||
// @ThatOneCalculator: Are these methods even used?
|
||||
// I can't find references to them anywhere else in the code...
|
||||
|
||||
// setParallax(el) {
|
||||
// new simpleParallax(el);
|
||||
// },
|
||||
|
||||
changePage(page) {
|
||||
if (page == null) return;
|
||||
// eslint-disable-next-line no-undef
|
||||
if (page[symbols.PAGE_INFO]) {
|
||||
// eslint-disable-next-line no-undef
|
||||
this.pageInfo = page[symbols.PAGE_INFO];
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="rwqkcmrc" :style="{ backgroundImage: transparent ? 'none' : `url(${ $instance.backgroundImageUrl })` }">
|
||||
<div class="back" :class="{ transparent }"></div>
|
||||
<div class="contents">
|
||||
<div class="wrapper">
|
||||
<h1 v-if="meta" :class="{ full }">
|
||||
<MkA to="/" class="link"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></MkA>
|
||||
<MkA to="/" class="link"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl" alt="logo"><span v-else class="text">{{ instanceName }}</span></MkA>
|
||||
</h1>
|
||||
<template v-if="full">
|
||||
<div v-if="meta" class="about">
|
||||
|
@ -21,7 +22,7 @@
|
|||
<div class="title">{{ announcement.title }}</div>
|
||||
<div class="content">
|
||||
<Mfm :text="announcement.text"/>
|
||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
|
||||
<img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt="announcement image"/>
|
||||
</div>
|
||||
</section>
|
||||
</MkPagination>
|
||||
|
|
|
@ -36,8 +36,9 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
|
|||
|
||||
const mergeProps = () => {
|
||||
for (const prop of Object.keys(propsDef)) {
|
||||
if (widgetProps.hasOwnProperty(prop)) continue;
|
||||
widgetProps[prop] = propsDef[prop].default;
|
||||
if (typeof widgetProps[prop] === 'undefined') {
|
||||
widgetProps[prop] = propsDef[prop].default;
|
||||
}
|
||||
}
|
||||
};
|
||||
watch(widgetProps, () => {
|
||||
|
|
|
@ -4215,10 +4215,10 @@ verror@1.10.0:
|
|||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
vite@3.0.0-beta.5:
|
||||
version "3.0.0-beta.5"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.0-beta.5.tgz#708d5b732dee98d77877cb094b567f5596508b5b"
|
||||
integrity sha512-SfesZuCME4fEmLy4hgsJAg55HRiTgDhH3oPM44XePrdKP5FqYvDkzpSWl6ldDOJYTskKWafGyyuYfXoxodv40Q==
|
||||
vite@3.0.0-beta.6:
|
||||
version "3.0.0-beta.6"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.0-beta.6.tgz#dd54c304ce7ceca243be8a114f28c431bbc447a1"
|
||||
integrity sha512-jAxxCGXs6oIO3dFh7gwDEP9RqFzYY+ULDWawS1dd3HfM4FCr8rkOnLljDoBBIDdTNM8M7pDzdoYSmpPEOJqyZQ==
|
||||
dependencies:
|
||||
esbuild "^0.14.47"
|
||||
postcss "^8.4.14"
|
||||
|
|
Loading…
Reference in a new issue