Merge branch 'error-style' of https://github.com/ThatOneCalculator/misskey into error-style
This commit is contained in:
		
						commit
						162d444d85
					
				
					 67 changed files with 281 additions and 157 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue