create-pmos 0.493.0 → 0.495.0
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/package.json +99 -27
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/briefing-premium-adddoc-test.js +2 -1
- package/workspace/briefing-premium-board-test.js +5 -4
- package/workspace/briefing-premium-initiative-test.js +4 -2
- package/workspace/changelog.json +98 -0
- package/workspace/guide/briefing-premium.md +4 -1
- package/workspace/guide/claude-proposes.md +2 -2
- package/workspace/guide/data-freshness.md +3 -1
- package/workspace/guide/initiatives-screen.md +3 -1
- package/workspace/guide/report-slides.md +14 -1
- package/workspace/guide/reports.md +2 -0
- package/workspace/guide/workflows.md +15 -3
- package/workspace/guide/your-impact.md +6 -7
- package/workspace/ini-workflow-test.js +2 -1
- package/workspace/lib/board-trail.js +30 -3
- package/workspace/lib/engine/workflows.js +19 -6
- package/workspace/lib/git-shape.js +21 -0
- package/workspace/lib/grill-runs.js +21 -6
- package/workspace/lib/grill.js +1 -1
- package/workspace/lib/impact.js +29 -16
- package/workspace/lib/initiative-cli.js +15 -1
- package/workspace/lib/open-mission.js +14 -0
- package/workspace/lib/page-warm.js +68 -0
- package/workspace/lib/report-design.js +6 -1
- package/workspace/lib/report-export.js +150 -9
- package/workspace/lib/report-pyramid.js +535 -0
- package/workspace/lib/report-structure.js +5 -1
- package/workspace/lib/report-templates.js +111 -20
- package/workspace/lib/routes-boards.js +36 -3
- package/workspace/lib/routes-brief-page.js +21 -3
- package/workspace/lib/routes-grill.js +10 -4
- package/workspace/lib/routes-impact.js +12 -4
- package/workspace/lib/routes-initiatives.js +1 -0
- package/workspace/lib/routes-programs.js +6 -4
- package/workspace/lib/routes-report-design.js +15 -0
- package/workspace/lib/routes-report-flow.js +115 -19
- package/workspace/lib/routes-report-setups.js +4 -2
- package/workspace/lib/routes-report-structure.js +3 -0
- package/workspace/lib/routes-workflows.js +6 -1
- package/workspace/lib/wf-bridge.js +5 -3
- package/workspace/lib/workflow-cli.js +2 -2
- package/workspace/lib/workflows-store.js +15 -1
- package/workspace/metric-drafts-ui-test.js +2 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/premium-board-actions-test.js +2 -1
- package/workspace/premium-chat-report-tab-test.js +5 -1
- package/workspace/premium-vw-backlog-test.js +3 -2
- package/workspace/premium-vw-item-test.js +3 -2
- package/workspace/premium-vw-jira-test.js +3 -2
- package/workspace/program-panel-test.js +2 -1
- package/workspace/public/pm/cozy-home.css +1 -0
- package/workspace/public/pm/cozy-vw-rp.css +12 -0
- package/workspace/public/pm/fetch-share.js +38 -0
- package/workspace/public/pm/home-app.js +24 -4
- package/workspace/public/pm/home-chat.js +53 -11
- package/workspace/public/pm/home-impact.css +23 -13
- package/workspace/public/pm/home-impact.js +49 -32
- package/workspace/public/pm/home-ini.css +2 -0
- package/workspace/public/pm/ini-view.css +55 -0
- package/workspace/public/pm/ini-view.js +7 -4
- package/workspace/public/pm/premium-board.js +1 -0
- package/workspace/public/pm/premium-insights.js +1 -0
- package/workspace/public/pm/premium-report-read.js +25 -6
- package/workspace/public/pm/premium-report-tab.js +69 -14
- package/workspace/public/pm/premium-shell.js +57 -6
- package/workspace/public/pm/premium.css +1 -0
- package/workspace/public/pm/report-flow.js +93 -10
- package/workspace/public/pm/wf-page.css +8 -0
- package/workspace/public/pm/wf-page.js +33 -5
- package/workspace/server.js +24 -3
- package/workspace/skills/report-template.skill +10 -0
- package/workspace/split-merge-ui-test.js +3 -2
- package/workspace/stage-card-test.js +2 -1
- package/workspace/task-press-test.js +2 -1
- package/workspace/test-lists/page-open-speed.json +5 -0
- package/workspace/test-lists/report-pyramid.json +9 -0
- package/workspace/tk-tasks-test.js +2 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/* report-export.js — a report document as a file on this machine: .docx, .pptx, .xlsx or .csv (initiative report-workspace, PRD section 6).
|
|
3
3
|
*
|
|
4
|
-
* exportOf(markdown, fmt)
|
|
4
|
+
* exportOf(markdown, fmt, { look }?) → { ok, fmt, name, mime, buffer } | { ok: false, error } fmt: docx | pptx | xlsx | csv
|
|
5
5
|
* parseReport(markdown) → { title, subtitle, blocks: [{ t: 'h', level, text } | { t: 'p', text } | { t: 'li', text } | { t: 'table', rows }] }
|
|
6
6
|
* sectionsOf(report) → [{ heading, blocks }] the blocks under each level-2 heading
|
|
7
|
-
* docx(report) · pptx(report) · xlsx(report) · csv(report) → Buffer (csv: string)
|
|
7
|
+
* docx(report) · pptx(report, deck?) · xlsx(report) · csv(report) → Buffer (csv: string)
|
|
8
8
|
* zip(entries) · unzip(buffer) → Buffer · [{ name, data }] a minimal zip writer and reader
|
|
9
9
|
*
|
|
10
10
|
* NO DEPENDENCY, NO NETWORK. The Office files are ZIP packages of small XML parts, built here from the report's markdown.
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* WHAT THE MARKDOWN BECOMES. The frontmatter gives the title. A level-2 heading starts a section; a level-3 heading is a
|
|
16
16
|
* sub-heading in it. A pipe table becomes rows. A `- ` line is a bullet, any other line a paragraph. A note marker, the slide data
|
|
17
17
|
* comment and any other HTML comment are dropped: they are for PMOS, not for the reader.
|
|
18
|
-
* .docx headings, paragraphs and bullets .pptx a title slide, then one slide per section (title and bullets)
|
|
18
|
+
* .docx headings, paragraphs and bullets .pptx a title slide, then one slide per section (title and bullets); a cadence draft
|
|
19
|
+
* (lib/report-draft.js) is the Pyramid deck instead (lib/report-pyramid.js, canvas RsA): the same slides as the slide view and the PDF
|
|
19
20
|
* .xlsx one sheet: a table is its rows; otherwise one row for each line, with its section beside it .csv the same rows
|
|
20
21
|
*/
|
|
21
22
|
const zlib = require('zlib');
|
|
@@ -209,7 +210,140 @@ function slideLines(blocks) {
|
|
|
209
210
|
return out;
|
|
210
211
|
}
|
|
211
212
|
|
|
212
|
-
|
|
213
|
+
// ── the Pyramid deck as shapes (lib/report-pyramid.js; canvas RsA): the same slides as the in-app view and the PDF ──────────────────────
|
|
214
|
+
const PX = 9525; // one 1280 x 720 px slide → 12192000 x 6858000 EMU
|
|
215
|
+
const hex = (c) => String(c || '#000000').replace('#', '').toUpperCase().replace(/^([0-9A-F])([0-9A-F])([0-9A-F])$/, '$1$1$2$2$3$3');
|
|
216
|
+
/** One slide of the Pyramid deck as <p:sld> XML: rectangles, shapes and text boxes placed on a 1280 x 720 grid. */
|
|
217
|
+
function pyramidSlideXml(s, L) {
|
|
218
|
+
let id = 1;
|
|
219
|
+
const out = [];
|
|
220
|
+
const E = (v) => Math.round(v * PX);
|
|
221
|
+
const fill = (c) => (c ? `<a:solidFill><a:srgbClr val="${hex(c)}"/></a:solidFill>` : '<a:noFill/>');
|
|
222
|
+
const ln = (c, w, dash) => (c ? `<a:ln w="${Math.round((w || 1) * PX)}">${fill(c)}${dash ? '<a:prstDash val="dash"/>' : ''}</a:ln>` : '<a:ln><a:noFill/></a:ln>');
|
|
223
|
+
const shape = (x, y, w, h, o = {}) => { id++; out.push(`<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${o.name || 'Shape ' + id}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm${o.flip ? ' flipV="1"' : ''}><a:off x="${E(x)}" y="${E(y)}"/><a:ext cx="${E(Math.max(w, 0.5))}" cy="${E(Math.max(h, 0.5))}"/></a:xfrm><a:prstGeom prst="${o.geom || 'rect'}"><a:avLst/></a:prstGeom>${fill(o.fill)}${ln(o.line, o.lw, o.dash)}</p:spPr></p:sp>`); };
|
|
224
|
+
const run = (t, o) => `<a:r><a:rPr lang="en-GB" sz="${Math.round((o.size || 18) * 75)}"${o.bold ? ' b="1"' : ''} dirty="0">${fill(o.color || L.ink)}<a:latin typeface="${xml(L.font)}"/></a:rPr><a:t>${xml(t)}</a:t></a:r>`;
|
|
225
|
+
/** a text box; paras = [string | [[text, opts], …]] */
|
|
226
|
+
const text = (x, y, w, h, paras, o = {}) => {
|
|
227
|
+
id++;
|
|
228
|
+
const ps = (Array.isArray(paras) ? paras : [paras]).map((p) => `<a:p><a:pPr algn="${o.align || 'l'}"/>${(Array.isArray(p) ? p : [[p, {}]]).map(([t, ro]) => run(t, { ...o, ...ro })).join('')}</a:p>`).join('');
|
|
229
|
+
out.push(`<p:sp><p:nvSpPr><p:cNvPr id="${id}" name="${o.name || 'Text ' + id}"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr><a:xfrm><a:off x="${E(x)}" y="${E(y)}"/><a:ext cx="${E(w)}" cy="${E(h)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/></p:spPr><p:txBody><a:bodyPr wrap="square" lIns="0" tIns="0" rIns="0" bIns="0" anchor="${o.anchor || 't'}"><a:normAutofit/></a:bodyPr><a:lstStyle/>${ps}</p:txBody></p:sp>`);
|
|
230
|
+
};
|
|
231
|
+
const navy = s.kind === 'title';
|
|
232
|
+
if (navy) shape(0, 0, 1280, 720, { fill: L.accent, name: 'Background' });
|
|
233
|
+
// the footer: the source of every number on the slide, and the page number
|
|
234
|
+
shape(0, 672, 1280, 1, { fill: navy ? '#4A5571' : L.line, name: 'Footer rule' });
|
|
235
|
+
text(67, 686, 1050, 22, s.source || '', { size: 15, color: navy ? '#C9D3EA' : '#8A8478', name: 'Source' });
|
|
236
|
+
text(1150, 686, 63, 22, String(s.n), { size: 15, color: navy ? '#C9D3EA' : '#8A8478', align: 'r', name: 'Page number' });
|
|
237
|
+
if (navy) {
|
|
238
|
+
text(67, 270, 1000, 22, s.tag.toUpperCase(), { size: 15, bold: true, color: '#C9D3EA' });
|
|
239
|
+
text(67, 300, 1100, 230, s.title, { size: s.size || 64, bold: true, color: '#FFFFFF', anchor: 'b', name: 'Title' });
|
|
240
|
+
text(67, 545, 1000, 70, s.sub, { size: 22, color: '#DDE4F3' });
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
let y = 48;
|
|
244
|
+
if (s.tag) { text(67, y, 900, 22, s.tag.toUpperCase(), { size: 15, bold: true, color: L.muted }); y += 34; }
|
|
245
|
+
text(67, y, 1120, 92, s.title, { size: 35, bold: true, name: 'Title' });
|
|
246
|
+
y += 100;
|
|
247
|
+
shape(67, y, 90, 3, { fill: L.accent, name: 'Rule' });
|
|
248
|
+
y += 30;
|
|
249
|
+
if (s.kind === 'answer') {
|
|
250
|
+
const right = s.ask && s.ask.question;
|
|
251
|
+
if (s.lead) { text(67, y - 8, 1120, 60, s.lead, { size: 18, color: L.soft, name: 'Lead' }); y += 64; }
|
|
252
|
+
s.supports.forEach((x, i) => {
|
|
253
|
+
const yy = y + i * 92;
|
|
254
|
+
if (i) shape(67, yy - 10, right ? 640 : 1146, 1, { fill: L.line });
|
|
255
|
+
shape(67, yy, 35, 35, { geom: 'ellipse', fill: L.accent });
|
|
256
|
+
text(67, yy + 6, 35, 24, String(i + 1), { size: 18, bold: true, color: '#FFFFFF', align: 'ctr' });
|
|
257
|
+
text(121, yy, right ? 586 : 1092, 30, x.head, { size: 22, bold: true });
|
|
258
|
+
text(121, yy + 32, right ? 586 : 1092, 44, x.text, { size: 18, color: L.soft });
|
|
259
|
+
});
|
|
260
|
+
if (right) {
|
|
261
|
+
shape(750, y, 463, 210, { geom: 'roundRect', fill: L.ask, line: L.accent, lw: 3 });
|
|
262
|
+
text(776, y + 22, 411, 20, 'YOUR CALL', { size: 15, bold: true, color: L.accent });
|
|
263
|
+
text(776, y + 50, 411, 110, s.ask.question, { size: 22, bold: true });
|
|
264
|
+
text(776, y + 170, 411, 24, `For ${s.ask.who}${s.ask.by ? ' · by ' + s.ask.by : ''}`, { size: 18, color: L.soft });
|
|
265
|
+
}
|
|
266
|
+
} else if (s.kind === 'scorecard') {
|
|
267
|
+
const cols = s.kr ? [220, 120, 80, 230, 270, 226] : [260, 140, 90, 260, 396];
|
|
268
|
+
const xs = cols.reduce((a, w) => a.concat(a[a.length - 1] + w), [67]);
|
|
269
|
+
const hd = ['INITIATIVE', 'STAGE', 'STATUS', 'DONE OF TOTAL'].concat(s.kr ? [`${s.kr.toUpperCase()} · NOW VS TARGET`] : [], ['WHY, WHAT WE DO']);
|
|
270
|
+
hd.forEach((h, i) => text(xs[i] + 8, y, cols[i] - 12, 20, h, { size: 15, bold: true, color: L.muted }));
|
|
271
|
+
shape(67, y + 28, 1146, 3, { fill: '#D3CCBF' });
|
|
272
|
+
const rowH = 62;
|
|
273
|
+
s.rows.forEach((r, k) => {
|
|
274
|
+
const ry = y + 40 + k * rowH; const mid = ry + rowH / 2 - 12;
|
|
275
|
+
text(xs[0] + 8, mid, cols[0] - 12, 24, r.title, { size: 17, bold: true });
|
|
276
|
+
text(xs[1] + 8, mid, cols[1] - 12, 24, r.stage, { size: 17 });
|
|
277
|
+
const g = { on: ['ellipse', L.on], 'at-risk': ['triangle', L.risk], off: ['rect', L.off] }[r.level];
|
|
278
|
+
shape(xs[2] + 10, mid + 4, 16, 16, g ? { geom: g[0], fill: g[1], name: `Status: ${r.level}` } : { geom: 'ellipse', line: L.unknown, lw: 2, name: 'Status: unknown' });
|
|
279
|
+
if (r.progress.total) {
|
|
280
|
+
shape(xs[3] + 8, mid + 6, 120, 12, { geom: 'roundRect', fill: L.track });
|
|
281
|
+
shape(xs[3] + 8, mid + 6, Math.max(1, (120 * r.progress.done) / r.progress.total), 12, { geom: 'roundRect', fill: L.accent });
|
|
282
|
+
text(xs[3] + 136, mid, 90, 24, [[[`${r.progress.done} of ${r.progress.total}`, { bold: true }], ...(r.progress.delta ? [[` +${r.progress.delta}`, { color: L.on }]] : [])]], { size: 16 });
|
|
283
|
+
} else text(xs[3] + 8, mid, cols[3] - 12, 24, r.progress.label || 'no breakdown', { size: 16, color: L.soft });
|
|
284
|
+
let c = 4;
|
|
285
|
+
if (s.kr) {
|
|
286
|
+
const m = r.measure;
|
|
287
|
+
if (m && m.target != null) {
|
|
288
|
+
const max = m.unit === '%' && Math.max(m.value, m.target) <= 100 ? 100 : Math.max(m.value, m.target, 1) * 1.25;
|
|
289
|
+
const w = 200; const at = (v) => (w * Math.max(0, v)) / max;
|
|
290
|
+
shape(xs[4] + 8, mid - 2, w, 8, { geom: 'roundRect', fill: L.track });
|
|
291
|
+
shape(xs[4] + 8, mid - 4, Math.max(1, at(m.value)), 12, { fill: L.accent });
|
|
292
|
+
shape(xs[4] + 8 + Math.min(at(m.target), w - 3), mid - 9, 3, 22, { line: L.ink, lw: 2 });
|
|
293
|
+
text(xs[4] + 8, mid + 16, cols[4] - 12, 20, [[[`${m.value}${m.unit}`, { bold: true }], [` of ${m.target}${m.unit}`, {}], ...(r.gap ? [[` · ${r.gap}`, { bold: true, color: m.value < m.target ? L.off : L.on }]] : [])]], { size: 14 });
|
|
294
|
+
} else if (m) text(xs[4] + 8, mid, cols[4] - 12, 24, `${m.value}${m.unit}`, { size: 16, bold: true });
|
|
295
|
+
c = 5;
|
|
296
|
+
}
|
|
297
|
+
if (r.comment) text(xs[c] + 8, ry + 6, cols[c] - 12, rowH - 10, r.comment, { size: 15, color: L.soft, anchor: 'ctr' });
|
|
298
|
+
shape(67, ry + rowH, 1146, 1, { fill: L.line });
|
|
299
|
+
});
|
|
300
|
+
} else if (s.kind === 'timeline') {
|
|
301
|
+
const P = require('./report-pyramid.js');
|
|
302
|
+
const DAY = 86400000; const ms = (d) => Date.parse(d + 'T00:00:00Z');
|
|
303
|
+
const from = ms(s.from); const to = Math.max(ms(s.to), from + DAY); const left = 67 + 240; const right = 67 + 1036;
|
|
304
|
+
const X = (d) => left + ((ms(d) - from) / (to - from)) * (right - left);
|
|
305
|
+
const top = y + 30; const rowH = 70; const bottom = top + s.rows.length * rowH;
|
|
306
|
+
const step = Math.max(1, Math.ceil((to - from) / DAY / 6));
|
|
307
|
+
for (let t = from; t <= to; t += step * DAY) { const d = new Date(t).toISOString().slice(0, 10); shape(X(d), top - 6, 1, bottom - top, { fill: L.line }); text(X(d) - 40, y, 80, 20, P.short(d), { size: 15, color: '#8A8478', align: 'ctr' }); }
|
|
308
|
+
s.rows.forEach((r, i) => {
|
|
309
|
+
const ry = top + i * rowH;
|
|
310
|
+
text(67, ry + 8, 230, 26, r.title, { size: 18, bold: true });
|
|
311
|
+
for (const h of r.holds) shape(X(h.from), ry + 6, Math.max(4, X(h.to || s.to) - X(h.from)), 32, { geom: 'roundRect', fill: L.track, name: 'On hold' });
|
|
312
|
+
for (const sp of r.spans) {
|
|
313
|
+
const w = Math.max(6, X(sp.due) - X(sp.start));
|
|
314
|
+
shape(X(sp.start), ry + 6, w, 32, { geom: 'roundRect', fill: L.accent, name: `Span ${sp.stage}` });
|
|
315
|
+
if (sp.stage && w > 60) text(X(sp.start) + 11, ry + 13, w - 16, 22, sp.stage, { size: 16, bold: true, color: '#FFFFFF' });
|
|
316
|
+
}
|
|
317
|
+
const due = r.spans.reduce((a, sp) => (sp.due > a ? sp.due : a), '');
|
|
318
|
+
const m = r.milestone && /^\d{4}-\d{2}-\d{2}$/.test(String(r.milestone.due)) ? r.milestone.due : due;
|
|
319
|
+
if (m) { shape(X(m) + 3, ry + 12, 20, 20, { geom: 'diamond', line: L.ink, lw: 2.6, name: 'Due date' }); text(X(m) + 30, ry + 13, 90, 22, P.short(m), { size: 16, bold: true, color: L.muted }); }
|
|
320
|
+
r.overdue.slice(0, 1).forEach((o) => { shape(X(o.due) - 7, ry + 46, 14, 14, { geom: 'roundRect', fill: L.off, name: 'Overdue' }); text(X(o.due) + 14, ry + 43, 520, 22, `${o.title}, due ${P.short(o.due)}, overdue`, { size: 16, bold: true, color: L.off }); });
|
|
321
|
+
});
|
|
322
|
+
if (s.today) { shape(X(s.today), top - 10, 1, bottom - top + 10, { line: L.today, lw: 2.4, dash: true, name: 'Today' }); text(X(s.today) - 40, bottom + 4, 80, 20, 'Today', { size: 15, bold: true, color: L.today, align: 'ctr' }); }
|
|
323
|
+
} else if (s.kind === 'ask') {
|
|
324
|
+
const a = s.ask; const n = a.options.length;
|
|
325
|
+
const w = n ? (1146 - 26 * (n - 1)) / n : 0;
|
|
326
|
+
a.options.forEach((o, i) => {
|
|
327
|
+
const x = 67 + i * (w + 26);
|
|
328
|
+
shape(x, y, w, 120, { geom: 'roundRect', fill: i ? '#FFFFFF' : L.ask, line: i ? '#CFC8BA' : L.accent, lw: 3 });
|
|
329
|
+
text(x + 26, y + 22, w - 52, 20, `OPTION ${'ABC'[i]}`, { size: 15, bold: true, color: L.muted });
|
|
330
|
+
text(x + 26, y + 50, w - 52, 60, o, { size: 22, bold: true });
|
|
331
|
+
});
|
|
332
|
+
let yy = y + (n ? 146 : 0);
|
|
333
|
+
if (a.more) { text(67, yy, 1146, 60, a.more, { size: 18, color: L.soft }); yy += 70; }
|
|
334
|
+
text(67, yy, 1146, 26, [[['Who ', { bold: true }], [a.who, {}], ...(a.by ? [[' By ', { bold: true }], [a.by, {}]] : [])]], { size: 18, color: L.soft });
|
|
335
|
+
} else if (s.kind === 'sources') {
|
|
336
|
+
text(67, y, 540, 20, 'REACHED', { size: 15, bold: true, color: L.muted });
|
|
337
|
+
(s.reached.length ? s.reached : ['None']).slice(0, 8).forEach((x, i) => text(67, y + 34 + i * 44, 540, 40, x, { size: 18, color: L.soft }));
|
|
338
|
+
if (s.notReached.length) {
|
|
339
|
+
text(655, y, 540, 20, 'WHAT WE DON’T KNOW YET', { size: 15, bold: true, color: L.off });
|
|
340
|
+
s.notReached.slice(0, 6).forEach((x, i) => text(655, y + 34 + i * 70, 558, 64, [[[x.source, { bold: true, size: 21 }]], [[x.why ? x.why[0].toUpperCase() + x.why.slice(1) + '.' : '', { color: L.soft }]]], { size: 18 }));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return out;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function pptx(report, deck) {
|
|
213
347
|
const slides = []; // { title, lines, kind }
|
|
214
348
|
slides.push({ kind: 'title', title: report.title, lines: report.subtitle ? [report.subtitle] : [] });
|
|
215
349
|
for (const s of sectionsOf(report)) {
|
|
@@ -226,6 +360,10 @@ function pptx(report) {
|
|
|
226
360
|
const body = s.lines.length ? sp(3, 'Content', s.kind === 'title' ? 'subTitle' : 'body', s.kind === 'title' ? 914400 : 609600, s.kind === 'title' ? 3810000 : 1371600, s.kind === 'title' ? 10363200 : 10972800, s.kind === 'title' ? 914400 : 4846320, bodyParas) : '';
|
|
227
361
|
return `${DECL}<p:sld xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>${title}${body}</p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>`;
|
|
228
362
|
};
|
|
363
|
+
const look = deck ? deck.look : null;
|
|
364
|
+
const font = look ? xml(look.font) : 'Calibri';
|
|
365
|
+
if (deck) { slides.length = 0; for (const p of deck.slides) slides.push({ kind: p.kind, xml: pyramidSlideXml(p, look) }); }
|
|
366
|
+
const shapesSlide = (parts) => `${DECL}<p:sld xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}"><p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>${parts.join('')}</p:spTree></p:cSld><p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr></p:sld>`;
|
|
229
367
|
const layoutXml = `${DECL}<p:sldLayout xmlns:a="${NS_A}" xmlns:r="${NS_R}" xmlns:p="${NS_P}" type="obj" preserve="1"><p:cSld name="Title and Content"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>`
|
|
230
368
|
+ sp(2, 'Title 1', 'title', 609600, 365760, 10972800, 914400, '<a:p><a:endParaRPr lang="en-GB"/></a:p>')
|
|
231
369
|
+ sp(3, 'Content Placeholder 2', 'body', 609600, 1371600, 10972800, 4846320, '<a:p><a:endParaRPr lang="en-GB"/></a:p>')
|
|
@@ -237,7 +375,8 @@ function pptx(report) {
|
|
|
237
375
|
+ '<p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst><p:txStyles><p:titleStyle><a:lvl1pPr algn="l"><a:defRPr sz="3200" b="1"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mj-lt"/></a:defRPr></a:lvl1pPr></p:titleStyle>'
|
|
238
376
|
+ '<p:bodyStyle><a:lvl1pPr marL="285750" indent="-285750"><a:defRPr sz="1800"><a:solidFill><a:schemeClr val="tx1"/></a:solidFill><a:latin typeface="+mn-lt"/></a:defRPr></a:lvl1pPr></p:bodyStyle><p:otherStyle><a:defPPr><a:defRPr lang="en-GB"/></a:defPPr></p:otherStyle></p:txStyles></p:sldMaster>';
|
|
239
377
|
const themeXml = `${DECL}<a:theme xmlns:a="${NS_A}" name="PMOS"><a:themeElements><a:clrScheme name="PMOS"><a:dk1><a:srgbClr val="1F2328"/></a:dk1><a:lt1><a:srgbClr val="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="44546A"/></a:dk2><a:lt2><a:srgbClr val="E7E6E6"/></a:lt2><a:accent1><a:srgbClr val="2F5FD0"/></a:accent1><a:accent2><a:srgbClr val="7A8CA5"/></a:accent2><a:accent3><a:srgbClr val="2E8B57"/></a:accent3><a:accent4><a:srgbClr val="C58A00"/></a:accent4><a:accent5><a:srgbClr val="B3412F"/></a:accent5><a:accent6><a:srgbClr val="6B5BB0"/></a:accent6><a:hlink><a:srgbClr val="2F5FD0"/></a:hlink><a:folHlink><a:srgbClr val="6B5BB0"/></a:folHlink></a:clrScheme>`
|
|
240
|
-
+
|
|
378
|
+
+ `<a:fontScheme name="PMOS"><a:majorFont><a:latin typeface="${font}"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont><a:minorFont><a:latin typeface="${font}"/>`
|
|
379
|
+
+ '<a:ea typeface=""/><a:cs typeface=""/></a:minorFont></a:fontScheme>'
|
|
241
380
|
+ '<a:fmtScheme name="PMOS"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst>'
|
|
242
381
|
+ '<a:lnStyleLst><a:ln w="9525"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln><a:ln w="19050"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln><a:ln w="28575"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst>'
|
|
243
382
|
+ '<a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst>'
|
|
@@ -250,7 +389,7 @@ function pptx(report) {
|
|
|
250
389
|
{ name: '[Content_Types].xml', data: `${DECL}<Types xmlns="${NS_CT}"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/><Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/><Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/><Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/><Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>`
|
|
251
390
|
+ slides.map((s, i) => `<Override PartName="/ppt/slides/slide${i + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`).join('') + '</Types>' },
|
|
252
391
|
{ name: '_rels/.rels', data: `${DECL}<Relationships xmlns="${NS_PKG_REL}">${rel('rId1', 'officeDocument', 'ppt/presentation.xml')}<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/></Relationships>` },
|
|
253
|
-
{ name: 'docProps/core.xml', data: coreProps(report.title) },
|
|
392
|
+
{ name: 'docProps/core.xml', data: coreProps(deck ? deck.name : report.title) },
|
|
254
393
|
{ name: 'ppt/presentation.xml', data: presXml },
|
|
255
394
|
{ name: 'ppt/_rels/presentation.xml.rels', data: `${DECL}<Relationships xmlns="${NS_PKG_REL}">${rel('rId1', 'slideMaster', 'slideMasters/slideMaster1.xml')}${slides.map((s, i) => rel('rId' + (i + 2), 'slide', `slides/slide${i + 1}.xml`)).join('')}${rel('rId' + (n + 2), 'theme', 'theme/theme1.xml')}</Relationships>` },
|
|
256
395
|
{ name: 'ppt/slideMasters/slideMaster1.xml', data: masterXml },
|
|
@@ -260,7 +399,7 @@ function pptx(report) {
|
|
|
260
399
|
{ name: 'ppt/theme/theme1.xml', data: themeXml },
|
|
261
400
|
];
|
|
262
401
|
slides.forEach((s, i) => {
|
|
263
|
-
entries.push({ name: `ppt/slides/slide${i + 1}.xml`, data: slideXml(s) });
|
|
402
|
+
entries.push({ name: `ppt/slides/slide${i + 1}.xml`, data: s.xml ? shapesSlide(s.xml) : slideXml(s) });
|
|
264
403
|
entries.push({ name: `ppt/slides/_rels/slide${i + 1}.xml.rels`, data: `${DECL}<Relationships xmlns="${NS_PKG_REL}">${rel('rId1', 'slideLayout', '../slideLayouts/slideLayout1.xml')}</Relationships>` });
|
|
265
404
|
});
|
|
266
405
|
return zip(entries);
|
|
@@ -335,11 +474,13 @@ const FORMATS = Object.freeze({
|
|
|
335
474
|
});
|
|
336
475
|
const BUILD = { docx, pptx, xlsx, csv: (r) => Buffer.from(csv(r), 'utf8') };
|
|
337
476
|
|
|
338
|
-
|
|
477
|
+
/** opts.look: the look lib/report-pyramid.js lookOf read (the route passes it); a cadence draft's .pptx is the Pyramid deck in that look.
|
|
478
|
+
* opts.data: the structured cycle's <slot>.data.json (report-pyramid dataOf), for the sources not reached. */
|
|
479
|
+
function exportOf(markdown, fmt, opts = {}) {
|
|
339
480
|
if (!Object.prototype.hasOwnProperty.call(FORMATS, fmt)) return { ok: false, error: `fmt must be one of ${Object.keys(FORMATS).join(', ')}` };
|
|
340
481
|
const report = parseReport(markdown);
|
|
341
482
|
let buffer;
|
|
342
|
-
try { buffer = BUILD[fmt](report); } catch (e) { return { ok: false, error: 'the file could not be built: ' + String((e && e.message) || e).slice(0, 160) }; }
|
|
483
|
+
try { buffer = fmt === 'pptx' ? pptx(report, require('./report-pyramid.js').deckOf(markdown, opts.look, { data: opts.data || null })) : BUILD[fmt](report); } catch (e) { return { ok: false, error: 'the file could not be built: ' + String((e && e.message) || e).slice(0, 160) }; }
|
|
343
484
|
const stem = plain(report.title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60) || 'report';
|
|
344
485
|
return { ok: true, fmt, name: `${stem}.${fmt}`, mime: FORMATS[fmt].mime, buffer };
|
|
345
486
|
}
|