client/read only note info bar: refactor

This commit is contained in:
Adorian Doran
2025-11-06 00:45:16 +02:00
parent 33be7f828b
commit 95e5c2563e
4 changed files with 77 additions and 102 deletions

View File

@@ -4,7 +4,7 @@ import Component from "../components/component";
import NoteContext from "../components/note_context"; import NoteContext from "../components/note_context";
import FNote from "../entities/fnote"; import FNote from "../entities/fnote";
import ActionButton, { ActionButtonProps } from "./react/ActionButton"; import ActionButton, { ActionButtonProps } from "./react/ActionButton";
import { useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks"; import { useIsNoteReadOnly, useNoteLabelBoolean, useTriliumEvent, useTriliumOption, useWindowSize } from "./react/hooks";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils"; import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
import server from "../services/server"; import server from "../services/server";
@@ -13,8 +13,6 @@ import toast from "../services/toast";
import { t } from "../services/i18n"; import { t } from "../services/i18n";
import { copyImageReferenceToClipboard } from "../services/image"; import { copyImageReferenceToClipboard } from "../services/image";
import tree from "../services/tree"; import tree from "../services/tree";
import protected_session_holder from "../services/protected_session_holder";
import options from "../services/options";
import { getHelpUrlForNote } from "../services/in_app_help"; import { getHelpUrlForNote } from "../services/in_app_help";
import froca from "../services/froca"; import froca from "../services/froca";
import NoteLink from "./react/NoteLink"; import NoteLink from "./react/NoteLink";
@@ -102,47 +100,25 @@ function ToggleReadOnlyButton({ note, viewType, isDefaultViewMode }: FloatingBut
} }
function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) { function EditButton({ note, noteContext, isDefaultViewMode }: FloatingButtonContext) {
const [ animationClass, setAnimationClass ] = useState(""); const [animationClass, setAnimationClass] = useState("");
const [ isEnabled, setIsEnabled ] = useState(false); const {isReadOnly, enableEditing} = useIsNoteReadOnly();
useEffect(() => {
noteContext.isReadOnly().then(isReadOnly => {
setIsEnabled(
isDefaultViewMode
&& (!note.isProtected || protected_session_holder.isProtectedSessionAvailable())
&& !options.is("databaseReadonly")
&& isReadOnly
);
});
}, [ note ]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsEnabled(false);
}
});
// make the edit button stand out on the first display, otherwise // make the edit button stand out on the first display, otherwise
// it's difficult to notice that the note is readonly // it's difficult to notice that the note is readonly
useEffect(() => { useEffect(() => {
if (isEnabled) { if (isReadOnly) {
setAnimationClass("bx-tada bx-lg"); setAnimationClass("bx-tada bx-lg");
setTimeout(() => { setTimeout(() => {
setAnimationClass(""); setAnimationClass("");
}, 1700); }, 1700);
} }
}, [ isEnabled ]); }, [ isReadOnly ]);
return isEnabled && <FloatingButton return isReadOnly && <FloatingButton
text={t("edit_button.edit_this_note")} text={t("edit_button.edit_this_note")}
icon="bx bx-pencil" icon="bx bx-pencil"
className={animationClass} className={animationClass}
onClick={() => { onClick={() => enableEditing()}
if (noteContext.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext });
}
}}
/> />
} }

View File

@@ -1,25 +1,26 @@
import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; import { CSSProperties } from "preact/compat";
import { CommandListenerData, EventData, EventNames } from "../../components/app_context"; import { DragData } from "../note_tree";
import { ParentComponent } from "./react_utils";
import SpacedUpdate from "../../services/spaced_update";
import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons"; import { FilterLabelsByType, KeyboardActionNames, OptionNames, RelationNames } from "@triliumnext/commons";
import options, { type OptionValue } from "../../services/options"; import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks";
import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils"; import { ParentComponent } from "./react_utils";
import NoteContext from "../../components/note_context";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import FNote from "../../entities/fnote";
import attributes from "../../services/attributes";
import FBlob from "../../entities/fblob";
import NoteContextAwareWidget from "../note_context_aware_widget";
import { RefObject, VNode } from "preact"; import { RefObject, VNode } from "preact";
import { Tooltip } from "bootstrap"; import { Tooltip } from "bootstrap";
import { CSSProperties } from "preact/compat"; import { ViewMode } from "../../services/link";
import appContext, { CommandListenerData, EventData, EventNames } from "../../components/app_context";
import attributes from "../../services/attributes";
import BasicWidget, { ReactWrappedWidget } from "../basic_widget";
import Component from "../../components/component";
import FBlob from "../../entities/fblob";
import FNote from "../../entities/fnote";
import keyboard_actions from "../../services/keyboard_actions"; import keyboard_actions from "../../services/keyboard_actions";
import Mark from "mark.js"; import Mark from "mark.js";
import { DragData } from "../note_tree"; import NoteContext from "../../components/note_context";
import Component from "../../components/component"; import NoteContextAwareWidget from "../note_context_aware_widget";
import options, { type OptionValue } from "../../services/options";
import protected_session_holder from "../../services/protected_session_holder";
import SpacedUpdate from "../../services/spaced_update";
import toast, { ToastOptions } from "../../services/toast"; import toast, { ToastOptions } from "../../services/toast";
import { ViewMode } from "../../services/link"; import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils";
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) { export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
@@ -701,3 +702,52 @@ export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => v
return () => observer.disconnect(); return () => observer.disconnect();
}, [ callback, ref ]); }, [ callback, ref ]);
} }
/**
* Indicates that the current note is in read-only mode, while an editing mode is available,
* and provides a way to switch to editing mode.
*/
export function useIsNoteReadOnly() {
const {note, noteContext} = useNoteContext();
const [isReadOnly, setIsReadOnly] = useState(false);
const enableEditing = useCallback(() => {
if (noteContext?.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
}
}, [noteContext]);
useEffect(() => {
if (note && noteContext) {
isNoteReadOnly(note, noteContext).then((readOnly) => {
setIsReadOnly(readOnly);
});
}
}, [note, noteContext]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsReadOnly(false);
}
});
return {isReadOnly, enableEditing};
}
async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
if (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) {
return false;
}
if (options.is("databaseReadonly")) {
return false;
}
if (noteContext.viewScope?.viewMode !== "default" || !await noteContext.isReadOnly()) {
return false;
}
return true;
}

View File

@@ -1,16 +1,10 @@
import "./read-only-note-info-bar.css"; import "./read_only_note_info_bar.css";
import { t } from "../services/i18n"; import { t } from "../services/i18n";
import { useCallback, useEffect, useState } from "preact/hooks"; import { useIsNoteReadOnly, useNoteContext, useTriliumEvent } from "./react/hooks"
import { useNoteContext, useTriliumEvent } from "./react/hooks"
import appContext from "../components/app_context";
import Button from "./react/Button"; import Button from "./react/Button";
import FNote from "../entities/fnote";
import NoteContext from "../components/note_context";
import options from "../services/options";
import protected_session_holder from "../services/protected_session_holder";
export default function ReadOnlyNoteInfoBar() { export default function ReadOnlyNoteInfoBar() {
const {isReadOnly, enableEditing} = useIsReadOnly(); const {isReadOnly, enableEditing} = useIsNoteReadOnly();
const {note} = useNoteContext(); const {note} = useNoteContext();
return <div class={`read-only-note-info-bar-widget ${(isReadOnly) ? " visible" : ""}`}> return <div class={`read-only-note-info-bar-widget ${(isReadOnly) ? " visible" : ""}`}>
@@ -34,48 +28,3 @@ export default function ReadOnlyNoteInfoBar() {
</>} </>}
</div>; </div>;
} }
export function useIsReadOnly() {
const {note, noteContext} = useNoteContext();
const [isReadOnly, setIsReadOnly] = useState(false);
const enableEditing = useCallback(() => {
if (noteContext?.viewScope) {
noteContext.viewScope.readOnlyTemporarilyDisabled = true;
appContext.triggerEvent("readOnlyTemporarilyDisabled", {noteContext});
}
}, [noteContext]);
useEffect(() => {
if (note && noteContext) {
isNoteReadOnly(note, noteContext).then((readOnly) => {
setIsReadOnly(readOnly);
});
}
}, [note, noteContext]);
useTriliumEvent("readOnlyTemporarilyDisabled", ({noteContext: eventNoteContext}) => {
if (noteContext?.ntxId === eventNoteContext.ntxId) {
setIsReadOnly(false);
}
});
return {isReadOnly, enableEditing};
}
async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
if (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) {
return false;
}
if (options.is("databaseReadonly")) {
return false;
}
if (noteContext.viewScope?.viewMode !== "default" || !await noteContext.isReadOnly()) {
return false;
}
return true;
}