mirror of
https://github.com/Theodor-Springmann-Stiftung/musenalm.git
synced 2026-02-04 10:35:30 +00:00
+Trix editor for annotations
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
31
views/package-lock.json
generated
31
views/package-lock.json
generated
@@ -8,6 +8,9 @@
|
||||
"name": "caveman_views",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"trix": "^2.1.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"daisyui": "^5.0.0-beta.8",
|
||||
@@ -1100,6 +1103,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
@@ -1266,6 +1276,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
|
||||
"integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -2241,6 +2260,18 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/trix": {
|
||||
"version": "2.1.16",
|
||||
"resolved": "https://registry.npmjs.org/trix/-/trix-2.1.16.tgz",
|
||||
"integrity": "sha512-XtZgWI+oBvLzX7CWnkIf+ZWC+chL+YG/TkY43iMTV0Zl+CJjn18B1GJUCEWJ8qgfpcyMBuysnNAfPWiv2sV14A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/ulid": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ulid/-/ulid-2.4.0.tgz",
|
||||
|
||||
@@ -30,5 +30,8 @@
|
||||
"css": "postcss transform/site.css -o assets/style.css",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"trix": "^2.1.16"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,72 @@
|
||||
<div class="inputlabelrow">
|
||||
<label for="annotation" class="inputlabel">{{ $label }}</label>
|
||||
</div>
|
||||
<textarea name="annotation" id="annotation" class="inputinput" placeholder="" autocomplete="off" rows="2">{{- $annotation -}}</textarea>
|
||||
|
||||
<trix-toolbar id="annotation-toolbar">
|
||||
<div class="trix-toolbar-container">
|
||||
<!-- Text formatting group -->
|
||||
<span class="trix-toolbar-group">
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="bold" data-trix-key="b" title="Bold">
|
||||
<i class="ri-bold"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="italic" data-trix-key="i" title="Italic">
|
||||
<i class="ri-italic"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="strike" title="Strikethrough">
|
||||
<i class="ri-strikethrough"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="href" data-trix-action="link" data-trix-key="k" title="Link">
|
||||
<i class="ri-links-line"></i>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<!-- Block formatting group -->
|
||||
<span class="trix-toolbar-group">
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="heading1" title="Heading">
|
||||
<i class="ri-h-1"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="quote" title="Quote">
|
||||
<i class="ri-double-quotes-l"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="bullet" title="Bullets">
|
||||
<i class="ri-list-unordered"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-attribute="number" title="Numbers">
|
||||
<i class="ri-list-ordered"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-action="decreaseNestingLevel" title="Decrease Indent">
|
||||
<i class="ri-indent-decrease"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-action="increaseNestingLevel" title="Increase Indent">
|
||||
<i class="ri-indent-increase"></i>
|
||||
</button>
|
||||
</span>
|
||||
|
||||
<!-- History group -->
|
||||
<span class="trix-toolbar-group">
|
||||
<button type="button" class="trix-toolbar-button" data-trix-action="undo" data-trix-key="z" title="Undo">
|
||||
<i class="ri-arrow-go-back-line"></i>
|
||||
</button>
|
||||
<button type="button" class="trix-toolbar-button" data-trix-action="redo" data-trix-key="shift+z" title="Redo">
|
||||
<i class="ri-arrow-go-forward-line"></i>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Link dialog (required for link functionality) -->
|
||||
<div class="trix-dialogs" data-trix-dialogs>
|
||||
<div class="trix-dialog trix-dialog--link" data-trix-dialog="href" data-trix-dialog-attribute="href">
|
||||
<div class="trix-dialog__link-fields">
|
||||
<input type="url" name="href" class="trix-input trix-input--dialog" placeholder="Enter a URL…" aria-label="URL" required data-trix-input>
|
||||
<div class="trix-button-group">
|
||||
<input type="button" class="trix-button trix-button--dialog" value="Link" data-trix-method="setAttribute">
|
||||
<input type="button" class="trix-button trix-button--dialog" value="Unlink" data-trix-method="removeAttribute">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</trix-toolbar>
|
||||
|
||||
<textarea hidden id="annotation" name="annotation" autocomplete="off">{{- $annotation -}}</textarea>
|
||||
<trix-editor input="annotation" toolbar="annotation-toolbar"></trix-editor>
|
||||
</div>
|
||||
|
||||
@@ -494,4 +494,155 @@
|
||||
select + reset-button .rbi-button {
|
||||
@apply ml-3;
|
||||
}
|
||||
|
||||
/* Trix Custom Toolbar Styles */
|
||||
.trix-toolbar-container {
|
||||
@apply flex flex-wrap gap-1 px-2 py-1.5 bg-stone-200 border-b border-stone-300;
|
||||
}
|
||||
|
||||
.trix-toolbar-group {
|
||||
@apply inline-flex gap-0.5 items-center;
|
||||
}
|
||||
|
||||
.trix-toolbar-group:not(:last-child) {
|
||||
@apply mr-2 pr-2 border-r border-stone-400;
|
||||
}
|
||||
|
||||
.trix-toolbar-button {
|
||||
@apply w-7 h-7 inline-flex items-center justify-center
|
||||
text-gray-700 hover:text-slate-900 hover:bg-stone-300
|
||||
rounded transition-all duration-100
|
||||
focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-1;
|
||||
}
|
||||
|
||||
.trix-toolbar-button i {
|
||||
@apply text-base pointer-events-none;
|
||||
}
|
||||
|
||||
/* Active state for toggle buttons */
|
||||
.trix-toolbar-button.trix-active {
|
||||
@apply bg-slate-600 text-white hover:bg-slate-700 hover:text-white;
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
.trix-toolbar-button:disabled {
|
||||
@apply opacity-30 cursor-not-allowed hover:bg-transparent hover:text-gray-400;
|
||||
}
|
||||
|
||||
.trix-toolbar-button:disabled i {
|
||||
@apply text-gray-400;
|
||||
}
|
||||
|
||||
/* Trix editor content area */
|
||||
trix-editor {
|
||||
@apply block w-full focus:border-none focus:outline-none px-3 py-2 min-h-[6rem];
|
||||
}
|
||||
|
||||
/* Trix content formatting styles */
|
||||
trix-editor h1 {
|
||||
@apply text-2xl font-bold mt-4 mb-2 first:mt-0;
|
||||
}
|
||||
|
||||
trix-editor blockquote {
|
||||
@apply border-l-4 border-stone-400 pl-4 py-1 italic text-gray-700;
|
||||
}
|
||||
|
||||
trix-editor ul {
|
||||
@apply list-disc list-outside ml-1 my-2;
|
||||
}
|
||||
|
||||
trix-editor ol {
|
||||
@apply list-decimal list-outside ml-1 my-2;
|
||||
}
|
||||
|
||||
trix-editor li {
|
||||
@apply leading-normal;
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
trix-editor ul > li {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
trix-editor ol > li {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
trix-editor ul ul,
|
||||
trix-editor ol ul {
|
||||
@apply list-disc my-0;
|
||||
}
|
||||
|
||||
trix-editor ul ol,
|
||||
trix-editor ol ol {
|
||||
@apply list-decimal my-0;
|
||||
}
|
||||
|
||||
trix-editor ul ul > li,
|
||||
trix-editor ol ul > li,
|
||||
trix-editor ul ol > li,
|
||||
trix-editor ol ol > li {
|
||||
@apply leading-normal;
|
||||
}
|
||||
|
||||
trix-editor a {
|
||||
@apply text-blue-600 underline hover:text-blue-800;
|
||||
}
|
||||
|
||||
trix-editor strong {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
trix-editor em {
|
||||
@apply italic;
|
||||
}
|
||||
|
||||
trix-editor del {
|
||||
@apply line-through;
|
||||
}
|
||||
|
||||
trix-editor pre {
|
||||
@apply bg-stone-100 border border-stone-300 rounded px-3 py-2 my-2 font-mono text-sm overflow-x-auto;
|
||||
}
|
||||
|
||||
/* Link dialog styling */
|
||||
.trix-dialogs {
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.trix-dialog {
|
||||
@apply hidden absolute top-full left-0 right-0 mt-1 p-3 bg-white border border-stone-300 rounded-md shadow-lg z-10;
|
||||
}
|
||||
|
||||
.trix-dialog[data-trix-active] {
|
||||
@apply block;
|
||||
}
|
||||
|
||||
.trix-dialog__link-fields {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
.trix-input--dialog {
|
||||
@apply w-full px-3 py-1.5 border border-stone-300 rounded-md
|
||||
focus:border-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-1;
|
||||
}
|
||||
|
||||
.trix-button-group {
|
||||
@apply flex gap-2;
|
||||
}
|
||||
|
||||
.trix-button--dialog {
|
||||
@apply px-4 py-1.5 text-sm font-medium rounded-md
|
||||
border border-transparent shadow-sm
|
||||
cursor-pointer transition-all duration-75
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500;
|
||||
}
|
||||
|
||||
.trix-button--dialog[value="Link"] {
|
||||
@apply bg-slate-700 text-white hover:bg-slate-800 active:bg-slate-900;
|
||||
}
|
||||
|
||||
.trix-button--dialog[value="Unlink"] {
|
||||
@apply bg-stone-200 text-gray-800 hover:bg-stone-300 active:bg-stone-400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// INFO: We import this so vite processes the stylesheet
|
||||
import "./site.css";
|
||||
|
||||
import Trix from "trix";
|
||||
import { FilterPill } from "./filter-pill.js";
|
||||
import { FilterList } from "./filter-list.js";
|
||||
import { ScrollButton } from "./scroll-button.js";
|
||||
@@ -337,7 +338,7 @@ function FormLoad(form) {
|
||||
// Attach resize handler to all textareas
|
||||
for (const textarea of textareas) {
|
||||
console.log("Attaching input listener to:", textarea.name || textarea.id);
|
||||
textarea.addEventListener('input', function() {
|
||||
textarea.addEventListener("input", function () {
|
||||
console.log("Input event on textarea:", this.name || this.id);
|
||||
TextareaAutoResize(this);
|
||||
});
|
||||
@@ -369,9 +370,7 @@ function FormLoad(form) {
|
||||
const target = mutation.target;
|
||||
// Check if this element or its children contain textareas
|
||||
if (target instanceof HTMLElement) {
|
||||
const textareasInTarget = target.matches("textarea")
|
||||
? [target]
|
||||
: Array.from(target.querySelectorAll("textarea"));
|
||||
const textareasInTarget = target.matches("textarea") ? [target] : Array.from(target.querySelectorAll("textarea"));
|
||||
|
||||
for (const textarea of textareasInTarget) {
|
||||
// Only resize if now visible
|
||||
@@ -392,9 +391,9 @@ function FormLoad(form) {
|
||||
|
||||
// Handle boolean checkboxes
|
||||
const booleanCheckboxes = form.querySelectorAll('input[type="checkbox"][data-boolean-checkbox]');
|
||||
booleanCheckboxes.forEach(checkbox => {
|
||||
booleanCheckboxes.forEach((checkbox) => {
|
||||
// Ensure each boolean checkbox has proper value handling
|
||||
checkbox.value = 'true';
|
||||
checkbox.value = "true";
|
||||
|
||||
// Add change handler to manage hidden input
|
||||
const updateHiddenInput = () => {
|
||||
@@ -406,10 +405,10 @@ function FormLoad(form) {
|
||||
|
||||
// If checkbox is unchecked, add hidden input with false value
|
||||
if (!checkbox.checked) {
|
||||
const hidden = document.createElement('input');
|
||||
hidden.type = 'hidden';
|
||||
const hidden = document.createElement("input");
|
||||
hidden.type = "hidden";
|
||||
hidden.name = checkbox.name;
|
||||
hidden.value = 'false';
|
||||
hidden.value = "false";
|
||||
checkbox.parentNode.insertBefore(hidden, checkbox);
|
||||
}
|
||||
};
|
||||
@@ -418,7 +417,7 @@ function FormLoad(form) {
|
||||
updateHiddenInput();
|
||||
|
||||
// Update on change
|
||||
checkbox.addEventListener('change', updateHiddenInput);
|
||||
checkbox.addEventListener("change", updateHiddenInput);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -443,22 +442,4 @@ window.HookupRBChange = HookupRBChange;
|
||||
window.FormLoad = FormLoad;
|
||||
window.TextareaAutoResize = TextareaAutoResize;
|
||||
|
||||
export {
|
||||
FilterList,
|
||||
ScrollButton,
|
||||
AbbreviationTooltips,
|
||||
MultiSelectSimple,
|
||||
MultiSelectRole,
|
||||
ToolTip,
|
||||
PopupImage,
|
||||
TabList,
|
||||
FilterPill,
|
||||
ImageReel,
|
||||
IntLink,
|
||||
ItemsEditor,
|
||||
SingleSelectRemote,
|
||||
AlmanachEditPage,
|
||||
RelationsEditor,
|
||||
EditPage,
|
||||
FabMenu,
|
||||
};
|
||||
export { FilterList, ScrollButton, AbbreviationTooltips, MultiSelectSimple, MultiSelectRole, ToolTip, PopupImage, TabList, FilterPill, ImageReel, IntLink, ItemsEditor, SingleSelectRemote, AlmanachEditPage, RelationsEditor, EditPage, FabMenu };
|
||||
|
||||
Reference in New Issue
Block a user