diff --git a/src/public/app/layouts/desktop_layout.ts b/src/public/app/layouts/desktop_layout.ts index 548d1c3d9..9d8a06ac1 100644 --- a/src/public/app/layouts/desktop_layout.ts +++ b/src/public/app/layouts/desktop_layout.ts @@ -90,6 +90,7 @@ import CloseZenButton from "../widgets/close_zen_button.js"; import type { AppContext } from "./../components/app_context.js"; import type { WidgetsByParent } from "../services/bundle.js"; import SwitchSplitOrientationButton from "../widgets/floating_buttons/switch_layout_button.js"; +import ToggleReadOnlyButton from "../widgets/floating_buttons/toggle_read_only_button.js"; export default class DesktopLayout { @@ -204,6 +205,7 @@ export default class DesktopLayout { .child( new FloatingButtons() .child(new SwitchSplitOrientationButton()) + .child(new ToggleReadOnlyButton()) .child(new EditButton()) .child(new ShowTocWidgetButton()) .child(new ShowHighlightsListWidgetButton()) diff --git a/src/public/app/services/attributes.ts b/src/public/app/services/attributes.ts index b47e297cd..8de409e9b 100644 --- a/src/public/app/services/attributes.ts +++ b/src/public/app/services/attributes.ts @@ -23,6 +23,23 @@ async function removeAttributeById(noteId: string, attributeId: string) { await server.remove(`notes/${noteId}/attributes/${attributeId}`); } +/** + * Removes a label identified by its name from the given note, if it exists. Note that the label must be owned, i.e. + * it will not remove inherited attributes. + * + * @param note the note from which to remove the label. + * @param labelName the name of the label to remove. + * @returns `true` if an attribute was identified and removed, `false` otherwise. + */ +function removeOwnedLabelByName(note: FNote, labelName: string) { + const label = note.getOwnedLabel(labelName); + if (label) { + removeAttributeById(note.noteId, label.attributeId); + return true; + } + return false; +} + /** * Sets the attribute of the given note to the provided value if its truthy, or removes the attribute if the value is falsy. * For an attribute with an empty value, pass an empty string instead. @@ -90,5 +107,6 @@ export default { setLabel, setAttribute, removeAttributeById, + removeOwnedLabelByName, isAffecting }; diff --git a/src/public/app/widgets/floating_buttons/toggle_read_only_button.ts b/src/public/app/widgets/floating_buttons/toggle_read_only_button.ts new file mode 100644 index 000000000..f6b159243 --- /dev/null +++ b/src/public/app/widgets/floating_buttons/toggle_read_only_button.ts @@ -0,0 +1,45 @@ +import type FNote from "../../entities/fnote.js"; +import attributes from "../../services/attributes.js"; +import { t } from "../../services/i18n.js"; +import OnClickButtonWidget from "../buttons/onclick_button.js"; + +export default class ToggleReadOnlyButton extends OnClickButtonWidget { + + private isReadOnly?: boolean; + + constructor() { + super(); + + this + .title(() => this.isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing")) + .titlePlacement("bottom") + .icon(() => this.isReadOnly ? "bx-lock-open-alt" : "bx-lock-alt") + .onClick(() => this.#toggleReadOnly()); + } + + #toggleReadOnly() { + if (!this.noteId || !this.note) { + return; + } + + if (this.isReadOnly) { + attributes.removeOwnedLabelByName(this.note, "readOnly"); + } else { + attributes.setLabel(this.noteId, "readOnly"); + } + } + + async refreshWithNote(note: FNote | null | undefined) { + const isReadOnly = !!note?.hasLabel("readOnly"); + + if (isReadOnly !== this.isReadOnly) { + this.isReadOnly = isReadOnly; + this.refreshIcon(); + } + } + + isEnabled() { + return super.isEnabled() && this.note?.type === "mermaid"; + } + +} diff --git a/src/public/translations/en/translation.json b/src/public/translations/en/translation.json index 99d9b48b1..da4751e36 100644 --- a/src/public/translations/en/translation.json +++ b/src/public/translations/en/translation.json @@ -1707,5 +1707,9 @@ "switch_layout_button": { "title_vertical": "Move editing pane to the bottom", "title_horizontal": "Move editing pane to the left" + }, + "toggle_read_only_button": { + "unlock-editing": "Unlock editing", + "lock-editing": "Lock editing" } }