Add form to create events in UI
This commit is contained in:
parent
07b3f19814
commit
8def7af701
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<div class="zmdxowut">
|
||||
<MkInput v-model="title" small type="text" class="input">
|
||||
<template #label>*{{ "Title" }}</template>
|
||||
</MkInput>
|
||||
<section>
|
||||
<div>
|
||||
<section>
|
||||
<MkInput v-model="startDate" small type="date" class="input">
|
||||
<template #label>*{{ "Start Date" }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="startTime" small type="time" class="input">
|
||||
<template #label>*{{ "Start Time" }}</template>
|
||||
</MkInput>
|
||||
</section>
|
||||
<section>
|
||||
<MkInput v-model="endDate" small type="date" class="input">
|
||||
<template #label>{{ "End Date" }}</template>
|
||||
</MkInput>
|
||||
<MkInput v-model="endTime" small type="time" class="input">
|
||||
<template #label>{{ "End Time" }}</template>
|
||||
</MkInput>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<p>Details</p>
|
||||
<ul>
|
||||
<li v-for="(entry, i) in meta" :key="i">
|
||||
<MkInput class="input" small :model-value="entry[0]" placeholder="placeholder key"
|
||||
@update:model-value="onKeyInput(i, $event)">
|
||||
</MkInput>
|
||||
<MkInput class="input" small :model-value="entry[1]" placeholder="placeholder value"
|
||||
@update:model-value="onValueInput(i, $event)">
|
||||
</MkInput>
|
||||
<button class="_button" @click="remove(i)">
|
||||
<i class="ti ti-x"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<MkButton class="add" @click="add">{{ i18n.ts.add }}</MkButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Ref, ref, watch } from 'vue';
|
||||
import MkInput from './MkInput.vue';
|
||||
import MkButton from './MkButton.vue';
|
||||
import { formatDateTimeString } from '@/scripts/format-time-string';
|
||||
import { addTime } from '@/scripts/time';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: {
|
||||
title: string,
|
||||
start: string,
|
||||
end: string | null,
|
||||
metadata: Record<string, string>,
|
||||
}
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', v: {
|
||||
title: string,
|
||||
start: string,
|
||||
end: string | null,
|
||||
metadata: Record<string, string>,
|
||||
})
|
||||
}>();
|
||||
|
||||
const title = ref(props.modelValue.title);
|
||||
const startDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd'));
|
||||
const startTime = ref('00:00');
|
||||
const endDate = ref('');
|
||||
const endTime = ref('');
|
||||
const meta = ref(Object.entries(props.modelValue.metadata));
|
||||
|
||||
function add() {
|
||||
meta.value.push(['', '']);
|
||||
}
|
||||
|
||||
function onKeyInput(i, newKey) {
|
||||
meta.value[i][0] = newKey;
|
||||
}
|
||||
|
||||
function onValueInput(i, value) {
|
||||
meta.value[i][1] = value;
|
||||
}
|
||||
|
||||
function remove(i) {
|
||||
meta.value.splice(i, 1);
|
||||
}
|
||||
|
||||
function get() {
|
||||
const calcAt = (date: Ref<string>, time: Ref<string>): number => (new Date(`${date.value} ${time.value}`)).getTime();
|
||||
|
||||
return {
|
||||
title: title.value,
|
||||
start: calcAt(startDate, startTime),
|
||||
end: endDate.value ? calcAt(endDate, endTime) : null,
|
||||
metadata: meta.value.reduce((obj, [k, v]) => ({ ...obj, [k]: v }), {}),
|
||||
};
|
||||
}
|
||||
|
||||
watch([title, startDate, startTime, endDate, endTime, meta], () => emit('update:modelValue', get()), {
|
||||
deep: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zmdxowut {
|
||||
padding: 8px 16px;
|
||||
|
||||
>.caution {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 0.8em;
|
||||
color: #f00;
|
||||
|
||||
>i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
>ul {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
|
||||
>li {
|
||||
display: flex;
|
||||
margin: 8px 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
|
||||
>.input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
>button {
|
||||
width: 32px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.add {
|
||||
margin: 8px 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
>section {
|
||||
margin: 16px 0 0 0;
|
||||
|
||||
>div {
|
||||
margin: 0 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
|
||||
&:last-child {
|
||||
flex: 1 0 auto;
|
||||
|
||||
>div {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
>section {
|
||||
// MAGIC: Prevent div above from growing unless wrapped to its own line
|
||||
flex-grow: 9999;
|
||||
align-items: end;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
>.input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -49,6 +49,7 @@
|
|||
<MkNoteSimple v-if="reply" :class="$style.targetNote" :note="reply"/>
|
||||
<MkNoteSimple v-if="renote" :class="$style.targetNote" :note="renote"/>
|
||||
<div v-if="quoteId" :class="$style.withQuote"><i class="ti ti-quote"></i> {{ i18n.ts.quoteAttached }}<button @click="quoteId = null"><i class="ti ti-x"></i></button></div>
|
||||
<MkEventEditor v-if="event" v-model="event" @destroyed="event = null"/>
|
||||
<div v-if="visibility === 'specified'" :class="$style.toSpecified">
|
||||
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
|
||||
<div :class="$style.visibleUsers">
|
||||
|
@ -75,6 +76,7 @@
|
|||
<div :class="$style.footerLeft">
|
||||
<button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
|
||||
<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
|
||||
<button v-tooltip="i18n.ts.event" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: event }]" @click="toggleEvent"><i class="ti ti-calendar"></i></button>
|
||||
<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
|
||||
<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
|
||||
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
|
||||
|
@ -103,6 +105,7 @@ import MkNoteSimple from '@/components/MkNoteSimple.vue';
|
|||
import MkNotePreview from '@/components/MkNotePreview.vue';
|
||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||
import MkPollEditor from '@/components/MkPollEditor.vue';
|
||||
import MkEventEditor from '@/components/MkEventEditor.vue';
|
||||
import { host, url } from '@/config';
|
||||
import { erase, unique } from '@/scripts/array';
|
||||
import { extractMentions } from '@/scripts/extract-mentions';
|
||||
|
@ -165,6 +168,12 @@ let poll = $ref<{
|
|||
expiresAt: string | null;
|
||||
expiredAfter: string | null;
|
||||
} | null>(null);
|
||||
let event = $ref<{
|
||||
title: string;
|
||||
start: string;
|
||||
end: string | null;
|
||||
metadata: Record<string, string>;
|
||||
} | null>(null);
|
||||
let useCw = $ref(false);
|
||||
let showPreview = $ref(false);
|
||||
let cw = $ref<string | null>(null);
|
||||
|
@ -235,7 +244,7 @@ const maxTextLength = $computed((): number => {
|
|||
|
||||
const canPost = $computed((): boolean => {
|
||||
return !posting && !posted &&
|
||||
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
|
||||
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote || !!event) &&
|
||||
(textLength <= maxTextLength) &&
|
||||
(!poll || poll.choices.length >= 2);
|
||||
});
|
||||
|
@ -331,6 +340,7 @@ function watchForDraft() {
|
|||
watch($$(useCw), () => saveDraft());
|
||||
watch($$(cw), () => saveDraft());
|
||||
watch($$(poll), () => saveDraft());
|
||||
watch($$(event), () => saveDraft());
|
||||
watch($$(files), () => saveDraft(), { deep: true });
|
||||
watch($$(visibility), () => saveDraft());
|
||||
watch($$(localOnly), () => saveDraft());
|
||||
|
@ -375,6 +385,19 @@ function togglePoll() {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleEvent() {
|
||||
if (event) {
|
||||
event = null;
|
||||
} else {
|
||||
event = {
|
||||
title: '',
|
||||
start: (new Date()).toString(),
|
||||
end: null,
|
||||
metadata: {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function addTag(tag: string) {
|
||||
insertTextAtCursor(textareaEl, ` #${tag} `);
|
||||
}
|
||||
|
@ -513,6 +536,7 @@ function clear() {
|
|||
text = '';
|
||||
files = [];
|
||||
poll = null;
|
||||
event = null;
|
||||
quoteId = null;
|
||||
}
|
||||
|
||||
|
@ -626,6 +650,7 @@ function saveDraft() {
|
|||
localOnly: localOnly,
|
||||
files: files,
|
||||
poll: poll,
|
||||
event: event,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -687,6 +712,7 @@ async function post(ev?: MouseEvent) {
|
|||
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
|
||||
channelId: props.channel ? props.channel.id : undefined,
|
||||
poll: poll,
|
||||
event: event,
|
||||
cw: useCw ? cw ?? '' : undefined,
|
||||
localOnly: localOnly,
|
||||
visibility: visibility,
|
||||
|
@ -847,6 +873,9 @@ onMounted(() => {
|
|||
if (draft.data.poll) {
|
||||
poll = draft.data.poll;
|
||||
}
|
||||
if (draft.data.event) {
|
||||
event = draft.data.event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -865,6 +894,14 @@ onMounted(() => {
|
|||
expiredAfter: init.poll.expiredAfter,
|
||||
};
|
||||
}
|
||||
if (init.event) {
|
||||
event = {
|
||||
title: init.event.title,
|
||||
start: init.event.start,
|
||||
end: init.event.end,
|
||||
metadata: init.event.metadata,
|
||||
};
|
||||
}
|
||||
visibility = init.visibility;
|
||||
localOnly = init.localOnly;
|
||||
quoteId = init.renote ? init.renote.id : null;
|
||||
|
|
Loading…
Reference in New Issue