mirror of
https://github.com/zadam/trilium.git
synced 2025-10-21 15:49:00 +02:00
chore(react/ribbon): start implementing attribute editor
This commit is contained in:
parent
2c33ef2b0d
commit
6d37e19b40
@ -24,58 +24,6 @@ const HELP_TEXT = `
|
|||||||
<p>${t("attribute_editor.help_text_body3")}</p>`;
|
<p>${t("attribute_editor.help_text_body3")}</p>`;
|
||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
|
|
||||||
<style>
|
|
||||||
.attribute-list-editor {
|
|
||||||
border: 0 !important;
|
|
||||||
outline: 0 !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
padding: 0 0 0 5px !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
max-height: 100px;
|
|
||||||
overflow: auto;
|
|
||||||
transition: opacity .1s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-list-editor.ck-content .mention {
|
|
||||||
color: var(--muted-text-color) !important;
|
|
||||||
background: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-attributes-button {
|
|
||||||
color: var(--muted-text-color);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 14px;
|
|
||||||
right: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
font-size: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new-attribute-button {
|
|
||||||
color: var(--muted-text-color);
|
|
||||||
position: absolute;
|
|
||||||
bottom: 13px;
|
|
||||||
right: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
font-size: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
|
||||||
border: 1px solid var(--button-border-color);
|
|
||||||
border-radius: var(--button-border-radius);
|
|
||||||
background: var(--button-background-color);
|
|
||||||
color: var(--button-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-errors {
|
|
||||||
color: red;
|
|
||||||
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="attribute-list-editor" tabindex="200"></div>
|
|
||||||
|
|
||||||
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
|
<div class="bx bx-save save-attributes-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.save_attributes"))}"></div>
|
||||||
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
|
<div class="bx bx-plus add-new-attribute-button tn-tool-button" title="${escapeQuotes(t("attribute_editor.add_a_new_attribute"))}"></div>
|
||||||
@ -84,61 +32,6 @@ const TPL = /*html*/`
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const mentionSetup: MentionFeed[] = [
|
|
||||||
{
|
|
||||||
marker: "@",
|
|
||||||
feed: (queryText) => noteAutocompleteService.autocompleteSourceForCKEditor(queryText),
|
|
||||||
itemRenderer: (_item) => {
|
|
||||||
const item = _item as Suggestion;
|
|
||||||
const itemElement = document.createElement("button");
|
|
||||||
|
|
||||||
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
|
||||||
|
|
||||||
return itemElement;
|
|
||||||
},
|
|
||||||
minimumCharacters: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
marker: "#",
|
|
||||||
feed: async (queryText) => {
|
|
||||||
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
|
||||||
|
|
||||||
return names.map((name) => {
|
|
||||||
return {
|
|
||||||
id: `#${name}`,
|
|
||||||
name: name
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minimumCharacters: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
marker: "~",
|
|
||||||
feed: async (queryText) => {
|
|
||||||
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
|
||||||
|
|
||||||
return names.map((name) => {
|
|
||||||
return {
|
|
||||||
id: `~${name}`,
|
|
||||||
name: name
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minimumCharacters: 0
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const editorConfig: EditorConfig = {
|
|
||||||
toolbar: {
|
|
||||||
items: []
|
|
||||||
},
|
|
||||||
placeholder: t("attribute_editor.placeholder"),
|
|
||||||
mention: {
|
|
||||||
feeds: mentionSetup
|
|
||||||
},
|
|
||||||
licenseKey: "GPL"
|
|
||||||
};
|
|
||||||
|
|
||||||
type AttributeCommandNames = FilteredCommandNames<CommandData>;
|
type AttributeCommandNames = FilteredCommandNames<CommandData>;
|
||||||
|
|
||||||
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
|
export default class AttributeEditorWidget extends NoteContextAwareWidget implements EventListener<"entitiesReloaded">, EventListener<"addNewLabel">, EventListener<"addNewRelation"> {
|
||||||
@ -324,7 +217,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
|
|
||||||
this.$editor.on("click", (e) => this.handleEditorClick(e));
|
this.$editor.on("click", (e) => this.handleEditorClick(e));
|
||||||
|
|
||||||
this.textEditor = await AttributeEditor.create(this.$editor[0], editorConfig);
|
|
||||||
this.textEditor.model.document.on("change:data", () => this.dataChanged());
|
this.textEditor.model.document.on("change:data", () => this.dataChanged());
|
||||||
this.textEditor.editing.view.document.on(
|
this.textEditor.editing.view.document.on(
|
||||||
"enter",
|
"enter",
|
||||||
|
10
apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx
Normal file
10
apps/client/src/widgets/ribbon/OwnedAttributesTab.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import AttributeEditor from "./components/AttributeEditor";
|
||||||
|
import { TabContext } from "./ribbon-interface";
|
||||||
|
|
||||||
|
export default function OwnedAttributesTab({ note }: TabContext) {
|
||||||
|
return (
|
||||||
|
<div className="attribute-list">
|
||||||
|
<AttributeEditor />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -19,6 +19,7 @@ import FilePropertiesTab from "./FilePropertiesTab";
|
|||||||
import ImagePropertiesTab from "./ImagePropertiesTab";
|
import ImagePropertiesTab from "./ImagePropertiesTab";
|
||||||
import NotePathsTab from "./NotePathsTab";
|
import NotePathsTab from "./NotePathsTab";
|
||||||
import NoteMapTab from "./NoteMapTab";
|
import NoteMapTab from "./NoteMapTab";
|
||||||
|
import OwnedAttributesTab from "./OwnedAttributesTab";
|
||||||
|
|
||||||
interface TitleContext {
|
interface TitleContext {
|
||||||
note: FNote | null | undefined;
|
note: FNote | null | undefined;
|
||||||
@ -105,9 +106,11 @@ const TAB_CONFIGURATION = numberObjectsInPlace<TabConfiguration>([
|
|||||||
toggleCommand: "toggleRibbonTabBasicProperties"
|
toggleCommand: "toggleRibbonTabBasicProperties"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// OwnedAttributeListWidget
|
|
||||||
title: t("owned_attribute_list.owned_attributes"),
|
title: t("owned_attribute_list.owned_attributes"),
|
||||||
icon: "bx bx-list-check"
|
icon: "bx bx-list-check",
|
||||||
|
content: OwnedAttributesTab,
|
||||||
|
show: ({note}) => !note?.isLaunchBarConfig(),
|
||||||
|
toggleCommand: "toggleRibbonTabOwnedAttributes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// InheritedAttributesWidget
|
// InheritedAttributesWidget
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import { useEffect, useRef } from "preact/hooks"
|
||||||
|
import { AttributeEditor as CKEditor, EditorConfig, MentionFeed } from "@triliumnext/ckeditor5";
|
||||||
|
import { t } from "../../../services/i18n";
|
||||||
|
import server from "../../../services/server";
|
||||||
|
import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete";
|
||||||
|
|
||||||
|
const mentionSetup: MentionFeed[] = [
|
||||||
|
{
|
||||||
|
marker: "@",
|
||||||
|
feed: (queryText) => note_autocomplete.autocompleteSourceForCKEditor(queryText),
|
||||||
|
itemRenderer: (_item) => {
|
||||||
|
const item = _item as Suggestion;
|
||||||
|
const itemElement = document.createElement("button");
|
||||||
|
|
||||||
|
itemElement.innerHTML = `${item.highlightedNotePathTitle} `;
|
||||||
|
|
||||||
|
return itemElement;
|
||||||
|
},
|
||||||
|
minimumCharacters: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
marker: "#",
|
||||||
|
feed: async (queryText) => {
|
||||||
|
const names = await server.get<string[]>(`attribute-names/?type=label&query=${encodeURIComponent(queryText)}`);
|
||||||
|
|
||||||
|
return names.map((name) => {
|
||||||
|
return {
|
||||||
|
id: `#${name}`,
|
||||||
|
name: name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
minimumCharacters: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
marker: "~",
|
||||||
|
feed: async (queryText) => {
|
||||||
|
const names = await server.get<string[]>(`attribute-names/?type=relation&query=${encodeURIComponent(queryText)}`);
|
||||||
|
|
||||||
|
return names.map((name) => {
|
||||||
|
return {
|
||||||
|
id: `~${name}`,
|
||||||
|
name: name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
minimumCharacters: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const editorConfig: EditorConfig = {
|
||||||
|
toolbar: {
|
||||||
|
items: []
|
||||||
|
},
|
||||||
|
placeholder: t("attribute_editor.placeholder"),
|
||||||
|
mention: {
|
||||||
|
feeds: mentionSetup
|
||||||
|
},
|
||||||
|
licenseKey: "GPL"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AttributeEditor() {
|
||||||
|
const editorContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editorContainerRef.current) return;
|
||||||
|
|
||||||
|
CKEditor.create(editorContainerRef.current, editorConfig).then((textEditor) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style="position: relative; padding-top: 10px; padding-bottom: 10px">
|
||||||
|
<div ref={editorContainerRef} class="attribute-list-editor" tabindex={200} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -268,3 +268,67 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/* #endregion */
|
/* #endregion */
|
||||||
|
|
||||||
|
/* #region Attribute editor */
|
||||||
|
.attribute-list-editor {
|
||||||
|
border: 0 !important;
|
||||||
|
outline: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
padding: 0 0 0 5px !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
max-height: 100px;
|
||||||
|
overflow: auto;
|
||||||
|
transition: opacity .1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-list-editor.ck-content .mention {
|
||||||
|
color: var(--muted-text-color) !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-attributes-button {
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 14px;
|
||||||
|
right: 25px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-attribute-button {
|
||||||
|
color: var(--muted-text-color);
|
||||||
|
position: absolute;
|
||||||
|
bottom: 13px;
|
||||||
|
right: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
font-size: 130%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-new-attribute-button:hover, .save-attributes-button:hover {
|
||||||
|
border: 1px solid var(--button-border-color);
|
||||||
|
border-radius: var(--button-border-radius);
|
||||||
|
background: var(--button-background-color);
|
||||||
|
color: var(--button-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-errors {
|
||||||
|
color: red;
|
||||||
|
padding: 5px 50px 0px 5px; /* large right padding to avoid buttons */
|
||||||
|
}
|
||||||
|
/* #endregion */
|
||||||
|
|
||||||
|
/* #region Owned Attributes */
|
||||||
|
.attribute-list {
|
||||||
|
margin-left: 7px;
|
||||||
|
margin-right: 7px;
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attribute-list-editor p {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
/* #endregion */
|
@ -5,40 +5,12 @@ import AttributeEditorWidget from "../attribute_widgets/attribute_editor.js";
|
|||||||
import type { CommandListenerData } from "../../components/app_context.js";
|
import type { CommandListenerData } from "../../components/app_context.js";
|
||||||
import type FAttribute from "../../entities/fattribute.js";
|
import type FAttribute from "../../entities/fattribute.js";
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="attribute-list">
|
|
||||||
<style>
|
|
||||||
.attribute-list {
|
|
||||||
margin-left: 7px;
|
|
||||||
margin-right: 7px;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attribute-list-editor p {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="attr-editor-placeholder"></div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
|
export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
|
||||||
|
|
||||||
private attributeDetailWidget: AttributeDetailWidget;
|
private attributeDetailWidget: AttributeDetailWidget;
|
||||||
private attributeEditorWidget: AttributeEditorWidget;
|
private attributeEditorWidget: AttributeEditorWidget;
|
||||||
private $title!: JQuery<HTMLElement>;
|
private $title!: JQuery<HTMLElement>;
|
||||||
|
|
||||||
get name() {
|
|
||||||
return "ownedAttributes";
|
|
||||||
}
|
|
||||||
|
|
||||||
get toggleCommand() {
|
|
||||||
return "toggleRibbonTabOwnedAttributes";
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -49,23 +21,6 @@ export default class OwnedAttributeListWidget extends NoteContextAwareWidget {
|
|||||||
this.child(this.attributeEditorWidget, this.attributeDetailWidget);
|
this.child(this.attributeEditorWidget, this.attributeDetailWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTitle() {
|
|
||||||
return {
|
|
||||||
show: !this.note?.isLaunchBarConfig(),
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.contentSized();
|
|
||||||
|
|
||||||
this.$widget.find(".attr-editor-placeholder").replaceWith(this.attributeEditorWidget.render());
|
|
||||||
this.$widget.append(this.attributeDetailWidget.render());
|
|
||||||
|
|
||||||
this.$title = $("<div>");
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveAttributesCommand() {
|
async saveAttributesCommand() {
|
||||||
await this.attributeEditorWidget.save();
|
await this.attributeEditorWidget.save();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user