153 lines
4.1 KiB
Vue
153 lines
4.1 KiB
Vue
<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>(1);
|
|
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>
|