feat(react/floating_buttons): port copy image reference

This commit is contained in:
Elian Doran
2025-08-27 23:56:09 +03:00
parent cc362393be
commit 0ca30e0e87
5 changed files with 37 additions and 54 deletions

View File

@@ -1,7 +1,7 @@
import { t } from "./i18n.js";
import toastService, { showError } from "./toast.js";
function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
export function copyImageReferenceToClipboard($imageWrapper: JQuery<HTMLElement>) {
try {
$imageWrapper.attr("contenteditable", "true");
selectImage($imageWrapper.get(0));

View File

@@ -1,5 +1,6 @@
import dayjs from "dayjs";
import type { ViewScope } from "./link.js";
import { FNote } from "./frontend_script_entrypoint.js";
const SVG_MIME = "image/svg+xml";
@@ -574,8 +575,7 @@ function copyHtmlToClipboard(content: string) {
document.removeEventListener("copy", listener);
}
// TODO: Set to FNote once the file is ported.
function createImageSrcUrl(note: { noteId: string; title: string }) {
export function createImageSrcUrl(note: FNote) {
return `api/images/${note.noteId}/${encodeURIComponent(note.title)}?timestamp=${Date.now()}`;
}

View File

@@ -1477,13 +1477,6 @@ div.floating-buttons-children .close-floating-buttons:has(.close-floating-button
padding-inline-start: 8px;
}
/* Copy image reference */
.floating-buttons .copy-image-reference-button .hidden-image-copy {
/* Take out of the the hidden image from flexbox to prevent the layout being affected */
position: absolute;
}
/* Code, relation map buttons */
.floating-buttons .code-buttons-widget,

View File

@@ -5,7 +5,7 @@ import ActionButton, { ActionButtonProps } from "./react/ActionButton";
import FNote from "../entities/fnote";
import NoteContext from "../components/note_context";
import { useNoteContext, useNoteLabel, useNoteLabelBoolean, useNoteProperty, useTriliumEvent, useTriliumEvents, useTriliumOption, useTriliumOptionBool } from "./react/hooks";
import { useContext, useEffect, useMemo, useState } from "preact/hooks";
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { ParentComponent } from "./react/react_utils";
import Component from "../components/component";
import { VNode } from "preact";
@@ -13,11 +13,12 @@ import attributes from "../services/attributes";
import appContext, { EventData, EventNames } from "../components/app_context";
import protected_session_holder from "../services/protected_session_holder";
import options from "../services/options";
import { openInAppHelpFromUrl } from "../services/utils";
import { createImageSrcUrl, openInAppHelpFromUrl } from "../services/utils";
import toast from "../services/toast";
import server from "../services/server";
import { SaveSqlConsoleResponse } from "@triliumnext/commons";
import tree from "../services/tree";
import { copyImageReferenceToClipboard } from "../services/image";
interface FloatingButtonContext {
parentComponent: Component;
@@ -87,6 +88,12 @@ const FLOATING_BUTTON_DEFINITIONS: FloatingButtonDefinition[] = [
{
component: GeoMapButtons,
isEnabled: ({ note }) => note?.getLabelValue("viewType") === "geoMap" && !note.hasLabel("readOnly")
},
{
component: CopyImageReferenceButton,
isEnabled: ({ note, noteContext }) =>
["mermaid", "canvas", "mindMap"].includes(note?.type ?? "")
&& note?.isContentAvailable() && noteContext.viewScope?.viewMode === "default"
}
];
@@ -320,6 +327,31 @@ function GeoMapButtons({ triggerEvent }) {
);
}
function CopyImageReferenceButton({ note }: FloatingButtonContext) {
const hiddenImageCopyRef = useRef<HTMLDivElement>(null);
return (
<>
<FloatingButton
icon="bx bx-copy"
text={t("copy_image_reference_button.button_title")}
onClick={() => {
if (!hiddenImageCopyRef.current) return;
const imageEl = document.createElement("img");
imageEl.src = createImageSrcUrl(note);
hiddenImageCopyRef.current.replaceChildren(imageEl);
copyImageReferenceToClipboard($(hiddenImageCopyRef.current));
hiddenImageCopyRef.current.removeChild(imageEl);
}}
/>
<div ref={hiddenImageCopyRef} className="hidden-image-copy" style={{
position: "absolute" // Take out of the the hidden image from flexbox to prevent the layout being affected
}} />
</>
)
}
function FloatingButton({ className, ...props }: ActionButtonProps) {
return <ActionButton
className={`floating-button ${className ?? ""}`}

View File

@@ -1,42 +0,0 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import utils from "../../services/utils.js";
import imageService from "../../services/image.js";
const TPL = /*html*/`
<button type="button"
class="copy-image-reference-button"
title="${t("copy_image_reference_button.button_title")}">
<span class="bx bx-copy"></span>
<div class="hidden-image-copy"></div>
</button>`;
export default class CopyImageReferenceButton extends NoteContextAwareWidget {
private $hiddenImageCopy!: JQuery<HTMLElement>;
isEnabled() {
return super.isEnabled() && ["mermaid", "canvas", "mindMap"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
}
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$hiddenImageCopy = this.$widget.find(".hidden-image-copy");
this.$widget.on("click", () => {
if (!this.note) {
return;
}
this.$hiddenImageCopy.empty().append($("<img>").attr("src", utils.createImageSrcUrl(this.note)));
imageService.copyImageReferenceToClipboard(this.$hiddenImageCopy);
this.$hiddenImageCopy.empty();
});
this.contentSized();
}
}