Compare commits

..

3 Commits

Author SHA1 Message Date
Elian Doran
47a7fb5708 feat(client/mermaid): integrate same pan/zoom 2026-01-05 22:11:07 +02:00
Elian Doran
a7c0c52610 feat(client/image): integrate a better image viewer 2026-01-05 22:00:47 +02:00
Elian Doran
51513d3779 fix(status_bar): count not refreshing properly after change 2026-01-05 21:03:32 +02:00
8 changed files with 75 additions and 53 deletions

View File

@@ -62,6 +62,7 @@
"preact": "10.28.1",
"react-i18next": "16.5.1",
"react-window": "2.2.3",
"react-zoom-pan-pinch": "3.7.0",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",

View File

@@ -154,7 +154,6 @@ export type CommandMappings = {
};
openInTab: ContextMenuCommandData;
openNoteInSplit: ContextMenuCommandData;
openNoteInWindow: ContextMenuCommandData;
openNoteInPopup: ContextMenuCommandData;
toggleNoteHoisting: ContextMenuCommandData;
insertNoteAfter: ContextMenuCommandData;

View File

@@ -79,7 +79,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const items: (MenuItem<TreeCommandNames> | null)[] = [
{ title: t("tree-context-menu.open-in-a-new-tab"), command: "openInTab", shortcut: "Ctrl+Click", uiIcon: "bx bx-link-external", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-a-new-window"), command: "openNoteInWindow", uiIcon: "bx bx-window-open", enabled: noSelectedNotes },
{ title: t("tree-context-menu.open-in-popup"), command: "openNoteInPopup", uiIcon: "bx bx-edit", enabled: noSelectedNotes },
isHoisted
@@ -310,11 +309,6 @@ export default class TreeContextMenu implements SelectMenuItemEventListener<Tree
const { ntxId } = subContexts?.[subContexts.length - 1] ?? {};
this.treeWidget.triggerCommand("openNewNoteSplit", { ntxId, notePath });
} else if (command === "openNoteInWindow") {
appContext.triggerCommand("openInWindow", {
notePath,
hoistedNoteId: appContext.tabManager.getActiveContext()?.hoistedNoteId
});
} else if (command === "openNoteInPopup") {
appContext.triggerCommand("openInPopup", { noteIdOrPath: notePath })
} else if (command === "convertNoteToAttachment") {

View File

@@ -1643,7 +1643,6 @@
"tree-context-menu": {
"open-in-a-new-tab": "Open in a new tab",
"open-in-a-new-split": "Open in a new split",
"open-in-a-new-window": "Open in a new window",
"insert-note-after": "Insert note after",
"insert-child-note": "Insert child note",
"archive": "Archive",

View File

@@ -5,7 +5,7 @@ import { Dropdown as BootstrapDropdown } from "bootstrap";
import clsx from "clsx";
import { type ComponentChildren, RefObject } from "preact";
import { createPortal } from "preact/compat";
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { CommandNames } from "../../components/app_context";
import NoteContext from "../../components/note_context";
@@ -338,15 +338,19 @@ interface AttributesProps extends StatusBarContext {
function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) {
const [ count, setCount ] = useState(note.attributes.length);
const refreshCount = useCallback((note: FNote) => {
return note.getAttributes().filter(a => !a.isAutoLink).length;
}, []);
// React to note changes.
useEffect(() => {
setCount(note.getAttributes().filter(a => !a.isAutoLink).length);
}, [ note ]);
setCount(refreshCount(note));
}, [ note, refreshCount ]);
// React to changes in count.
useTriliumEvent("entitiesReloaded", (({loadResults}) => {
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
setCount(note.attributes.length);
setCount(refreshCount(note));
}
}));

View File

@@ -1,29 +1,20 @@
import "./Image.css";
import { useEffect, useRef, useState } from "preact/hooks";
import { TransformComponent,TransformWrapper } from "react-zoom-pan-pinch";
import image_context_menu from "../../menus/image_context_menu";
import { copyImageReferenceToClipboard } from "../../services/image";
import { createImageSrcUrl } from "../../services/utils";
import { useTriliumEvent, useUniqueName } from "../react/hooks";
import "./Image.css";
import { TypeWidgetProps } from "./type_widget";
import WheelZoom from 'vanilla-js-wheel-zoom';
import image_context_menu from "../../menus/image_context_menu";
import { refToJQuerySelector } from "../react/react_utils";
import { copyImageReferenceToClipboard } from "../../services/image";
import { TypeWidgetProps } from "./type_widget";
export default function Image({ note, ntxId }: TypeWidgetProps) {
const uniqueId = useUniqueName("image");
const containerRef = useRef<HTMLDivElement>(null);
const [ refreshCounter, setRefreshCounter ] = useState(0);
// Set up pan & zoom
useEffect(() => {
const zoomInstance = WheelZoom.create(`#${uniqueId}`, {
maxScale: 50,
speed: 1.3,
zoomOnClick: false
});
return () => zoomInstance.destroy();
}, [ note ]);
// Set up context menu
useEffect(() => image_context_menu.setupContextMenu(refToJQuerySelector(containerRef)), []);
@@ -42,11 +33,23 @@ export default function Image({ note, ntxId }: TypeWidgetProps) {
return (
<div ref={containerRef} className="note-detail-image-wrapper">
<img
id={uniqueId}
className="note-detail-image-view"
src={createImageSrcUrl(note)}
/>
<TransformWrapper
initialScale={1}
centerOnInit
>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%"
}}
>
<img
id={uniqueId}
className="note-detail-image-view"
src={createImageSrcUrl(note)}
/>
</TransformComponent>
</TransformWrapper>
</div>
)
);
}

View File

@@ -1,13 +1,15 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server";
import svgPanZoom from "svg-pan-zoom";
import { RefObject } from "preact";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import utils from "../../../services/utils";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import svgPanZoom from "svg-pan-zoom";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import utils from "../../../services/utils";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import { RawHtmlBlock } from "../../react/RawHtml";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/**
@@ -117,11 +119,20 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
onContentChanged={onContentChanged}
dataSaved={onSave}
previewContent={(
<RawHtmlBlock
className="render-container"
containerRef={containerRef}
html={svg}
/>
<TransformWrapper>
<TransformComponent
wrapperStyle={{
width: "100%",
height: "100%"
}}
>
<RawHtmlBlock
className="render-container"
containerRef={containerRef}
html={svg}
/>
</TransformComponent>
</TransformWrapper>
)}
previewButtons={
<>
@@ -144,7 +155,7 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
}
{...props}
/>
)
);
}
function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) {
@@ -181,7 +192,7 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
lastPanZoom.current = {
pan: zoomInstance.getPan(),
zoom: zoomInstance.getZoom()
}
};
zoomRef.current = undefined;
zoomInstance.destroy();
};

19
pnpm-lock.yaml generated
View File

@@ -295,6 +295,9 @@ importers:
react-window:
specifier: 2.2.3
version: 2.2.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-zoom-pan-pinch:
specifier: 3.7.0
version: 3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
reveal.js:
specifier: 5.2.1
version: 5.2.1
@@ -12144,6 +12147,13 @@ packages:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react-zoom-pan-pinch@3.7.0:
resolution: {integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
react: '*'
react-dom: '*'
react@16.14.0:
resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==}
engines: {node: '>=0.10.0'}
@@ -15361,8 +15371,6 @@ snapshots:
'@ckeditor/ckeditor5-core': 47.3.0
'@ckeditor/ckeditor5-utils': 47.3.0
ckeditor5: 47.3.0
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
dependencies:
@@ -15428,8 +15436,6 @@ snapshots:
'@ckeditor/ckeditor5-utils': 47.3.0
'@ckeditor/ckeditor5-watchdog': 47.3.0
es-toolkit: 1.39.5
transitivePeerDependencies:
- supports-color
'@ckeditor/ckeditor5-dev-build-tools@54.2.3(@swc/helpers@0.5.17)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
@@ -28467,6 +28473,11 @@ snapshots:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react-zoom-pan-pinch@3.7.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
dependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
react@16.14.0:
dependencies:
loose-envify: 1.4.0