mirror of
https://github.com/zadam/trilium.git
synced 2025-11-12 16:25:51 +01:00
chore(react/ribbon): save attribute changes
This commit is contained in:
@@ -16,7 +16,6 @@ import { escapeQuotes } from "../../services/utils.js";
|
|||||||
|
|
||||||
const TPL = /*html*/`
|
const TPL = /*html*/`
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<div class="attribute-errors" style="display: none;"></div>
|
<div class="attribute-errors" style="display: none;"></div>
|
||||||
@@ -49,11 +48,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
this.initialized = this.initEditor();
|
this.initialized = this.initEditor();
|
||||||
|
|
||||||
this.$editor.on("keydown", async (e) => {
|
this.$editor.on("keydown", async (e) => {
|
||||||
if (e.which === 13) {
|
|
||||||
// allow autocomplete to fill the result textarea
|
|
||||||
setTimeout(() => this.save(), 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.attributeDetailWidget.hide();
|
this.attributeDetailWidget.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -62,7 +56,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
|
this.$addNewAttributeButton = this.$widget.find(".add-new-attribute-button");
|
||||||
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
|
this.$addNewAttributeButton.on("click", (e) => this.addNewAttribute(e));
|
||||||
|
|
||||||
this.$saveAttributesButton = this.$widget.find(".save-attributes-button");
|
|
||||||
this.$saveAttributesButton.on("click", () => this.save());
|
this.$saveAttributesButton.on("click", () => this.save());
|
||||||
|
|
||||||
this.$errors = this.$widget.find(".attribute-errors");
|
this.$errors = this.$widget.find(".attribute-errors");
|
||||||
@@ -170,39 +163,11 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
console.warn("Ignoring blur event because a different note is loaded.");
|
console.warn("Ignoring blur event because a different note is loaded.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const attributes = this.parseAttributes();
|
|
||||||
|
|
||||||
if (attributes) {
|
|
||||||
await server.put(`notes/${this.noteId}/attributes`, attributes, this.componentId);
|
|
||||||
|
|
||||||
this.$saveAttributesButton.fadeOut();
|
|
||||||
|
|
||||||
// blink the attribute text to give a visual hint that save has been executed
|
|
||||||
this.$editor.css("opacity", 0);
|
|
||||||
|
|
||||||
// revert back
|
|
||||||
setTimeout(() => this.$editor.css("opacity", 1), 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parseAttributes() {
|
|
||||||
try {
|
|
||||||
return attributeParser.lexAndParse(this.getPreprocessedData());
|
|
||||||
} catch (e: any) {
|
|
||||||
this.$errors.text(e.message).slideDown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataChanged() {
|
dataChanged() {
|
||||||
this.lastUpdatedNoteId = this.noteId;
|
this.lastUpdatedNoteId = this.noteId;
|
||||||
|
|
||||||
if (this.lastSavedContent === this.textEditor.getData()) {
|
|
||||||
this.$saveAttributesButton.fadeOut();
|
|
||||||
} else {
|
|
||||||
this.$saveAttributesButton.fadeIn();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.$errors.is(":visible")) {
|
if (this.$errors.is(":visible")) {
|
||||||
// using .hide() instead of .slideUp() since this will also hide the error after confirming
|
// using .hide() instead of .slideUp() since this will also hide the error after confirming
|
||||||
// mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
|
// mention for relation name which suits up. When using.slideUp() error will appear and the slideUp which is weird
|
||||||
@@ -218,14 +183,6 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget implem
|
|||||||
$el.text(title);
|
$el.text(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
async renderOwnedAttributes(ownedAttributes: FAttribute[], saved: boolean) {
|
|
||||||
if (saved) {
|
|
||||||
this.lastSavedContent = this.textEditor.getData();
|
|
||||||
|
|
||||||
this.$saveAttributesButton.fadeOut(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createNoteForReferenceLink(title: string) {
|
async createNoteForReferenceLink(title: string) {
|
||||||
let result;
|
let result;
|
||||||
if (this.notePath) {
|
if (this.notePath) {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import attribute_renderer from "../../../services/attribute_renderer";
|
|||||||
import FNote from "../../../entities/fnote";
|
import FNote from "../../../entities/fnote";
|
||||||
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
import AttributeDetailWidget from "../../attribute_widgets/attribute_detail";
|
||||||
import attribute_parser, { Attribute } from "../../../services/attribute_parser";
|
import attribute_parser, { Attribute } from "../../../services/attribute_parser";
|
||||||
|
import ActionButton from "../../react/ActionButton";
|
||||||
|
import { escapeQuotes } from "../../../services/utils";
|
||||||
|
|
||||||
const HELP_TEXT = `
|
const HELP_TEXT = `
|
||||||
<p>${t("attribute_editor.help_text_body1")}</p>
|
<p>${t("attribute_editor.help_text_body1")}</p>
|
||||||
@@ -63,10 +65,13 @@ const mentionSetup: MentionFeed[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export default function AttributeEditor({ note }: { note: FNote }) {
|
export default function AttributeEditor({ note, componentId }: { note: FNote, componentId: string }) {
|
||||||
|
|
||||||
const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">();
|
const [ state, setState ] = useState<"normal" | "showHelpTooltip" | "showAttributeDetail">();
|
||||||
|
const [ error, setError ] = useState<unknown>();
|
||||||
|
const [ needsSaving, setNeedsSaving ] = useState(false);
|
||||||
const [ initialValue, setInitialValue ] = useState<string>("");
|
const [ initialValue, setInitialValue ] = useState<string>("");
|
||||||
|
const lastSavedContent = useRef<string>();
|
||||||
const currentValueRef = useRef(initialValue);
|
const currentValueRef = useRef(initialValue);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const { showTooltip, hideTooltip } = useTooltip(wrapperRef, {
|
const { showTooltip, hideTooltip } = useTooltip(wrapperRef, {
|
||||||
@@ -97,16 +102,55 @@ export default function AttributeEditor({ note }: { note: FNote }) {
|
|||||||
htmlAttrs += " ";
|
htmlAttrs += " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (saved) {
|
||||||
|
lastSavedContent.current = currentValueRef.current;
|
||||||
|
setNeedsSaving(false);
|
||||||
|
}
|
||||||
|
|
||||||
setInitialValue(htmlAttrs);
|
setInitialValue(htmlAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseAttributes() {
|
||||||
|
try {
|
||||||
|
return attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current));
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
const attributes = parseAttributes();
|
||||||
|
if (!attributes) {
|
||||||
|
// An error occurred and will be reported to the user.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.put(`notes/${note.noteId}/attributes`, attributes, componentId);
|
||||||
|
setNeedsSaving(false);
|
||||||
|
|
||||||
|
// blink the attribute text to give a visual hint that save has been executed
|
||||||
|
if (wrapperRef.current) {
|
||||||
|
wrapperRef.current.style.opacity = "0";
|
||||||
|
setTimeout(() => wrapperRef.current!.style.opacity = "1", 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderOwnedAttributes(note.getOwnedAttributes(), true);
|
renderOwnedAttributes(note.getOwnedAttributes(), true);
|
||||||
}, [ note ]);
|
}, [ note ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div ref={wrapperRef} style="position: relative; padding-top: 10px; padding-bottom: 10px">
|
<div
|
||||||
|
ref={wrapperRef}
|
||||||
|
style="position: relative; padding-top: 10px; padding-bottom: 10px"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
// allow autocomplete to fill the result textarea
|
||||||
|
setTimeout(() => save(), 100);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<CKEditor
|
<CKEditor
|
||||||
className="attribute-list-editor"
|
className="attribute-list-editor"
|
||||||
tabIndex={200}
|
tabIndex={200}
|
||||||
@@ -120,6 +164,7 @@ export default function AttributeEditor({ note }: { note: FNote }) {
|
|||||||
}}
|
}}
|
||||||
onChange={(currentValue) => {
|
onChange={(currentValue) => {
|
||||||
currentValueRef.current = currentValue ?? "";
|
currentValueRef.current = currentValue ?? "";
|
||||||
|
setNeedsSaving(lastSavedContent.current !== currentValue);
|
||||||
}}
|
}}
|
||||||
onClick={(e, pos) => {
|
onClick={(e, pos) => {
|
||||||
if (pos && pos.textNode && pos.textNode.data) {
|
if (pos && pos.textNode && pos.textNode.data) {
|
||||||
@@ -163,6 +208,13 @@ export default function AttributeEditor({ note }: { note: FNote }) {
|
|||||||
}}
|
}}
|
||||||
disableNewlines disableSpellcheck
|
disableNewlines disableSpellcheck
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{ needsSaving && <ActionButton
|
||||||
|
icon="bx bx-save"
|
||||||
|
className="save-attributes-button"
|
||||||
|
text={escapeQuotes(t("attribute_editor.save_attributes"))}
|
||||||
|
onClick={save}
|
||||||
|
/> }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{attributeDetailWidgetEl}
|
{attributeDetailWidgetEl}
|
||||||
|
|||||||
Reference in New Issue
Block a user