weifuwu 0.84.0 → 0.86.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/README.md +5 -5
- package/dist/components/Editor/Editor.d.ts +8 -7
- package/dist/components/Editor/edit-events.d.ts +27 -0
- package/dist/components/Editor/model/apply.d.ts +23 -0
- package/dist/components/Editor/model/diff.d.ts +25 -0
- package/dist/components/Editor/model/dom.d.ts +27 -0
- package/dist/components/Editor/model/history.d.ts +21 -0
- package/dist/components/Editor/model/html.d.ts +16 -0
- package/dist/components/Editor/model/inverse.d.ts +20 -0
- package/dist/components/Editor/model/types.d.ts +116 -0
- package/dist/components/Editor/tools/table.d.ts +5 -2
- package/dist/components/Editor/tools/toolbar.d.ts +1 -1
- package/dist/components/Editor/tools/types.d.ts +25 -0
- package/dist/components/FilePreview/FilePreview.d.ts +42 -0
- package/dist/components/FilePreview/markdown.d.ts +23 -0
- package/dist/components/OfficeEditor/ai/ai-bridge.d.ts +39 -0
- package/dist/components/OfficeEditor/model/apply.d.ts +25 -0
- package/dist/components/OfficeEditor/model/types.d.ts +224 -0
- package/dist/components/SheetGrid/SheetGrid.d.ts +30 -0
- package/dist/components/SlideCanvas/SlideCanvas.d.ts +28 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.js +54 -14
- package/dist/components/style.css +679 -4
- package/dist/index.js +128 -17
- package/dist/office/docx.d.ts +33 -0
- package/dist/office/pptx.d.ts +24 -0
- package/dist/office/xlsx.d.ts +25 -0
- package/dist/office/xml-serialize.d.ts +18 -0
- package/dist/office/xml.d.ts +21 -0
- package/dist/office/zip.d.ts +21 -0
- package/dist/ui-dom/index.js +8 -7
- package/dist/ui-dom/locale/en_US.d.ts +14 -0
- package/dist/ui-dom/locale/zh_CN.d.ts +14 -0
- package/dist/ui-dom/middleware/ws.d.ts +8 -0
- package/dist/ui-dom/testing.js +5 -5
- package/dist/ui-dom/types.d.ts +3 -1
- package/dist/ui-dom/vdom3/delegate.d.ts +9 -0
- package/dist/ui-dom/vdom3/types.d.ts +27 -1
- package/dist/ui-dom/vdom3.js +10 -9
- package/docs/components-map.md +12 -0
- package/docs/components.md +124 -2
- package/docs/frontend-ui-dom.md +1 -0
- package/package.json +1 -1
- package/dist/components/Editor/tools/format.d.ts +0 -11
package/dist/index.js
CHANGED
|
@@ -3316,7 +3316,6 @@ var HtmlSafe = class {
|
|
|
3316
3316
|
|
|
3317
3317
|
// src/ui-dom/vdom3/types.ts
|
|
3318
3318
|
function flatten(c) {
|
|
3319
|
-
if (c == null || typeof c === "boolean") return [];
|
|
3320
3319
|
if (Array.isArray(c)) return c.flatMap((x) => flatten(x));
|
|
3321
3320
|
return [c];
|
|
3322
3321
|
}
|
|
@@ -3368,8 +3367,19 @@ function createEventStream(max = 2e4, opts) {
|
|
|
3368
3367
|
}
|
|
3369
3368
|
}
|
|
3370
3369
|
};
|
|
3370
|
+
let currentSession = null;
|
|
3371
|
+
let sessionUid = 0;
|
|
3371
3372
|
return {
|
|
3373
|
+
/** 新渲染会话开始(每次渲染入口调用——同一次渲染的事件共享 session id) */
|
|
3374
|
+
setSession() {
|
|
3375
|
+
currentSession = `s${++sessionUid}`;
|
|
3376
|
+
return currentSession;
|
|
3377
|
+
},
|
|
3378
|
+
currentSession() {
|
|
3379
|
+
return currentSession;
|
|
3380
|
+
},
|
|
3372
3381
|
emit(evt) {
|
|
3382
|
+
if (currentSession != null && evt.session == null) evt.session = currentSession;
|
|
3373
3383
|
emitOne(evt, false);
|
|
3374
3384
|
if (watermark > 0 && !watermarkFired && len >= max * watermark) {
|
|
3375
3385
|
watermarkFired = true;
|
|
@@ -3417,6 +3427,10 @@ function createEventStream(max = 2e4, opts) {
|
|
|
3417
3427
|
for (let i = 0; i < len; i++) out[i] = at(i);
|
|
3418
3428
|
return out;
|
|
3419
3429
|
},
|
|
3430
|
+
/** 按渲染会话过滤(阶段 5——session 字段的消费 API——一次渲染的事件全量) */
|
|
3431
|
+
eventsBySession(sessionId) {
|
|
3432
|
+
return this.events().filter((e) => e.session === sessionId);
|
|
3433
|
+
},
|
|
3420
3434
|
inverse(evt) {
|
|
3421
3435
|
switch (evt.entity + ":" + evt.action) {
|
|
3422
3436
|
case "node:insert":
|
|
@@ -3444,6 +3458,35 @@ function createEventStream(max = 2e4, opts) {
|
|
|
3444
3458
|
};
|
|
3445
3459
|
}
|
|
3446
3460
|
var stream = createEventStream();
|
|
3461
|
+
if (typeof globalThis !== "undefined") {
|
|
3462
|
+
const w = globalThis;
|
|
3463
|
+
if (!w.__wf_tail) {
|
|
3464
|
+
w.__wf_tail = (n = 200, filter) => {
|
|
3465
|
+
const evs = stream.events().slice(-n);
|
|
3466
|
+
if (!filter) return evs;
|
|
3467
|
+
return evs.filter((e) => {
|
|
3468
|
+
if (filter.comp && e.payload?.name !== filter.comp) return false;
|
|
3469
|
+
if (filter.decision && e.payload?.reason !== filter.decision) return false;
|
|
3470
|
+
if (filter.causeId && e.payload?.causeId !== filter.causeId) return false;
|
|
3471
|
+
return true;
|
|
3472
|
+
});
|
|
3473
|
+
};
|
|
3474
|
+
w.__wf_builds = (comp) => {
|
|
3475
|
+
const evs = stream.events().filter((e) => e.entity === "comp" && e.action === "build");
|
|
3476
|
+
if (!comp) return evs.map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
|
|
3477
|
+
return evs.filter((e) => e.payload?.name === comp).map((e) => ({ name: e.payload?.name, reason: e.payload?.reason, changedKeys: e.payload?.changedKeys }));
|
|
3478
|
+
};
|
|
3479
|
+
w.__wf_recent = (n = 50) => stream.events().slice(-n);
|
|
3480
|
+
w.__wf_comp = (compId) => {
|
|
3481
|
+
const evs = stream.events().filter((e) => {
|
|
3482
|
+
if (e.target === compId) return true;
|
|
3483
|
+
if (e.payload?.name && !compId) return true;
|
|
3484
|
+
return false;
|
|
3485
|
+
});
|
|
3486
|
+
return evs.map((e) => ({ entity: e.entity, action: e.action, target: e.target, payload: e.payload, session: e.session }));
|
|
3487
|
+
};
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3447
3490
|
var nodeUid = 0;
|
|
3448
3491
|
function nextNodeId() {
|
|
3449
3492
|
return `n${++nodeUid}`;
|
|
@@ -3564,6 +3607,17 @@ function createClientBrowser() {
|
|
|
3564
3607
|
if (typeof window !== "undefined") window.location.hash = h2;
|
|
3565
3608
|
},
|
|
3566
3609
|
timeout: (fn, ms) => typeof window !== "undefined" ? window.setTimeout(fn, ms) : 0,
|
|
3610
|
+
/** 浏览器表单状态恢复(load/pageshow)监听——SSR 安全 no-op */
|
|
3611
|
+
onFormRestore: (cb) => {
|
|
3612
|
+
if (typeof window === "undefined" || typeof document === "undefined") return () => {
|
|
3613
|
+
};
|
|
3614
|
+
window.addEventListener("load", cb, { once: true });
|
|
3615
|
+
window.addEventListener("pageshow", cb);
|
|
3616
|
+
return () => {
|
|
3617
|
+
window.removeEventListener("load", cb);
|
|
3618
|
+
window.removeEventListener("pageshow", cb);
|
|
3619
|
+
};
|
|
3620
|
+
},
|
|
3567
3621
|
rootElement: () => typeof document !== "undefined" ? document.documentElement : null,
|
|
3568
3622
|
storageGet: (key) => {
|
|
3569
3623
|
try {
|
|
@@ -3727,7 +3781,15 @@ var globalHandlers = /* @__PURE__ */ new Map();
|
|
|
3727
3781
|
var globalRoots = /* @__PURE__ */ new Map();
|
|
3728
3782
|
var EVENT_MAP = {
|
|
3729
3783
|
focus: "focusin",
|
|
3730
|
-
blur: "focusout"
|
|
3784
|
+
blur: "focusout",
|
|
3785
|
+
mouseenter: "mouseover",
|
|
3786
|
+
mouseleave: "mouseout"
|
|
3787
|
+
};
|
|
3788
|
+
var REVERSE_MAP = {
|
|
3789
|
+
focusin: "focus",
|
|
3790
|
+
focusout: "blur",
|
|
3791
|
+
mouseover: "mouseenter",
|
|
3792
|
+
mouseout: "mouseleave"
|
|
3731
3793
|
};
|
|
3732
3794
|
var NON_BUBBLING = /* @__PURE__ */ new Set(["error", "load", "loadstart", "loadend", "abort", "unload", "resize", "message"]);
|
|
3733
3795
|
var TEXT_INPUT_EVENTS = /* @__PURE__ */ new Set(["input", "change", "compositionstart", "compositionend"]);
|
|
@@ -3762,11 +3824,11 @@ function rootOf(node) {
|
|
|
3762
3824
|
return null;
|
|
3763
3825
|
}
|
|
3764
3826
|
function ensureRootEvent(root, event) {
|
|
3827
|
+
const realEvent = EVENT_MAP[event] ?? event;
|
|
3765
3828
|
const set = registered.get(root) ?? /* @__PURE__ */ new Set();
|
|
3766
|
-
if (set.has(
|
|
3767
|
-
set.add(
|
|
3829
|
+
if (set.has(realEvent)) return;
|
|
3830
|
+
set.add(realEvent);
|
|
3768
3831
|
registered.set(root, set);
|
|
3769
|
-
const realEvent = EVENT_MAP[event] ?? event;
|
|
3770
3832
|
const fn = (e) => dispatch(e);
|
|
3771
3833
|
const useCapture = NON_BUBBLING.has(realEvent);
|
|
3772
3834
|
root.addEventListener(realEvent, fn, useCapture ? { capture: true } : void 0);
|
|
@@ -3814,8 +3876,11 @@ function addGlobalListener(target, event, handler, opts) {
|
|
|
3814
3876
|
function dispatch(e) {
|
|
3815
3877
|
let el = e.target;
|
|
3816
3878
|
if (el && el.nodeType === 3) el = el.parentElement;
|
|
3817
|
-
const
|
|
3818
|
-
if (
|
|
3879
|
+
const keys = [];
|
|
3880
|
+
if (handlers.has(e.type)) keys.push(e.type);
|
|
3881
|
+
const mapped = REVERSE_MAP[e.type];
|
|
3882
|
+
if (mapped && mapped !== e.type && handlers.has(mapped)) keys.push(mapped);
|
|
3883
|
+
if (keys.length === 0) return;
|
|
3819
3884
|
while (el) {
|
|
3820
3885
|
if (!el.hasAttribute?.("data-v3-id")) {
|
|
3821
3886
|
const idEl = el.closest?.("[data-v3-id]");
|
|
@@ -3826,8 +3891,9 @@ function dispatch(e) {
|
|
|
3826
3891
|
}
|
|
3827
3892
|
const id = el.getAttribute?.("data-v3-id");
|
|
3828
3893
|
if (id) {
|
|
3829
|
-
const
|
|
3830
|
-
|
|
3894
|
+
for (const k of keys) {
|
|
3895
|
+
const entry = handlers.get(k)?.get(id);
|
|
3896
|
+
if (!entry) continue;
|
|
3831
3897
|
try {
|
|
3832
3898
|
Object.defineProperty(e, "currentTarget", { value: el, configurable: true });
|
|
3833
3899
|
} catch {
|
|
@@ -3841,11 +3907,12 @@ function dispatch(e) {
|
|
|
3841
3907
|
stream.emit(ev("text", "input", id, { event: e.type, value: typeof value === "string" ? value.slice(0, 100) : null }));
|
|
3842
3908
|
}
|
|
3843
3909
|
if (entry.once) {
|
|
3844
|
-
|
|
3910
|
+
handlers.get(k).delete(id);
|
|
3845
3911
|
stream.emit(ev("event", "unbind", id, { event: e.type }));
|
|
3846
3912
|
}
|
|
3847
3913
|
if (e.cancelBubble) break;
|
|
3848
3914
|
}
|
|
3915
|
+
if (e.cancelBubble) break;
|
|
3849
3916
|
}
|
|
3850
3917
|
el = el.parentElement;
|
|
3851
3918
|
}
|
|
@@ -4359,7 +4426,8 @@ function usePopup(env, options) {
|
|
|
4359
4426
|
prevOpen = true;
|
|
4360
4427
|
lastEl = el;
|
|
4361
4428
|
}
|
|
4362
|
-
const props = content.props ?? {};
|
|
4429
|
+
const props = (content ?? h("span")).props ?? {};
|
|
4430
|
+
if (!content) return null;
|
|
4363
4431
|
latestContentRef = props.ref ?? null;
|
|
4364
4432
|
const cls = options.positioning === "none" ? props.class ?? "" : ["wf-popup", props.class].filter(Boolean).join(" ");
|
|
4365
4433
|
const style = options.positioning === "none" ? { ...props.style ?? {}, position: "fixed" } : {
|
|
@@ -5367,6 +5435,21 @@ var unmountHooks = /* @__PURE__ */ new Map();
|
|
|
5367
5435
|
function isVNode(v) {
|
|
5368
5436
|
return v != null && typeof v === "object" && !Array.isArray(v) && "type" in v;
|
|
5369
5437
|
}
|
|
5438
|
+
function propsSnap(props) {
|
|
5439
|
+
try {
|
|
5440
|
+
const picked = {};
|
|
5441
|
+
for (const [k, v] of Object.entries(props)) {
|
|
5442
|
+
if (v != null && typeof v === "object") {
|
|
5443
|
+
const s = JSON.stringify(v);
|
|
5444
|
+
if (s != null && s.length <= 1e4) picked[k] = s;
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
return Object.keys(picked).length > 0 ? JSON.stringify(picked) : null;
|
|
5448
|
+
} catch {
|
|
5449
|
+
return null;
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
5452
|
+
var warnedContentChange = /* @__PURE__ */ new Set();
|
|
5370
5453
|
function propsEqual(a, b) {
|
|
5371
5454
|
const ka = Object.keys(a);
|
|
5372
5455
|
const kb = Object.keys(b);
|
|
@@ -5387,17 +5470,33 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
|
|
|
5387
5470
|
v2._render = reuse._render;
|
|
5388
5471
|
v2._id = reuse._id;
|
|
5389
5472
|
indexComponent(v2);
|
|
5390
|
-
stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true }));
|
|
5391
5473
|
if (!isRoot && propsEqual(reuse.props, v2.props)) {
|
|
5392
5474
|
v2._child = reuse._child;
|
|
5475
|
+
if (reuse.el != null) v2.el = reuse.el;
|
|
5476
|
+
if (globalThis.__WF_V3_AUDIT !== "0" && reuse._propsSnap != null) {
|
|
5477
|
+
const cur = propsSnap(v2.props);
|
|
5478
|
+
if (cur != null && cur !== reuse._propsSnap) {
|
|
5479
|
+
const key = `${compName(v2.type)}:${cur.slice(0, 40)}`;
|
|
5480
|
+
if (!warnedContentChange.has(key)) {
|
|
5481
|
+
warnedContentChange.add(key);
|
|
5482
|
+
console.warn(
|
|
5483
|
+
`[vdom3/audit] \u7EC4\u4EF6 ${compName(v2.type)} \u7684 props \u5BF9\u8C61\u5185\u5BB9\u5DF2\u53D8\u4F46\u5F15\u7528\u672A\u53D8\uFF08\u539F\u5730\u4FEE\u6539\u5BF9\u8C61\uFF1F\uFF09\u2014\u2014\u526A\u679D\u5C06\u8DF3\u8FC7\u91CD\u6E32\u67D3\u2014\u2014\u8BF7\u65B0\u5EFA\u5BF9\u8C61\u4F20 props\uFF08props \u4E0D\u53EF\u53D8\u5951\u7EA6\uFF09`
|
|
5484
|
+
);
|
|
5485
|
+
}
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: "reuse-skip", propsKeys: Object.keys(v2.props) }));
|
|
5489
|
+
v2._propsSnap = propsSnap(v2.props);
|
|
5393
5490
|
return v2;
|
|
5394
5491
|
}
|
|
5395
5492
|
const changedKeys = Object.keys({ ...reuse.props, ...v2.props }).filter((k) => reuse.props[k] !== v2.props[k]);
|
|
5396
5493
|
stream.emit(ev("props", "update", v2._id, { name: compName(v2.type), keys: changedKeys }));
|
|
5494
|
+
v2._propsSnap = propsSnap(v2.props);
|
|
5495
|
+
stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: true, index: true, reason: isRoot ? "root-render" : "props-changed", changedKeys }));
|
|
5397
5496
|
} else {
|
|
5398
5497
|
v2._id = nextNodeId();
|
|
5399
5498
|
indexComponent(v2);
|
|
5400
|
-
stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: false, index: true }));
|
|
5499
|
+
stream.emit(ev("comp", "build", v2._id, { name: compName(v2.type), reused: false, index: true, reason: "mount" }));
|
|
5401
5500
|
stream.emit(ev("comp", "mount", v2._id, { name: compName(v2.type) }));
|
|
5402
5501
|
const compId = v2._id;
|
|
5403
5502
|
const componentRender = () => {
|
|
@@ -5423,7 +5522,9 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
|
|
|
5423
5522
|
// SSR 下 document undefined → null/no-op——组件 36 处 ctx.browser 消费
|
|
5424
5523
|
// 无需各自 fallback(Tour 曾因 ctx.browser 缺失 → query 返回 undefined →
|
|
5425
5524
|
// targetEl 永不赋值 → rect 恒 0 → 引导高亮定位失效)
|
|
5426
|
-
browser
|
|
5525
|
+
// 注入优先(测试/宿主可覆盖——真实事故:ctx.browser 注入被覆盖——
|
|
5526
|
+
// Editor draftKey 的 storageSet 走真实 browser 而非 fake)
|
|
5527
|
+
browser: ctx.browser ?? createClientBrowser(),
|
|
5427
5528
|
ui: createV3Ui(compId, componentRender, (fn) => {
|
|
5428
5529
|
unmountHooks.set(compId, fn);
|
|
5429
5530
|
})
|
|
@@ -5437,6 +5538,7 @@ async function buildVNode(vnode, ctx, oldV, isRoot = false) {
|
|
|
5437
5538
|
throw e;
|
|
5438
5539
|
}
|
|
5439
5540
|
v2._render = renderFn;
|
|
5541
|
+
v2._propsSnap = propsSnap(v2.props);
|
|
5440
5542
|
}
|
|
5441
5543
|
stream.emit(ev("comp", "render", v2._id, { name: compName(v2.type) }));
|
|
5442
5544
|
let output = null;
|
|
@@ -5535,7 +5637,11 @@ async function renderToEvents(vnode) {
|
|
|
5535
5637
|
}
|
|
5536
5638
|
events.push(ev("node", "insert", id, { parent: parentId, ref: null }));
|
|
5537
5639
|
for (const c of childrenOf(v)) {
|
|
5538
|
-
if (
|
|
5640
|
+
if (c == null || c === false || c === true) {
|
|
5641
|
+
const hid = nextId();
|
|
5642
|
+
events.push(ev("node", "create", hid, { kind: "hole" }));
|
|
5643
|
+
events.push(ev("node", "insert", hid, { parent: id, ref: null }));
|
|
5644
|
+
} else if (typeof c === "string" || typeof c === "number") {
|
|
5539
5645
|
const tid = nextId();
|
|
5540
5646
|
events.push(ev("text", "create", tid, { value: String(c) }));
|
|
5541
5647
|
events.push(ev("node", "insert", tid, { parent: id, ref: null }));
|
|
@@ -5551,9 +5657,13 @@ function eventsToHtml(events) {
|
|
|
5551
5657
|
const tags = /* @__PURE__ */ new Map();
|
|
5552
5658
|
const attrs = /* @__PURE__ */ new Map();
|
|
5553
5659
|
const texts = /* @__PURE__ */ new Map();
|
|
5660
|
+
const holes = /* @__PURE__ */ new Set();
|
|
5554
5661
|
for (const e of events) {
|
|
5555
|
-
if (e.entity === "node" && e.action === "create")
|
|
5556
|
-
|
|
5662
|
+
if (e.entity === "node" && e.action === "create") {
|
|
5663
|
+
const pl = e.payload;
|
|
5664
|
+
if (pl.kind === "hole") holes.add(e.target);
|
|
5665
|
+
else tags.set(e.target, pl.tag);
|
|
5666
|
+
} else if (e.entity === "prop" && e.action === "update") {
|
|
5557
5667
|
const pl = e.payload;
|
|
5558
5668
|
const m = attrs.get(e.target) ?? /* @__PURE__ */ new Map();
|
|
5559
5669
|
if (pl.value == null || pl.value === false) m.delete(pl.key);
|
|
@@ -5572,6 +5682,7 @@ function eventsToHtml(events) {
|
|
|
5572
5682
|
}
|
|
5573
5683
|
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5574
5684
|
const emit = (id) => {
|
|
5685
|
+
if (holes.has(id)) return "<!--wf-hole-->";
|
|
5575
5686
|
const tag = tags.get(id);
|
|
5576
5687
|
if (tag) {
|
|
5577
5688
|
const a = attrs.get(id);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/docx — docx ↔ ODES DocState 双向转换(前端/服务端通用——零依赖)
|
|
3
|
+
*
|
|
4
|
+
* 参考算法:office2json(unzip → XML 树 → 路径提取 w:body → w:p → w:r → w:t),
|
|
5
|
+
* 增强提取到 ODES IR(design/office-events-plan.md):
|
|
6
|
+
* - 段落属性:pStyle → h1/h2/h3、jc → 对齐(裁剪:样式名映射子集)
|
|
7
|
+
* - 内联格式:rPr b/i/u → marks(区间)
|
|
8
|
+
* - 表格:w:tbl → Editor 表格 embed 快照(HTML 复用现有模型)
|
|
9
|
+
* - 图片:w:drawing r:embed → rels → media → data url → img embed
|
|
10
|
+
*
|
|
11
|
+
* 导出(DocState → minimal docx):VNode 组件化(OOXML 也是 VNode——与前端
|
|
12
|
+
* "VNode → DOM"同构)→ vnodeToXml → store ZIP。块级 embed(pre/hr)打断段落。
|
|
13
|
+
*
|
|
14
|
+
* 前端可用(无需后端):ZIP 解压走 DecompressionStream(浏览器/Node 18+);
|
|
15
|
+
* base64 跨环境(Buffer 守卫/atob)。诚实裁剪:分节/页眉页脚/域代码/批注
|
|
16
|
+
* 不导入;http 图片导出裁剪(warning)。
|
|
17
|
+
*/
|
|
18
|
+
import type { DocState } from '../components/Editor/model/types.ts';
|
|
19
|
+
/** base64 → Uint8Array(跨环境——浏览器 atob / Node Buffer) */
|
|
20
|
+
export declare function base64ToBytes(b64: string): Uint8Array;
|
|
21
|
+
/** Uint8Array → base64(跨环境) */
|
|
22
|
+
export declare function bytesToBase64(u8: Uint8Array): string;
|
|
23
|
+
export interface DocxImportResult {
|
|
24
|
+
doc: DocState;
|
|
25
|
+
/** 裁剪提示(不可映射内容——诚实不静默) */
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function docxToDoc(u8: Uint8Array): Promise<DocxImportResult>;
|
|
29
|
+
export interface DocxExportResult {
|
|
30
|
+
data: Uint8Array;
|
|
31
|
+
warnings: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function docToDocx(doc: DocState): DocxExportResult;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/pptx — pptx ↔ ODES DeckState 双向转换(前端/服务端通用——零依赖)
|
|
3
|
+
*
|
|
4
|
+
* 参考算法:office2json(ppt/slides/slideN.xml → p:sld → p:cSld → p:spTree →
|
|
5
|
+
* p:sp → p:txBody → a:p → a:r → a:t),增强提取到 ODES IR:
|
|
6
|
+
* - 多幻灯片:slideN.xml 全量(office2json 只取文本——ODES 保留结构)
|
|
7
|
+
* - shape 几何:p:xfrm(a:off x/y + a:ext cx/cy——EMU → px:/9525)
|
|
8
|
+
* - shape 类型:p:sp(文本/图形——p:prstGeom prst)/ p:pic(图片)
|
|
9
|
+
* - 文本:txBody → 段落拼接(a:br 换行)
|
|
10
|
+
*
|
|
11
|
+
* 导出(DeckState → minimal pptx):slideN.xml(spTree 结构)+ presentation.xml
|
|
12
|
+
* + rels + contentTypes——store ZIP(VNode 组件化)。裁剪:动画/母版/备注/主题。
|
|
13
|
+
*/
|
|
14
|
+
import type { DeckState } from '../components/OfficeEditor/model/types.ts';
|
|
15
|
+
export interface PptxImportResult {
|
|
16
|
+
deck: DeckState;
|
|
17
|
+
warnings: string[];
|
|
18
|
+
}
|
|
19
|
+
export declare function pptxToDeck(u8: Uint8Array): Promise<PptxImportResult>;
|
|
20
|
+
export interface PptxExportResult {
|
|
21
|
+
data: Uint8Array;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
}
|
|
24
|
+
export declare function deckToPptx(deck: DeckState): PptxExportResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/xlsx — xlsx ↔ ODES WorkbookState 双向转换(前端/服务端通用——零依赖)
|
|
3
|
+
*
|
|
4
|
+
* 参考算法:office2json(sheet1.xml → sheetData → row → c{t,v} + sharedStrings),
|
|
5
|
+
* 增强提取到 ODES IR(design/office-events-plan.md):
|
|
6
|
+
* - 多 sheet:workbook.xml(sheet 名 + rId)→ rels → 各 sheetN.xml
|
|
7
|
+
* - 单元格引用:c@r(A1——office2json 丢弃——ODES 必须)
|
|
8
|
+
* - 类型:c@t(s=shared/n=number/b=bool/f=formula)→ SheetCell.kind
|
|
9
|
+
* - 公式:f 元素(公式字符串保留——不计算——服务端导出重算)
|
|
10
|
+
* - 行高/列宽/样式索引:裁剪(v1——诚实:不读不写)
|
|
11
|
+
*
|
|
12
|
+
* 导出(WorkbookState → minimal xlsx):共享字符串表去重 + sheetData XML +
|
|
13
|
+
* store ZIP(VNode 组件化——OOXML 也是 VNode)。
|
|
14
|
+
*/
|
|
15
|
+
import type { WorkbookState } from '../components/OfficeEditor/model/types.ts';
|
|
16
|
+
export interface XlsxImportResult {
|
|
17
|
+
workbook: WorkbookState;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function xlsxToWorkbook(u8: Uint8Array): Promise<XlsxImportResult>;
|
|
21
|
+
export interface XlsxExportResult {
|
|
22
|
+
data: Uint8Array;
|
|
23
|
+
warnings: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function workbookToXlsx(wb: WorkbookState): XlsxExportResult;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/xml-serialize — VNode 树 → XML 字符串(服务端 OOXML 生成)
|
|
3
|
+
*
|
|
4
|
+
* 架构(与前端 "VNode → DOM" 同构——"OOXML 也是写 VNode"):
|
|
5
|
+
* DocState → VNode 树(h()——vdom3 纯数据结构,零 DOM 依赖)
|
|
6
|
+
* → vnodeToXml(本模块)→ ZIP 打包
|
|
7
|
+
*
|
|
8
|
+
* 序列化约定(仅输出 XML 子集——与 vdom3 渲染管线解耦):
|
|
9
|
+
* - type: string = 元素名(含命名空间前缀——w:p、a:graphic 等原样输出)
|
|
10
|
+
* - props: 字符串值 = XML 属性;number/boolean 字符串化;key/children 排除
|
|
11
|
+
* - children: 字符串 = 文本(转义);VNode = 子元素;数组 = 序列
|
|
12
|
+
* - 空元素 → 自闭合 <w:b/>(OOXML 合法——MS Word 接受)
|
|
13
|
+
* - 函数/symbol type、事件 props(on*)→ 抛错(诚实——序列化器不支持)
|
|
14
|
+
*/
|
|
15
|
+
import type { VNode, VNodeChild } from '../ui-dom/vdom3/types.ts';
|
|
16
|
+
export declare function vnodeToXml(v: VNode | VNodeChild, xmlDecl?: boolean): string;
|
|
17
|
+
/** 便捷:把 VNode 树序列化为 OOXML 部件 XML(名称检查由调用方保证) */
|
|
18
|
+
export declare function vnodeToXmlDecl(v: VNode): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/xml — 自研轻量 XML 解析(零 npm 依赖)
|
|
3
|
+
*
|
|
4
|
+
* 参考算法:office2json(@xmldom/xmldom → { name, attrs, children } 树)。
|
|
5
|
+
* OOXML 是规整 XML——只做元素树 + 文本;裁剪:DTD/实体展开(除内建五实体)/
|
|
6
|
+
* 命名空间处理(前缀保留原名——w:p 等)。解析失败抛错(诚实——不静默)。
|
|
7
|
+
*/
|
|
8
|
+
export interface XmlNode {
|
|
9
|
+
name: string;
|
|
10
|
+
attrs: Record<string, string>;
|
|
11
|
+
children: XmlNode[];
|
|
12
|
+
/** 直接文本内容(子元素外的文本) */
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
/** 手写扫描器:<name attrs>...</name> / <name/> / 文本 / 指令 / 注释 / CDATA */
|
|
16
|
+
export declare function parseXml(xml: string): XmlNode;
|
|
17
|
+
/** 便捷:找子元素(按名字——含命名空间前缀匹配) */
|
|
18
|
+
export declare function child(el: XmlNode, name: string): XmlNode | null;
|
|
19
|
+
export declare function children(el: XmlNode, name: string): XmlNode[];
|
|
20
|
+
/** 全部后代文本(拼接) */
|
|
21
|
+
export declare function allText(el: XmlNode): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* weifuwu/office/zip — 自研 ZIP 容器读写(零 npm 依赖)
|
|
3
|
+
*
|
|
4
|
+
* 参考算法:office2json(unzip 结构——EOCD → central directory → local header)。
|
|
5
|
+
* 实现取舍:
|
|
6
|
+
* - 读:EOCD(尾部扫描)→ central directory → local header offset 取数据;
|
|
7
|
+
* 压缩方法 0(store)/ 8(deflate——Node 内置 zlib.inflateRawSync)
|
|
8
|
+
* - 写:store(无压缩——合法 ZIP,文件较大但零依赖正确性优先);
|
|
9
|
+
* CRC32 自研查表
|
|
10
|
+
* - 裁剪:ZIP64/加密/数据描述符(flags bit3)/多盘——不支持(诚实——读到即抛错)
|
|
11
|
+
*/
|
|
12
|
+
export interface ZipEntry {
|
|
13
|
+
name: string;
|
|
14
|
+
data: Uint8Array;
|
|
15
|
+
}
|
|
16
|
+
export declare function crc32(buf: Uint8Array): number;
|
|
17
|
+
/** inflate(ZIP method 8)——跨环境:浏览器 DecompressionStream / Node 18+(同 API)。
|
|
18
|
+
* 老环境无 DecompressionStream → 抛错(诚实裁剪:OOXML 必用 deflate)。 */
|
|
19
|
+
export declare function inflateRaw(data: Uint8Array): Promise<Uint8Array>;
|
|
20
|
+
export declare function readZip(u8: Uint8Array): Promise<Map<string, Uint8Array>>;
|
|
21
|
+
export declare function writeZip(files: Map<string, Uint8Array>): Uint8Array;
|