@openpresentation/opf-editor 0.24.0 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -2
- package/dist/agent-handoff-dialog.d.ts +45 -0
- package/dist/agent-handoff-dialog.js +182 -0
- package/dist/agent-handoff.d.ts +33 -0
- package/dist/agent-handoff.js +95 -0
- package/dist/block-controls.js +8 -8
- package/dist/export-dom.js +57 -0
- package/dist/export-menu.d.ts +125 -0
- package/dist/export-menu.js +392 -0
- package/dist/json-editor.d.ts +9 -0
- package/dist/json-editor.js +97 -8
- package/dist/json-options.d.ts +21 -0
- package/dist/json-options.js +127 -27
- package/dist/json-source.js +2 -1
- package/dist/slide-navigator.d.ts +44 -0
- package/dist/slide-navigator.js +191 -0
- package/dist/source-findings.js +27 -0
- package/dist/source-lines.js +206 -0
- package/dist/source-pane.d.ts +90 -0
- package/dist/source-pane.js +669 -0
- package/dist/styles.css +3383 -0
- package/dist/thumbnail-cache.d.ts +25 -0
- package/dist/thumbnail-cache.js +50 -0
- package/dist/thumbnail-strip.d.ts +50 -0
- package/dist/thumbnail-strip.js +310 -0
- package/package.json +44 -7
package/dist/block-controls.js
CHANGED
|
@@ -29,9 +29,9 @@ export function createBlockControls(root, options) {
|
|
|
29
29
|
if(parts.at(-2)!=='blocks'||!Number.isInteger(index)||index<0||!Array.isArray(source)||index>=source.length){options.onError(new Error('Choose an existing content block.'));return;}
|
|
30
30
|
const count=source.length;
|
|
31
31
|
panel=doc.createElement('div');panel.setAttribute('role','dialog');panel.setAttribute('aria-label','Move content block');
|
|
32
|
-
panel.style.cssText='position:absolute;right:8px;bottom:8px;width:280px;max-width:calc(100% - 16px);box-sizing:border-box;padding:14px;background:white;color
|
|
32
|
+
panel.style.cssText='position:absolute;right:8px;bottom:8px;width:280px;max-width:calc(100% - 16px);box-sizing:border-box;padding:14px;background:var(--opf-ui-surface,white);color:var(--opf-ui-fg,#332e40);border:1px solid var(--opf-ui-border,#d5cde5);border-radius:var(--opf-ui-radius-md,8px);box-shadow:var(--opf-ui-shadow-pop,0 5px 24px #28203922);pointer-events:auto;font:12px/1.5 var(--opf-ui-font,system-ui);z-index:3';
|
|
33
33
|
const heading=doc.createElement('strong');heading.textContent='Move content';panel.append(heading);
|
|
34
|
-
const button=(label,handler,disabled=false)=>{const b=doc.createElement('button');b.type='button';b.textContent=label;b.disabled=disabled;b.style.cssText='margin:6px 5px 0 0;padding:5px 8px;border:1px solid
|
|
34
|
+
const button=(label,handler,disabled=false)=>{const b=doc.createElement('button');b.type='button';b.textContent=label;b.disabled=disabled;b.style.cssText='margin:6px 5px 0 0;padding:5px 8px;border:1px solid var(--opf-ui-border,#ded8e8);border-radius:var(--opf-ui-radius-sm,4px);background:var(--opf-ui-muted-2,#faf9fc);color:inherit;cursor:pointer';b.onclick=handler;panel.append(b);return b;};
|
|
35
35
|
button('Earlier',()=>move(path,container,index-1,base),index===0);
|
|
36
36
|
button('Later',()=>move(path,container,index+2,base),index===count-1);
|
|
37
37
|
const label=doc.createElement('label');label.textContent='Destination';label.style.cssText='display:block;margin-top:10px';
|
|
@@ -63,7 +63,7 @@ export function createBlockControls(root, options) {
|
|
|
63
63
|
if(!containers.some(c=>c.path===requested)){options.onError(new Error('Choose a content container on this slide.'));return;}
|
|
64
64
|
if(index!==undefined&&(!Number.isInteger(index)||index<0||index>containers.find(c=>c.path===requested).count)){options.onError(new Error('Insertion index is out of range.'));return;}
|
|
65
65
|
panel=doc.createElement('div');panel.setAttribute('role','dialog');panel.setAttribute('aria-label','Add content');
|
|
66
|
-
panel.style.cssText='position:absolute;right:8px;top:40px;width:300px;max-width:calc(100% - 16px);max-height:calc(100% - 48px);overflow:auto;box-sizing:border-box;padding:14px;background:white;color
|
|
66
|
+
panel.style.cssText='position:absolute;right:8px;top:40px;width:300px;max-width:calc(100% - 16px);max-height:calc(100% - 48px);overflow:auto;box-sizing:border-box;padding:14px;background:var(--opf-ui-surface,white);color:var(--opf-ui-fg,#332e40);border:1px solid var(--opf-ui-border,#d5cde5);border-radius:var(--opf-ui-radius-md,8px);box-shadow:var(--opf-ui-shadow-pop,0 5px 24px #28203922);pointer-events:auto;font:12px/1.5 var(--opf-ui-font,system-ui);z-index:3';
|
|
67
67
|
const heading=doc.createElement('strong');heading.textContent='Add content';panel.append(heading);
|
|
68
68
|
function field(label,node){const wrapper=doc.createElement('label');wrapper.textContent=label;wrapper.style.cssText='display:block;margin-top:10px';node.setAttribute('aria-label',label);node.style.cssText='display:block;box-sizing:border-box;width:100%;padding:6px;margin-top:4px';wrapper.append(node);panel.append(wrapper);return wrapper;}
|
|
69
69
|
const kind=doc.createElement('select');for(const [value,label] of [['text','Text'],['list','List'],['chart','Chart'],['table','Table'],['metric','Metric'],['quote','Quote'],['code','Code'],['timeline','Timeline'],['group','Group'],['image','Image'],['video','Video']]){const option=doc.createElement('option');option.value=value;option.textContent=label;kind.append(option);}field('Content type',kind);
|
|
@@ -74,7 +74,7 @@ export function createBlockControls(root, options) {
|
|
|
74
74
|
const source=doc.createElement('input');source.type='text';const media=field('Media URL or asset reference',source);media.style.display='none';
|
|
75
75
|
const file=doc.createElement('input');file.type='file';file.accept='image/png,image/jpeg,image/webp';const upload=field('Or choose an image',file);upload.style.display='none';
|
|
76
76
|
let loading=false,fileRevision=0;const thisPanel=panel;
|
|
77
|
-
const add=doc.createElement('button');add.type='button';add.textContent='Add';add.style.cssText='margin:12px 6px 0 0;padding:6px 12px;border:1px solid
|
|
77
|
+
const add=doc.createElement('button');add.type='button';add.textContent='Add';add.style.cssText='margin:12px 6px 0 0;padding:6px 12px;border:1px solid var(--opf-ui-border-strong,#bfb1de);border-radius:var(--opf-ui-radius-sm,4px);background:var(--opf-ui-muted,#ede7f9);color:inherit;cursor:pointer';
|
|
78
78
|
const close=doc.createElement('button');close.type='button';close.textContent='Cancel';close.onclick=cancel;close.style.cssText=add.style.cssText;
|
|
79
79
|
const cancelRead=()=>{fileRevision++;loading=false;add.disabled=false;};
|
|
80
80
|
source.oninput=()=>{cancelRead();file.value='';};
|
|
@@ -97,7 +97,7 @@ export function createBlockControls(root, options) {
|
|
|
97
97
|
cancel();layer.replaceChildren();cells=[];
|
|
98
98
|
if(!options.enabled()||options.isEditing())return;
|
|
99
99
|
const add=doc.createElement('button');add.type='button';add.textContent='+ Add content';add.setAttribute('aria-label','Add content');add.dataset.blockAdd='';
|
|
100
|
-
add.style.cssText='position:absolute;right:8px;top:8px;pointer-events:auto;padding:5px 9px;background
|
|
100
|
+
add.style.cssText='position:absolute;right:8px;top:8px;pointer-events:auto;padding:5px 9px;background:var(--opf-ui-muted-2,#f6f3fc);color:var(--opf-ui-accent-strong,#675394);border:1px solid var(--opf-ui-border-strong,#cfc4e7);border-radius:var(--opf-ui-radius-sm,5px);font:12px var(--opf-ui-font,system-ui);cursor:pointer';add.onclick=()=>openInsert();layer.append(add);
|
|
101
101
|
for(const flow of geometry.flows??[]){
|
|
102
102
|
const blocks=getValueAtPath(presentation,[...splitOpfPath(flow.path),'blocks']);
|
|
103
103
|
if(!Array.isArray(blocks)||blocks.length!==flow.itemCount)continue;
|
|
@@ -110,7 +110,7 @@ export function createBlockControls(root, options) {
|
|
|
110
110
|
node.textContent=Array.isArray(block.blocks)?`Group ${index+1} ⋮⋮`:`${index+1} ⋮⋮`;
|
|
111
111
|
node.setAttribute('aria-label',`Move ${Array.isArray(block.blocks)?'group':'block'} ${index+1} in ${flow.path===`slides.${options.slideIndex()}`?'slide':'group'}`);
|
|
112
112
|
node.title='Drag to reorder · Click to move between groups · Arrow keys reorder';
|
|
113
|
-
node.style.cssText=`position:absolute;pointer-events:auto;touch-action:none;cursor:grab;left:calc(${(box.x+box.width)/geometry.width*100}% - ${Array.isArray(block.blocks)?70:38}px);top:calc(${box.y/geometry.height*100}% + ${(depth-1)*23}px);height:22px;min-width:34px;padding:2px 5px;background
|
|
113
|
+
node.style.cssText=`position:absolute;pointer-events:auto;touch-action:none;cursor:grab;left:calc(${(box.x+box.width)/geometry.width*100}% - ${Array.isArray(block.blocks)?70:38}px);top:calc(${box.y/geometry.height*100}% + ${(depth-1)*23}px);height:22px;min-width:34px;padding:2px 5px;background:var(--opf-ui-muted-2,#f6f3fc);color:var(--opf-ui-accent-strong,#675394);border:1px solid var(--opf-ui-border-strong,#cfc4e7);border-radius:var(--opf-ui-radius-sm,4px);font:11px var(--opf-ui-font,system-ui);z-index:${depth}`;
|
|
114
114
|
const cell={path,container,index,box,axis:flow.composition.mode==='column'?'y':'x',node};cells.push(cell);
|
|
115
115
|
node.onclick=()=>open(path);
|
|
116
116
|
node.onkeydown=event=>{if(['ArrowLeft','ArrowUp','ArrowRight','ArrowDown'].includes(event.key)){event.preventDefault();event.stopPropagation();if(!options.beforeEdit())return;const delta=['ArrowLeft','ArrowUp'].includes(event.key)?-1:1;if(index+delta>=0&&index+delta<blocks.length)move(path,container,index+(delta>0?2:-1));}};
|
|
@@ -132,7 +132,7 @@ export function createBlockControls(root, options) {
|
|
|
132
132
|
const node=doc.createElement('button');node.type='button';node.draggable=true;node.dataset.blockPath=path;
|
|
133
133
|
node.textContent=`${index+1} ⋮⋮`;node.setAttribute('aria-label',`Move block ${index+1} from ${item.region} to another region`);
|
|
134
134
|
node.title='Drag to a region marked Drop content here';
|
|
135
|
-
node.style.cssText=`position:absolute;pointer-events:auto;cursor:grab;left:calc(${item.box.x/geometry.width*100}% + 3px);top:calc(${item.box.y/geometry.height*100}% + 3px);padding:2px 5px;background
|
|
135
|
+
node.style.cssText=`position:absolute;pointer-events:auto;cursor:grab;left:calc(${item.box.x/geometry.width*100}% + 3px);top:calc(${item.box.y/geometry.height*100}% + 3px);padding:2px 5px;background:var(--opf-ui-muted-2,#f6f3fc);color:var(--opf-ui-accent-strong,#675394);border:1px solid var(--opf-ui-border-strong,#cfc4e7);border-radius:var(--opf-ui-radius-sm,4px);font:11px var(--opf-ui-font,system-ui)`;
|
|
136
136
|
node.ondragstart=event=>{if(!options.beforeEdit()){event.preventDefault();return;}event.dataTransfer.effectAllowed='move';event.dataTransfer.setData('text/plain',path);};
|
|
137
137
|
layer.append(node);
|
|
138
138
|
}
|
|
@@ -147,7 +147,7 @@ export function createBlockControls(root, options) {
|
|
|
147
147
|
const after=target.axis==='x'?x>target.box.x+target.box.width/2:y>target.box.y+target.box.height/2;
|
|
148
148
|
try{drag.prepared=prepareBlockMove(drag.base,drag.from,target.container,target.index+(after?1:0));}catch(error){options.onError(error);return;}
|
|
149
149
|
const marker=doc.createElement('div');marker.dataset.blockDrop='';const vertical=target.axis==='x',box=target.box;
|
|
150
|
-
marker.style.cssText=`position:absolute;pointer-events:none;background
|
|
150
|
+
marker.style.cssText=`position:absolute;pointer-events:none;background:var(--opf-ui-accent,#7765d0);z-index:10;${vertical?`left:${(box.x+(after?box.width:0))/geometry.width*100}%;top:${box.y/geometry.height*100}%;width:3px;height:${box.height/geometry.height*100}%`:`top:${(box.y+(after?box.height:0))/geometry.height*100}%;left:${box.x/geometry.width*100}%;height:3px;width:${box.width/geometry.width*100}%`}`;layer.append(marker);
|
|
151
151
|
}
|
|
152
152
|
function drop(event){if(!drag)return;event.preventDefault();const prepared=drag.prepared;try{apply(prepared);}catch(error){cancel();options.onError(error);}finally{cancel();options.render();}}
|
|
153
153
|
const escape=event=>{if(event.key==='Escape'&&(drag||panel)){event.preventDefault();event.stopPropagation();cancel();options.render();}};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Shared by the export menu (RR-98) and the agent handoff dialog: the few DOM helpers both use, and the Lucide icons they draw.
|
|
2
|
+
// Lucide (https://lucide.dev) is ISC-licensed, Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT) and
|
|
3
|
+
// Lucide Contributors 2022. The path data below is inlined from lucide-react 1.48.0; nothing is loaded from a CDN. Internal: not a package entry.
|
|
4
|
+
|
|
5
|
+
const SVG_NS = "http://www.w3.org/2000/svg";
|
|
6
|
+
|
|
7
|
+
const ICONS = {
|
|
8
|
+
Download: [["path",{"d":"M12 15V3"}],["path",{"d":"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}],["path",{"d":"m7 10 5 5 5-5"}]],
|
|
9
|
+
ChevronDown: [["path",{"d":"m6 9 6 6 6-6"}]],
|
|
10
|
+
X: [["path",{"d":"M18 6 6 18"}],["path",{"d":"m6 6 12 12"}]],
|
|
11
|
+
Check: [["path",{"d":"M20 6 9 17l-5-5"}]],
|
|
12
|
+
Copy: [["rect",{"width":"14","height":"14","x":"8","y":"8","rx":"2","ry":"2"}],["path",{"d":"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"}]],
|
|
13
|
+
Bot: [["path",{"d":"M12 8V4H8"}],["rect",{"width":"16","height":"12","x":"4","y":"8","rx":"2"}],["path",{"d":"M2 14h2"}],["path",{"d":"M20 14h2"}],["path",{"d":"M15 13v2"}],["path",{"d":"M9 13v2"}]],
|
|
14
|
+
FileText: [["path",{"d":"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{"d":"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{"d":"M10 9H8"}],["path",{"d":"M16 13H8"}],["path",{"d":"M16 17H8"}]],
|
|
15
|
+
FileJson: [["path",{"d":"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{"d":"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{"d":"M10 12a1 1 0 0 0-1 1v1a1 1 0 0 1-1 1 1 1 0 0 1 1 1v1a1 1 0 0 0 1 1"}],["path",{"d":"M14 18a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1 1 1 0 0 1-1-1v-1a1 1 0 0 0-1-1"}]],
|
|
16
|
+
FileCode: [["path",{"d":"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{"d":"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{"d":"M10 12.5 8 15l2 2.5"}],["path",{"d":"m14 12.5 2 2.5-2 2.5"}]],
|
|
17
|
+
FileType: [["path",{"d":"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"}],["path",{"d":"M14 2v5a1 1 0 0 0 1 1h5"}],["path",{"d":"M11 18h2"}],["path",{"d":"M12 12v6"}],["path",{"d":"M9 13v-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v.5"}]],
|
|
18
|
+
Image: [["rect",{"width":"18","height":"18","x":"3","y":"3","rx":"2","ry":"2"}],["circle",{"cx":"9","cy":"9","r":"2"}],["path",{"d":"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"}]],
|
|
19
|
+
Images: [["path",{"d":"m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16"}],["path",{"d":"M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2"}],["circle",{"cx":"13","cy":"7","r":"1","fill":"currentColor"}],["rect",{"x":"8","y":"2","width":"14","height":"14","rx":"2"}]],
|
|
20
|
+
Presentation: [["path",{"d":"M2 3h20"}],["path",{"d":"M21 3v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V3"}],["path",{"d":"m7 21 5-5 5 5"}]],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** An inline Lucide icon as an SVG element. It is decorative (aria-hidden); the control that holds it carries the name. */
|
|
24
|
+
export function icon(doc, name, size = 14) {
|
|
25
|
+
const nodes = ICONS[name];
|
|
26
|
+
const svg = doc.createElementNS(SVG_NS, "svg");
|
|
27
|
+
for (const [key, value] of Object.entries({ width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", focusable: "false", class: "opf-icon" })) svg.setAttribute(key, String(value));
|
|
28
|
+
for (const [tag, attributes] of nodes ?? []) {
|
|
29
|
+
const child = doc.createElementNS(SVG_NS, tag);
|
|
30
|
+
for (const [key, value] of Object.entries(attributes)) child.setAttribute(key, value);
|
|
31
|
+
svg.append(child);
|
|
32
|
+
}
|
|
33
|
+
return svg;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `el(doc, "button", { class, type, "aria-label" }, ...children)`; `text` sets textContent, a false or nullish value leaves the attribute off. */
|
|
37
|
+
export function el(doc, tag, props = {}, ...children) {
|
|
38
|
+
const node = doc.createElement(tag);
|
|
39
|
+
for (const [key, value] of Object.entries(props)) {
|
|
40
|
+
if (value === undefined || value === null || value === false) continue;
|
|
41
|
+
if (key === "class") node.className = value;
|
|
42
|
+
else if (key === "text") node.textContent = value;
|
|
43
|
+
else node.setAttribute(key, value === true ? "" : String(value));
|
|
44
|
+
}
|
|
45
|
+
node.append(...children.filter((child) => child !== null && child !== undefined && child !== false));
|
|
46
|
+
return node;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Save a Blob through a temporary link, the default for every download the export components make. */
|
|
50
|
+
export function saveBlob(doc, blob, fileName) {
|
|
51
|
+
const url = URL.createObjectURL(blob);
|
|
52
|
+
const link = el(doc, "a", { href: url, download: fileName, hidden: true });
|
|
53
|
+
doc.body.append(link);
|
|
54
|
+
link.click();
|
|
55
|
+
link.remove();
|
|
56
|
+
globalThis.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
57
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Catalog } from "@openpresentation/opf";
|
|
2
|
+
import type { ToSvgOptions } from "@openpresentation/opf-render";
|
|
3
|
+
import type { AgentHandoffTarget } from "./agent-handoff.js";
|
|
4
|
+
import type { EditorFonts } from "./canvas.js";
|
|
5
|
+
import type { ConvertOptions } from "./export.js";
|
|
6
|
+
|
|
7
|
+
/** The slide formats, in menu order. `pptx` shows only when `exportPptx` is given (or you list it, and then it is disabled). */
|
|
8
|
+
export type ExportSlideFormat = "pptx" | "pdf" | "svg" | "png";
|
|
9
|
+
/** The OPF source formats: `json` is the document as "Save OPF" writes it, `yaml` and `markdown` are core's `toYaml` and `toMarkdown`. */
|
|
10
|
+
export type ExportSourceFormat = "json" | "yaml" | "markdown";
|
|
11
|
+
/** What `onDownload` is told: a slide or source format, or `"handoff"` for the agent prompt's "Download .md". */
|
|
12
|
+
export type ExportedFormat = ExportSlideFormat | ExportSourceFormat | "handoff";
|
|
13
|
+
|
|
14
|
+
export interface ExportDownload {
|
|
15
|
+
blob: Blob;
|
|
16
|
+
fileName: string;
|
|
17
|
+
format: ExportedFormat;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A note about an export: a plain sentence, or a finding / diagnostic (`slide` is zero-based, as core's Finding defines it). */
|
|
21
|
+
export type ExportNote = string | { message?: string; ruleId?: string; slide?: number; path?: string; severity?: string };
|
|
22
|
+
|
|
23
|
+
export interface ExportPptxOptions {
|
|
24
|
+
/** Aborted by Cancel (or `destroy()`). The menu stops waiting at once even if the exporter ignores it. */
|
|
25
|
+
signal: AbortSignal;
|
|
26
|
+
/** Tell the menu how far along the export is; `message` is shown in the status line. */
|
|
27
|
+
onProgress(progress: { stage?: string; done?: number; total?: number; message?: string }): void;
|
|
28
|
+
}
|
|
29
|
+
export type ExportPptxResult = Uint8Array | ArrayBuffer | Blob | { bytes: Uint8Array | ArrayBuffer | Blob; fileName?: string; warnings?: ExportNote[] };
|
|
30
|
+
|
|
31
|
+
export interface ExportMenuItems {
|
|
32
|
+
/** The slide formats to show, in this order (default all four; `pptx` only when `exportPptx` is set). `false` hides the group. */
|
|
33
|
+
slides?: readonly ExportSlideFormat[] | false;
|
|
34
|
+
/** Default `["json", "yaml", "markdown"]`. `false` hides the group. */
|
|
35
|
+
source?: readonly ExportSourceFormat[] | false;
|
|
36
|
+
/** `false` hides "For an agent". */
|
|
37
|
+
agent?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ExportStatus {
|
|
41
|
+
state: "busy" | "success" | "error" | "cancelled";
|
|
42
|
+
message: string;
|
|
43
|
+
notes: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ExportMenuOptions {
|
|
47
|
+
/** The document to export. Called on every export; the menu exports a `structuredClone` of what it returns. */
|
|
48
|
+
getDeck(): unknown;
|
|
49
|
+
/**
|
|
50
|
+
* Called before an export or the handoff reads the document, for example to commit an open canvas edit. Return `false` to stop.
|
|
51
|
+
*/
|
|
52
|
+
beforeExport?(kind: "slides" | "source" | "agent", format: string): boolean | void;
|
|
53
|
+
/** PDF, PNG and SVG: the renderer's fonts handle (`loadFonts()`), as for the canvas, so preview and export share one set of fonts. */
|
|
54
|
+
fonts?: EditorFonts;
|
|
55
|
+
/** Host catalogs for references and for embedding, or a function that returns them (for example `() => editor.catalogs`). */
|
|
56
|
+
catalogs?: readonly Catalog[] | (() => readonly Catalog[] | undefined);
|
|
57
|
+
/** The options the host's preview renders with. */
|
|
58
|
+
renderOptions?: ToSvgOptions;
|
|
59
|
+
/** More options for `convert` (`export.js`): `scale`, `includeHidden`, `converters`, `text`, ... Not `format`, `signal` or `onProgress`. */
|
|
60
|
+
convertOptions?: Partial<Omit<ConvertOptions, "format" | "signal" | "onProgress">>;
|
|
61
|
+
/**
|
|
62
|
+
* PowerPoint lives in @openpresentation/opf-pptx, which the editor does not import: the host runs `toPptx` here and returns the bytes.
|
|
63
|
+
* Load the fonts the deck needs first, as the host's preview does. Reject to report a failure.
|
|
64
|
+
*/
|
|
65
|
+
exportPptx?(deck: any, options: ExportPptxOptions): Promise<ExportPptxResult> | ExportPptxResult;
|
|
66
|
+
/** Take the file instead of the default Blob-link download. May return a promise; a rejection is reported as a failure. */
|
|
67
|
+
onDownload?(download: ExportDownload): void | Promise<void>;
|
|
68
|
+
/** Called after a file was handed on, with its notes. */
|
|
69
|
+
onExport?(done: ExportDownload & { notes: string[] }): void;
|
|
70
|
+
/** Every change of the status line (busy, success, error, cancelled). Also announced in the menu's `aria-live` region. */
|
|
71
|
+
onStatus?(status: ExportStatus): void;
|
|
72
|
+
/** "Agent handoff": handle it yourself. Default: open the agent handoff dialog. */
|
|
73
|
+
onAgentHandoff?(handoff: { document: unknown; target: AgentHandoffTarget }): void;
|
|
74
|
+
/** The target the handoff dialog starts on (default `"pptx"`). */
|
|
75
|
+
handoffTarget?: AgentHandoffTarget;
|
|
76
|
+
/** Where the default handoff dialog mounts (default `document.body`). */
|
|
77
|
+
handoffContainer?: HTMLElement;
|
|
78
|
+
/** The name the handoff prompt tells the agent to save the deck as (default `deck.opf.json`). */
|
|
79
|
+
handoffFileName?: string;
|
|
80
|
+
specUrl?: string;
|
|
81
|
+
agentGuideUrl?: string;
|
|
82
|
+
/** Hide groups or formats. */
|
|
83
|
+
items?: ExportMenuItems;
|
|
84
|
+
/** `"menu"` (default) is a popover under the trigger; `"sheet"` is the mobile bottom sheet with a scrim, a drag handle and Close. */
|
|
85
|
+
mode?: "menu" | "sheet";
|
|
86
|
+
/** `"touch"` makes controls 40px (sheet rows 44px). Default `"comfortable"` (32px). */
|
|
87
|
+
density?: "comfortable" | "touch";
|
|
88
|
+
/**
|
|
89
|
+
* Disable the slide exports, for example while the JSON is invalid. `true` gives the default reason; a string is your own. The reason is the
|
|
90
|
+
* items' title. Source exports and the handoff stay available.
|
|
91
|
+
*/
|
|
92
|
+
disabled?: boolean | string;
|
|
93
|
+
/** The trigger's text (default "Export"). */
|
|
94
|
+
label?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ExportMenu {
|
|
98
|
+
/** The root element, mounted in the container. */
|
|
99
|
+
element: HTMLElement;
|
|
100
|
+
/** The trigger button. */
|
|
101
|
+
trigger: HTMLButtonElement;
|
|
102
|
+
/** Re-read the document for the menu's hints ("ZIP" or one file). Called on every open as well. */
|
|
103
|
+
refresh(): void;
|
|
104
|
+
open(which?: "first" | "last"): void;
|
|
105
|
+
close(options?: { restoreFocus?: boolean }): void;
|
|
106
|
+
/** `true` disables the slide exports with the default reason, a string with your own, `false`/`undefined` enables them. */
|
|
107
|
+
setDisabled(reason?: boolean | string): void;
|
|
108
|
+
/** Switch between the popover and the bottom sheet, for example at a media query. */
|
|
109
|
+
setMode(mode: "menu" | "sheet"): void;
|
|
110
|
+
setDensity(density: "comfortable" | "touch"): void;
|
|
111
|
+
/** Cancel the export that is running, if any. */
|
|
112
|
+
cancel(): void;
|
|
113
|
+
/** Cancel a running export and remove the menu and any handoff dialog it opened. */
|
|
114
|
+
destroy(): void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** The reason `setDisabled(true)` gives. */
|
|
118
|
+
export declare const EXPORT_DISABLED_REASON: string;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The Export menu: a trigger button and a grouped `role="menu"` (Slides, OPF source, For an agent). Slides use this package's headless
|
|
122
|
+
* `convert`; PowerPoint comes from `exportPptx`; source formats use core's `toYaml` and `toMarkdown`. Progress, Cancel and a success or
|
|
123
|
+
* failure line show under the trigger and are announced through `aria-live`. CSS: `src/styles/export-menu.css`, classes `opf-export-*`.
|
|
124
|
+
*/
|
|
125
|
+
export declare function createExportMenu(container: HTMLElement, options: ExportMenuOptions): ExportMenu;
|