refactor: ♻️ Redesign post composer

This commit is contained in:
Jesse Wierzbinski 2025-12-09 22:12:23 +01:00
parent ef7475aead
commit 7ff9d2302a
No known key found for this signature in database
17 changed files with 327 additions and 203 deletions

View file

@ -3,29 +3,94 @@
<Note :note="relation.note" :hide-actions="true" :small-layout="true" />
</div>
<ContentWarning v-if="store.sensitive" v-model="store.contentWarning" />
<EditorContent @paste-files="uploadFiles" v-model:content="store.content" v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()"
class="[&>.tiptap]:!border-none [&>.tiptap]:!ring-0 [&>.tiptap]:!outline-none [&>.tiptap]:rounded-none p-0 [&>.tiptap]:max-h-[50dvh] [&>.tiptap]:overflow-y-auto [&>.tiptap]:min-h-48 [&>.tiptap]:!ring-offset-0 [&>.tiptap]:h-full"
:disabled="store.sending" :mode="store.contentType === 'text/html' ? 'rich' : 'plain'" />
<InputGroup class="p-1">
<InputGroupAddon v-if="store.sensitive" align="block-start" class="pt-3">
<Input v-model:model-value="store.contentWarning" placeholder="Put your content warning here" />
</InputGroupAddon>
<div class="w-full flex flex-row gap-2 overflow-x-auto *:shrink-0 pb-2">
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple />
<Files v-model:files="store.files" :composer-key="composerKey" />
</div>
<EditorContent data-slot="input-group-control" @paste-files="uploadFiles" v-model:content="store.content"
v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()"
class=" placeholder:text-muted-foreground flex field-sizing-content min-h-58 w-full px-4 text-base disabled:opacity-50 md:text-sm flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none"
:disabled="store.sending" :mode="store.contentType === 'text/html' ? 'rich' : 'plain'" />
<DialogFooter class="items-center flex-row overflow-x-auto">
<ComposerButtons @submit="send" @pick-file="fileInput?.click()" v-model:content-type="store.contentType" v-model:sensitive="store.sensitive" v-model:visibility="store.visibility" :relation="store.relation" :sending="store.sending" :can-send="store.canSend" :raw-content="store.rawContent" />
</DialogFooter>
<InputGroupAddon v-if="store.files.length > 0" align="block-end" class="overflow-x-auto *:shrink-0">
<Files v-model:files="store.files" :composer-key="composerKey" />
</InputGroupAddon>
<InputGroupAddon align="block-end">
<Select v-model:model-value="store.contentType">
<SelectTrigger as-child disable-default-classes disable-select-icon>
<InputGroupButton variant="ghost" size="icon-sm">
<LetterText v-if="store.contentType === 'text/html'" />
<Type v-else />
</InputGroupButton>
</SelectTrigger>
<SelectContent>
<SelectItem value="text/plain">
Plain text
</SelectItem>
<SelectItem value="text/html">
Rich text
</SelectItem>
</SelectContent>
</Select>
<VisibilityPicker v-model:visibility="store.visibility">
<InputGroupButton variant="ghost" size="icon-sm" :disabled="store.relation?.type === 'edit'">
<component :is="visibilities[store.visibility].icon" />
</InputGroupButton>
</VisibilityPicker>
<InputGroupButton variant="ghost" size="icon-sm" @click="fileInput?.click()">
<FilePlus2 />
</InputGroupButton>
<Toggle size="sm" v-model="store.sensitive">
<TriangleAlert />
</Toggle>
<InputGroupText :class="['ml-auto', charactersLeft < 0 && 'text-destructive']">
{{ charactersLeft.toLocaleString(getLocale(), {
maximumFractionDigits: 2,
notation: 'compact',
compactDisplay: 'short',
}) }}
</InputGroupText>
<Separator orientation="vertical" class="h-4!" />
<InputGroupButton variant="default" size="icon-sm" :disabled="store.sending || !store.canSend"
@click="send">
<Spinner v-if="store.sending" />
<ArrowUp v-else />
<span class="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple />
</template>
<script lang="ts" setup>
import {
ArrowUp,
FilePlus2,
LetterText,
TriangleAlert,
Type,
} from "lucide-vue-next";
import { Separator } from "reka-ui";
import Note from "~/components/notes/note.vue";
import { getLocale } from "~~/paraglide/runtime";
import EditorContent from "../editor/content.vue";
import { DialogFooter } from "../ui/dialog";
import ComposerButtons from "./buttons.vue";
import ContentWarning from "./content-warning.vue";
import { Input } from "../ui/input";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
} from "../ui/input-group";
import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
import { Spinner } from "../ui/spinner";
import { Toggle } from "../ui/toggle";
import Files from "./files.vue";
import { visibilities } from "./visibilities";
import VisibilityPicker from "./visibility-picker.vue";
const props = defineProps<{
relation?: ComposerState["relation"];
@ -38,6 +103,12 @@ const composerKey = props.relation
? (`${props.relation.type}-${props.relation.note.id}` as const)
: "blank";
const store = useComposerStore(composerKey)();
const authStore = useAuthStore();
const charactersLeft = computed(() => {
const max = authStore.instance?.configuration.statuses.max_characters ?? 0;
return max - store.rawContent.length;
});
watch([Control_Enter, Command_Enter], () => {
if (store.sending || !preferences.ctrl_enter_send.value) {