@cyberskill/design 1.5.0 → 1.6.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/DESIGN.md +1 -1
- package/README.md +4 -4
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/_ds_bundle.js +2 -2
- package/_esm/cs.mjs +2 -2
- package/docs/contrast-report.md +1 -1
- package/docs/conventions.md +4 -4
- package/docs/quality-gates.md +3 -1
- package/docs/release-notes.md +1 -1
- package/docs/vi/contrast-report.md +1 -1
- package/docs/vi/conventions.md +2 -2
- package/docs/vi/quality-gates.md +3 -1
- package/docs/vi/release-notes.md +1 -1
- package/llms.txt +3 -3
- package/package.json +6 -4
- package/templates/_vendor/doc-export.js +435 -0
- package/templates/_vendor/support.js +1860 -0
- package/templates/app-shell/ds-base.js +8 -0
- package/templates/app-shell/support.js +41 -15
- package/templates/article/ds-base.js +8 -0
- package/templates/article/support.js +41 -15
- package/templates/auth/ds-base.js +8 -0
- package/templates/auth/support.js +41 -15
- package/templates/bod-deck/ds-base.js +8 -0
- package/templates/bod-deck/support.js +41 -15
- package/templates/bod-investor-update/ds-base.js +8 -0
- package/templates/bod-investor-update/support.js +41 -15
- package/templates/bod-memo/ds-base.js +8 -0
- package/templates/bod-memo/support.js +41 -15
- package/templates/bod-minutes/ds-base.js +8 -0
- package/templates/bod-minutes/support.js +41 -15
- package/templates/bod-report/ds-base.js +8 -0
- package/templates/bod-report/support.js +41 -15
- package/templates/bod-resolution/ds-base.js +8 -0
- package/templates/bod-resolution/support.js +41 -15
- package/templates/culture-event-invite/ds-base.js +8 -0
- package/templates/culture-event-invite/support.js +41 -15
- package/templates/dashboard/ds-base.js +8 -0
- package/templates/dashboard/support.js +41 -15
- package/templates/delivery-change-note/ds-base.js +8 -0
- package/templates/delivery-change-note/support.js +41 -15
- package/templates/delivery-kickoff/ds-base.js +8 -0
- package/templates/delivery-kickoff/support.js +41 -15
- package/templates/delivery-qbr-deck/ds-base.js +8 -0
- package/templates/delivery-qbr-deck/support.js +41 -15
- package/templates/delivery-runbook/ds-base.js +8 -0
- package/templates/delivery-runbook/support.js +41 -15
- package/templates/delivery-status-email/ds-base.js +8 -0
- package/templates/delivery-status-email/support.js +41 -15
- package/templates/doc-style-guide/ds-base.js +8 -0
- package/templates/doc-style-guide/support.js +41 -15
- package/templates/doc-suite-index/ds-base.js +8 -0
- package/templates/doc-suite-index/support.js +41 -15
- package/templates/doc-templates/ds-base.js +8 -0
- package/templates/doc-templates/support.js +41 -15
- package/templates/email/ds-base.js +8 -0
- package/templates/email/support.js +41 -15
- package/templates/finance-dunning-email/ds-base.js +8 -0
- package/templates/finance-dunning-email/support.js +41 -15
- package/templates/finance-expense-report/ds-base.js +8 -0
- package/templates/finance-expense-report/support.js +41 -15
- package/templates/finance-invoice/ds-base.js +8 -0
- package/templates/finance-invoice/support.js +41 -15
- package/templates/finance-quote/ds-base.js +8 -0
- package/templates/finance-quote/support.js +41 -15
- package/templates/hr-announcement/ds-base.js +8 -0
- package/templates/hr-announcement/support.js +41 -15
- package/templates/hr-interview-kit/ds-base.js +8 -0
- package/templates/hr-interview-kit/support.js +41 -15
- package/templates/hr-offboarding/ds-base.js +8 -0
- package/templates/hr-offboarding/support.js +41 -15
- package/templates/hr-performance-review/ds-base.js +8 -0
- package/templates/hr-performance-review/support.js +41 -15
- package/templates/hr-pip/ds-base.js +8 -0
- package/templates/hr-pip/support.js +41 -15
- package/templates/legal-msa/ds-base.js +8 -0
- package/templates/legal-msa/support.js +41 -15
- package/templates/legal-sow/ds-base.js +8 -0
- package/templates/legal-sow/support.js +41 -15
- package/templates/marketing-campaign-brief/ds-base.js +8 -0
- package/templates/marketing-campaign-brief/support.js +41 -15
- package/templates/marketing-case-study/ds-base.js +8 -0
- package/templates/marketing-case-study/support.js +41 -15
- package/templates/marketing-launch/ds-base.js +8 -0
- package/templates/marketing-launch/support.js +41 -15
- package/templates/marketing-newsletter/ds-base.js +8 -0
- package/templates/marketing-newsletter/support.js +41 -15
- package/templates/marketing-page/ds-base.js +8 -0
- package/templates/marketing-page/support.js +41 -15
- package/templates/marketing-press-release/ds-base.js +8 -0
- package/templates/marketing-press-release/support.js +41 -15
- package/templates/marketing-social-kit/ds-base.js +8 -0
- package/templates/marketing-social-kit/support.js +41 -15
- package/templates/sales-one-pager/ds-base.js +8 -0
- package/templates/sales-one-pager/support.js +41 -15
- package/templates/sales-proposal-deck/ds-base.js +8 -0
- package/templates/sales-proposal-deck/support.js +41 -15
- package/templates/settings/ds-base.js +8 -0
- package/templates/settings/support.js +41 -15
- package/templates/slide-deck/ds-base.js +8 -0
- package/templates/slide-deck/support.js +41 -15
- package/templates/team-meeting-agenda/ds-base.js +8 -0
- package/templates/team-meeting-agenda/support.js +41 -15
- package/templates/tech-incident-report/ds-base.js +8 -0
- package/templates/tech-incident-report/support.js +41 -15
- package/templates/tech-release-notes/ds-base.js +8 -0
- package/templates/tech-release-notes/support.js +41 -15
- package/templates/tech-retro/ds-base.js +8 -0
- package/templates/tech-retro/support.js +41 -15
- package/templates/tech-rfc/ds-base.js +8 -0
- package/templates/tech-rfc/support.js +41 -15
- package/templates/vn-appointment-transfer/ds-base.js +8 -0
- package/templates/vn-appointment-transfer/support.js +41 -15
- package/templates/vn-certificate-of-employment/ds-base.js +8 -0
- package/templates/vn-certificate-of-employment/support.js +41 -15
- package/templates/vn-code-of-conduct/ds-base.js +8 -0
- package/templates/vn-code-of-conduct/support.js +41 -15
- package/templates/vn-compensation-regulation/ds-base.js +8 -0
- package/templates/vn-compensation-regulation/support.js +41 -15
- package/templates/vn-contract-amendment/ds-base.js +8 -0
- package/templates/vn-contract-amendment/support.js +41 -15
- package/templates/vn-contractor-agreement/ds-base.js +8 -0
- package/templates/vn-contractor-agreement/support.js +41 -15
- package/templates/vn-data-protection-policy/ds-base.js +8 -0
- package/templates/vn-data-protection-policy/support.js +41 -15
- package/templates/vn-disciplinary-case-file/ds-base.js +8 -0
- package/templates/vn-disciplinary-case-file/support.js +41 -15
- package/templates/vn-disciplinary-schedule/ds-base.js +8 -0
- package/templates/vn-disciplinary-schedule/support.js +41 -15
- package/templates/vn-employee-request/ds-base.js +8 -0
- package/templates/vn-employee-request/support.js +41 -15
- package/templates/vn-exit-handover/ds-base.js +8 -0
- package/templates/vn-exit-handover/support.js +41 -15
- package/templates/vn-foreign-employee-pack/ds-base.js +8 -0
- package/templates/vn-foreign-employee-pack/support.js +41 -15
- package/templates/vn-framework-agreement/ds-base.js +8 -0
- package/templates/vn-framework-agreement/support.js +41 -15
- package/templates/vn-grassroots-democracy/ds-base.js +8 -0
- package/templates/vn-grassroots-democracy/support.js +41 -15
- package/templates/vn-infosec-policy/ds-base.js +8 -0
- package/templates/vn-infosec-policy/support.js +41 -15
- package/templates/vn-internal-labor-regulations/ds-base.js +8 -0
- package/templates/vn-internal-labor-regulations/support.js +41 -15
- package/templates/vn-internship-agreement/ds-base.js +8 -0
- package/templates/vn-internship-agreement/support.js +41 -15
- package/templates/vn-job-description/ds-base.js +8 -0
- package/templates/vn-job-description/support.js +41 -15
- package/templates/vn-labor-contract/ds-base.js +8 -0
- package/templates/vn-labor-contract/support.js +41 -15
- package/templates/vn-labour-management-book/ds-base.js +8 -0
- package/templates/vn-labour-management-book/support.js +41 -15
- package/templates/vn-mutual-nda/ds-base.js +8 -0
- package/templates/vn-mutual-nda/support.js +41 -15
- package/templates/vn-ndnca-ip/ds-base.js +8 -0
- package/templates/vn-ndnca-ip/support.js +41 -15
- package/templates/vn-offer-letter/ds-base.js +8 -0
- package/templates/vn-offer-letter/support.js +41 -15
- package/templates/vn-onboarding-checklist/ds-base.js +8 -0
- package/templates/vn-onboarding-checklist/support.js +41 -15
- package/templates/vn-pdpl-consent/ds-base.js +8 -0
- package/templates/vn-pdpl-consent/support.js +41 -15
- package/templates/vn-performance-evaluation-regulation/ds-base.js +8 -0
- package/templates/vn-performance-evaluation-regulation/support.js +41 -15
- package/templates/vn-performance-review-form/ds-base.js +8 -0
- package/templates/vn-performance-review-form/support.js +41 -15
- package/templates/vn-phantom-stock/ds-base.js +8 -0
- package/templates/vn-phantom-stock/support.js +41 -15
- package/templates/vn-probation-contract/ds-base.js +8 -0
- package/templates/vn-probation-contract/support.js +41 -15
- package/templates/vn-receipt-acknowledgement/ds-base.js +8 -0
- package/templates/vn-receipt-acknowledgement/support.js +41 -15
- package/templates/vn-remote-work-policy/ds-base.js +8 -0
- package/templates/vn-remote-work-policy/support.js +41 -15
- package/templates/vn-resignation-letter/ds-base.js +8 -0
- package/templates/vn-resignation-letter/support.js +41 -15
- package/templates/vn-salary-scale/ds-base.js +8 -0
- package/templates/vn-salary-scale/support.js +41 -15
- package/templates/vn-sow-appendix/ds-base.js +8 -0
- package/templates/vn-sow-appendix/support.js +41 -15
- package/templates/vn-termination-decision/ds-base.js +8 -0
- package/templates/vn-termination-decision/support.js +41 -15
- package/templates/vn-total-rewards-appendix/ds-base.js +8 -0
- package/templates/vn-total-rewards-appendix/support.js +41 -15
- package/templates/vn-training-commitment/ds-base.js +8 -0
- package/templates/vn-training-commitment/support.js +41 -15
- package/templates/vn-travel-expense-policy/ds-base.js +8 -0
- package/templates/vn-travel-expense-policy/support.js +41 -15
- package/tokens/element-seeds.json +1 -1
- package/tokens/native/CSTokens.kt +1 -1
- package/tokens/native/CSTokens.swift +1 -1
- package/tokens/native/cs_tokens.dart +1 -1
- package/tokens/provenance.json +6 -6
- package/tokens/tokens.dtcg.json +1 -1
- package/tokens/tokens.js +2 -2
- package/tokens/tokens.json +1 -1
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CyberSkill document export — PDF (print) + editable OOXML DOCX.
|
|
3
|
+
* Loaded by ds-base.js when <meta name="omelette-owns-print"> is present.
|
|
4
|
+
* No CDN; ZIP via CompressionStream (deflate-raw).
|
|
5
|
+
*/
|
|
6
|
+
(() => {
|
|
7
|
+
const UMBER = '45210E';
|
|
8
|
+
const A4 = { w: 11906, h: 16838, name: 'A4' }; // twips
|
|
9
|
+
const LETTER = { w: 12240, h: 15840, name: 'Letter' };
|
|
10
|
+
|
|
11
|
+
function pageSizeFromCss() {
|
|
12
|
+
const styles = [...document.querySelectorAll('style')].map((s) => s.textContent || '').join('\n');
|
|
13
|
+
if (/@page\s*\{[^}]*size:\s*letter/i.test(styles)) return LETTER;
|
|
14
|
+
return A4;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function xmlEscape(s) {
|
|
18
|
+
return String(s)
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/"/g, '"');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function crc32(buf) {
|
|
26
|
+
let c = ~0;
|
|
27
|
+
for (let i = 0; i < buf.length; i++) {
|
|
28
|
+
c ^= buf[i];
|
|
29
|
+
for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
|
|
30
|
+
}
|
|
31
|
+
return ~c >>> 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function deflateRaw(u8) {
|
|
35
|
+
if (typeof CompressionStream === 'undefined') {
|
|
36
|
+
// Store uncompressed (ZIP method 0) — still a valid OOXML package.
|
|
37
|
+
return { data: u8, method: 0 };
|
|
38
|
+
}
|
|
39
|
+
const cs = new CompressionStream('deflate-raw');
|
|
40
|
+
const writer = cs.writable.getWriter();
|
|
41
|
+
writer.write(u8);
|
|
42
|
+
writer.close();
|
|
43
|
+
const ab = await new Response(cs.readable).arrayBuffer();
|
|
44
|
+
return { data: new Uint8Array(ab), method: 8 };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function buildZip(files) {
|
|
48
|
+
// files: [{ name, bytes: Uint8Array }]
|
|
49
|
+
const enc = new TextEncoder();
|
|
50
|
+
const locals = [];
|
|
51
|
+
const centrals = [];
|
|
52
|
+
let offset = 0;
|
|
53
|
+
for (const f of files) {
|
|
54
|
+
const nameBytes = enc.encode(f.name);
|
|
55
|
+
const { data, method } = await deflateRaw(f.bytes);
|
|
56
|
+
const crc = crc32(f.bytes);
|
|
57
|
+
const local = new Uint8Array(30 + nameBytes.length + data.length);
|
|
58
|
+
const dv = new DataView(local.buffer);
|
|
59
|
+
dv.setUint32(0, 0x04034b50, true);
|
|
60
|
+
dv.setUint16(4, 20, true);
|
|
61
|
+
dv.setUint16(6, 0, true);
|
|
62
|
+
dv.setUint16(8, method, true);
|
|
63
|
+
dv.setUint16(10, 0, true);
|
|
64
|
+
dv.setUint16(12, 0, true);
|
|
65
|
+
dv.setUint32(14, crc, true);
|
|
66
|
+
dv.setUint32(18, data.length, true);
|
|
67
|
+
dv.setUint32(22, f.bytes.length, true);
|
|
68
|
+
dv.setUint16(26, nameBytes.length, true);
|
|
69
|
+
dv.setUint16(28, 0, true);
|
|
70
|
+
local.set(nameBytes, 30);
|
|
71
|
+
local.set(data, 30 + nameBytes.length);
|
|
72
|
+
locals.push(local);
|
|
73
|
+
|
|
74
|
+
const central = new Uint8Array(46 + nameBytes.length);
|
|
75
|
+
const cv = new DataView(central.buffer);
|
|
76
|
+
cv.setUint32(0, 0x02014b50, true);
|
|
77
|
+
cv.setUint16(4, 20, true);
|
|
78
|
+
cv.setUint16(6, 20, true);
|
|
79
|
+
cv.setUint16(8, 0, true);
|
|
80
|
+
cv.setUint16(10, method, true);
|
|
81
|
+
cv.setUint16(12, 0, true);
|
|
82
|
+
cv.setUint16(14, 0, true);
|
|
83
|
+
cv.setUint32(16, crc, true);
|
|
84
|
+
cv.setUint32(20, data.length, true);
|
|
85
|
+
cv.setUint32(24, f.bytes.length, true);
|
|
86
|
+
cv.setUint16(28, nameBytes.length, true);
|
|
87
|
+
cv.setUint16(30, 0, true);
|
|
88
|
+
cv.setUint16(32, 0, true);
|
|
89
|
+
cv.setUint16(34, 0, true);
|
|
90
|
+
cv.setUint16(36, 0, true);
|
|
91
|
+
cv.setUint32(38, 0, true);
|
|
92
|
+
cv.setUint32(42, offset, true);
|
|
93
|
+
central.set(nameBytes, 46);
|
|
94
|
+
centrals.push(central);
|
|
95
|
+
offset += local.length;
|
|
96
|
+
}
|
|
97
|
+
const centralSize = centrals.reduce((n, b) => n + b.length, 0);
|
|
98
|
+
const end = new Uint8Array(22);
|
|
99
|
+
const ev = new DataView(end.buffer);
|
|
100
|
+
ev.setUint32(0, 0x06054b50, true);
|
|
101
|
+
ev.setUint16(4, 0, true);
|
|
102
|
+
ev.setUint16(6, 0, true);
|
|
103
|
+
ev.setUint16(8, files.length, true);
|
|
104
|
+
ev.setUint16(10, files.length, true);
|
|
105
|
+
ev.setUint32(12, centralSize, true);
|
|
106
|
+
ev.setUint32(16, offset, true);
|
|
107
|
+
ev.setUint16(20, 0, true);
|
|
108
|
+
|
|
109
|
+
const total =
|
|
110
|
+
locals.reduce((n, b) => n + b.length, 0) + centralSize + end.length;
|
|
111
|
+
const out = new Uint8Array(total);
|
|
112
|
+
let o = 0;
|
|
113
|
+
for (const b of locals) {
|
|
114
|
+
out.set(b, o);
|
|
115
|
+
o += b.length;
|
|
116
|
+
}
|
|
117
|
+
for (const b of centrals) {
|
|
118
|
+
out.set(b, o);
|
|
119
|
+
o += b.length;
|
|
120
|
+
}
|
|
121
|
+
out.set(end, o);
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function textRuns(el) {
|
|
126
|
+
const parts = [];
|
|
127
|
+
const walk = (node, italic, bold, color) => {
|
|
128
|
+
if (node.nodeType === 3) {
|
|
129
|
+
const t = node.textContent;
|
|
130
|
+
if (t && t.replace(/\s+/g, '').length) parts.push({ t, italic, bold, color });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (node.nodeType !== 1) return;
|
|
134
|
+
if (node.getAttribute && node.getAttribute('data-omelette-chrome') != null) return;
|
|
135
|
+
const tag = node.tagName.toLowerCase();
|
|
136
|
+
if (tag === 'script' || tag === 'style' || tag === 'noscript') return;
|
|
137
|
+
const st = window.getComputedStyle(node);
|
|
138
|
+
const nextItalic =
|
|
139
|
+
italic || st.fontStyle === 'italic' || tag === 'i' || tag === 'em';
|
|
140
|
+
const nextBold =
|
|
141
|
+
bold ||
|
|
142
|
+
Number(st.fontWeight) >= 600 ||
|
|
143
|
+
tag === 'b' ||
|
|
144
|
+
tag === 'strong' ||
|
|
145
|
+
/^h[1-6]$/.test(tag);
|
|
146
|
+
let nextColor = color;
|
|
147
|
+
if (/^h[1-6]$/.test(tag) || (st.color && /rgb\(\s*69\s*,\s*33\s*,\s*14\s*\)/.test(st.color))) {
|
|
148
|
+
nextColor = UMBER;
|
|
149
|
+
}
|
|
150
|
+
for (const c of node.childNodes) walk(c, nextItalic, nextBold, nextColor);
|
|
151
|
+
};
|
|
152
|
+
walk(el, false, false, null);
|
|
153
|
+
return parts;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function runsToXml(runs) {
|
|
157
|
+
if (!runs.length) {
|
|
158
|
+
return '<w:r><w:t xml:space="preserve"></w:t></w:r>';
|
|
159
|
+
}
|
|
160
|
+
return runs
|
|
161
|
+
.map((r) => {
|
|
162
|
+
const rPr = [];
|
|
163
|
+
if (r.bold) rPr.push('<w:b/>');
|
|
164
|
+
if (r.italic) rPr.push('<w:i/>');
|
|
165
|
+
if (r.color) rPr.push(`<w:color w:val="${r.color}"/>`);
|
|
166
|
+
rPr.push('<w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/>');
|
|
167
|
+
const pr = rPr.length ? `<w:rPr>${rPr.join('')}</w:rPr>` : '';
|
|
168
|
+
return `<w:r>${pr}<w:t xml:space="preserve">${xmlEscape(r.t)}</w:t></w:r>`;
|
|
169
|
+
})
|
|
170
|
+
.join('');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function paraXml(el, style) {
|
|
174
|
+
const runs = textRuns(el);
|
|
175
|
+
if (!runs.length && !el.querySelector?.('br')) return '';
|
|
176
|
+
const pPr = style ? `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>` : '';
|
|
177
|
+
return `<w:p>${pPr}${runsToXml(runs)}</w:p>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function tableXml(table) {
|
|
181
|
+
const rows = [...table.querySelectorAll(':scope > tbody > tr, :scope > thead > tr, :scope > tr')];
|
|
182
|
+
if (!rows.length) return '';
|
|
183
|
+
const gridCols = Math.max(...rows.map((r) => r.children.length), 1);
|
|
184
|
+
const colW = Math.floor(9000 / gridCols);
|
|
185
|
+
let xml = `<w:tbl><w:tblPr><w:tblW w:w="5000" w:type="pct"/><w:tblBorders>
|
|
186
|
+
<w:top w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
187
|
+
<w:left w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
188
|
+
<w:bottom w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
189
|
+
<w:right w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
190
|
+
<w:insideH w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
191
|
+
<w:insideV w:val="single" w:sz="4" w:color="C9B8A8"/>
|
|
192
|
+
</w:tblBorders></w:tblPr><w:tblGrid>${'<w:gridCol w:w="' + colW + '"/>'.repeat(gridCols)}</w:tblGrid>`;
|
|
193
|
+
for (const tr of rows) {
|
|
194
|
+
xml += '<w:tr>';
|
|
195
|
+
for (const cell of tr.children) {
|
|
196
|
+
const bg = window.getComputedStyle(cell).backgroundColor;
|
|
197
|
+
const shade =
|
|
198
|
+
bg && /rgb\(\s*69\s*,\s*33\s*,\s*14\s*\)/.test(bg)
|
|
199
|
+
? `<w:tcPr><w:shd w:val="clear" w:fill="${UMBER}"/><w:tcW w:w="${colW}" w:type="dxa"/></w:tcPr>`
|
|
200
|
+
: `<w:tcPr><w:tcW w:w="${colW}" w:type="dxa"/></w:tcPr>`;
|
|
201
|
+
const cellColor = shade.includes(UMBER) ? 'FFFFFF' : null;
|
|
202
|
+
const runs = textRuns(cell).map((r) =>
|
|
203
|
+
cellColor ? { ...r, color: r.color || cellColor } : r,
|
|
204
|
+
);
|
|
205
|
+
xml += `<w:tc>${shade}<w:p>${runsToXml(runs)}</w:p></w:tc>`;
|
|
206
|
+
}
|
|
207
|
+
xml += '</w:tr>';
|
|
208
|
+
}
|
|
209
|
+
xml += '</w:tbl>';
|
|
210
|
+
return xml;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function sheetBodyXml(sheet) {
|
|
214
|
+
const blocks = [];
|
|
215
|
+
const kids = [...sheet.children];
|
|
216
|
+
const visit = (nodes) => {
|
|
217
|
+
for (const el of nodes) {
|
|
218
|
+
if (el.nodeType !== 1) continue;
|
|
219
|
+
if (el.getAttribute('data-omelette-chrome') != null) continue;
|
|
220
|
+
const tag = el.tagName.toLowerCase();
|
|
221
|
+
if (tag === 'table') {
|
|
222
|
+
blocks.push(tableXml(el));
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (/^h[1-6]$/.test(tag)) {
|
|
226
|
+
blocks.push(paraXml(el, 'Heading1'));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (tag === 'p' || tag === 'div' || tag === 'section' || tag === 'li') {
|
|
230
|
+
// Prefer leaf paragraphs; if a div only wraps structure, descend.
|
|
231
|
+
const hasBlock = [...el.children].some((c) =>
|
|
232
|
+
/^(DIV|TABLE|SECTION|UL|OL|H1|H2|H3|H4|H5|H6)$/.test(c.tagName),
|
|
233
|
+
);
|
|
234
|
+
if (hasBlock && tag !== 'p') {
|
|
235
|
+
visit([...el.children]);
|
|
236
|
+
} else {
|
|
237
|
+
const px = paraXml(el);
|
|
238
|
+
if (px) blocks.push(px);
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (tag === 'ul' || tag === 'ol') {
|
|
243
|
+
visit([...el.children]);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
visit([...el.children]);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
visit(kids);
|
|
250
|
+
return blocks.filter(Boolean).join('');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function headerFooterText(sheet) {
|
|
254
|
+
const headerEl = sheet.querySelector(':scope > div');
|
|
255
|
+
const header = headerEl ? textRuns(headerEl).map((r) => r.t).join('').trim() : 'CyberSkill';
|
|
256
|
+
const footer = 'CyberSkill · MST 0316475200 · Hiện Thực Hoá Ý Chí';
|
|
257
|
+
return { header: header.slice(0, 200) || 'CyberSkill', footer };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function buildDocxBlob(sheet) {
|
|
261
|
+
const page = pageSizeFromCss();
|
|
262
|
+
const enc = new TextEncoder();
|
|
263
|
+
const { header, footer } = headerFooterText(sheet);
|
|
264
|
+
const body = sheetBodyXml(sheet);
|
|
265
|
+
|
|
266
|
+
const contentTypes = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
267
|
+
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
|
268
|
+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
|
|
269
|
+
<Default Extension="xml" ContentType="application/xml"/>
|
|
270
|
+
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
|
|
271
|
+
<Override PartName="/word/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
|
|
272
|
+
<Override PartName="/word/header1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml"/>
|
|
273
|
+
<Override PartName="/word/footer1.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"/>
|
|
274
|
+
</Types>`;
|
|
275
|
+
|
|
276
|
+
const rels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
277
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
278
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
|
|
279
|
+
</Relationships>`;
|
|
280
|
+
|
|
281
|
+
const docRels = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
282
|
+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
283
|
+
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
|
|
284
|
+
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/header" Target="header1.xml"/>
|
|
285
|
+
<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer" Target="footer1.xml"/>
|
|
286
|
+
</Relationships>`;
|
|
287
|
+
|
|
288
|
+
const styles = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
289
|
+
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
290
|
+
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
|
|
291
|
+
<w:name w:val="Normal"/><w:qFormat/>
|
|
292
|
+
<w:rPr><w:rFonts w:ascii="Arial" w:hAnsi="Arial" w:cs="Arial"/><w:sz w:val="22"/></w:rPr>
|
|
293
|
+
</w:style>
|
|
294
|
+
<w:style w:type="paragraph" w:styleId="Heading1">
|
|
295
|
+
<w:name w:val="heading 1"/><w:basedOn w:val="Normal"/><w:qFormat/>
|
|
296
|
+
<w:rPr><w:b/><w:color w:val="${UMBER}"/><w:sz w:val="28"/></w:rPr>
|
|
297
|
+
</w:style>
|
|
298
|
+
</w:styles>`;
|
|
299
|
+
|
|
300
|
+
const headerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
301
|
+
<w:hdr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
302
|
+
<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:color="C9B8A8"/></w:pBdr></w:pPr>
|
|
303
|
+
<w:r><w:rPr><w:b/><w:color w:val="${UMBER}"/><w:sz w:val="18"/></w:rPr>
|
|
304
|
+
<w:t xml:space="preserve">${xmlEscape(header)}</w:t></w:r>
|
|
305
|
+
</w:p>
|
|
306
|
+
</w:hdr>`;
|
|
307
|
+
|
|
308
|
+
const footerXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
309
|
+
<w:ftr xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
310
|
+
xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml">
|
|
311
|
+
<w:p>
|
|
312
|
+
<w:r><w:rPr><w:sz w:val="16"/><w:color w:val="6B5D4F"/></w:rPr>
|
|
313
|
+
<w:t xml:space="preserve">${xmlEscape(footer)} · </w:t></w:r>
|
|
314
|
+
<w:r><w:fldChar w:fldCharType="begin"/></w:r>
|
|
315
|
+
<w:r><w:instrText xml:space="preserve"> PAGE </w:instrText></w:r>
|
|
316
|
+
<w:r><w:fldChar w:fldCharType="end"/></w:r>
|
|
317
|
+
</w:p>
|
|
318
|
+
</w:ftr>`;
|
|
319
|
+
|
|
320
|
+
const documentXml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
321
|
+
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
|
|
322
|
+
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
|
|
323
|
+
<w:body>
|
|
324
|
+
${body || '<w:p><w:r><w:t></w:t></w:r></w:p>'}
|
|
325
|
+
<w:sectPr>
|
|
326
|
+
<w:headerReference w:type="default" r:id="rId2"/>
|
|
327
|
+
<w:footerReference w:type="default" r:id="rId3"/>
|
|
328
|
+
<w:pgSz w:w="${page.w}" w:h="${page.h}"/>
|
|
329
|
+
<w:pgMar w:top="1440" w:right="1134" w:bottom="1134" w:left="1134" w:header="720" w:footer="720"/>
|
|
330
|
+
</w:sectPr>
|
|
331
|
+
</w:body>
|
|
332
|
+
</w:document>`;
|
|
333
|
+
|
|
334
|
+
const zip = await buildZip([
|
|
335
|
+
{ name: '[Content_Types].xml', bytes: enc.encode(contentTypes) },
|
|
336
|
+
{ name: '_rels/.rels', bytes: enc.encode(rels) },
|
|
337
|
+
{ name: 'word/document.xml', bytes: enc.encode(documentXml) },
|
|
338
|
+
{ name: 'word/_rels/document.xml.rels', bytes: enc.encode(docRels) },
|
|
339
|
+
{ name: 'word/styles.xml', bytes: enc.encode(styles) },
|
|
340
|
+
{ name: 'word/header1.xml', bytes: enc.encode(headerXml) },
|
|
341
|
+
{ name: 'word/footer1.xml', bytes: enc.encode(footerXml) },
|
|
342
|
+
]);
|
|
343
|
+
return new Blob([zip], {
|
|
344
|
+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function downloadBlob(blob, filename) {
|
|
349
|
+
const a = document.createElement('a');
|
|
350
|
+
a.href = URL.createObjectURL(blob);
|
|
351
|
+
a.download = filename;
|
|
352
|
+
a.rel = 'noopener';
|
|
353
|
+
document.body.appendChild(a);
|
|
354
|
+
a.click();
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
URL.revokeObjectURL(a.href);
|
|
357
|
+
a.remove();
|
|
358
|
+
}, 1500);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function suggestName(ext) {
|
|
362
|
+
const t = (document.title || 'document').replace(/\s*[·|].*$/, '').trim() || 'document';
|
|
363
|
+
return t.replace(/[^\w\u00C0-\u024F\- ]+/g, '').replace(/\s+/g, '-').slice(0, 80) + ext;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function injectToolbar() {
|
|
367
|
+
if (document.querySelector('[data-omelette-chrome="export"]')) return;
|
|
368
|
+
const meta = document.querySelector('meta[name="omelette-owns-print"]');
|
|
369
|
+
if (!meta) return;
|
|
370
|
+
|
|
371
|
+
const style = document.createElement('style');
|
|
372
|
+
style.setAttribute('data-omelette-chrome', 'export-style');
|
|
373
|
+
style.textContent = `
|
|
374
|
+
[data-omelette-chrome="export"]{
|
|
375
|
+
position:fixed;z-index:2147483000;top:12px;right:12px;display:flex;gap:8px;
|
|
376
|
+
font-family:var(--cs-font-family-ui,system-ui,sans-serif);pointer-events:auto;
|
|
377
|
+
}
|
|
378
|
+
[data-omelette-chrome="export"] button{
|
|
379
|
+
appearance:none;border:1px solid #c9b8a8;background:#fff;color:#45210E;
|
|
380
|
+
border-radius:8px;padding:8px 12px;font-size:12.5px;font-weight:700;cursor:pointer;
|
|
381
|
+
box-shadow:0 1px 2px rgba(35,26,18,.08);
|
|
382
|
+
}
|
|
383
|
+
[data-omelette-chrome="export"] button:hover{background:#f7f0e6}
|
|
384
|
+
[data-omelette-chrome="export"] button:focus-visible{outline:2px solid #F4BA17;outline-offset:2px}
|
|
385
|
+
@media print{
|
|
386
|
+
[data-omelette-chrome="export"],[data-omelette-chrome="export-style"]{display:none!important}
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
document.head.appendChild(style);
|
|
390
|
+
|
|
391
|
+
const bar = document.createElement('div');
|
|
392
|
+
bar.setAttribute('data-omelette-chrome', 'export');
|
|
393
|
+
bar.setAttribute('role', 'toolbar');
|
|
394
|
+
bar.setAttribute('aria-label', 'Document export · Xuất tài liệu');
|
|
395
|
+
|
|
396
|
+
const pdfBtn = document.createElement('button');
|
|
397
|
+
pdfBtn.type = 'button';
|
|
398
|
+
pdfBtn.textContent = 'PDF · In / Print';
|
|
399
|
+
pdfBtn.addEventListener('click', () => window.print());
|
|
400
|
+
|
|
401
|
+
const docxBtn = document.createElement('button');
|
|
402
|
+
docxBtn.type = 'button';
|
|
403
|
+
docxBtn.textContent = 'DOCX · Tải / Download';
|
|
404
|
+
docxBtn.addEventListener('click', async () => {
|
|
405
|
+
try {
|
|
406
|
+
const sheet = document.querySelector('.cs-sheet') || document.body;
|
|
407
|
+
const blob = await buildDocxBlob(sheet);
|
|
408
|
+
downloadBlob(blob, suggestName('.docx'));
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error('[doc-export] DOCX failed', e);
|
|
411
|
+
alert('DOCX export failed: ' + (e && e.message ? e.message : e));
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
bar.appendChild(pdfBtn);
|
|
416
|
+
bar.appendChild(docxBtn);
|
|
417
|
+
document.body.appendChild(bar);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Test / automation hooks
|
|
421
|
+
window.__csDocExport = {
|
|
422
|
+
buildDocxBlob,
|
|
423
|
+
pageSizeFromCss,
|
|
424
|
+
injectToolbar,
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
const boot = () => injectToolbar();
|
|
428
|
+
if (document.readyState === 'loading') {
|
|
429
|
+
document.addEventListener('DOMContentLoaded', boot);
|
|
430
|
+
} else {
|
|
431
|
+
boot();
|
|
432
|
+
}
|
|
433
|
+
// DC boots async — re-try after paint so .cs-sheet exists for future exports.
|
|
434
|
+
setTimeout(boot, 800);
|
|
435
|
+
})();
|