245 lines
6.0 KiB
Vue
245 lines
6.0 KiB
Vue
<script setup>
|
|
import { onMounted, onUnmounted, ref, createApp } from 'vue';
|
|
import { GetWidget, ParseMarkdown } from '~/services/Marker';
|
|
import Server from '~/services/Server';
|
|
import { DeleteNote, FetchCampaignNotes } from '~/services/Content';
|
|
|
|
// import { GetNote, GetContent } from '@/services/Content';
|
|
const props = defineProps(['text', 'title', 'noteKey']);
|
|
const noteContent = ref(null); // Markdown text
|
|
|
|
const sourceText = ref(''); // Original markdown source, used for editing
|
|
const displayText = ref(''); // Compiled HTML from markdown
|
|
|
|
const editingMode = ref(false);
|
|
const title = ref(props.title);
|
|
const displayTitle = ref('');
|
|
|
|
function closeNote(){
|
|
DeleteNote(props.noteKey);
|
|
}
|
|
|
|
const compiledMarkdown = computed(() => {
|
|
return ParseMarkdown(sourceText.value);
|
|
});
|
|
|
|
function mountComponents() {
|
|
// Should no need more
|
|
const widget_types = ['display', 'inline', 'link'];
|
|
widget_types.forEach((widget_type) => {
|
|
const nodes = document.querySelectorAll('.vue-component-' + widget_type);
|
|
nodes.forEach(el => {
|
|
const app = createApp(GetWidget(widget_type, el.dataset.component), { content: el.dataset.content });
|
|
app.mount(el);
|
|
});
|
|
});
|
|
}
|
|
///
|
|
|
|
function update(){
|
|
displayText.value = compiledMarkdown.value;
|
|
setTimeout(() => {
|
|
setupCallout()
|
|
mountComponents();
|
|
}, 0);
|
|
}
|
|
|
|
watch(sourceText, (newText) => {
|
|
// update();
|
|
});
|
|
|
|
onMounted(() => {
|
|
sourceText.value = props.text;
|
|
title.value = props.title;
|
|
displayTitle.value = props.title;
|
|
// window.addEventListener('keydown', handleKeydown);
|
|
setTimeout(() => setupCallout(), 0);
|
|
update();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
// window.removeEventListener('keydown', handleKeydown);
|
|
});
|
|
|
|
function handleKeydown(e) {
|
|
// Check for Ctrl + E (or Cmd + E on Mac)
|
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'e') {
|
|
e.preventDefault(); // prevent browser default behavior
|
|
editingMode.value = !editingMode.value;
|
|
if(!editingMode.value){
|
|
update();
|
|
SaveNote(); // Save when switching to display mode
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
|
|
e.preventDefault(); // prevent browser default behavior
|
|
// Save the note (you can emit an event or call a method here)
|
|
SaveNote();
|
|
return;
|
|
}
|
|
}
|
|
|
|
function SaveNote(){
|
|
Server().post('/note/update', {
|
|
id: props.noteKey,
|
|
content: sourceText.value,
|
|
title: title.value,
|
|
}).then((response) => {
|
|
if(response.data.status !== 'ok'){
|
|
// Handle error (e.g., show a notification)
|
|
return;
|
|
}
|
|
// DisplayToast('green', "Note saved successfully.", 500);
|
|
FetchCampaignNotes();
|
|
}).catch((error) => {
|
|
// Handle error (e.g., show a notification)
|
|
});
|
|
}
|
|
|
|
function toggleCallout() {
|
|
const outerBlock = this.parentElement;
|
|
outerBlock.classList.toggle("is-collapsed")
|
|
const collapsed = outerBlock.classList.contains("is-collapsed")
|
|
const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
|
|
outerBlock.style.maxHeight = height + "px"
|
|
|
|
// walk and adjust height of all parents
|
|
let current = outerBlock
|
|
let parent = outerBlock.parentElement
|
|
while (parent) {
|
|
if (!parent.classList.contains("callout")) {
|
|
return
|
|
}
|
|
|
|
const collapsed = parent.classList.contains("is-collapsed")
|
|
const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
|
|
parent.style.maxHeight = height + "px"
|
|
|
|
current = parent
|
|
parent = parent.parentElement
|
|
}
|
|
}
|
|
|
|
function setupCallout() {
|
|
if (!noteContent.value) {
|
|
return;
|
|
}
|
|
|
|
const collapsible = noteContent.value.getElementsByClassName(
|
|
`callout is-collapsible`,
|
|
);
|
|
for (const div of collapsible) {
|
|
const title = div.firstElementChild;
|
|
|
|
if (title) {
|
|
title.addEventListener("click", toggleCallout)
|
|
|
|
const collapsed = div.classList.contains("is-collapsed")
|
|
const height = collapsed ? title.scrollHeight : div.scrollHeight
|
|
div.style.maxHeight = height + "px"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
const editTitle = (e) => {
|
|
title.value = e.target.innerText;
|
|
SaveNote();
|
|
}
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="note" @keydown="handleKeydown" tabindex="0">
|
|
<div class="note-stunt">
|
|
<div class="close-button" v-on:click="closeNote">
|
|
<img class="icon" src="/icons/Pixelarticons/white/close.svg" alt="My Happy SVG"/>
|
|
</div>
|
|
<span>{{ title }}</span>
|
|
</div>
|
|
<div class="note-content-container">
|
|
<textarea v-model="sourceText" class="full-editor" v-if="editingMode"></textarea>
|
|
<div v-else class="note-content" ref="noteContent">
|
|
<h1 contenteditable="true" ref="editableTitle" @input="editTitle">{{ displayTitle }}</h1>
|
|
<div ref="noteContent" v-html="displayText"></div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.note-stunt {
|
|
writing-mode: vertical-lr;
|
|
position: sticky;
|
|
top: 0px;
|
|
left: 0px;
|
|
bottom: 0px;
|
|
padding: 10px;
|
|
display: flex;
|
|
user-select: none;
|
|
}
|
|
|
|
.full-editor {
|
|
width: 100%;
|
|
height: 100%;
|
|
box-sizing: border-box;
|
|
|
|
border: none;
|
|
outline: none;
|
|
resize: none;
|
|
|
|
padding: 20px;
|
|
font-size: 16px;
|
|
font-family: monospace; /* optional, gives document/editor feel */
|
|
|
|
padding-bottom: 400px; /* Small bottom margin */
|
|
}
|
|
|
|
.close-button {
|
|
height: 20px;
|
|
width: 20px;
|
|
margin-bottom: 5px;
|
|
display: flex;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
filter: invert(var(--color-icon-invert));
|
|
}
|
|
|
|
.note {
|
|
min-width: 700px;
|
|
max-width: 700px;
|
|
overflow-y: auto;
|
|
|
|
border-color: var(--color-note-border);
|
|
border-width: 0px;
|
|
border-right-width: 1px;
|
|
border-style: solid;
|
|
display: flex;
|
|
|
|
background-color: var(--color-background);
|
|
position: sticky;
|
|
top: 0px;
|
|
}
|
|
|
|
/* Contingut de cada nota */
|
|
.note-content {
|
|
padding-bottom: 400px;
|
|
overflow-y: auto;
|
|
max-width: 600px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.note-content-container {
|
|
width: 100%;
|
|
}
|
|
|
|
.note-content :deep(img) {
|
|
max-width: 100%;
|
|
height: auto;
|
|
display: block; /* optional: avoids inline spacing issues */
|
|
}
|
|
</style>
|