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;

View File

@ -4,6 +4,7 @@ import appContext, { type NoteCommandData } from "../components/app_context.js";
import froca from "./froca.js";
import utils from "./utils.js";
import { ALLOWED_PROTOCOLS } from "@triliumnext/commons";
import { openInCurrentNoteContext } from "../components/note_context.js";
function getNotePathFromUrl(url: string) {
const notePathMatch = /#(root[A-Za-z0-9_/]*)$/.exec(url);
@ -316,21 +317,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
viewScope
});
} else if (isLeftClick) {
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 });
}
openInCurrentNoteContext(evt, notePath, viewScope);
}
} else if (hrefLink) {
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 "./index.css";
import { RefObject } from "preact";
import { openInCurrentNoteContext } from "../../../components/note_context";
const stylesheets = [
slideBaseStylesheet,
@ -19,6 +20,7 @@ const stylesheets = [
export default function PresentationView({ note }: ViewModeProps<{}>) {
const [ presentation, setPresentation ] = useState<PresentationModel>();
const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<Reveal.Api>(null);
useLayoutEffect(() => {
buildPresentationModel(note).then(setPresentation);
@ -31,29 +33,41 @@ export default function PresentationView({ note }: ViewModeProps<{}>) {
containerRef={containerRef}
>
{stylesheets.map(stylesheet => <style>{stylesheet}</style>)}
<Presentation presentation={presentation} />
<Presentation presentation={presentation} apiRef={apiRef} />
</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 (
<div className="presentation-button-bar">
<ActionButton
icon="bx bx-fullscreen" text="Start presentation"
onClick={() => {
containerRef.current?.requestFullscreen();
icon="bx bx-edit"
text="Edit this slide"
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>
)
}
function Presentation({ presentation } : { presentation: PresentationModel }) {
function Presentation({ presentation, apiRef: externalApiRef } : { presentation: PresentationModel, apiRef: RefObject<Reveal.Api> }) {
const containerRef = useRef<HTMLDivElement>(null);
const apiRef = useRef<Reveal.Api | null>(null);
const apiRef = useRef<Reveal.Api>(null);
useEffect(() => {
if (apiRef.current || !containerRef.current) return;
@ -70,8 +84,9 @@ function Presentation({ presentation } : { presentation: PresentationModel }) {
return true;
},
});
externalApiRef.current = apiRef.current;
apiRef.current.initialize().then(() => {
console.log("Slide.js initialized.");
// Initialization logic.
});
return () => {
@ -97,16 +112,21 @@ function Presentation({ presentation } : { presentation: PresentationModel }) {
function Slide({ slide }: { slide: PresentationSlideModel }) {
if (!slide.verticalSlides) {
// Normal slide.
return <section dangerouslySetInnerHTML={slide.content} />;
return <section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />;
} else {
// Slide with sub notes (show as vertical slides).
return (
<section>
<section dangerouslySetInnerHTML={slide.content} />
<section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />
{slide.verticalSlides.map((slide) => (
<section dangerouslySetInnerHTML={slide.content} />
<section data-note-id={slide.noteId} dangerouslySetInnerHTML={slide.content} />
))}
</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; };
export interface PresentationSlideModel {
content: DangerouslySetInnerHTML;
verticalSlides: PresentationVerticalSlideModel[] | undefined;
/** A top-level slide with optional vertical slides. */
export interface PresentationSlideModel extends PresentationSlideBaseModel {
verticalSlides: PresentationSlideBaseModel[] | undefined;
}
interface PresentationVerticalSlideModel {
/** Either a top-level slide or a vertical slide. */
interface PresentationSlideBaseModel {
noteId: string;
content: DangerouslySetInnerHTML;
}
@ -22,6 +24,7 @@ export async function buildPresentationModel(note: FNote): Promise<PresentationM
for (const slideNote of slideNotes) {
slides.push({
noteId: slideNote.noteId,
content: processContent(await slideNote.getContent() ?? ""),
verticalSlides: await buildVerticalSlides(slideNote)
})
@ -30,13 +33,14 @@ export async function buildPresentationModel(note: FNote): Promise<PresentationM
return { slides };
}
async function buildVerticalSlides(parentSlideNote: FNote): Promise<undefined | PresentationVerticalSlideModel[]> {
async function buildVerticalSlides(parentSlideNote: FNote): Promise<undefined | PresentationSlideBaseModel[]> {
const children = await parentSlideNote.getChildNotes();
if (!children.length) return;
const slides: PresentationVerticalSlideModel[] = [];
const slides: PresentationSlideBaseModel[] = [];
for (const child of children) {
slides.push({
noteId: child.noteId,
content: processContent(await child.getContent())
});
}