feat(collection/presentation): add button to edit slide

This commit is contained in:
Elian Doran 2025-10-15 22:08:04 +03:00
parent 8a85edf2db
commit 502e9b86bc
No known key found for this signature in database
4 changed files with 62 additions and 33 deletions

View File

@ -438,4 +438,22 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
} }
} }
export function openInCurrentNoteContext(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, notePath: string, viewScope?: ViewScope) {
const ntxId = $(evt?.target as any)
.closest("[data-ntx-id]")
.attr("data-ntx-id");
const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext();
if (noteContext) {
noteContext.setNote(notePath, { viewScope }).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
});
} else {
appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true });
}
}
export default NoteContext; export default NoteContext;

View File

@ -4,6 +4,7 @@ import appContext, { type NoteCommandData } from "../components/app_context.js";
import froca from "./froca.js"; import froca from "./froca.js";
import utils from "./utils.js"; import utils from "./utils.js";
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons"; import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
import { openInCurrentNoteContext } from "../components/note_context.js";
function getNotePathFromUrl(url: string) { function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url); const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);
@ -316,21 +317,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
viewScope viewScope
}); });
} else if (isLeftClick) { } else if (isLeftClick) {
const ntxId = $(evt?.target as any) openInCurrentNoteContext(evt, notePath, viewScope);
.closest("[data-ntx-id]")
.attr("data-ntx-id");
const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext();
if (noteContext) {
noteContext.setNote(notePath, { viewScope }).then(() => {
if (noteContext !== appContext.tabManager.getActiveContext()) {
appContext.tabManager.activateNoteContext(noteContext.ntxId);
}
});
} else {
appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true });
}
} }
} else if (hrefLink) { } else if (hrefLink) {
const withinEditLink = $link?.hasClass("ck-link-actions__preview"); const withinEditLink = $link?.hasClass("ck-link-actions__preview");

View File

@ -9,6 +9,7 @@ import ShadowDom from "../../react/ShadowDom";
import ActionButton from "../../react/ActionButton"; import ActionButton from "../../react/ActionButton";
import "./index.css"; import "./index.css";
import { RefObject } from "preact"; import { RefObject } from "preact";
import { openInCurrentNoteContext } from "../../../components/note_context";
const stylesheets = [ const stylesheets = [
slideBaseStylesheet, slideBaseStylesheet,
@ -19,6 +20,7 @@ const stylesheets = [
export default function PresentationView({ note }: ViewModeProps<{}>) { export default function PresentationView({ note }: ViewModeProps<{}>) {
const [ presentation, setPresentation ] = useState<PresentationModel>(); const [ presentation, setPresentation ] = useState<PresentationModel>();
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<Reveal.Api>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
buildPresentationModel(note).then(setPresentation); buildPresentationModel(note).then(setPresentation);
@ -31,29 +33,41 @@ export default function PresentationView({ note }: ViewModeProps<{}>) {
containerRef={containerRef} containerRef={containerRef}
> >
{stylesheets.map(stylesheet => <style>{stylesheet}</style>)} {stylesheets.map(stylesheet => <style>{stylesheet}</style>)}
<Presentation presentation={presentation} /> <Presentation presentation={presentation} apiRef={apiRef} />
</ShadowDom> </ShadowDom>
<ButtonOverlay containerRef={containerRef} /> <ButtonOverlay containerRef={containerRef} apiRef={apiRef} />
</> </>
) )
} }
function ButtonOverlay({ containerRef }: { containerRef: RefObject<HTMLDivElement> }) { function ButtonOverlay({ containerRef, apiRef }: { containerRef: RefObject<HTMLDivElement>, apiRef: RefObject<Reveal.Api> }) {
return ( return (
<div className="presentation-button-bar"> <div className="presentation-button-bar">
<ActionButton <ActionButton
icon="bx bx-fullscreen" text="Start presentation" icon="bx bx-edit"
onClick={() => { text="Edit this slide"
containerRef.current?.requestFullscreen(); onClick={e => {
const currentSlide = apiRef.current?.getCurrentSlide();
const noteId = getNoteIdFromSlide(currentSlide);
if (noteId) {
openInCurrentNoteContext(e, noteId);
}
}} }}
/> />
<ActionButton
icon="bx bx-fullscreen"
text="Start presentation"
onClick={() => containerRef.current?.requestFullscreen()}
/>
</div> </div>
) )
} }
function Presentation({ presentation } : { presentation: PresentationModel }) { function Presentation({ presentation, apiRef: externalApiRef } : { presentation: PresentationModel, apiRef: RefObject<Reveal.Api> }) {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<Reveal.Api | null>(null); const apiRef = useRef<Reveal.Api>(null);
useEffect(() => { useEffect(() => {
if (apiRef.current || !containerRef.current) return; if (apiRef.current || !containerRef.current) return;
@ -70,8 +84,9 @@ function Presentation({ presentation } : { presentation: PresentationModel }) {
return true; return true;
}, },
}); });
externalApiRef.current = apiRef.current;
apiRef.current.initialize().then(() => { apiRef.current.initialize().then(() => {
console.log("Slide.js initialized."); // Initialization logic.
}); });
return () => { return () => {
@ -97,16 +112,21 @@ function Presentation({ presentation } : { presentation: PresentationModel }) {
function Slide({ slide }: { slide: PresentationSlideModel }) { function Slide({ slide }: { slide: PresentationSlideModel }) {
if (!slide.verticalSlides) { if (!slide.verticalSlides) {
// Normal slide. // Normal slide.
return <section dangerouslySetInnerHTML={slide.content} />; return <section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />;
} else { } else {
// Slide with sub notes (show as vertical slides). // Slide with sub notes (show as vertical slides).
return ( return (
<section> <section>
<section dangerouslySetInnerHTML={slide.content} /> <section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />
{slide.verticalSlides.map((slide) => ( {slide.verticalSlides.map((slide) => (
<section dangerouslySetInnerHTML={slide.content} /> <section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />
))} ))}
</section> </section>
) )
} }
} }
function getNoteIdFromSlide(slide: HTMLElement | undefined) {
if (!slide) return;
return slide.dataset.noteId;
}

View File

@ -2,12 +2,14 @@ import FNote from "../../../entities/fnote";
type DangerouslySetInnerHTML = { __html: string; }; type DangerouslySetInnerHTML = { __html: string; };
export interface PresentationSlideModel { /** A top-level slide with optional vertical slides. */
content: DangerouslySetInnerHTML; export interface PresentationSlideModel extends PresentationSlideBaseModel {
verticalSlides: PresentationVerticalSlideModel[] | undefined; verticalSlides: PresentationSlideBaseModel[] | undefined;
} }
interface PresentationVerticalSlideModel { /** Either a top-level slide or a vertical slide. */
interface PresentationSlideBaseModel {
noteId: string;
content: DangerouslySetInnerHTML; content: DangerouslySetInnerHTML;
} }
@ -22,6 +24,7 @@ export async function buildPresentationModel(note: FNote): Promise<PresentationM
for (const slideNote of slideNotes) { for (const slideNote of slideNotes) {
slides.push({ slides.push({
noteId: slideNote.noteId,
content: processContent(await slideNote.getContent() ?? ""), content: processContent(await slideNote.getContent() ?? ""),
verticalSlides: await buildVerticalSlides(slideNote) verticalSlides: await buildVerticalSlides(slideNote)
}) })
@ -30,13 +33,14 @@ export async function buildPresentationModel(note: FNote): Promise<PresentationM
return { slides }; return { slides };
} }
async function buildVerticalSlides(parentSlideNote: FNote): Promise<undefined | PresentationVerticalSlideModel[]> { async function buildVerticalSlides(parentSlideNote: FNote): Promise<undefined | PresentationSlideBaseModel[]> {
const children = await parentSlideNote.getChildNotes(); const children = await parentSlideNote.getChildNotes();
if (!children.length) return; if (!children.length) return;
const slides: PresentationVerticalSlideModel[] = []; const slides: PresentationSlideBaseModel[] = [];
for (const child of children) { for (const child of children) {
slides.push({ slides.push({
noteId: child.noteId,
content: processContent(await child.getContent()) content: processContent(await child.getContent())
}); });
} }