generated from corrad-software/corrad-af-2024
221 lines
6.0 KiB
Vue
221 lines
6.0 KiB
Vue
<template>
|
|
<div class="document-viewer w-full h-full bg-white dark:bg-gray-900 relative">
|
|
<!-- Toolbar -->
|
|
<div v-if="mode === 'edit'" class="border-b border-gray-200 dark:border-gray-700 p-2 bg-gray-50 dark:bg-gray-800">
|
|
<div class="flex items-center space-x-2">
|
|
<!-- Formatting Tools -->
|
|
<div class="flex items-center space-x-1 border-r border-gray-300 dark:border-gray-600 pr-2">
|
|
<button
|
|
@click="execCommand('bold')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Bold"
|
|
>
|
|
<Icon name="mdi:format-bold" class="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
@click="execCommand('italic')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Italic"
|
|
>
|
|
<Icon name="mdi:format-italic" class="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
@click="execCommand('underline')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Underline"
|
|
>
|
|
<Icon name="mdi:format-underline" class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Alignment -->
|
|
<div class="flex items-center space-x-1 border-r border-gray-300 dark:border-gray-600 pr-2">
|
|
<button
|
|
@click="execCommand('justifyLeft')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Align Left"
|
|
>
|
|
<Icon name="mdi:format-align-left" class="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
@click="execCommand('justifyCenter')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Align Center"
|
|
>
|
|
<Icon name="mdi:format-align-center" class="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
@click="execCommand('justifyRight')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Align Right"
|
|
>
|
|
<Icon name="mdi:format-align-right" class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Lists -->
|
|
<div class="flex items-center space-x-1">
|
|
<button
|
|
@click="execCommand('insertUnorderedList')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Bullet List"
|
|
>
|
|
<Icon name="mdi:format-list-bulleted" class="w-4 h-4" />
|
|
</button>
|
|
<button
|
|
@click="execCommand('insertOrderedList')"
|
|
class="p-1.5 rounded hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
title="Numbered List"
|
|
>
|
|
<Icon name="mdi:format-list-numbered" class="w-4 h-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Document Content -->
|
|
<div class="flex-1 overflow-auto p-8">
|
|
<div class="max-w-4xl mx-auto bg-white shadow-lg min-h-full">
|
|
<!-- Document Header -->
|
|
<div class="p-8 border-b border-gray-200">
|
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">{{ document.name }}</h1>
|
|
<div class="text-sm text-gray-500">
|
|
Last modified: {{ formatDate(document.lastModified) }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Editable Content -->
|
|
<div
|
|
ref="contentEditor"
|
|
:contenteditable="mode === 'edit'"
|
|
@input="handleContentChange"
|
|
@keydown="handleKeydown"
|
|
class="p-8 min-h-96 focus:outline-none"
|
|
:class="{
|
|
'cursor-text': mode === 'edit',
|
|
'select-text': mode === 'view'
|
|
}"
|
|
v-html="content"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Button (Edit Mode) -->
|
|
<div v-if="mode === 'edit'" class="absolute bottom-4 right-4">
|
|
<button
|
|
@click="saveDocument"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 flex items-center space-x-2"
|
|
>
|
|
<Icon name="mdi:content-save" class="w-4 h-4" />
|
|
<span>Save</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, onMounted, nextTick } from 'vue';
|
|
|
|
const props = defineProps({
|
|
document: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
mode: {
|
|
type: String,
|
|
default: 'view' // 'view' | 'edit'
|
|
},
|
|
content: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'content-changed',
|
|
'save-requested'
|
|
]);
|
|
|
|
const contentEditor = ref(null);
|
|
|
|
const execCommand = (command, value = null) => {
|
|
document.execCommand(command, false, value);
|
|
handleContentChange();
|
|
};
|
|
|
|
const handleContentChange = () => {
|
|
if (contentEditor.value) {
|
|
emit('content-changed', contentEditor.value.innerHTML);
|
|
}
|
|
};
|
|
|
|
const handleKeydown = (event) => {
|
|
if (event.ctrlKey && event.key === 's') {
|
|
event.preventDefault();
|
|
saveDocument();
|
|
}
|
|
};
|
|
|
|
const saveDocument = () => {
|
|
emit('save-requested');
|
|
};
|
|
|
|
const formatDate = (dateString) => {
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
};
|
|
|
|
// Watch for content changes
|
|
watch(() => props.content, (newContent) => {
|
|
if (contentEditor.value && contentEditor.value.innerHTML !== newContent) {
|
|
contentEditor.value.innerHTML = newContent;
|
|
}
|
|
});
|
|
|
|
onMounted(() => {
|
|
if (contentEditor.value && props.content) {
|
|
contentEditor.value.innerHTML = props.content;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.document-viewer {
|
|
font-family: 'Times New Roman', serif;
|
|
}
|
|
|
|
:deep(.content-editor) {
|
|
line-height: 1.6;
|
|
}
|
|
|
|
:deep(.content-editor h1) {
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
:deep(.content-editor h2) {
|
|
font-size: 1.5rem;
|
|
font-weight: bold;
|
|
margin: 0.8rem 0;
|
|
}
|
|
|
|
:deep(.content-editor h3) {
|
|
font-size: 1.25rem;
|
|
font-weight: bold;
|
|
margin: 0.6rem 0;
|
|
}
|
|
|
|
:deep(.content-editor p) {
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
:deep(.content-editor ul, .content-editor ol) {
|
|
margin: 0.5rem 0;
|
|
padding-left: 2rem;
|
|
}
|
|
|
|
:deep(.content-editor li) {
|
|
margin: 0.25rem 0;
|
|
}
|
|
</style> |