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="reply" :class="$style.targetNote" :note="reply"/>
|
||||||
<MkNoteSimple v-if="renote" :class="$style.targetNote" :note="renote"/>
|
<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>
|
<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">
|
<div v-if="visibility === 'specified'" :class="$style.toSpecified">
|
||||||
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
|
<span style="margin-right: 8px;">{{ i18n.ts.recipient }}</span>
|
||||||
<div :class="$style.visibleUsers">
|
<div :class="$style.visibleUsers">
|
||||||
|
@ -75,6 +76,7 @@
|
||||||
<div :class="$style.footerLeft">
|
<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.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.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.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.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>
|
<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 MkNotePreview from '@/components/MkNotePreview.vue';
|
||||||
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
|
||||||
import MkPollEditor from '@/components/MkPollEditor.vue';
|
import MkPollEditor from '@/components/MkPollEditor.vue';
|
||||||
|
import MkEventEditor from '@/components/MkEventEditor.vue';
|
||||||
import { host, url } from '@/config';
|
import { host, url } from '@/config';
|
||||||
import { erase, unique } from '@/scripts/array';
|
import { erase, unique } from '@/scripts/array';
|
||||||
import { extractMentions } from '@/scripts/extract-mentions';
|
import { extractMentions } from '@/scripts/extract-mentions';
|
||||||
|
@ -165,6 +168,12 @@ let poll = $ref<{
|
||||||
expiresAt: string | null;
|
expiresAt: string | null;
|
||||||
expiredAfter: string | null;
|
expiredAfter: string | null;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
let event = $ref<{
|
||||||
|
title: string;
|
||||||
|
start: string;
|
||||||
|
end: string | null;
|
||||||
|
metadata: Record<string, string>;
|
||||||
|
} | null>(null);
|
||||||
let useCw = $ref(false);
|
let useCw = $ref(false);
|
||||||
let showPreview = $ref(false);
|
let showPreview = $ref(false);
|
||||||
let cw = $ref<string | null>(null);
|
let cw = $ref<string | null>(null);
|
||||||
|
@ -235,7 +244,7 @@ const maxTextLength = $computed((): number => {
|
||||||
|
|
||||||
const canPost = $computed((): boolean => {
|
const canPost = $computed((): boolean => {
|
||||||
return !posting && !posted &&
|
return !posting && !posted &&
|
||||||
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
|
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote || !!event) &&
|
||||||
(textLength <= maxTextLength) &&
|
(textLength <= maxTextLength) &&
|
||||||
(!poll || poll.choices.length >= 2);
|
(!poll || poll.choices.length >= 2);
|
||||||
});
|
});
|
||||||
|
@ -331,6 +340,7 @@ function watchForDraft() {
|
||||||
watch($$(useCw), () => saveDraft());
|
watch($$(useCw), () => saveDraft());
|
||||||
watch($$(cw), () => saveDraft());
|
watch($$(cw), () => saveDraft());
|
||||||
watch($$(poll), () => saveDraft());
|
watch($$(poll), () => saveDraft());
|
||||||
|
watch($$(event), () => saveDraft());
|
||||||
watch($$(files), () => saveDraft(), { deep: true });
|
watch($$(files), () => saveDraft(), { deep: true });
|
||||||
watch($$(visibility), () => saveDraft());
|
watch($$(visibility), () => saveDraft());
|
||||||
watch($$(localOnly), () => 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) {
|
function addTag(tag: string) {
|
||||||
insertTextAtCursor(textareaEl, ` #${tag} `);
|
insertTextAtCursor(textareaEl, ` #${tag} `);
|
||||||
}
|
}
|
||||||
|
@ -513,6 +536,7 @@ function clear() {
|
||||||
text = '';
|
text = '';
|
||||||
files = [];
|
files = [];
|
||||||
poll = null;
|
poll = null;
|
||||||
|
event = null;
|
||||||
quoteId = null;
|
quoteId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,6 +650,7 @@ function saveDraft() {
|
||||||
localOnly: localOnly,
|
localOnly: localOnly,
|
||||||
files: files,
|
files: files,
|
||||||
poll: poll,
|
poll: poll,
|
||||||
|
event: event,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -687,6 +712,7 @@ async function post(ev?: MouseEvent) {
|
||||||
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
|
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
|
||||||
channelId: props.channel ? props.channel.id : undefined,
|
channelId: props.channel ? props.channel.id : undefined,
|
||||||
poll: poll,
|
poll: poll,
|
||||||
|
event: event,
|
||||||
cw: useCw ? cw ?? '' : undefined,
|
cw: useCw ? cw ?? '' : undefined,
|
||||||
localOnly: localOnly,
|
localOnly: localOnly,
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
|
@ -847,6 +873,9 @@ onMounted(() => {
|
||||||
if (draft.data.poll) {
|
if (draft.data.poll) {
|
||||||
poll = draft.data.poll;
|
poll = draft.data.poll;
|
||||||
}
|
}
|
||||||
|
if (draft.data.event) {
|
||||||
|
event = draft.data.event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,6 +894,14 @@ onMounted(() => {
|
||||||
expiredAfter: init.poll.expiredAfter,
|
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;
|
visibility = init.visibility;
|
||||||
localOnly = init.localOnly;
|
localOnly = init.localOnly;
|
||||||
quoteId = init.renote ? init.renote.id : null;
|
quoteId = init.renote ? init.renote.id : null;
|
||||||
|
|
Loading…
Reference in New Issue