1
0

vault backup: 2025-12-26 02:09:22
All checks were successful
Deploy Quartz site to GitHub Pages / build (push) Successful in 2m29s

This commit is contained in:
2025-12-26 02:09:22 +01:00
parent 3fddadfe50
commit 50366b9b9c
288 changed files with 58893 additions and 750 deletions

3249
stroma/file/frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
{
"name": "stroma-file-editor",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/typography": "^0.5.19",
"autoprefixer": "^10.4.23",
"postcss": "^8.5.6",
"sass": "^1.83.0",
"tailwindcss": "^4.1.18",
"vite": "^6.0.0"
},
"dependencies": {
"@tiptap/core": "^2.10.3",
"@tiptap/extension-bubble-menu": "^2.10.3",
"@tiptap/extension-placeholder": "^2.10.3",
"@tiptap/pm": "^2.10.3",
"@tiptap/starter-kit": "^2.10.3",
"remixicon": "^4.5.0",
"tippy.js": "^6.3.7",
"tiptap-markdown": "^0.8.10"
}
}

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,130 @@
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import { Markdown } from 'tiptap-markdown';
import Placeholder from '@tiptap/extension-placeholder';
import BubbleMenu from '@tiptap/extension-bubble-menu';
import './styles.css';
import 'remixicon/fonts/remixicon.css';
// DOM Elements
const saveBtn = document.getElementById('save-btn');
const editorContainer = document.getElementById('editor-container');
const bubbleMenuEl = document.getElementById('bubble-menu');
// Editor Instance
let editor;
let currentFileId = null;
// CSRF Helper
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Initialize Editor (only once)
function initEditor() {
// Bubble Menu Buttons
const btnBold = document.getElementById('btn-bold');
const btnItalic = document.getElementById('btn-italic');
const btnStrike = document.getElementById('btn-strike');
const btnCode = document.getElementById('btn-code');
editor = new Editor({
element: document.querySelector('#editor'),
extensions: [
StarterKit,
Markdown,
Placeholder.configure({
placeholder: "Type '/' for commands...",
}),
BubbleMenu.configure({
element: bubbleMenuEl,
tippyOptions: { duration: 100 },
}),
],
content: '',
editorProps: {
attributes: {
class: 'prose prose-sm sm:prose lg:prose-lg max-w-none focus:outline-none',
},
},
onSelectionUpdate({ editor }) {
btnBold?.classList.toggle('is-active', editor.isActive('bold'));
btnItalic?.classList.toggle('is-active', editor.isActive('italic'));
btnStrike?.classList.toggle('is-active', editor.isActive('strike'));
btnCode?.classList.toggle('is-active', editor.isActive('code'));
}
});
// Bubble Menu Listeners
if (btnBold) btnBold.addEventListener('click', () => editor.chain().focus().toggleBold().run());
if (btnItalic) btnItalic.addEventListener('click', () => editor.chain().focus().toggleItalic().run());
if (btnStrike) btnStrike.addEventListener('click', () => editor.chain().focus().toggleStrike().run());
if (btnCode) btnCode.addEventListener('click', () => editor.chain().focus().toggleCode().run());
// Container Focus
if (editorContainer) {
editorContainer.addEventListener('click', () => {
if (editor && !editor.isFocused) {
editor.chain().focus().run();
}
});
}
// Expose to global scope for SPA usage
window.editorInstance = editor;
window.setEditorFileId = (fileId) => { currentFileId = fileId; };
}
// Save Content
if (saveBtn) {
saveBtn.addEventListener('click', async () => {
if (!editor || !currentFileId) return;
saveBtn.disabled = true;
saveBtn.textContent = 'Saving...';
const markdownContent = editor.storage.markdown.getMarkdown();
const saveUrl = `/file/content/${currentFileId}/save/`;
try {
const response = await fetch(saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ content: markdownContent })
});
if (response.ok) {
saveBtn.textContent = 'Saved!';
setTimeout(() => {
saveBtn.textContent = 'Save Changes';
saveBtn.disabled = false;
}, 1000);
} else {
throw new Error('Save failed');
}
} catch (err) {
alert('Failed to save file');
saveBtn.textContent = 'Save Changes';
saveBtn.disabled = false;
}
});
}
// Initialize editor on load
if (document.querySelector('#editor')) {
initEditor();
}

View File

@@ -0,0 +1,2 @@
@import "tailwindcss";
@import 'remixicon/fonts/remixicon.css';

View File

@@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
"../../templates/**/*.html",
],
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
],
}

View File

@@ -0,0 +1,25 @@
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
root: 'src',
base: '/static/file/editor_dist/',
build: {
outDir: '../../static/file/editor_dist',
emptyOutDir: true,
manifest: false, // simplified for now
rollupOptions: {
input: path.resolve(__dirname, 'src/editor.js'),
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
},
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
});