デフォルトでノート自動削除できる機能
This commit is contained in:
parent
f0797b556b
commit
26da54d372
16
locales/index.d.ts
vendored
16
locales/index.d.ts
vendored
@ -5213,6 +5213,22 @@ export interface Locale extends ILocale {
|
|||||||
* 高度なMFMのピッカーを表示する
|
* 高度なMFMのピッカーを表示する
|
||||||
*/
|
*/
|
||||||
"enableQuickAddMfmFunction": string;
|
"enableQuickAddMfmFunction": string;
|
||||||
|
/**
|
||||||
|
* ノートの自己消滅の初期値
|
||||||
|
*/
|
||||||
|
"defaultScheduledNoteDeleteTime": string;
|
||||||
|
/**
|
||||||
|
* ノートの自己消滅が有効になっています
|
||||||
|
*/
|
||||||
|
"scheduledNoteDeleteEnabled": string;
|
||||||
|
/**
|
||||||
|
* 1年以上先の日時を指定することはできません
|
||||||
|
*/
|
||||||
|
"cannotScheduleLaterThanOneYear": string;
|
||||||
|
/**
|
||||||
|
* デフォルトでノートが自己消滅するように
|
||||||
|
*/
|
||||||
|
"defaultScheduledNoteDelete": string;
|
||||||
/**
|
/**
|
||||||
* バブルゲーム
|
* バブルゲーム
|
||||||
*/
|
*/
|
||||||
|
@ -1344,6 +1344,10 @@ _delivery:
|
|||||||
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"
|
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"
|
||||||
scheduledNoteDelete: "時限爆弾"
|
scheduledNoteDelete: "時限爆弾"
|
||||||
noteDeletationAt: "このノートは{time}に削除されます"
|
noteDeletationAt: "このノートは{time}に削除されます"
|
||||||
|
defaultScheduledNoteDeleteTime: "ノートの自己消滅の初期値"
|
||||||
|
scheduledNoteDeleteEnabled: "ノートの自己消滅が有効になっています"
|
||||||
|
cannotScheduleLaterThanOneYear: "1年以上先の日時を指定することはできません"
|
||||||
|
defaultScheduledNoteDelete: "デフォルトでノートが自己消滅するように"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
@ -110,6 +110,12 @@ export const meta = {
|
|||||||
id: '9576c3c8-d8f3-11ee-ac15-00155d19d35d',
|
id: '9576c3c8-d8f3-11ee-ac15-00155d19d35d',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cannotScheduleDeleteLaterThanOneYear: {
|
||||||
|
message: 'Scheduled delete time is later than one year.',
|
||||||
|
code: 'CANNOT_SCHEDULE_DELETE_LATER_THAN_ONE_YEAR',
|
||||||
|
id: 'b02b5edb-2741-4841-b692-d9893f1e6515',
|
||||||
|
},
|
||||||
|
|
||||||
noSuchChannel: {
|
noSuchChannel: {
|
||||||
message: 'No such channel.',
|
message: 'No such channel.',
|
||||||
code: 'NO_SUCH_CHANNEL',
|
code: 'NO_SUCH_CHANNEL',
|
||||||
@ -387,6 +393,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||||||
} else if (typeof ps.scheduledDelete.deleteAfter === 'number') {
|
} else if (typeof ps.scheduledDelete.deleteAfter === 'number') {
|
||||||
ps.scheduledDelete.deleteAt = Date.now() + ps.scheduledDelete.deleteAfter;
|
ps.scheduledDelete.deleteAt = Date.now() + ps.scheduledDelete.deleteAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.scheduledDelete.deleteAt && ps.scheduledDelete.deleteAt > Date.now() + ms('1year')) {
|
||||||
|
throw new ApiError(meta.errors.cannotScheduleDeleteLaterThanOneYear);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let channel: MiChannel | null = null;
|
let channel: MiChannel | null = null;
|
||||||
|
@ -1,74 +1,108 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="zmdxowus">
|
<div :class="[$style.root, { [$style.padding]: !afterOnly }]">
|
||||||
<span>{{ i18n.ts.scheduledNoteDelete }}</span>
|
<div v-if="!afterOnly" :class="[$style.label, { [$style.withAccent]: !showDetail }]" @click="showDetail = !showDetail"><i class="ti" :class="showDetail ? 'ti-chevron-up' : 'ti-chevron-down'"></i> {{ summaryText }}</div>
|
||||||
<section>
|
<MkInfo v-if="!isValid" warn>{{ i18n.ts.cannotScheduleLaterThanOneYear }}</MkInfo>
|
||||||
<div>
|
<section v-if="afterOnly || showDetail">
|
||||||
<MkSelect v-model="expiration" small>
|
<div>
|
||||||
<template #label>{{ i18n.ts._poll.expiration }}</template>
|
<MkSelect v-if="!afterOnly" v-model="expiration" small>
|
||||||
<option value="at">{{ i18n.ts._poll.at }}</option>
|
<template #label>{{ i18n.ts._poll.expiration }}</template>
|
||||||
<option value="after">{{ i18n.ts._poll.after }}</option>
|
<option value="at">{{ i18n.ts._poll.at }}</option>
|
||||||
</MkSelect>
|
<option value="after">{{ i18n.ts._poll.after }}</option>
|
||||||
<section v-if="expiration === 'at'">
|
|
||||||
<MkInput v-model="atDate" small type="date" class="input">
|
|
||||||
<template #label>{{ i18n.ts._poll.deadlineDate }}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkInput v-model="atTime" small type="time" class="input">
|
|
||||||
<template #label>{{ i18n.ts._poll.deadlineTime }}</template>
|
|
||||||
</MkInput>
|
|
||||||
</section>
|
|
||||||
<section v-else-if="expiration === 'after'">
|
|
||||||
<MkInput v-model="after" small type="number" class="input">
|
|
||||||
<template #label>{{ i18n.ts._poll.duration }}</template>
|
|
||||||
</MkInput>
|
|
||||||
<MkSelect v-model="unit" small>
|
|
||||||
<option value="second">{{ i18n.ts._time.second }}</option>
|
|
||||||
<option value="minute">{{ i18n.ts._time.minute }}</option>
|
|
||||||
<option value="hour">{{ i18n.ts._time.hour }}</option>
|
|
||||||
<option value="day">{{ i18n.ts._time.day }}</option>
|
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
</section>
|
<section v-if="expiration === 'at'">
|
||||||
</div>
|
<MkInput v-model="atDate" small type="date" class="input">
|
||||||
</section>
|
<template #label>{{ i18n.ts._poll.deadlineDate }}</template>
|
||||||
</div>
|
</MkInput>
|
||||||
</template>
|
<MkInput v-model="atTime" small type="time" class="input">
|
||||||
|
<template #label>{{ i18n.ts._poll.deadlineTime }}</template>
|
||||||
<script lang="ts" setup>
|
</MkInput>
|
||||||
import { ref, watch } from 'vue';
|
</section>
|
||||||
import MkInput from './MkInput.vue';
|
<section v-else-if="expiration === 'after'">
|
||||||
import MkSelect from './MkSelect.vue';
|
<MkInput v-model="after" small type="number" class="input">
|
||||||
import { formatDateTimeString } from '@/scripts/format-time-string.js';
|
<template #label>{{ i18n.ts._poll.duration }}</template>
|
||||||
import { addTime } from '@/scripts/time.js';
|
</MkInput>
|
||||||
import { i18n } from '@/i18n.js';
|
<MkSelect v-model="unit" small>
|
||||||
|
<option value="second">{{ i18n.ts._time.second }}</option>
|
||||||
export type DeleteScheduleEditorModelValue = {
|
<option value="minute">{{ i18n.ts._time.minute }}</option>
|
||||||
|
<option value="hour">{{ i18n.ts._time.hour }}</option>
|
||||||
|
<option value="day">{{ i18n.ts._time.day }}</option>
|
||||||
|
</MkSelect>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import MkInput from './MkInput.vue';
|
||||||
|
import MkSelect from './MkSelect.vue';
|
||||||
|
import MkInfo from './MkInfo.vue';
|
||||||
|
import { formatDateTimeString } from '@/scripts/format-time-string.js';
|
||||||
|
import { addTime } from '@/scripts/time.js';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
export type DeleteScheduleEditorModelValue = {
|
||||||
deleteAt: number | null;
|
deleteAt: number | null;
|
||||||
deleteAfter: number | null;
|
deleteAfter: number | null;
|
||||||
|
isValid: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: DeleteScheduleEditorModelValue;
|
modelValue: DeleteScheduleEditorModelValue;
|
||||||
|
afterOnly?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', v: DeleteScheduleEditorModelValue): void;
|
(ev: 'update:modelValue', v: DeleteScheduleEditorModelValue): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const expiration = ref('at');
|
const expiration = ref<'at' | 'after'>('after');
|
||||||
const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'));
|
const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'));
|
||||||
const atTime = ref('00:00');
|
const atTime = ref('00:00');
|
||||||
const after = ref(0);
|
const after = ref(0);
|
||||||
const unit = ref('second');
|
const unit = ref<'second' | 'minute' | 'hour' | 'day'>('second');
|
||||||
|
const isValid = ref(true);
|
||||||
|
const showDetail = ref(!defaultStore.state.defaultScheduledNoteDelete);
|
||||||
|
const summaryText = computed(() => {
|
||||||
|
if (showDetail.value) {
|
||||||
|
return i18n.ts.scheduledNoteDelete;
|
||||||
|
}
|
||||||
|
if (expiration.value === 'at') {
|
||||||
|
return `${i18n.ts.scheduledNoteDeleteEnabled} (${formatDateTimeString(new Date(calcAt()), 'yyyy/MM/dd HH:mm')})`;
|
||||||
|
} else {
|
||||||
|
const time = unit.value === 'second' ? i18n.tsx._timeIn.seconds({ n: (after.value).toString() })
|
||||||
|
: unit.value === 'minute' ? i18n.tsx._timeIn.minutes({ n: (after.value).toString() })
|
||||||
|
: unit.value === 'hour' ? i18n.tsx._timeIn.hours({ n: (after.value).toString() })
|
||||||
|
: i18n.tsx._timeIn.days({ n: (after.value).toString() });
|
||||||
|
return `${i18n.ts.scheduledNoteDeleteEnabled} (${time})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const beautifyAfter = (base: number) => {
|
||||||
|
let time = base;
|
||||||
|
if (time % 60 === 0) {
|
||||||
|
unit.value = 'minute';
|
||||||
|
time /= 60;
|
||||||
|
}
|
||||||
|
if (time % 60 === 0) {
|
||||||
|
unit.value = 'hour';
|
||||||
|
time /= 60;
|
||||||
|
}
|
||||||
|
if (time % 24 === 0) {
|
||||||
|
unit.value = 'day';
|
||||||
|
time /= 24;
|
||||||
|
}
|
||||||
|
after.value = time;
|
||||||
|
};
|
||||||
|
beautifyAfter(defaultStore.state.defaultScheduledNoteDeleteTime / 1000);
|
||||||
|
if (props.modelValue.deleteAt) {
|
||||||
|
expiration.value = 'at';
|
||||||
|
const deleteAt = new Date(props.modelValue.deleteAt);
|
||||||
|
atDate.value = formatDateTimeString(deleteAt, 'yyyy-MM-dd');
|
||||||
|
atTime.value = formatDateTimeString(deleteAt, 'HH:mm');
|
||||||
|
} else if (typeof props.modelValue.deleteAfter === 'number') {
|
||||||
|
expiration.value = 'after';
|
||||||
|
beautifyAfter(props.modelValue.deleteAfter / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
if (props.modelValue.deleteAt) {
|
|
||||||
expiration.value = 'at';
|
|
||||||
const deleteAt = new Date(props.modelValue.deleteAt);
|
|
||||||
atDate.value = formatDateTimeString(deleteAt, 'yyyy-MM-dd');
|
|
||||||
atTime.value = formatDateTimeString(deleteAt, 'HH:mm');
|
|
||||||
} else if (typeof props.modelValue.deleteAfter === 'number') {
|
|
||||||
expiration.value = 'after';
|
|
||||||
after.value = props.modelValue.deleteAfter / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(): DeleteScheduleEditorModelValue {
|
|
||||||
const calcAt = () => {
|
const calcAt = () => {
|
||||||
return new Date(`${atDate.value} ${atTime.value}`).getTime();
|
return new Date(`${atDate.value} ${atTime.value}`).getTime();
|
||||||
};
|
};
|
||||||
@ -88,20 +122,35 @@ function get(): DeleteScheduleEditorModelValue {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
const isValidTime = () => {
|
||||||
deleteAt: expiration.value === 'at' ? calcAt() : null,
|
if (expiration.value === 'at') {
|
||||||
deleteAfter: expiration.value === 'after' ? calcAfter() : null,
|
return calcAt() < Date.now() + (1000 * 60 * 60 * 24 * 365);
|
||||||
|
} else {
|
||||||
|
const afterMs = calcAfter();
|
||||||
|
if (afterMs === null) return false;
|
||||||
|
return afterMs < 1000 * 60 * 60 * 24 * 365;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
watch([expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), {
|
isValid.value = isValidTime();
|
||||||
deep: true,
|
watch([expiration, atDate, atTime, after, unit, isValid], () => {
|
||||||
});
|
const isValidTimeValue = isValidTime();
|
||||||
</script>
|
isValid.value = isValidTimeValue;
|
||||||
|
emit('update:modelValue', {
|
||||||
<style lang="scss" scoped>
|
deleteAt: expiration.value === 'at' ? calcAt() : null,
|
||||||
.zmdxowus {
|
deleteAfter: expiration.value === 'after' ? calcAfter() : null,
|
||||||
padding: 8px 16px;
|
isValid: isValidTimeValue,
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
deep: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" module>
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0px;
|
||||||
|
|
||||||
>span {
|
>span {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
@ -131,10 +180,7 @@ watch([expiration, atDate, atTime, after, unit], () => emit('update:modelValue',
|
|||||||
}
|
}
|
||||||
|
|
||||||
>section {
|
>section {
|
||||||
margin: 16px 0 0 0;
|
|
||||||
|
|
||||||
>div {
|
>div {
|
||||||
margin: 0 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -162,4 +208,19 @@ watch([expiration, atDate, atTime, after, unit], () => emit('update:modelValue',
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.padding {
|
||||||
|
padding: 8px 24px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 0 0 8px 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.withAccent {
|
||||||
|
color: var(--MI_THEME-accent);
|
||||||
|
}
|
||||||
|
.chevronOpening {
|
||||||
|
transform: rotateX(180deg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -185,7 +185,7 @@ const posted = ref(false);
|
|||||||
const text = ref(props.initialText ?? '');
|
const text = ref(props.initialText ?? '');
|
||||||
const files = ref(props.initialFiles ?? []);
|
const files = ref(props.initialFiles ?? []);
|
||||||
const poll = ref<PollEditorModelValue | null>(null);
|
const poll = ref<PollEditorModelValue | null>(null);
|
||||||
const scheduledNoteDelete = ref<DeleteScheduleEditorModelValue | null>(null);
|
const scheduledNoteDelete = ref<DeleteScheduleEditorModelValue | null>(defaultStore.state.defaultScheduledNoteDelete ? { deleteAt: null, deleteAfter: defaultStore.state.defaultScheduledNoteDeleteTime, isValid: true } : null);
|
||||||
const useCw = ref<boolean>(!!props.initialCw);
|
const useCw = ref<boolean>(!!props.initialCw);
|
||||||
const showPreview = ref(defaultStore.state.showPreview);
|
const showPreview = ref(defaultStore.state.showPreview);
|
||||||
watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
|
watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
|
||||||
@ -260,6 +260,7 @@ const maxTextLength = computed((): number => {
|
|||||||
|
|
||||||
const canPost = computed((): boolean => {
|
const canPost = computed((): boolean => {
|
||||||
return !props.mock && !posting.value && !posted.value &&
|
return !props.mock && !posting.value && !posted.value &&
|
||||||
|
(scheduledNoteDelete.value ? scheduledNoteDelete.value.isValid : true) &&
|
||||||
(
|
(
|
||||||
1 <= textLength.value ||
|
1 <= textLength.value ||
|
||||||
1 <= files.value.length ||
|
1 <= files.value.length ||
|
||||||
@ -433,6 +434,7 @@ function toggleScheduledNoteDelete() {
|
|||||||
scheduledNoteDelete.value = {
|
scheduledNoteDelete.value = {
|
||||||
deleteAt: null,
|
deleteAt: null,
|
||||||
deleteAfter: null,
|
deleteAfter: null,
|
||||||
|
isValid: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1074,6 +1076,7 @@ onMounted(() => {
|
|||||||
scheduledNoteDelete.value = {
|
scheduledNoteDelete.value = {
|
||||||
deleteAt: init.deleteAt ? (new Date(init.deleteAt)).getTime() : null,
|
deleteAt: init.deleteAt ? (new Date(init.deleteAt)).getTime() : null,
|
||||||
deleteAfter: null,
|
deleteAfter: null,
|
||||||
|
isValid: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (init.visibleUserIds) {
|
if (init.visibleUserIds) {
|
||||||
|
@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
|
<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
|
||||||
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
|
<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
|
||||||
|
<FormLink to="/settings/post-form">{{ i18n.ts.defaultScheduledNoteDelete }}</FormLink>
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
<template #label>{{ i18n.ts.pinnedList }}</template>
|
<template #label>{{ i18n.ts.pinnedList }}</template>
|
||||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||||
|
62
packages/frontend/src/pages/settings/post-form.vue
Normal file
62
packages/frontend/src/pages/settings/post-form.vue
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<!-- <MkSwitch v-model="disableNoteDrafting">
|
||||||
|
<template #caption>{{ i18n.ts.disableNoteDraftingDescription }}</template>
|
||||||
|
{{ i18n.ts.disableNoteDrafting }}
|
||||||
|
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
|
||||||
|
</MkSwitch> -->
|
||||||
|
<div>
|
||||||
|
<div :class="$style.label">
|
||||||
|
{{ i18n.ts.defaultScheduledNoteDeleteTime }}
|
||||||
|
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
|
||||||
|
</div>
|
||||||
|
<MkDeleteScheduleEditor v-model="scheduledNoteDelete" :afterOnly="true"/>
|
||||||
|
</div>
|
||||||
|
<MkSwitch v-model="defaultScheduledNoteDelete">
|
||||||
|
{{ i18n.ts.defaultScheduledNoteDelete }}
|
||||||
|
<span class="_beta">{{ i18n.ts.originalFeature }}</span>
|
||||||
|
</MkSwitch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||||
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
|
import MkDeleteScheduleEditor from '@/components/MkDeleteScheduleEditor.vue';
|
||||||
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
// const disableNoteDrafting = computed(defaultStore.makeGetterSetter('disableNoteDrafting'));
|
||||||
|
const defaultScheduledNoteDelete = computed(defaultStore.makeGetterSetter('defaultScheduledNoteDelete'));
|
||||||
|
const scheduledNoteDelete = ref({ deleteAt: null, deleteAfter: defaultStore.state.defaultScheduledNoteDeleteTime, isValid: true });
|
||||||
|
watch(scheduledNoteDelete, () => {
|
||||||
|
if (!scheduledNoteDelete.value.isValid) return;
|
||||||
|
defaultStore.set('defaultScheduledNoteDeleteTime', scheduledNoteDelete.value.deleteAfter);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" module>
|
||||||
|
.items {
|
||||||
|
padding: 8px;
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
|
||||||
|
grid-auto-rows: 40px;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
&:hover {
|
||||||
|
background: var(--X5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 0 0 8px 0;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
</style>
|
@ -116,6 +116,10 @@ const routes: RouteDef[] = [{
|
|||||||
path: '/navbar',
|
path: '/navbar',
|
||||||
name: 'navbar',
|
name: 'navbar',
|
||||||
component: page(() => import('@/pages/settings/navbar.vue')),
|
component: page(() => import('@/pages/settings/navbar.vue')),
|
||||||
|
}, {
|
||||||
|
path: '/post-form',
|
||||||
|
name: 'post-form',
|
||||||
|
component: page(() => import('@/pages/settings/post-form.vue')),
|
||||||
}, {
|
}, {
|
||||||
path: '/statusbar',
|
path: '/statusbar',
|
||||||
name: 'statusbar',
|
name: 'statusbar',
|
||||||
|
@ -117,6 +117,14 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
where: 'account',
|
where: 'account',
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
defaultScheduledNoteDelete: {
|
||||||
|
where: 'account',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
defaultScheduledNoteDeleteTime: {
|
||||||
|
where: 'account',
|
||||||
|
default: 86400000,
|
||||||
|
},
|
||||||
uploadFolder: {
|
uploadFolder: {
|
||||||
where: 'account',
|
where: 'account',
|
||||||
default: null as string | null,
|
default: null as string | null,
|
||||||
@ -569,10 +577,6 @@ export const defaultStore = markRaw(new Storage('base', {
|
|||||||
where: 'device',
|
where: 'device',
|
||||||
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore,
|
||||||
},
|
},
|
||||||
searchEngine: {
|
|
||||||
where: 'device',
|
|
||||||
default: 'https://google.com/search?q=',
|
|
||||||
},
|
|
||||||
reactionChecksMuting: {
|
reactionChecksMuting: {
|
||||||
where: 'device',
|
where: 'device',
|
||||||
default: true,
|
default: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user