All checks were successful
Build and Deploy Nuxt / build (push) Successful in 32s
248 lines
5.9 KiB
Vue
248 lines
5.9 KiB
Vue
<script setup>
|
|
import { marked } from 'marked';
|
|
import { onMounted, onUnmounted, ref, createApp } from 'vue';
|
|
import ToastManager from '~/components/managers/ToastManager.vue';
|
|
import { emitter } from '~/services/Emitter';
|
|
import Server from '~/services/Server';
|
|
import { DisplayToast } from '~/services/Toaster';
|
|
import TestWidget from '../widgets/TestWidget.vue';
|
|
|
|
// 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);
|
|
|
|
function closeNote(){
|
|
emitter.emit('delete-note', props.noteKey);
|
|
}
|
|
|
|
// MARKED
|
|
|
|
const renderer = new marked.Renderer();
|
|
|
|
renderer.paragraph = (token) => {
|
|
const text = token.text || '';
|
|
|
|
if (text.startsWith(':::my-component')) {
|
|
return `<div class="vue-component" data-component="TestWidget"></div>`;
|
|
}
|
|
|
|
return `<p>${text}</p>`;
|
|
};
|
|
|
|
marked.setOptions({
|
|
renderer: renderer,
|
|
});
|
|
|
|
const compiledMarkdown = computed(() => {
|
|
return marked.parse(sourceText.value);
|
|
});
|
|
|
|
function mountComponents() {
|
|
const nodes = document.querySelectorAll('.vue-component');
|
|
|
|
nodes.forEach(el => {
|
|
const app = createApp(TestWidget, { /* props */ });
|
|
app.mount(el);
|
|
console.log("Mounted a component")
|
|
});
|
|
|
|
console.log("Huh")
|
|
}
|
|
///
|
|
|
|
function update(){
|
|
displayText.value = compiledMarkdown.value;
|
|
setTimeout(() => {
|
|
setupCallout()
|
|
mountComponents();
|
|
}, 0);
|
|
}
|
|
|
|
watch(sourceText, (newText) => {
|
|
// update();
|
|
});
|
|
|
|
onMounted(() => {
|
|
sourceText.value = props.text;
|
|
// 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();
|
|
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
|
|
}).then((response) => {
|
|
if(response.data.status !== 'ok'){
|
|
// Handle error (e.g., show a notification)
|
|
return;
|
|
}
|
|
DisplayToast('green', "Note saved successfully.", 500);
|
|
}).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"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
</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 class="note-content" ref="noteContent" v-html="displayText" v-else></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>
|