basic text editor
This commit is contained in:
152
frontend/components/viewers/TextEditor.vue
Normal file
152
frontend/components/viewers/TextEditor.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useTextEditor } from '~/stores/viewers';
|
||||
|
||||
const editor = useTextEditor();
|
||||
const textarea = ref<HTMLTextAreaElement>(
|
||||
null as unknown as HTMLTextAreaElement
|
||||
);
|
||||
|
||||
const currentLine = ref<number>(0);
|
||||
const totalLines = computed(
|
||||
() => editor.data?.editedContent.split('\n').length ?? 0
|
||||
);
|
||||
|
||||
function onOpenUpdate(state: boolean) {
|
||||
if (!state) {
|
||||
editor.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
if (editor.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = editor.data.entry.name;
|
||||
|
||||
const success = await editor.save();
|
||||
|
||||
if (success) {
|
||||
toast.success('Save', {
|
||||
id: 'TEXT_EDITOR_SAVE_TOAST',
|
||||
description: `Successfully saved ${name}`,
|
||||
});
|
||||
} else {
|
||||
toast.error('Save', {
|
||||
id: 'TEXT_EDITOR_SAVE_TOAST',
|
||||
description: `Failed to save ${name}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onEditorKeyDown(e: KeyboardEvent) {
|
||||
if (editor.saving) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateCurrentLine);
|
||||
|
||||
if (!e.ctrlKey || e.key !== 's') {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
saveFile();
|
||||
}
|
||||
|
||||
function updateCurrentLine() {
|
||||
if (editor.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentLine.value = editor.data.editedContent
|
||||
.substring(0, textarea.value.selectionStart)
|
||||
.split('\n').length;
|
||||
}
|
||||
|
||||
function onEditorClick() {
|
||||
requestAnimationFrame(updateCurrentLine);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
requestAnimationFrame(updateCurrentLine);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog :open="editor.data != null" @update:open="onOpenUpdate">
|
||||
<DialogContent
|
||||
v-if="editor.data"
|
||||
class="flex h-full max-h-[min(98vh,700px)] w-full !max-w-[min(98vw,1000px)] flex-col"
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ editor.data.entry.name }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="editor.data.content == null">
|
||||
<span class="text-muted-foreground animate-pulse"
|
||||
>Loading content...</span
|
||||
>
|
||||
</div>
|
||||
<div v-else class="flex h-full w-full flex-col gap-2">
|
||||
<div
|
||||
class="flex h-fit w-full flex-row items-center justify-end gap-1"
|
||||
>
|
||||
<Button
|
||||
title="Reset"
|
||||
variant="destructive"
|
||||
size="icon"
|
||||
:disabled="
|
||||
editor.data.content === editor.data.editedContent ||
|
||||
editor.saving
|
||||
"
|
||||
@click="() => editor.discardEdits()"
|
||||
>
|
||||
<Icon name="lucide:bomb" />
|
||||
</Button>
|
||||
|
||||
<Separator orientation="vertical" class="mx-1" />
|
||||
|
||||
<Button
|
||||
title="Save"
|
||||
size="icon"
|
||||
:disabled="
|
||||
editor.saving ||
|
||||
editor.data.content === editor.data.editedContent
|
||||
"
|
||||
@click="saveFile"
|
||||
>
|
||||
<Icon name="lucide:save" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
id="editor"
|
||||
ref="textarea"
|
||||
v-model="editor.data.editedContent"
|
||||
class="h-full w-full resize-none overflow-auto rounded-md border p-4 whitespace-pre focus:outline-0"
|
||||
@keydown="onEditorKeyDown"
|
||||
@click="onEditorClick"
|
||||
></textarea>
|
||||
|
||||
<div class="flex w-full flex-row justify-end">
|
||||
<p class="text-muted-foreground">
|
||||
Line {{ currentLine }} / {{ totalLines }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#editor {
|
||||
scrollbar-width: 0 !important;
|
||||
}
|
||||
|
||||
#editor::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user