thursday-agent 0.16.0 → 0.17.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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/artifact/route.js +9 -9
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js +3 -3
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js +8 -8
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js +8 -8
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js +3 -3
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js +3 -3
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +4 -4
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +11 -11
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/reach/route.js +11 -11
- package/.next/server/app/api/reach/route.js.nft.json +1 -1
- package/.next/server/app/api/routine/route.js +7 -7
- package/.next/server/app/api/routine/route.js.nft.json +1 -1
- package/.next/server/app/api/signins/route.js +5 -5
- package/.next/server/app/api/signins/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +7 -7
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +7 -7
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +5 -5
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/settings/route.js +5 -5
- package/.next/server/app/api/thursday/settings/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/text/route.js +13 -13
- package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +12 -12
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js +8 -8
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +9 -9
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +3 -3
- package/.next/server/app/artifact/[...path]/page.js +4 -4
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +60 -60
- package/.next/server/app/page.js +5 -5
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__00qyw01._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__01ypm_z._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__021dkui._.js +3 -0
- package/.next/server/chunks/{[root-of-the-server]__18e65_-._.js → [root-of-the-server]__03xamry._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__05dp5tr._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__09gdscj._.js +7 -0
- package/.next/server/chunks/{[root-of-the-server]__10_7vqz._.js → [root-of-the-server]__0b36yfw._.js} +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1gamvcz._.js → [root-of-the-server]__0jzwdg-._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0nmust6._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__0svzjwa._.js +7 -0
- package/.next/server/chunks/{[root-of-the-server]__16yw3mb._.js → [root-of-the-server]__12m24xl._.js} +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1zrwxis._.js → [root-of-the-server]__12tahb4._.js} +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1ep-pao._.js → [root-of-the-server]__1_h4hf9._.js} +4 -4
- package/.next/server/chunks/{[root-of-the-server]__1-tdj60._.js → [root-of-the-server]__1de1o6m._.js} +2 -2
- package/.next/server/chunks/{[root-of-the-server]__1voqqyc._.js → [root-of-the-server]__1ignh6-._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1t02-1p._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__11g20j-._.js → [root-of-the-server]__1yl5l25._.js} +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1mbd2w_._.js → [root-of-the-server]__208vmyf._.js} +1 -1
- package/.next/server/chunks/{_1mfu9a3._.js → _02lipqs._.js} +1 -1
- package/.next/server/chunks/_05sa7xl._.js +1 -1
- package/.next/server/chunks/{_1rg5bfd._.js → _074f2ts._.js} +1 -1
- package/.next/server/chunks/_07nxi75._.js +3 -3
- package/.next/server/chunks/_08btiir._.js +7 -0
- package/.next/server/chunks/_0caua_s._.js +1 -1
- package/.next/server/chunks/{_0qoush_._.js → _0gt6cbj._.js} +1 -1
- package/.next/server/chunks/_0xygcxs._.js +3 -3
- package/.next/server/chunks/{_09c4we8._.js → _17i1lt2._.js} +1 -1
- package/.next/server/chunks/_19kea1l._.js +9 -9
- package/.next/server/chunks/_1gn2r2q._.js +3 -3
- package/.next/server/chunks/{_03niwml._.js → _1ntwp1s._.js} +1 -1
- package/.next/server/chunks/_1q6e56i._.js +3 -3
- package/.next/server/chunks/{_02fn1o3._.js → _1tkzgtm._.js} +1 -1
- package/.next/server/chunks/_1wl2g8_._.js +2 -2
- package/.next/server/chunks/{_1w6uf9k._.js → _1y3mnz4._.js} +1 -1
- package/.next/server/chunks/features_workspace_workspace_ts_13h82gv._.js +4 -4
- package/.next/server/chunks/features_workspace_workspace_ts_1iyjroj._.js +4 -4
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0cux6zt._.js → node_modules_consola_dist_chunks_prompt_mjs_04oxl25._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0w32bqh._.js → node_modules_consola_dist_chunks_prompt_mjs_0g87zst._.js} +1 -1
- package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_1clmnaj._.js → node_modules_consola_dist_chunks_prompt_mjs_1341cnf._.js} +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__02jdtqa._.js → [root-of-the-server]__07660xu._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__0rnjkw2._.js → [root-of-the-server]__149x25m._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__17h1ggo._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__19s8qyj._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1jyeu61._.js +1 -1
- package/.next/server/chunks/ssr/_088vzgn._.js +3 -0
- package/.next/server/chunks/ssr/{_0wupyvd._.js → _0_vnw9v._.js} +2 -2
- package/.next/server/chunks/ssr/{_1u5vsy-._.js → _0aeknva._.js} +2 -2
- package/.next/server/chunks/ssr/_0fo4552._.js +3 -3
- package/.next/server/chunks/ssr/{_0-ou1b0._.js → _0n2scfq._.js} +2 -2
- package/.next/server/chunks/ssr/_0o_o9ji._.js +3 -0
- package/.next/server/chunks/ssr/_0qne8xs._.js +3 -0
- package/.next/server/chunks/ssr/_0r0g9ki._.js +1 -1
- package/.next/server/chunks/ssr/_0v9r8g2._.js +3 -0
- package/.next/server/chunks/ssr/_0x8ophf._.js +1 -1
- package/.next/server/chunks/ssr/{_0ncuqct._.js → _0ztbn5q._.js} +2 -2
- package/.next/server/chunks/ssr/{_0w8r7o7._.js → _1-41lck._.js} +2 -2
- package/.next/server/chunks/ssr/{_10fwcz_._.js → _14dfh7a._.js} +2 -2
- package/.next/server/chunks/ssr/{_1f6s34p._.js → _15m5ctz._.js} +2 -2
- package/.next/server/chunks/ssr/{_11pnnvd._.js → _18-e7zi._.js} +2 -2
- package/.next/server/chunks/ssr/{_0y28gl3._.js → _1c5iqz1._.js} +3 -3
- package/.next/server/chunks/ssr/{_1dqaksc._.js → _1hbjh6n._.js} +2 -2
- package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
- package/.next/server/chunks/ssr/{_1k0efi3._.js → _1sttj8x._.js} +2 -2
- package/.next/server/chunks/ssr/_1uij_rj._.js +1 -1
- package/.next/server/chunks/ssr/_1umbr78._.js +1 -1
- package/.next/server/chunks/ssr/{_17ble8k._.js → _1vtgiou._.js} +3 -3
- package/.next/server/chunks/ssr/_1w9w1ok._.js +1 -1
- package/.next/server/chunks/ssr/{_1z586yw._.js → _1z6pirx._.js} +2 -2
- package/.next/server/chunks/ssr/features_0796o8r._.js +1 -1
- package/.next/server/chunks/ssr/features_bot_1knyan7._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_1kr0_m-._.js +3 -0
- package/.next/server/chunks/ssr/{features_config_11dpa7r._.js → features_config_0n0zu89._.js} +3 -3
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
- package/.next/server/chunks/ssr/features_workspace_workspace_ts_1wc7_u_._.js +4 -4
- package/.next/server/chunks/ssr/{node_modules_1r5o-bo._.js → node_modules_00h1t1j._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1o9n7_n._.js → node_modules_02ficyi._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0u24mg0._.js → node_modules_03q71_j._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_138kcrz._.js → node_modules_03uejh6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0x_2io2._.js → node_modules_06grs09._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0n1js5k._.js → node_modules_07mo9rn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bas34k._.js → node_modules_09e8kz_._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1dzye0n._.js → node_modules_09ikzel._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_18yn_bs._.js → node_modules_0_d_7gq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ka8te1._.js → node_modules_0_p8w7p._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0mark4f._.js → node_modules_0f5bmkb._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0sdjzae._.js → node_modules_0kwie-o._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ozyial._.js → node_modules_0r-9e4n._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_156m1qh._.js → node_modules_0u_8z64._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0-ew8f8._.js → node_modules_0zf3wuu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xua-g1._.js → node_modules_1-7fc7h._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ld7p-1._.js → node_modules_142z3o5._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0-8r9yw._.js → node_modules_180l3i0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1tj7_9z._.js → node_modules_197vh1s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0y5uubt._.js → node_modules_1_n9o9s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0etz1i0._.js → node_modules_1fw32pg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0bbwe-u._.js → node_modules_1hefp6h._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_097cl9f._.js → node_modules_1irot3r._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0uibk9z._.js → node_modules_1om2itc._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zkzksd._.js → node_modules_1ubln7w._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_02uwbwp._.js → node_modules_1wyhtw9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_133hjui._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0aczkbn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1b3vwd3._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0ah684s._.js} +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +61 -61
- package/.next/static/chunks/{1ub9obhgfm4jl.js → 024plctckvq-1.js} +1 -1
- package/.next/static/chunks/04toe_3nqfjqt.js +1 -0
- package/.next/static/chunks/{0609ejkwuy4sf.js → 0_b_bi2pobasx.js} +2 -2
- package/.next/static/chunks/{3ro66d9dc70bc.js → 0dfvwgnyrp8qt.js} +1 -1
- package/.next/static/chunks/{314szf8yer-3n.js → 0f47nnshdlucs.js} +1 -1
- package/.next/static/chunks/0sowxe4nnl5qu.js +1 -0
- package/.next/static/chunks/{07vcuxousbmy1.js → 0sv142q1yrva2.js} +1 -1
- package/.next/static/chunks/{0q4ws7qd21chx.js → 0tp868040z9nn.js} +2 -2
- package/.next/static/chunks/{0unonxbajyknw.js → 1-63yde5eaz7i.js} +1 -1
- package/.next/static/chunks/14qitm0kwsb6g.js +15 -0
- package/.next/static/chunks/{2xjr5b-yk_h3o.js → 1bgvxd3xny53w.js} +1 -1
- package/.next/static/chunks/{245_cp14iz637.js → 1cyrrz4q871aj.js} +1 -1
- package/.next/static/chunks/1st3b0c9vhxg3.js +1 -0
- package/.next/static/chunks/{39miwvg4k-b2d.js → 1wbqaidq7vc3_.js} +1 -1
- package/.next/static/chunks/{0cl_fgkphk1oz.js → 28sxb_k460e9h.js} +1 -1
- package/.next/static/chunks/{3j-l8un0d77v7.js → 2v6e_9p95o2e3.js} +1 -1
- package/.next/static/chunks/{23tx-2paw9qa6.js → 3-qp777vtfglj.js} +1 -1
- package/.next/static/chunks/{2jbjyazqfqyoh.js → 33ekkscpcq-z1.js} +1 -1
- package/.next/static/chunks/388l-zszwa_jf.js +1 -0
- package/.next/static/chunks/{3vwjs5kq11417.js → 38edd4rpcbvzp.js} +1 -1
- package/.next/static/chunks/3bwrvpjk95_x4.js +1 -0
- package/.next/static/chunks/{42m371fn687r5.js → 3itdg6t243u6f.js} +1 -1
- package/.next/static/chunks/{2oa4gpkzjtpni.js → 44vre0f7xqsh-.js} +1 -1
- package/README.md +12 -12
- package/guide/bots.md +48 -8
- package/guide/calls.md +13 -12
- package/guide/setup.md +4 -2
- package/package.json +1 -1
- package/public/sounds/NOTICE +3 -0
- package/public/sounds/opening-land.ogg +0 -0
- package/public/sounds/opening-rise.ogg +0 -0
- package/public/sounds/opening-step-1.ogg +0 -0
- package/public/sounds/opening-step-2.ogg +0 -0
- package/public/sounds/opening-step-3.ogg +0 -0
- package/skills/README.md +3 -0
- package/skills/artifact/SKILL.md +6 -5
- package/skills/artifact/references/book.md +22 -8
- package/skills/artifact/references/deck.md +4 -2
- package/skills/artifact/references/document.md +9 -0
- package/skills/artifact/references/sheet.md +108 -0
- package/skills/artifact/runtime/book/book.css +75 -0
- package/skills/artifact/runtime/book/book.html +6 -3
- package/skills/artifact/runtime/book/book.js +21 -0
- package/skills/artifact/runtime/canvas/canvas.css +1 -1
- package/skills/artifact/runtime/canvas/canvas.js +11 -1
- package/skills/artifact/runtime/deck/deck.css +164 -1
- package/skills/artifact/runtime/deck/deck.js +76 -1
- package/skills/artifact/runtime/document/docx.js +631 -0
- package/skills/artifact/runtime/document/markdown.mjs +58 -2
- package/skills/artifact/runtime/document/quick.css +66 -0
- package/skills/artifact/runtime/document/quick.html +1 -0
- package/skills/artifact/runtime/document/quick.js +154 -0
- package/skills/artifact/runtime/document/word.mjs +65 -0
- package/skills/artifact/runtime/sheet/format.mjs +385 -0
- package/skills/artifact/runtime/sheet/formula.mjs +755 -0
- package/skills/artifact/runtime/sheet/sheet.css +499 -0
- package/skills/artifact/runtime/sheet/sheet.html +102 -0
- package/skills/artifact/runtime/sheet/sheet.js +1361 -0
- package/skills/artifact/runtime/sheet/xlsx.mjs +320 -0
- package/skills/artifact/runtime/shell/head.html +1 -1
- package/skills/artifact/runtime/shell/shell.css +3 -0
- package/skills/artifact/runtime/shell/wear.mjs +47 -1
- package/skills/artifact/runtime/vendor/fflate.LICENSE.md +23 -0
- package/skills/artifact/runtime/vendor/fflate.mjs +2710 -0
- package/skills/artifact/scripts/book.mjs +141 -5
- package/skills/artifact/scripts/chart.mjs +229 -11
- package/skills/artifact/scripts/document.mjs +29 -2
- package/skills/artifact/scripts/spreadsheet.mjs +1068 -0
- package/skills/data-report/SKILL.md +8 -1
- package/.next/server/chunks/[root-of-the-server]__0aijj66._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0tvoct-._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__118qjk_._.js +0 -7
- package/.next/server/chunks/[root-of-the-server]__1b8tp42._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1gzcbda._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1qcn1ti._.js +0 -7
- package/.next/server/chunks/_0xx3ryc._.js +0 -7
- package/.next/server/chunks/ssr/[root-of-the-server]__1y5_1cv._.js +0 -3
- package/.next/server/chunks/ssr/_0l1-xc6._.js +0 -3
- package/.next/server/chunks/ssr/_1g48r6t._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_1k25jdg._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +0 -3
- package/.next/server/chunks/ssr/lib_utils_ts_0_f1xvm._.js +0 -3
- package/.next/static/chunks/00d54fcfm-lko.js +0 -1
- package/.next/static/chunks/0gjlsu6vrfzod.js +0 -1
- package/.next/static/chunks/17yqt1p_svv13.js +0 -1
- package/.next/static/chunks/203pbwecfs3tm.js +0 -1
- package/.next/static/chunks/3ghvoe_mlljzs.js +0 -15
- package/.next/static/chunks/3gn7dubb4-pwy.js +0 -1
- /package/.next/static/{RtLRT9EEaGn-OqwpMFdYg → VtbCMn-0G969p8QJsWdGv}/_buildManifest.js +0 -0
- /package/.next/static/{RtLRT9EEaGn-OqwpMFdYg → VtbCMn-0G969p8QJsWdGv}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{RtLRT9EEaGn-OqwpMFdYg → VtbCMn-0G969p8QJsWdGv}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
// A sheet's formulas worked out, so the page shows what Excel will: the value each formula
|
|
2
|
+
// cell holds, stored in the .xlsx beside it too (Excel works them out again on opening). The
|
|
3
|
+
// common functions only; anything else stops rather than show a wrong number.
|
|
4
|
+
//
|
|
5
|
+
// Coordinates are Excel's: row 1 is the header, column A the first; a value is a number, a
|
|
6
|
+
// string, a boolean, null (empty) or { error: "#DIV/0!" }.
|
|
7
|
+
|
|
8
|
+
/** A formula this file cannot work out, said so the bot can fix it; `code` is what a cell
|
|
9
|
+
* shows instead when the book is worked out leniently (an edit in the page). */
|
|
10
|
+
export class FormulaError extends Error {
|
|
11
|
+
constructor(message, code = "#NAME?") {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** `A`, `B`, … `Z`, `AA`: a column's letters from its index, counted from 0. */
|
|
18
|
+
export const colName = (index) => {
|
|
19
|
+
let name = "";
|
|
20
|
+
for (let n = index + 1; n > 0; n = Math.floor((n - 1) / 26))
|
|
21
|
+
name = String.fromCharCode(65 + ((n - 1) % 26)) + name;
|
|
22
|
+
return name;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** A column's index, counted from 0, from its letters. */
|
|
26
|
+
export const colIndex = (letters) => {
|
|
27
|
+
let n = 0;
|
|
28
|
+
for (const ch of letters.toUpperCase()) n = n * 26 + (ch.charCodeAt(0) - 64);
|
|
29
|
+
return n - 1;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const FUNCTIONS = [
|
|
33
|
+
"SUM",
|
|
34
|
+
"AVERAGE",
|
|
35
|
+
"MIN",
|
|
36
|
+
"MAX",
|
|
37
|
+
"COUNT",
|
|
38
|
+
"COUNTA",
|
|
39
|
+
"SUBTOTAL",
|
|
40
|
+
"SUMIF",
|
|
41
|
+
"COUNTIF",
|
|
42
|
+
"AVERAGEIF",
|
|
43
|
+
"SUMIFS",
|
|
44
|
+
"COUNTIFS",
|
|
45
|
+
"AVERAGEIFS",
|
|
46
|
+
"ROUND",
|
|
47
|
+
"ABS",
|
|
48
|
+
"IF",
|
|
49
|
+
"IFERROR",
|
|
50
|
+
"AND",
|
|
51
|
+
"OR",
|
|
52
|
+
"NOT",
|
|
53
|
+
"CONCAT",
|
|
54
|
+
"DATE",
|
|
55
|
+
"YEAR",
|
|
56
|
+
"MONTH",
|
|
57
|
+
"DAY",
|
|
58
|
+
"EOMONTH",
|
|
59
|
+
"TODAY",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** Days from 1970-01-01 to Excel's day 0 (the 1900 date system). */
|
|
63
|
+
const DAY_ZERO = 25569;
|
|
64
|
+
const dayOf = (serial) => new Date(Math.floor(serial - DAY_ZERO) * 86400000);
|
|
65
|
+
const serialAt = (y, m, d) => Date.UTC(y, m, d) / 86400000 + DAY_ZERO;
|
|
66
|
+
|
|
67
|
+
const isError = (v) => v !== null && typeof v === "object" && "error" in v;
|
|
68
|
+
const error = (code) => ({ error: code });
|
|
69
|
+
|
|
70
|
+
// ── reading a formula ──────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
const REF = String.raw`\$?[A-Za-z]{1,3}\$?\d*`;
|
|
73
|
+
const PATTERNS = [
|
|
74
|
+
["space", /^\s+/],
|
|
75
|
+
["str", /^"((?:[^"]|"")*)"/],
|
|
76
|
+
["num", /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/],
|
|
77
|
+
["fn", /^([A-Za-z][A-Za-z0-9.]*)\s*\(/],
|
|
78
|
+
["bool", /^(TRUE|FALSE)\b/i],
|
|
79
|
+
["err", /^(#REF!|#DIV\/0!|#N\/A|#VALUE!|#NAME\?|#NUM!|#NULL!)/],
|
|
80
|
+
[
|
|
81
|
+
"ref",
|
|
82
|
+
new RegExp(
|
|
83
|
+
String.raw`^(?:(?:'((?:[^']|'')+)'|([\p{L}\p{N}_.]+))!)?(${REF})(?::(${REF}))?`,
|
|
84
|
+
"u",
|
|
85
|
+
),
|
|
86
|
+
],
|
|
87
|
+
["op", /^(<>|<=|>=|[-+*/^&=<>%])/],
|
|
88
|
+
["punct", /^[(),]/],
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
/** One part of a reference: its column and row, either missing for a whole column or row. */
|
|
92
|
+
function part(text) {
|
|
93
|
+
const match = /^\$?([A-Za-z]{1,3})\$?(\d*)$/.exec(text);
|
|
94
|
+
if (!match) return null;
|
|
95
|
+
return {
|
|
96
|
+
c: colIndex(match[1]),
|
|
97
|
+
r: match[2] ? Number(match[2]) - 1 : null,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function tokenize(text) {
|
|
102
|
+
const tokens = [];
|
|
103
|
+
let rest = text;
|
|
104
|
+
while (rest) {
|
|
105
|
+
let hit = null;
|
|
106
|
+
for (const [kind, re] of PATTERNS) {
|
|
107
|
+
const m = re.exec(rest);
|
|
108
|
+
if (!m) continue;
|
|
109
|
+
hit = { kind, m };
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (!hit) throw new FormulaError(`cannot read "${rest.slice(0, 12)}"`);
|
|
113
|
+
const { kind, m } = hit;
|
|
114
|
+
rest = rest.slice(m[0].length);
|
|
115
|
+
if (kind === "space") continue;
|
|
116
|
+
if (kind === "str")
|
|
117
|
+
tokens.push({ t: "str", v: m[1].replaceAll('""', '"') });
|
|
118
|
+
else if (kind === "num") tokens.push({ t: "num", v: Number(m[0]) });
|
|
119
|
+
else if (kind === "fn") tokens.push({ t: "fn", v: m[1].toUpperCase() });
|
|
120
|
+
else if (kind === "bool")
|
|
121
|
+
tokens.push({ t: "bool", v: m[1].toUpperCase() === "TRUE" });
|
|
122
|
+
else if (kind === "err") tokens.push({ t: "err", v: m[1] });
|
|
123
|
+
else if (kind === "ref") {
|
|
124
|
+
const sheet = m[1]?.replaceAll("''", "'") ?? m[2] ?? null;
|
|
125
|
+
const a = part(m[3]);
|
|
126
|
+
const b = m[4] ? part(m[4]) : null;
|
|
127
|
+
if (!a || (m[4] && !b)) throw new FormulaError(`cannot read "${m[0]}"`);
|
|
128
|
+
if (!b && a.r === null)
|
|
129
|
+
throw new FormulaError(
|
|
130
|
+
`"${m[0]}" is not a cell; a column is written ${m[3]}:${m[3]}`,
|
|
131
|
+
);
|
|
132
|
+
tokens.push({ t: "ref", sheet, a, b });
|
|
133
|
+
} else if (kind === "op") tokens.push({ t: "op", v: m[1] });
|
|
134
|
+
else tokens.push({ t: m[0] });
|
|
135
|
+
}
|
|
136
|
+
return tokens;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** A formula as a tree: comparison, &, + -, * /, ^, unary minus, %, then a value. */
|
|
140
|
+
export function parse(formula) {
|
|
141
|
+
const tokens = tokenize(String(formula).replace(/^=/, ""));
|
|
142
|
+
let at = 0;
|
|
143
|
+
const peek = () => tokens[at];
|
|
144
|
+
const take = () => tokens[at++];
|
|
145
|
+
const isOp = (...ops) => peek()?.t === "op" && ops.includes(peek().v);
|
|
146
|
+
const binary = (next, ops) => () => {
|
|
147
|
+
let left = next();
|
|
148
|
+
while (isOp(...ops)) {
|
|
149
|
+
const op = take().v;
|
|
150
|
+
left = { k: "bin", op, a: left, b: next() };
|
|
151
|
+
}
|
|
152
|
+
return left;
|
|
153
|
+
};
|
|
154
|
+
const primary = () => {
|
|
155
|
+
const token = take();
|
|
156
|
+
if (!token) throw new FormulaError("ends too soon");
|
|
157
|
+
if (token.t === "num") return { k: "val", v: token.v };
|
|
158
|
+
if (token.t === "str") return { k: "val", v: token.v };
|
|
159
|
+
if (token.t === "bool") return { k: "val", v: token.v };
|
|
160
|
+
if (token.t === "err") return { k: "val", v: error(token.v) };
|
|
161
|
+
if (token.t === "ref") return { k: "ref", ...token };
|
|
162
|
+
if (token.t === "(") {
|
|
163
|
+
const inner = comparison();
|
|
164
|
+
if (take()?.t !== ")") throw new FormulaError("a ( is not closed");
|
|
165
|
+
return inner;
|
|
166
|
+
}
|
|
167
|
+
if (token.t === "fn") {
|
|
168
|
+
if (!FUNCTIONS.includes(token.v))
|
|
169
|
+
throw new FormulaError(
|
|
170
|
+
`${token.v} is not one this sheet works out; use ${FUNCTIONS.join(", ")}, or put the value itself`,
|
|
171
|
+
);
|
|
172
|
+
const args = [];
|
|
173
|
+
if (peek()?.t !== ")") {
|
|
174
|
+
args.push(comparison());
|
|
175
|
+
while (peek()?.t === ",") {
|
|
176
|
+
take();
|
|
177
|
+
args.push(comparison());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (take()?.t !== ")")
|
|
181
|
+
throw new FormulaError(`${token.v}( is not closed`);
|
|
182
|
+
return { k: "call", name: token.v, args };
|
|
183
|
+
}
|
|
184
|
+
throw new FormulaError(`did not expect "${token.v ?? token.t}"`);
|
|
185
|
+
};
|
|
186
|
+
const postfix = () => {
|
|
187
|
+
let node = primary();
|
|
188
|
+
while (isOp("%")) {
|
|
189
|
+
take();
|
|
190
|
+
node = { k: "bin", op: "/", a: node, b: { k: "val", v: 100 } };
|
|
191
|
+
}
|
|
192
|
+
return node;
|
|
193
|
+
};
|
|
194
|
+
const unary = () => {
|
|
195
|
+
if (isOp("-")) {
|
|
196
|
+
take();
|
|
197
|
+
return { k: "neg", a: unary() };
|
|
198
|
+
}
|
|
199
|
+
if (isOp("+")) {
|
|
200
|
+
take();
|
|
201
|
+
return unary();
|
|
202
|
+
}
|
|
203
|
+
return postfix();
|
|
204
|
+
};
|
|
205
|
+
const power = binary(unary, ["^"]);
|
|
206
|
+
const product = binary(power, ["*", "/"]);
|
|
207
|
+
const sum = binary(product, ["+", "-"]);
|
|
208
|
+
const concat = binary(sum, ["&"]);
|
|
209
|
+
const comparison = binary(concat, ["=", "<>", "<", ">", "<=", ">="]);
|
|
210
|
+
const tree = comparison();
|
|
211
|
+
if (at < tokens.length)
|
|
212
|
+
throw new FormulaError(`did not expect "${peek().v ?? peek().t}"`);
|
|
213
|
+
return tree;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ── working it out ─────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
const toNumber = (v) => {
|
|
219
|
+
if (isError(v)) return v;
|
|
220
|
+
if (v === null || v === "") return 0;
|
|
221
|
+
if (typeof v === "number") return v;
|
|
222
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
223
|
+
const n = Number(String(v).trim());
|
|
224
|
+
return Number.isFinite(n) ? n : error("#VALUE!");
|
|
225
|
+
};
|
|
226
|
+
const toText = (v) =>
|
|
227
|
+
v === null ? "" : typeof v === "boolean" ? (v ? "TRUE" : "FALSE") : String(v);
|
|
228
|
+
const clean = (n) =>
|
|
229
|
+
Number.isFinite(n) ? Number(n.toPrecision(15)) : error("#NUM!");
|
|
230
|
+
|
|
231
|
+
/** Excel orders numbers before text before booleans; text ignores case. */
|
|
232
|
+
function compare(a, b) {
|
|
233
|
+
const rank = (v) =>
|
|
234
|
+
typeof v === "number" || v === null ? 0 : typeof v === "string" ? 1 : 2;
|
|
235
|
+
const [x, y] = [a ?? 0, b ?? 0];
|
|
236
|
+
if (rank(x) !== rank(y)) return rank(x) - rank(y);
|
|
237
|
+
if (typeof x === "string")
|
|
238
|
+
return x.toLowerCase() < y.toLowerCase()
|
|
239
|
+
? -1
|
|
240
|
+
: x.toLowerCase() > y.toLowerCase()
|
|
241
|
+
? 1
|
|
242
|
+
: 0;
|
|
243
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** A SUMIF/COUNTIF criterion: `>=100`, `<>x`, `=x`, `x`, with * and ? in text. */
|
|
247
|
+
function criterion(raw) {
|
|
248
|
+
const text = toText(raw);
|
|
249
|
+
const m = /^(<>|<=|>=|=|<|>)?(.*)$/s.exec(text);
|
|
250
|
+
const op = m[1] ?? "=";
|
|
251
|
+
const operand = m[2];
|
|
252
|
+
const n = operand.trim() === "" ? null : Number(operand);
|
|
253
|
+
const numeric = typeof raw === "number" || (n !== null && Number.isFinite(n));
|
|
254
|
+
const pattern = new RegExp(
|
|
255
|
+
`^${operand
|
|
256
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
257
|
+
.replaceAll("*", ".*")
|
|
258
|
+
.replaceAll("?", ".")}$`,
|
|
259
|
+
"is",
|
|
260
|
+
);
|
|
261
|
+
return (v) => {
|
|
262
|
+
if (isError(v)) return false;
|
|
263
|
+
if (numeric && typeof v === "number") {
|
|
264
|
+
const c = v - (typeof raw === "number" ? raw : n);
|
|
265
|
+
return {
|
|
266
|
+
"=": c === 0,
|
|
267
|
+
"<>": c !== 0,
|
|
268
|
+
"<": c < 0,
|
|
269
|
+
">": c > 0,
|
|
270
|
+
"<=": c <= 0,
|
|
271
|
+
">=": c >= 0,
|
|
272
|
+
}[op];
|
|
273
|
+
}
|
|
274
|
+
if (op === "=") return pattern.test(toText(v));
|
|
275
|
+
if (op === "<>") return !pattern.test(toText(v));
|
|
276
|
+
if (numeric) return false;
|
|
277
|
+
const c = compare(toText(v), operand);
|
|
278
|
+
return { "<": c < 0, ">": c > 0, "<=": c <= 0, ">=": c >= 0 }[op];
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Every formula in a workbook worked out. `sheets` is a list of `{ name, cells }`, `cells` rows
|
|
284
|
+
* of `{ v }` or `{ f }` (a formula, `=` or not) with row 0 the header. Each formula cell gets its
|
|
285
|
+
* `v`. A formula that cannot be read, names a sheet or function that is not there, or refers to
|
|
286
|
+
* itself throws a FormulaError naming the cell — or, given a `problems` list, holds that error's
|
|
287
|
+
* code (`#NAME?`, `#REF!`, …) and adds the message to the list, as a cell in Excel would.
|
|
288
|
+
*/
|
|
289
|
+
export function workOut(sheets, { problems } = {}) {
|
|
290
|
+
const byName = new Map(sheets.map((s) => [s.name.toLowerCase(), s]));
|
|
291
|
+
const trees = new Map();
|
|
292
|
+
const state = new Map(); // "sheet|r|c" → "busy" | "done"
|
|
293
|
+
|
|
294
|
+
const sheetOf = (name, from) => {
|
|
295
|
+
if (!name) return from;
|
|
296
|
+
const found = byName.get(name.toLowerCase());
|
|
297
|
+
if (!found) throw new FormulaError(`there is no sheet "${name}"`, "#REF!");
|
|
298
|
+
return found;
|
|
299
|
+
};
|
|
300
|
+
const value = (sheet, r, c) => {
|
|
301
|
+
const cell = sheet.cells[r]?.[c];
|
|
302
|
+
if (!cell) return null;
|
|
303
|
+
if (cell.f === undefined) return cell.v ?? null;
|
|
304
|
+
const key = `${sheet.name}|${r}|${c}`;
|
|
305
|
+
if (state.get(key) === "done") return cell.v;
|
|
306
|
+
if (state.get(key) === "busy")
|
|
307
|
+
throw new FormulaError(
|
|
308
|
+
`${sheet.name}!${colName(c)}${r + 1} refers to itself`,
|
|
309
|
+
"#VALUE!",
|
|
310
|
+
);
|
|
311
|
+
state.set(key, "busy");
|
|
312
|
+
try {
|
|
313
|
+
let tree = trees.get(key);
|
|
314
|
+
if (!tree) {
|
|
315
|
+
tree = parse(cell.f);
|
|
316
|
+
trees.set(key, tree);
|
|
317
|
+
}
|
|
318
|
+
const out = evaluate(tree, sheet);
|
|
319
|
+
cell.v = Array.isArray(out) ? error("#VALUE!") : out;
|
|
320
|
+
} catch (failed) {
|
|
321
|
+
if (!(failed instanceof FormulaError)) throw failed;
|
|
322
|
+
const named = failed.message.startsWith(`${sheet.name}!`)
|
|
323
|
+
? failed
|
|
324
|
+
: new FormulaError(
|
|
325
|
+
`${sheet.name}!${colName(c)}${r + 1} (${cell.f}): ${failed.message}`,
|
|
326
|
+
failed.code,
|
|
327
|
+
);
|
|
328
|
+
if (!problems) throw named;
|
|
329
|
+
problems.push(named.message);
|
|
330
|
+
cell.v = error(named.code);
|
|
331
|
+
}
|
|
332
|
+
state.set(key, "done");
|
|
333
|
+
return cell.v;
|
|
334
|
+
};
|
|
335
|
+
/** A reference as a grid of values; a whole column stops at the sheet's last row. */
|
|
336
|
+
const grid = (node, from) => {
|
|
337
|
+
const sheet = sheetOf(node.sheet, from);
|
|
338
|
+
const b = node.b ?? node.a;
|
|
339
|
+
const rows = sheet.cells.length;
|
|
340
|
+
const r1 = node.a.r ?? 0;
|
|
341
|
+
const r2 = b.r ?? rows - 1;
|
|
342
|
+
const [top, bottom] = [Math.min(r1, r2), Math.max(r1, r2)];
|
|
343
|
+
const [left, right] = [Math.min(node.a.c, b.c), Math.max(node.a.c, b.c)];
|
|
344
|
+
const out = [];
|
|
345
|
+
for (let r = top; r <= Math.min(bottom, rows - 1); r++) {
|
|
346
|
+
const row = [];
|
|
347
|
+
for (let c = left; c <= right; c++) row.push(value(sheet, r, c));
|
|
348
|
+
out.push(row);
|
|
349
|
+
}
|
|
350
|
+
return out;
|
|
351
|
+
};
|
|
352
|
+
const flat = (args, from) =>
|
|
353
|
+
args.flatMap((arg) =>
|
|
354
|
+
arg.k === "ref" && (arg.b || arg.a.r === null)
|
|
355
|
+
? grid(arg, from)
|
|
356
|
+
.flat()
|
|
357
|
+
.map((v) => ({ v, cell: true }))
|
|
358
|
+
: [{ v: evaluate(arg, from), cell: arg.k === "ref" }],
|
|
359
|
+
);
|
|
360
|
+
const numbers = (args, from) => {
|
|
361
|
+
const out = [];
|
|
362
|
+
for (const { v, cell } of flat(args, from)) {
|
|
363
|
+
if (isError(v)) return v;
|
|
364
|
+
if (typeof v === "number") out.push(v);
|
|
365
|
+
else if (!cell && v !== null) {
|
|
366
|
+
const n = toNumber(v);
|
|
367
|
+
if (isError(n)) return n;
|
|
368
|
+
out.push(n);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return out;
|
|
372
|
+
};
|
|
373
|
+
const aggregate = (name, list) => {
|
|
374
|
+
if (isError(list)) return list;
|
|
375
|
+
if (name === "SUM") return clean(list.reduce((s, n) => s + n, 0));
|
|
376
|
+
if (name === "COUNT") return list.length;
|
|
377
|
+
if (!list.length) return name === "AVERAGE" ? error("#DIV/0!") : 0;
|
|
378
|
+
if (name === "AVERAGE")
|
|
379
|
+
return clean(list.reduce((s, n) => s + n, 0) / list.length);
|
|
380
|
+
if (name === "MIN") return Math.min(...list);
|
|
381
|
+
return Math.max(...list);
|
|
382
|
+
};
|
|
383
|
+
const conditional = (name, args, from) => {
|
|
384
|
+
if (args[0]?.k !== "ref")
|
|
385
|
+
throw new FormulaError(`${name} needs a range first`);
|
|
386
|
+
const range = grid(args[0], from).flat();
|
|
387
|
+
const test = criterion(evaluate(args[1], from));
|
|
388
|
+
const target = args[2] ? grid(args[2], from).flat() : range;
|
|
389
|
+
const picked = range.flatMap((v, i) => (test(v) ? [target[i]] : []));
|
|
390
|
+
if (name === "COUNTIF") return picked.length;
|
|
391
|
+
const nums = picked.filter((v) => typeof v === "number");
|
|
392
|
+
if (name === "SUMIF") return clean(nums.reduce((s, n) => s + n, 0));
|
|
393
|
+
return nums.length
|
|
394
|
+
? clean(nums.reduce((s, n) => s + n, 0) / nums.length)
|
|
395
|
+
: error("#DIV/0!");
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
/** SUMIFS, COUNTIFS, AVERAGEIFS: every range and its criterion must hold. */
|
|
399
|
+
const conditionals = (name, args, from) => {
|
|
400
|
+
const counting = name === "COUNTIFS";
|
|
401
|
+
const pairs = counting ? args : args.slice(1);
|
|
402
|
+
if (pairs.length < 2 || pairs.length % 2)
|
|
403
|
+
throw new FormulaError(
|
|
404
|
+
`${name} needs ${counting ? "" : "a range to add up, then "}ranges each with a criterion`,
|
|
405
|
+
);
|
|
406
|
+
const ranges = [];
|
|
407
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
408
|
+
if (pairs[i].k !== "ref")
|
|
409
|
+
throw new FormulaError(`${name} needs a range before each criterion`);
|
|
410
|
+
ranges.push({
|
|
411
|
+
cells: grid(pairs[i], from).flat(),
|
|
412
|
+
test: criterion(evaluate(pairs[i + 1], from)),
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
if (!counting && args[0].k !== "ref")
|
|
416
|
+
throw new FormulaError(`${name} needs a range first`);
|
|
417
|
+
const target = counting ? ranges[0].cells : grid(args[0], from).flat();
|
|
418
|
+
if (ranges.some((one) => one.cells.length !== target.length))
|
|
419
|
+
return error("#VALUE!");
|
|
420
|
+
const picked = target.filter((_, i) =>
|
|
421
|
+
ranges.every((one) => one.test(one.cells[i])),
|
|
422
|
+
);
|
|
423
|
+
if (counting) return picked.length;
|
|
424
|
+
const nums = picked.filter((v) => typeof v === "number");
|
|
425
|
+
if (name === "SUMIFS") return clean(nums.reduce((s, n) => s + n, 0));
|
|
426
|
+
return nums.length
|
|
427
|
+
? clean(nums.reduce((s, n) => s + n, 0) / nums.length)
|
|
428
|
+
: error("#DIV/0!");
|
|
429
|
+
};
|
|
430
|
+
/** The numbers a function is given, or the first error among them. */
|
|
431
|
+
const numbersOf = (list, from) => {
|
|
432
|
+
const out = [];
|
|
433
|
+
for (const arg of list) {
|
|
434
|
+
const n = toNumber(evaluate(arg, from));
|
|
435
|
+
if (isError(n)) return n;
|
|
436
|
+
out.push(n);
|
|
437
|
+
}
|
|
438
|
+
return out;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
function evaluate(node, from) {
|
|
442
|
+
if (node.k === "val") return node.v;
|
|
443
|
+
if (node.k === "ref") {
|
|
444
|
+
if (node.b || node.a.r === null) return grid(node, from);
|
|
445
|
+
return value(sheetOf(node.sheet, from), node.a.r, node.a.c);
|
|
446
|
+
}
|
|
447
|
+
if (node.k === "neg") {
|
|
448
|
+
const n = toNumber(evaluate(node.a, from));
|
|
449
|
+
return isError(n) ? n : clean(-n);
|
|
450
|
+
}
|
|
451
|
+
if (node.k === "bin") {
|
|
452
|
+
const a = evaluate(node.a, from);
|
|
453
|
+
const b = evaluate(node.b, from);
|
|
454
|
+
if (Array.isArray(a) || Array.isArray(b)) return error("#VALUE!");
|
|
455
|
+
if (isError(a)) return a;
|
|
456
|
+
if (isError(b)) return b;
|
|
457
|
+
if (node.op === "&") return toText(a) + toText(b);
|
|
458
|
+
if (["=", "<>", "<", ">", "<=", ">="].includes(node.op)) {
|
|
459
|
+
const c = compare(a, b);
|
|
460
|
+
return {
|
|
461
|
+
"=": c === 0,
|
|
462
|
+
"<>": c !== 0,
|
|
463
|
+
"<": c < 0,
|
|
464
|
+
">": c > 0,
|
|
465
|
+
"<=": c <= 0,
|
|
466
|
+
">=": c >= 0,
|
|
467
|
+
}[node.op];
|
|
468
|
+
}
|
|
469
|
+
const x = toNumber(a);
|
|
470
|
+
const y = toNumber(b);
|
|
471
|
+
if (isError(x)) return x;
|
|
472
|
+
if (isError(y)) return y;
|
|
473
|
+
if (node.op === "+") return clean(x + y);
|
|
474
|
+
if (node.op === "-") return clean(x - y);
|
|
475
|
+
if (node.op === "*") return clean(x * y);
|
|
476
|
+
if (node.op === "/") return y === 0 ? error("#DIV/0!") : clean(x / y);
|
|
477
|
+
return clean(x ** y);
|
|
478
|
+
}
|
|
479
|
+
const { name, args } = node;
|
|
480
|
+
const need = (n) => {
|
|
481
|
+
if (args.length < n) throw new FormulaError(`${name} needs ${n} values`);
|
|
482
|
+
};
|
|
483
|
+
if (["SUM", "AVERAGE", "MIN", "MAX", "COUNT"].includes(name))
|
|
484
|
+
return aggregate(name, numbers(args, from));
|
|
485
|
+
if (name === "COUNTA")
|
|
486
|
+
return flat(args, from).filter(({ v }) => v !== null && v !== "").length;
|
|
487
|
+
if (name === "SUBTOTAL") {
|
|
488
|
+
need(2);
|
|
489
|
+
const kind = toNumber(evaluate(args[0], from)) % 100;
|
|
490
|
+
const which = { 1: "AVERAGE", 2: "COUNT", 4: "MAX", 5: "MIN", 9: "SUM" }[
|
|
491
|
+
kind
|
|
492
|
+
];
|
|
493
|
+
if (kind === 3)
|
|
494
|
+
return flat(args.slice(1), from).filter(
|
|
495
|
+
({ v }) => v !== null && v !== "",
|
|
496
|
+
).length;
|
|
497
|
+
if (!which)
|
|
498
|
+
throw new FormulaError(
|
|
499
|
+
`SUBTOTAL ${kind} is not one this sheet works out; use 1, 2, 3, 4, 5 or 9`,
|
|
500
|
+
);
|
|
501
|
+
return aggregate(which, numbers(args.slice(1), from));
|
|
502
|
+
}
|
|
503
|
+
if (["SUMIF", "COUNTIF", "AVERAGEIF"].includes(name)) {
|
|
504
|
+
need(2);
|
|
505
|
+
return conditional(name, args, from);
|
|
506
|
+
}
|
|
507
|
+
if (["SUMIFS", "COUNTIFS", "AVERAGEIFS"].includes(name))
|
|
508
|
+
return conditionals(name, args, from);
|
|
509
|
+
if (name === "TODAY") {
|
|
510
|
+
const now = new Date();
|
|
511
|
+
return serialAt(now.getFullYear(), now.getMonth(), now.getDate());
|
|
512
|
+
}
|
|
513
|
+
if (name === "DATE") {
|
|
514
|
+
need(3);
|
|
515
|
+
const got = numbersOf(args.slice(0, 3), from);
|
|
516
|
+
if (isError(got)) return got;
|
|
517
|
+
const [y, m, d] = got.map(Math.trunc);
|
|
518
|
+
const serial = serialAt(y < 1900 ? y + 1900 : y, m - 1, d);
|
|
519
|
+
return serial < 1 ? error("#NUM!") : serial;
|
|
520
|
+
}
|
|
521
|
+
if (["YEAR", "MONTH", "DAY"].includes(name)) {
|
|
522
|
+
need(1);
|
|
523
|
+
const got = numbersOf(args.slice(0, 1), from);
|
|
524
|
+
if (isError(got)) return got;
|
|
525
|
+
if (got[0] < 0) return error("#NUM!");
|
|
526
|
+
const at = dayOf(got[0]);
|
|
527
|
+
return name === "YEAR"
|
|
528
|
+
? at.getUTCFullYear()
|
|
529
|
+
: name === "MONTH"
|
|
530
|
+
? at.getUTCMonth() + 1
|
|
531
|
+
: at.getUTCDate();
|
|
532
|
+
}
|
|
533
|
+
if (name === "EOMONTH") {
|
|
534
|
+
need(2);
|
|
535
|
+
const got = numbersOf(args.slice(0, 2), from);
|
|
536
|
+
if (isError(got)) return got;
|
|
537
|
+
if (got[0] < 0) return error("#NUM!");
|
|
538
|
+
const at = dayOf(got[0]);
|
|
539
|
+
return serialAt(
|
|
540
|
+
at.getUTCFullYear(),
|
|
541
|
+
at.getUTCMonth() + Math.trunc(got[1]) + 1,
|
|
542
|
+
0,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
if (name === "ROUND") {
|
|
546
|
+
need(1);
|
|
547
|
+
const x = toNumber(evaluate(args[0], from));
|
|
548
|
+
const d = args[1] ? toNumber(evaluate(args[1], from)) : 0;
|
|
549
|
+
if (isError(x)) return x;
|
|
550
|
+
if (isError(d)) return d;
|
|
551
|
+
const f = 10 ** d;
|
|
552
|
+
return clean((Math.sign(x) * Math.round(Math.abs(x) * f + 1e-9)) / f);
|
|
553
|
+
}
|
|
554
|
+
if (name === "ABS") {
|
|
555
|
+
need(1);
|
|
556
|
+
const x = toNumber(evaluate(args[0], from));
|
|
557
|
+
return isError(x) ? x : Math.abs(x);
|
|
558
|
+
}
|
|
559
|
+
if (name === "IF") {
|
|
560
|
+
need(2);
|
|
561
|
+
const test = evaluate(args[0], from);
|
|
562
|
+
if (isError(test)) return test;
|
|
563
|
+
const yes =
|
|
564
|
+
typeof test === "string" ? error("#VALUE!") : Boolean(toNumber(test));
|
|
565
|
+
if (isError(yes)) return yes;
|
|
566
|
+
return yes
|
|
567
|
+
? evaluate(args[1], from)
|
|
568
|
+
: args[2]
|
|
569
|
+
? evaluate(args[2], from)
|
|
570
|
+
: false;
|
|
571
|
+
}
|
|
572
|
+
if (name === "IFERROR") {
|
|
573
|
+
need(2);
|
|
574
|
+
const v = evaluate(args[0], from);
|
|
575
|
+
return isError(v) ? evaluate(args[1], from) : v;
|
|
576
|
+
}
|
|
577
|
+
if (name === "AND" || name === "OR") {
|
|
578
|
+
const all = flat(args, from)
|
|
579
|
+
.map(({ v }) => v)
|
|
580
|
+
.filter((v) => v !== null);
|
|
581
|
+
if (all.some(isError)) return all.find(isError);
|
|
582
|
+
const truth = all.map((v) => Boolean(toNumber(v)));
|
|
583
|
+
return name === "AND" ? truth.every(Boolean) : truth.some(Boolean);
|
|
584
|
+
}
|
|
585
|
+
if (name === "NOT") {
|
|
586
|
+
need(1);
|
|
587
|
+
const x = toNumber(evaluate(args[0], from));
|
|
588
|
+
return isError(x) ? x : !x;
|
|
589
|
+
}
|
|
590
|
+
// CONCAT
|
|
591
|
+
return flat(args, from)
|
|
592
|
+
.map(({ v }) => toText(v))
|
|
593
|
+
.join("");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for (const sheet of sheets)
|
|
597
|
+
for (let r = 0; r < sheet.cells.length; r++)
|
|
598
|
+
for (let c = 0; c < (sheet.cells[r]?.length ?? 0); c++)
|
|
599
|
+
if (sheet.cells[r][c]?.f !== undefined) value(sheet, r, c);
|
|
600
|
+
return sheets;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ── changing a formula as the sheet around it changes ──────────────────────
|
|
604
|
+
|
|
605
|
+
const PIECE = /^\$?([A-Za-z]{1,3})(\$?)(\d*)$/;
|
|
606
|
+
|
|
607
|
+
/** A formula with each reference in it handed to `change`, which returns the new text or null
|
|
608
|
+
* to keep it; text in quotes, functions and the rest are kept as written. */
|
|
609
|
+
function rewrite(formula, change) {
|
|
610
|
+
const text = String(formula);
|
|
611
|
+
const lead = text.startsWith("=") ? "=" : "";
|
|
612
|
+
let rest = text.slice(lead.length);
|
|
613
|
+
let out = lead;
|
|
614
|
+
while (rest) {
|
|
615
|
+
let hit = null;
|
|
616
|
+
for (const [kind, re] of PATTERNS) {
|
|
617
|
+
const m = re.exec(rest);
|
|
618
|
+
if (m) {
|
|
619
|
+
hit = { kind, m };
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (!hit) return out + rest;
|
|
624
|
+
const { kind, m } = hit;
|
|
625
|
+
rest = rest.slice(m[0].length);
|
|
626
|
+
if (kind !== "ref") {
|
|
627
|
+
out += m[0];
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const sheet = m[1]?.replaceAll("''", "'") ?? m[2] ?? null;
|
|
631
|
+
const prefix = m[0].slice(
|
|
632
|
+
0,
|
|
633
|
+
m[0].length - m[3].length - (m[4] ? m[4].length + 1 : 0),
|
|
634
|
+
);
|
|
635
|
+
const read = (piece) => {
|
|
636
|
+
const [, letters, dollar, digits] = PIECE.exec(piece);
|
|
637
|
+
return {
|
|
638
|
+
colAbs: piece.startsWith("$"),
|
|
639
|
+
c: colIndex(letters),
|
|
640
|
+
rowAbs: dollar === "$",
|
|
641
|
+
r: digits ? Number(digits) - 1 : null,
|
|
642
|
+
};
|
|
643
|
+
};
|
|
644
|
+
const a = read(m[3]);
|
|
645
|
+
const b = m[4] ? read(m[4]) : null;
|
|
646
|
+
const changed = change({ sheet, a, b });
|
|
647
|
+
out +=
|
|
648
|
+
changed === null
|
|
649
|
+
? m[0]
|
|
650
|
+
: changed === "#REF!"
|
|
651
|
+
? "#REF!"
|
|
652
|
+
: prefix + changed;
|
|
653
|
+
}
|
|
654
|
+
return out;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const write = (p) =>
|
|
658
|
+
`${p.colAbs ? "$" : ""}${colName(p.c)}${p.r === null ? "" : `${p.rowAbs ? "$" : ""}${p.r + 1}`}`;
|
|
659
|
+
const both = (a, b) => (b ? `${write(a)}:${write(b)}` : write(a));
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* A formula on sheet `on` after `count` rows (axis "r") or columns ("c") of sheet `target` are
|
|
663
|
+
* put in before index `at` (count > 0) or taken out from `at` (count < 0), counted from 0 as
|
|
664
|
+
* Excel does it: a reference past them moves, a range across them grows or shrinks, and a
|
|
665
|
+
* reference to a cell taken out becomes #REF!.
|
|
666
|
+
*/
|
|
667
|
+
export function moveRefs(formula, { on, target, axis, at, count }) {
|
|
668
|
+
const mine = (sheet) => (sheet ?? on).toLowerCase() === target.toLowerCase();
|
|
669
|
+
const key = axis === "r" ? "r" : "c";
|
|
670
|
+
return rewrite(formula, ({ sheet, a, b }) => {
|
|
671
|
+
if (!mine(sheet)) return null;
|
|
672
|
+
if (a[key] === null) return null; // a whole column moves no row, a whole row no column
|
|
673
|
+
const shift = (n) => (n >= at ? n + count : n);
|
|
674
|
+
if (count > 0) {
|
|
675
|
+
const next = { ...a, [key]: shift(a[key]) };
|
|
676
|
+
const end = b && { ...b, [key]: shift(b[key]) };
|
|
677
|
+
return both(next, end);
|
|
678
|
+
}
|
|
679
|
+
const gone = (n) => n >= at && n < at - count;
|
|
680
|
+
const after = (n) => (n >= at - count ? n + count : n);
|
|
681
|
+
if (!b)
|
|
682
|
+
return gone(a[key]) ? "#REF!" : both({ ...a, [key]: after(a[key]) });
|
|
683
|
+
let [lo, hi] = [Math.min(a[key], b[key]), Math.max(a[key], b[key])];
|
|
684
|
+
if (gone(lo) && gone(hi)) return "#REF!";
|
|
685
|
+
if (gone(lo)) lo = at;
|
|
686
|
+
else lo = after(lo);
|
|
687
|
+
hi = gone(hi) ? at - 1 : after(hi);
|
|
688
|
+
return both({ ...a, [key]: lo }, { ...b, [key]: hi });
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** A formula copied `rows` rows down (up when negative): each row not fixed with `$` moves. */
|
|
693
|
+
export function fillDown(formula, rows) {
|
|
694
|
+
return rewrite(formula, ({ a, b }) => {
|
|
695
|
+
const move = (p) =>
|
|
696
|
+
p.r === null || p.rowAbs ? p : { ...p, r: p.r + rows };
|
|
697
|
+
const [x, y] = [move(a), b && move(b)];
|
|
698
|
+
if (x.r < 0 || (y && y.r < 0)) return "#REF!";
|
|
699
|
+
return both(x, y);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* A formula written in Excel row `row` as a column's formula: `{r}` wherever a reference names
|
|
705
|
+
* that row without `$`. Null when one names another row without `$`, which `{r}` cannot say.
|
|
706
|
+
*/
|
|
707
|
+
export function asColumnFormula(formula, row) {
|
|
708
|
+
let fits = true;
|
|
709
|
+
const one = (p) => {
|
|
710
|
+
if (p.r === null || p.rowAbs) return write(p);
|
|
711
|
+
if (p.r !== row - 1) fits = false;
|
|
712
|
+
return `${p.colAbs ? "$" : ""}${colName(p.c)}{r}`;
|
|
713
|
+
};
|
|
714
|
+
const out = rewrite(formula, ({ a, b }) =>
|
|
715
|
+
b ? `${one(a)}:${one(b)}` : one(a),
|
|
716
|
+
);
|
|
717
|
+
return fits ? out : null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* The references a formula names, in order, each with where it stands in the text (`start`,
|
|
722
|
+
* `end`) — as far as the formula reads, so one being typed shows what it names so far.
|
|
723
|
+
*/
|
|
724
|
+
export function referencesIn(formula) {
|
|
725
|
+
const text = String(formula);
|
|
726
|
+
const found = [];
|
|
727
|
+
let at = text.startsWith("=") ? 1 : 0;
|
|
728
|
+
while (at < text.length) {
|
|
729
|
+
const rest = text.slice(at);
|
|
730
|
+
let hit = null;
|
|
731
|
+
for (const [kind, re] of PATTERNS) {
|
|
732
|
+
const m = re.exec(rest);
|
|
733
|
+
if (m) {
|
|
734
|
+
hit = { kind, m };
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (!hit) break;
|
|
739
|
+
const { kind, m } = hit;
|
|
740
|
+
if (kind === "ref") {
|
|
741
|
+
const a = part(m[3]);
|
|
742
|
+
const b = m[4] ? part(m[4]) : null;
|
|
743
|
+
if (a && (!m[4] || b) && (b || a.r !== null))
|
|
744
|
+
found.push({
|
|
745
|
+
sheet: m[1]?.replaceAll("''", "'") ?? m[2] ?? null,
|
|
746
|
+
a,
|
|
747
|
+
b,
|
|
748
|
+
start: at,
|
|
749
|
+
end: at + m[0].length,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
at += m[0].length;
|
|
753
|
+
}
|
|
754
|
+
return found;
|
|
755
|
+
}
|