fika-editor 3.2.8 → 3.2.10
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/dist/embed/agentic-manifest.json +42 -6
- package/dist/embed/fika-embed.js +4 -4
- package/dist/layout/types/slides.js +1 -0
- package/dist/layout/utils/resizeDeck.js +252 -0
- package/dist/types/configs/designThemes.d.ts +24 -0
- package/dist/types/configs/theme.d.ts +3 -1
- package/dist/types/embed/agentic/themeFormatting.d.ts +8 -0
- package/dist/types/embed/agentic/types.d.ts +28 -0
- package/dist/types/embed/index.d.ts +3 -0
- package/dist/types/embed/types.d.ts +5 -0
- package/dist/types/hooks/useResizeDeck.d.ts +1 -0
- package/dist/types/utils/applyDesignTheme.d.ts +8 -0
- package/dist/types/utils/designColorRoles.d.ts +10 -0
- package/dist/types/utils/resizeDeck.d.ts +11 -0
- package/docs/AGENTIC_BRIDGE.md +9 -1
- package/docs/EMBED.md +63 -0
- package/package.json +11 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
function bounds(el) {
|
|
2
|
+
if (el.type === 'line') {
|
|
3
|
+
const points = [el.start, el.end, el.broken, el.broken2, el.curve, ...(el.cubic ?? [])].filter((p) => !!p);
|
|
4
|
+
return { left: el.left + Math.min(...points.map(p => p[0])), right: el.left + Math.max(...points.map(p => p[0])), top: el.top + Math.min(...points.map(p => p[1])), bottom: el.top + Math.max(...points.map(p => p[1])) };
|
|
5
|
+
}
|
|
6
|
+
const angle = (el.rotate ?? 0) * Math.PI / 180;
|
|
7
|
+
const w = Math.abs(el.width * Math.cos(angle)) + Math.abs(el.height * Math.sin(angle));
|
|
8
|
+
const h = Math.abs(el.width * Math.sin(angle)) + Math.abs(el.height * Math.cos(angle));
|
|
9
|
+
return { left: el.left + (el.width - w) / 2, right: el.left + (el.width + w) / 2, top: el.top + (el.height - h) / 2, bottom: el.top + (el.height + h) / 2 };
|
|
10
|
+
}
|
|
11
|
+
function union(a, b) {
|
|
12
|
+
return { left: Math.min(a.left, b.left), right: Math.max(a.right, b.right), top: Math.min(a.top, b.top), bottom: Math.max(a.bottom, b.bottom) };
|
|
13
|
+
}
|
|
14
|
+
function isBackdrop(el, canvas) {
|
|
15
|
+
return el.type === 'shape' && !el.text && !el.rotate && !el.fixedRatio && el.left <= 1 && el.top <= 1 && el.width >= canvas.width - 2 && el.height >= canvas.height - 2;
|
|
16
|
+
}
|
|
17
|
+
function isDecor(el, canvas) {
|
|
18
|
+
return el.type === 'shape' && !el.text && !el.groupId && (el.name === 'Decor' || el.left < -1 || el.top < -1 || el.left + el.width > canvas.width + 1 || el.top + el.height > canvas.height + 1);
|
|
19
|
+
}
|
|
20
|
+
function edgeBand(el, canvas) {
|
|
21
|
+
if (el.type !== 'shape' || el.text || el.groupId || el.rotate || el.fixedRatio)
|
|
22
|
+
return;
|
|
23
|
+
if (Math.abs(el.top) <= 1 && Math.abs(el.height - canvas.height) <= 2 && el.width < canvas.width * 0.3
|
|
24
|
+
&& (Math.abs(el.left) <= 1 || Math.abs(el.left + el.width - canvas.width) <= 1))
|
|
25
|
+
return 'vertical';
|
|
26
|
+
if (Math.abs(el.left) <= 1 && Math.abs(el.width - canvas.width) <= 2 && el.height < canvas.height * 0.2
|
|
27
|
+
&& (Math.abs(el.top) <= 1 || Math.abs(el.top + el.height - canvas.height) <= 1))
|
|
28
|
+
return 'horizontal';
|
|
29
|
+
}
|
|
30
|
+
/** Piecewise affine map: occupied tracks retain their scale; only empty gutters expand.
|
|
31
|
+
* Group bounds occupy a single track, so labels never detach from their diagram.
|
|
32
|
+
*/
|
|
33
|
+
function axisTracks(intervals, source) {
|
|
34
|
+
const merged = [];
|
|
35
|
+
for (const [start, end] of intervals.sort((a, b) => a[0] - b[0])) {
|
|
36
|
+
const lo = Math.max(0, start), hi = Math.min(source, end);
|
|
37
|
+
if (hi <= lo)
|
|
38
|
+
continue;
|
|
39
|
+
const last = merged.at(-1);
|
|
40
|
+
if (last && lo <= last[1] + 1)
|
|
41
|
+
last[1] = Math.max(last[1], hi);
|
|
42
|
+
else
|
|
43
|
+
merged.push([lo, hi]);
|
|
44
|
+
}
|
|
45
|
+
const gaps = [];
|
|
46
|
+
let cursor = 0;
|
|
47
|
+
for (const [lo, hi] of merged) {
|
|
48
|
+
if (lo > cursor)
|
|
49
|
+
gaps.push([cursor, lo]);
|
|
50
|
+
cursor = hi;
|
|
51
|
+
}
|
|
52
|
+
if (cursor < source)
|
|
53
|
+
gaps.push([cursor, source]);
|
|
54
|
+
const free = gaps.reduce((sum, [lo, hi]) => sum + hi - lo, 0);
|
|
55
|
+
return { gaps, free };
|
|
56
|
+
}
|
|
57
|
+
function axisMap(intervals, source, target, scale) {
|
|
58
|
+
const { gaps, free } = axisTracks(intervals, source);
|
|
59
|
+
const extra = target - source * scale;
|
|
60
|
+
return (position) => position * scale + (free > 0
|
|
61
|
+
? extra * gaps.reduce((sum, [lo, hi]) => sum + Math.max(0, Math.min(position, hi) - lo), 0) / free
|
|
62
|
+
: extra / 2);
|
|
63
|
+
}
|
|
64
|
+
/** Recognize a full-width heading above independent body columns. Each column
|
|
65
|
+
* gets its own vertical tracks; a long item in the left column cannot prevent
|
|
66
|
+
* the right column from distributing its rows through the available height.
|
|
67
|
+
*/
|
|
68
|
+
function bodyColumns(boxes, from) {
|
|
69
|
+
if (boxes.length < 3)
|
|
70
|
+
return;
|
|
71
|
+
const top = Math.min(...boxes.map(b => b.top));
|
|
72
|
+
const headers = boxes.filter(b => Math.abs(b.top - top) < 1 && b.right - b.left >= from.width * 0.55 && b.bottom <= from.height * 0.26);
|
|
73
|
+
if (!headers.length)
|
|
74
|
+
return;
|
|
75
|
+
const headerBottom = Math.max(...headers.map(b => b.bottom));
|
|
76
|
+
const body = boxes.filter(b => !headers.includes(b));
|
|
77
|
+
if (body.some(b => b.top < headerBottom))
|
|
78
|
+
return;
|
|
79
|
+
const bodyTop = Math.min(...body.map(b => b.top));
|
|
80
|
+
const ranges = [];
|
|
81
|
+
for (const box of [...body].sort((a, b) => a.left - b.left)) {
|
|
82
|
+
const last = ranges.at(-1);
|
|
83
|
+
if (last && box.left <= last.right + from.width * 0.025) {
|
|
84
|
+
last.right = Math.max(last.right, box.right);
|
|
85
|
+
last.boxes.push(box);
|
|
86
|
+
}
|
|
87
|
+
else
|
|
88
|
+
ranges.push({ left: box.left, right: box.right, boxes: [box] });
|
|
89
|
+
}
|
|
90
|
+
if (ranges.length > 4)
|
|
91
|
+
return;
|
|
92
|
+
const margin = Math.min(from.height * 0.06, Math.max(top, from.height * 0.03));
|
|
93
|
+
const columns = ranges.map(range => {
|
|
94
|
+
const height = Math.max(...range.boxes.map(b => b.bottom)) - bodyTop;
|
|
95
|
+
const intervals = range.boxes.map(b => [b.top - bodyTop, b.bottom - bodyTop]);
|
|
96
|
+
const { free } = axisTracks(intervals, height);
|
|
97
|
+
return { ...range, height, intervals, minHeight: height - free + Math.min(free, height * 0.08) };
|
|
98
|
+
});
|
|
99
|
+
return { bodyTop, margin, columns, minHeight: bodyTop + margin + Math.max(...columns.map(c => c.minHeight)) };
|
|
100
|
+
}
|
|
101
|
+
function scalePixels(html, scale) {
|
|
102
|
+
// CSS lengths only: do not rewrite lesson text containing quantities in px.
|
|
103
|
+
return html.replace(/style=(['"])(.*?)\1/gi, (_, quote, css) => `style=${quote}${css.replace(/(-?\d+(?:\.\d+)?)px\b/g, (_m, n) => `${Number((Number(n) * scale).toFixed(4))}px`)}${quote}`);
|
|
104
|
+
}
|
|
105
|
+
function scaleNumber(record, key, scale) {
|
|
106
|
+
const obj = record;
|
|
107
|
+
if (typeof obj[key] === 'number')
|
|
108
|
+
obj[key] = obj[key] * scale;
|
|
109
|
+
}
|
|
110
|
+
function scaleTypography(el, scale) {
|
|
111
|
+
if (scale === 1)
|
|
112
|
+
return;
|
|
113
|
+
const text = el.type === 'text' ? el : el.type === 'shape' ? el.text : undefined;
|
|
114
|
+
if (text) {
|
|
115
|
+
text.content = scalePixels(text.content, scale);
|
|
116
|
+
text.inset = (text.inset ?? [10, 10, 10, 10]).map(v => v * scale);
|
|
117
|
+
text.paragraphSpace = (text.paragraphSpace ?? 5);
|
|
118
|
+
for (const key of ['wordSpace', 'paragraphSpace', 'placeholderFontSize', 'placeholderLayoutHeight'])
|
|
119
|
+
scaleNumber(text, key, scale);
|
|
120
|
+
if (el.type === 'text' && el.structuredText) {
|
|
121
|
+
// HTML is the source of truth; discard the stale import cache.
|
|
122
|
+
delete el.structuredText;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (el.type === 'table') {
|
|
126
|
+
el.cellMinHeight *= scale;
|
|
127
|
+
for (const row of el.data)
|
|
128
|
+
for (const cell of row) {
|
|
129
|
+
cell.text = scalePixels(cell.text, scale);
|
|
130
|
+
if (cell.style?.fontsize)
|
|
131
|
+
cell.style.fontsize = cell.style.fontsize.replace(/^(\d+(?:\.\d+)?)(px|pt)$/i, (_, value, unit) => `${Number(value) * scale}${unit}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (el.type === 'chart')
|
|
135
|
+
el.options = { ...el.options, fontSize: (el.options?.fontSize ?? 12) * scale };
|
|
136
|
+
if (el.type === 'code')
|
|
137
|
+
el.fontSize *= scale;
|
|
138
|
+
if (el.type === 'latex')
|
|
139
|
+
el.strokeWidth *= scale;
|
|
140
|
+
if ('outline' in el && el.outline)
|
|
141
|
+
scaleNumber(el.outline, 'width', scale);
|
|
142
|
+
if ('shadow' in el && el.shadow)
|
|
143
|
+
for (const key of ['h', 'v', 'blur'])
|
|
144
|
+
scaleNumber(el.shadow, key, scale);
|
|
145
|
+
}
|
|
146
|
+
/** Resize a deck without distorting media or changing reading order.
|
|
147
|
+
* Uniformly scale content and typography, redistribute available space through
|
|
148
|
+
* empty tracks, and keep decorative edge shapes attached to their canvas edge.
|
|
149
|
+
* Overlaps, rotations, z-order and grouped compositions remain intact.
|
|
150
|
+
*/
|
|
151
|
+
export function resizeDeckSlides(slides, from, to) {
|
|
152
|
+
for (const n of [from.width, from.height, to.width, to.height]) {
|
|
153
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
154
|
+
throw new Error('Canvas dimensions must be finite and positive.');
|
|
155
|
+
}
|
|
156
|
+
const result = structuredClone(slides);
|
|
157
|
+
if (from.width === to.width && from.height === to.height)
|
|
158
|
+
return result;
|
|
159
|
+
for (const slide of result) {
|
|
160
|
+
const groups = new Map();
|
|
161
|
+
for (const el of slide.elements) {
|
|
162
|
+
if ((el.type === 'line' && !el.groupId) || isBackdrop(el, from) || isDecor(el, from) || edgeBand(el, from))
|
|
163
|
+
continue;
|
|
164
|
+
const key = el.groupId ?? el.id, box = bounds(el);
|
|
165
|
+
groups.set(key, groups.has(key) ? union(groups.get(key), box) : box);
|
|
166
|
+
}
|
|
167
|
+
const boxes = [...groups.values()];
|
|
168
|
+
const vertical = boxes.map(b => [b.top, b.bottom]);
|
|
169
|
+
const { free } = axisTracks(vertical, from.height);
|
|
170
|
+
// Width sets the preferred type size. Consume spare vertical gutters before
|
|
171
|
+
// shrinking text; reversing a ratio change should not progressively shrink it.
|
|
172
|
+
const minHeight = from.height - free + Math.min(free, from.height * 0.08);
|
|
173
|
+
const body = bodyColumns(boxes, from);
|
|
174
|
+
const scale = Math.min(to.width / from.width, to.height / Math.max(1, body?.minHeight ?? minHeight));
|
|
175
|
+
const x = axisMap(boxes.map(b => [b.left, b.right]), from.width, to.width, scale);
|
|
176
|
+
const y = axisMap(boxes.map(b => [b.top, b.bottom]), from.height, to.height, scale);
|
|
177
|
+
const columnMaps = body?.columns.map(column => ({ ...column,
|
|
178
|
+
map: axisMap(column.intervals, column.height, Math.max(1, to.height - (body.bodyTop + body.margin) * scale), scale),
|
|
179
|
+
}));
|
|
180
|
+
const mapY = (px, py) => {
|
|
181
|
+
if (!body || !columnMaps)
|
|
182
|
+
return y(py);
|
|
183
|
+
if (py < body.bodyTop - 1)
|
|
184
|
+
return py * scale;
|
|
185
|
+
const column = columnMaps.find(c => px >= c.left - 1 && px <= c.right + 1);
|
|
186
|
+
return column ? body.bodyTop * scale + column.map(py - body.bodyTop) : y(py);
|
|
187
|
+
};
|
|
188
|
+
for (const el of slide.elements) {
|
|
189
|
+
if (el.type === 'shape' && isBackdrop(el, from)) {
|
|
190
|
+
el.left = 0;
|
|
191
|
+
el.top = 0;
|
|
192
|
+
el.width = to.width;
|
|
193
|
+
el.height = to.height;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const band = edgeBand(el, from);
|
|
197
|
+
if (el.type === 'shape' && band) {
|
|
198
|
+
if (band === 'vertical') {
|
|
199
|
+
el.width *= scale;
|
|
200
|
+
el.height = to.height;
|
|
201
|
+
el.top = 0;
|
|
202
|
+
el.left = el.left < from.width / 2 ? 0 : to.width - el.width;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
el.height *= scale;
|
|
206
|
+
el.width = to.width;
|
|
207
|
+
el.left = 0;
|
|
208
|
+
el.top = el.top < from.height / 2 ? 0 : to.height - el.height;
|
|
209
|
+
}
|
|
210
|
+
scaleTypography(el, scale);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const left = el.left, top = el.top;
|
|
214
|
+
if (el.type === 'line') {
|
|
215
|
+
// Remap connector geometry with the same tracks as its attached objects.
|
|
216
|
+
const group = el.groupId ? groups.get(el.groupId) : undefined;
|
|
217
|
+
const lineX = group ? (v) => x(group.left) + (v - group.left) * scale : x;
|
|
218
|
+
const lineY = group ? (_px, v) => mapY(group.left, group.top) + (v - group.top) * scale : mapY;
|
|
219
|
+
el.left = lineX(left);
|
|
220
|
+
el.top = lineY(left, top);
|
|
221
|
+
el.width *= scale;
|
|
222
|
+
const point = (p) => [lineX(left + p[0]) - el.left, lineY(left + p[0], top + p[1]) - el.top];
|
|
223
|
+
el.start = point(el.start);
|
|
224
|
+
el.end = point(el.end);
|
|
225
|
+
if (el.broken)
|
|
226
|
+
el.broken = point(el.broken);
|
|
227
|
+
if (el.broken2)
|
|
228
|
+
el.broken2 = point(el.broken2);
|
|
229
|
+
if (el.curve)
|
|
230
|
+
el.curve = point(el.curve);
|
|
231
|
+
if (el.cubic)
|
|
232
|
+
el.cubic = [point(el.cubic[0]), point(el.cubic[1])];
|
|
233
|
+
}
|
|
234
|
+
else if (isDecor(el, from)) {
|
|
235
|
+
const cx = left + el.width / 2, cy = top + el.height / 2;
|
|
236
|
+
el.left = cx > from.width / 2 ? to.width - (from.width - left) * scale : left * scale;
|
|
237
|
+
el.top = cy > from.height / 2 ? to.height - (from.height - top) * scale : top * scale;
|
|
238
|
+
el.width *= scale;
|
|
239
|
+
el.height *= scale;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const box = groups.get(el.groupId ?? el.id);
|
|
243
|
+
el.left = x(box.left) + (left - box.left) * scale;
|
|
244
|
+
el.top = mapY(box.left, box.top) + (top - box.top) * scale;
|
|
245
|
+
el.width *= scale;
|
|
246
|
+
el.height *= scale;
|
|
247
|
+
}
|
|
248
|
+
scaleTypography(el, scale);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type PresetTheme } from './theme';
|
|
2
|
+
/** Host-localized designs displayed in the slide design picker, in supplied order. */
|
|
3
|
+
export type FikaDesignTheme = Omit<PresetTheme, 'id' | 'name'> & {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
/** Undefined restores Fika defaults; [] intentionally hides the preset catalog. */
|
|
8
|
+
export declare function resolveDesignThemes(themes?: readonly FikaDesignTheme[]): readonly PresetTheme[];
|
|
9
|
+
export declare const useDesignThemes: import("zustand").UseBoundStore<import("zustand").StoreApi<{
|
|
10
|
+
themes: readonly PresetTheme[];
|
|
11
|
+
}>>;
|
|
12
|
+
/** Updates the catalog only; never restyles or replaces the host document. */
|
|
13
|
+
export declare function setFikaDesignThemes(themes?: readonly FikaDesignTheme[]): void;
|
|
14
|
+
/** Obtain independent copies of Fika's built-in designs for filtering/extending. */
|
|
15
|
+
export declare function getFikaDefaultDesignThemes(): FikaDesignTheme[];
|
|
16
|
+
/** Small JSON-safe catalog for host/agent discovery, not full document content. */
|
|
17
|
+
export declare function listFikaDesignThemes(): {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
background: string;
|
|
21
|
+
fontColor: string;
|
|
22
|
+
colors: string[];
|
|
23
|
+
}[];
|
|
24
|
+
export declare function getFikaDesignTheme(id: string): PresetTheme;
|
|
@@ -7,6 +7,8 @@ export interface PresetTheme {
|
|
|
7
7
|
background: string;
|
|
8
8
|
fontColor: string;
|
|
9
9
|
featureFontColor?: string;
|
|
10
|
+
/** Retain pale panels and accent roles when restyling host-authored decks. */
|
|
11
|
+
preserveColorRoles?: boolean;
|
|
10
12
|
fontname: string;
|
|
11
13
|
colors: string[];
|
|
12
14
|
/** Series palette when `colors` includes near-background swatches (Ink #171717). */
|
|
@@ -36,7 +38,7 @@ export declare const themeBackgroundCycle: (theme: PresetTheme) => SlideBackgrou
|
|
|
36
38
|
export declare const resolvePresetBackground: (theme: PresetTheme, slideType?: SlideType, index?: number) => SlideBackground;
|
|
37
39
|
export declare const resolvePresetFontColor: (theme: PresetTheme, _slideType?: SlideType) => string;
|
|
38
40
|
export declare const themeChartColors: (theme: Pick<PresetTheme, 'colors' | 'chartColors'>) => string[];
|
|
39
|
-
export declare const matchPresetTheme: (themeColors: string[]) => PresetTheme | undefined;
|
|
41
|
+
export declare const matchPresetTheme: (themeColors: string[], presets?: readonly PresetTheme[]) => PresetTheme | undefined;
|
|
40
42
|
/** Paint a layout preview/insert with the active preset's type-specific background and type. */
|
|
41
43
|
export declare const applyPresetToLayoutSlide: (slide: Slide, theme: PresetTheme, index?: number) => void;
|
|
42
44
|
export declare const sameSlideBackground: (a?: SlideBackground, b?: SlideBackground) => boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Remove only explicitly replaced theme properties from HTML style attributes.
|
|
2
|
+
* Matching whole declarations avoids corrupting background-color / border-color
|
|
3
|
+
* or literal lesson text that happens to contain CSS examples.
|
|
4
|
+
*/
|
|
5
|
+
export declare function stripThemeInlineStyles(content: string, patch: {
|
|
6
|
+
fontColor?: string;
|
|
7
|
+
fontName?: string;
|
|
8
|
+
}): string;
|
|
@@ -598,6 +598,18 @@ export interface FikaAgentDeckApi {
|
|
|
598
598
|
setTitle(title: string, meta?: FikaCommandMeta): Promise<FikaCommandResult<{
|
|
599
599
|
title: string;
|
|
600
600
|
}>>;
|
|
601
|
+
listDesignThemes(): Array<{
|
|
602
|
+
id: string;
|
|
603
|
+
name: string;
|
|
604
|
+
background: string;
|
|
605
|
+
fontColor: string;
|
|
606
|
+
colors: string[];
|
|
607
|
+
}>;
|
|
608
|
+
applyDesignTheme(themeId: string, meta?: FikaCommandMeta): Promise<FikaCommandResult<{
|
|
609
|
+
themeId: string;
|
|
610
|
+
name: string;
|
|
611
|
+
slideCount: number;
|
|
612
|
+
}>>;
|
|
601
613
|
getTheme(): SlideTheme;
|
|
602
614
|
setTheme(theme: FikaSlideThemePatch, meta?: FikaCommandMeta): Promise<FikaCommandResult<SlideTheme>>;
|
|
603
615
|
applyTemplate(templateId: string, meta?: FikaCommandMeta): Promise<FikaCommandResult<FikaApplyTemplateResult>>;
|
|
@@ -1200,6 +1212,10 @@ export interface FikaCommandPayloadMap {
|
|
|
1200
1212
|
'deck.setTitle': {
|
|
1201
1213
|
title: string;
|
|
1202
1214
|
};
|
|
1215
|
+
'deck.listDesignThemes': undefined;
|
|
1216
|
+
'deck.applyDesignTheme': {
|
|
1217
|
+
themeId: string;
|
|
1218
|
+
};
|
|
1203
1219
|
'deck.getTheme': undefined;
|
|
1204
1220
|
'deck.setTheme': {
|
|
1205
1221
|
theme: FikaSlideThemePatch | Partial<SlideTheme>;
|
|
@@ -1892,6 +1908,18 @@ export interface FikaCommandResultDataMap {
|
|
|
1892
1908
|
'deck.setTitle': {
|
|
1893
1909
|
title: string;
|
|
1894
1910
|
};
|
|
1911
|
+
'deck.listDesignThemes': Array<{
|
|
1912
|
+
id: string;
|
|
1913
|
+
name: string;
|
|
1914
|
+
background: string;
|
|
1915
|
+
fontColor: string;
|
|
1916
|
+
colors: string[];
|
|
1917
|
+
}>;
|
|
1918
|
+
'deck.applyDesignTheme': {
|
|
1919
|
+
themeId: string;
|
|
1920
|
+
name: string;
|
|
1921
|
+
slideCount: number;
|
|
1922
|
+
};
|
|
1895
1923
|
'deck.getTheme': SlideTheme;
|
|
1896
1924
|
'deck.setTheme': SlideTheme;
|
|
1897
1925
|
'deck.applyTheme': SlideTheme;
|
|
@@ -10,3 +10,6 @@ export type { FikaMediaConfig, FikaMediaConstraints, FikaMediaKind, FikaMediaSiz
|
|
|
10
10
|
export type * from './agentic/types';
|
|
11
11
|
export type { Locales as FikaLocales } from '../i18n/locale';
|
|
12
12
|
export type { ChartData, ChartOptions, ChartType, Note, NoteReply, PPTAnimation, PPTAudioElement, PPTChartElement, PPTElement, PPTElementLink, PPTImageElement, PPTLatexElement, PPTLineElement, PPTShapeElement, PPTTableElement, PPTTextElement, PPTVideoElement, ShapeText, Slide, SlideBackground, SlideTemplate, SlideTheme, TableCell, TableCellStyle, TextAlign, TurningMode, } from '../types/slides';
|
|
13
|
+
export type { FikaDesignTheme } from '../configs/designThemes';
|
|
14
|
+
export { getFikaDefaultDesignThemes } from '../configs/designThemes';
|
|
15
|
+
export { resizeDeckSlides, type SlideCanvas } from '../utils/resizeDeck';
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FikaDesignTheme } from '../configs/designThemes';
|
|
1
2
|
import type { FikaExportTabsConfig } from '../configs/exportTabs';
|
|
2
3
|
import type { FikaExportMediaResolver } from '../configs/exportMediaResolver';
|
|
3
4
|
import type { FikaExportWatermark, FikaExportWatermarkPosition, FikaExportWatermarkResolver, FikaWatermarkSurface } from '../configs/exportWatermark';
|
|
@@ -63,6 +64,8 @@ export interface FikaHeaderMenuItem {
|
|
|
63
64
|
/** `editor` is the default full editor; `presentation` boots straight into the fullscreen slideshow with no editor chrome. */
|
|
64
65
|
export type FikaViewMode = 'editor' | 'presentation';
|
|
65
66
|
export interface FikaMountOptions {
|
|
67
|
+
/** Replacement design catalog in display order. Omit for Fika defaults; [] hides presets. Does not restyle the loaded document. */
|
|
68
|
+
designThemes?: readonly FikaDesignTheme[];
|
|
66
69
|
/** UI locale (`cs` | `en` | `sk` | `pl`). */
|
|
67
70
|
locale?: Locales;
|
|
68
71
|
/** Initial deck; takes precedence over `loadDocument` and the starter slide. */
|
|
@@ -191,6 +194,8 @@ export interface FikaController extends FikaAgentApi {
|
|
|
191
194
|
importPptx(data: File | Blob | ArrayBuffer, options?: FikaImportPptxOptions): Promise<boolean>;
|
|
192
195
|
setTitle(title: string): void;
|
|
193
196
|
setLocale(locale: Locales): Promise<void>;
|
|
197
|
+
/** Replace the visible design catalog without changing slide content. Undefined restores defaults. */
|
|
198
|
+
setDesignThemes(themes?: readonly FikaDesignTheme[]): void;
|
|
194
199
|
/**
|
|
195
200
|
* Paint one slide offscreen (thumbnail painter, no DOM capture) and return
|
|
196
201
|
* the encoded image. Defaults to the current slide.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function useResizeDeck(): (width: number, height: number) => void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Slide, SlideTheme } from '../types/slides';
|
|
2
|
+
import { type PresetTheme } from '../configs/theme';
|
|
3
|
+
export declare const setSlideTheme: (slide: Slide, theme: PresetTheme, index?: number, sourceAccents?: readonly string[]) => void;
|
|
4
|
+
/** Shared by the design picker and agent command, with no store/history effects. */
|
|
5
|
+
export declare function applyDesignTheme(slides: readonly Slide[], currentTheme: SlideTheme, preset: PresetTheme): {
|
|
6
|
+
slides: Slide[];
|
|
7
|
+
theme: SlideTheme;
|
|
8
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PresetTheme } from '../configs/theme';
|
|
2
|
+
import type { PPTElement, Slide } from '../types/slides';
|
|
3
|
+
/** Keep neutral surfaces, pale panels and saturated accents in distinct roles. */
|
|
4
|
+
export declare function designColorRoles(theme: PresetTheme, sourceAccents: readonly string[]): {
|
|
5
|
+
fill: (color: string) => string;
|
|
6
|
+
ink: (surface: string, original?: string) => string;
|
|
7
|
+
};
|
|
8
|
+
/** Find the painted panel below a text object; preserve white lettering on badges. */
|
|
9
|
+
export declare function textSurface(slide: Slide, el: PPTElement, fallback: string): string;
|
|
10
|
+
export declare function retintInlineText(html: string, surface: string, ink: (surface: string, original?: string) => string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Slide } from '../types/slides';
|
|
2
|
+
export interface SlideCanvas {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
}
|
|
6
|
+
/** Resize a deck without distorting media or changing reading order.
|
|
7
|
+
* Uniformly scale content and typography, redistribute available space through
|
|
8
|
+
* empty tracks, and keep decorative edge shapes attached to their canvas edge.
|
|
9
|
+
* Overlaps, rotations, z-order and grouped compositions remain intact.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resizeDeckSlides(slides: readonly Slide[], from: SlideCanvas, to: SlideCanvas): Slide[];
|
package/docs/AGENTIC_BRIDGE.md
CHANGED
|
@@ -518,4 +518,12 @@ await savePresentationDocument(controller.getDocument())
|
|
|
518
518
|
await controller.executeBatch(commands, { atomic: true })
|
|
519
519
|
```
|
|
520
520
|
|
|
521
|
-
Use `controller.export.json()` for a synchronous serializable snapshot. Use `controller.import.json(document)`, `controller.execute({ type: 'import.json', payload: { document } })`, or `controller.execute({ type: 'export.json' })` when sciobot needs command results for whole-document import/export.
|
|
521
|
+
Use `controller.export.json()` for a synchronous serializable snapshot. Use `controller.import.json(document)`, `controller.execute({ type: 'import.json', payload: { document } })`, or `controller.execute({ type: 'export.json' })` when sciobot needs command results for whole-document import/export.
|
|
522
|
+
### Partial theme application
|
|
523
|
+
|
|
524
|
+
`deck.applyTheme` with `options.applyToSlides: true` applies only the supplied
|
|
525
|
+
properties to existing slide content. For example, `{ theme: { fontName:
|
|
526
|
+
'Calibri' }, options: { applyToSlides: true } }` replaces text/shape/table font
|
|
527
|
+
families without resetting backgrounds, text colors, chart palettes or sizes.
|
|
528
|
+
Inline property removal is restricted to style declarations; background-color,
|
|
529
|
+
border-color and literal CSS examples in lesson text are preserved.
|
package/docs/EMBED.md
CHANGED
|
@@ -479,3 +479,66 @@ The ProseMirror schema preserves hard line breaks and Markdown strikethrough
|
|
|
479
479
|
through storage, static rendering, and editing.
|
|
480
480
|
Pass `assetBaseUrl` to `mountFika()` to resolve lazy editor CSS under the same
|
|
481
481
|
asset directory as the embed stylesheet.
|
|
482
|
+
|
|
483
|
+
### Host-configured slide designs
|
|
484
|
+
|
|
485
|
+
Pass `designThemes` to replace the built-in design cards (not editor chrome).
|
|
486
|
+
The supplied order and host-localized `name` appear in the picker. Custom designs
|
|
487
|
+
also drive layout previews, new layout slides, background looks and chart colors.
|
|
488
|
+
Omit the option for Fika defaults; pass `[]` to hide the preset section.
|
|
489
|
+
Configuring the catalog does not apply a design to the existing document.
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
import { mountFika, getFikaDefaultDesignThemes, type FikaDesignTheme } from 'fika-editor/embed'
|
|
493
|
+
|
|
494
|
+
const paper: FikaDesignTheme = {
|
|
495
|
+
id: 'my-paper', name: 'Paper',
|
|
496
|
+
background: '#FAF8F4', fontColor: '#2B3445', fontname: 'Inter',
|
|
497
|
+
colors: ['#D25F2B', '#2A7FA3', '#5F8A2C'],
|
|
498
|
+
borderColor: '#E3DFD6',
|
|
499
|
+
}
|
|
500
|
+
const { controller } = await mountFika('#editor', { designThemes: [paper] })
|
|
501
|
+
controller.setDesignThemes([{ ...paper, name: 'Papír' }]) // reactive label/catalog update
|
|
502
|
+
controller.setDesignThemes(getFikaDefaultDesignThemes()) // independent copies of defaults
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Design IDs must be nonempty and unique; names and accent palettes are required.
|
|
506
|
+
Use distinct accent palettes so the active design can be recognized after a
|
|
507
|
+
saved document is loaded. Optional `chartColors`, `featureFontColor`,
|
|
508
|
+
`contentBackground`, `featureBackground`, `altBackground` and `backgrounds`
|
|
509
|
+
provide the same appearance controls as built-in designs. Catalogs are runtime
|
|
510
|
+
host configuration, not part of the persisted document. Like Fika's other embed
|
|
511
|
+
configuration and stores, the catalog is shared by the active editor runtime;
|
|
512
|
+
use separate runtimes for independently configured simultaneous editors.
|
|
513
|
+
|
|
514
|
+
Agents can use the same active catalog via `controller.deck.listDesignThemes()`
|
|
515
|
+
(read-only), then apply one by ID with
|
|
516
|
+
`controller.deck.applyDesignTheme('scio-blue')`. The command-bus equivalent is
|
|
517
|
+
`{ type: 'deck.applyDesignTheme', payload: { themeId: 'scio-blue' } }`.
|
|
518
|
+
Application uses the picker implementation and creates one undo step for the
|
|
519
|
+
whole deck. Unknown IDs fail without changing the presentation. Hosts should
|
|
520
|
+
include the small catalog in agent context so applying a named theme needs only
|
|
521
|
+
one call, without a preceding discovery request.
|
|
522
|
+
|
|
523
|
+
### Content-aware canvas resizing
|
|
524
|
+
|
|
525
|
+
`controller.deck.setViewport({ ratio: 0.75 })` fits the entire deck to 4:3 in
|
|
526
|
+
one undo step, using the same operation as the size menu and custom size dialog.
|
|
527
|
+
It identifies full-width headings and independent body columns, distributes
|
|
528
|
+
rows through their available height, scales type with its boxes, preserves media
|
|
529
|
+
proportions and groups, remaps connectors, and anchors vector edge decorations.
|
|
530
|
+
It consumes spare vertical gutters before shrinking type when returning to a
|
|
531
|
+
shorter canvas. It preserves the authored composition rather than inventing a
|
|
532
|
+
new reading order or cropping photographs. Imported objects already outside the
|
|
533
|
+
canvas may still need manual correction.
|
|
534
|
+
|
|
535
|
+
Hosts authoring slides into a differently sized deck can use the pure,
|
|
536
|
+
DOM-free `resizeDeckSlides(slides, from, to)` export from `fika-editor/layout`;
|
|
537
|
+
`from` and `to` are `{ width, height }`. Raw document loading remains available
|
|
538
|
+
through `deck.set` / `deck.patch` with `slides` and `viewport` supplied together.
|
|
539
|
+
|
|
540
|
+
Host design themes can opt into `preserveColorRoles: true` to keep pale panels,
|
|
541
|
+
neutral surfaces and saturated accents separate during theme application.
|
|
542
|
+
Text is retinted against the actual panel beneath it, including badge and table
|
|
543
|
+
cell fills. Named `Decor` shapes become soft accents on the selected background.
|
|
544
|
+
Reapplying a selected theme is supported, including for older generated decks.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fika-editor",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.10",
|
|
4
4
|
"description": "Fika presentation editor embed bundle with a typed agentic bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/embed/fika-embed.js",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"docs/EMBED.md",
|
|
13
13
|
"docs/AGENTIC_BRIDGE.md",
|
|
14
14
|
"docs/RELEASE.md",
|
|
15
|
-
"dist/text"
|
|
15
|
+
"dist/text",
|
|
16
|
+
"dist/layout"
|
|
16
17
|
],
|
|
17
18
|
"exports": {
|
|
18
19
|
".": {
|
|
@@ -35,6 +36,11 @@
|
|
|
35
36
|
"types": "./dist/text/index.d.ts",
|
|
36
37
|
"import": "./dist/text/index.js",
|
|
37
38
|
"default": "./dist/text/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./layout": {
|
|
41
|
+
"types": "./dist/types/utils/resizeDeck.d.ts",
|
|
42
|
+
"import": "./dist/layout/utils/resizeDeck.js",
|
|
43
|
+
"default": "./dist/layout/utils/resizeDeck.js"
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
46
|
"engines": {
|
|
@@ -55,7 +61,7 @@
|
|
|
55
61
|
"dx:start": "1dxway start",
|
|
56
62
|
"doctor": "react-doctor --no-telemetry",
|
|
57
63
|
"build": "run-p type-check \"build-only {@}\" --",
|
|
58
|
-
"build:embed": "node scripts/run-i18n.mjs --no-watch && npm run build:text && npm run build:types && rsbuild build --config rsbuild.config.embed.ts && node scripts/rewrite-embed-base-uri.mjs && node scripts/merge-embed-css.mjs && node scripts/scope-embed-css.mjs && node scripts/extract-embed-fonts.mjs && node scripts/copy-embed-public.mjs && node scripts/generate-agentic-manifest.mjs",
|
|
64
|
+
"build:embed": "node scripts/run-i18n.mjs --no-watch && npm run build:text && npm run build:layout && npm run build:types && rsbuild build --config rsbuild.config.embed.ts && node scripts/rewrite-embed-base-uri.mjs && node scripts/merge-embed-css.mjs && node scripts/scope-embed-css.mjs && node scripts/extract-embed-fonts.mjs && node scripts/copy-embed-public.mjs && node scripts/generate-agentic-manifest.mjs",
|
|
59
65
|
"agentic:manifest": "node scripts/generate-agentic-manifest.mjs",
|
|
60
66
|
"build:types": "node -e \"require('node:fs').rmSync('dist/types',{recursive:true,force:true})\" && tsc -p tsconfig.types.json && node scripts/rewrite-embed-types.mjs",
|
|
61
67
|
"prepack": "node scripts/verify-embed-pack.mjs",
|
|
@@ -69,7 +75,8 @@
|
|
|
69
75
|
"test": "node scripts/test.mjs",
|
|
70
76
|
"i18n": "node scripts/run-i18n.mjs",
|
|
71
77
|
"i18n:build": "node scripts/run-i18n.mjs --no-watch",
|
|
72
|
-
"build:text": "tsc -p tsconfig.text.json"
|
|
78
|
+
"build:text": "tsc -p tsconfig.text.json",
|
|
79
|
+
"build:layout": "tsc -p tsconfig.layout.json"
|
|
73
80
|
},
|
|
74
81
|
"author": "Matěj \"lofcz\" Štágl",
|
|
75
82
|
"license": "MIT",
|