Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

701 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@stll/folio

Browser editor and framework-neutral engine for OOXML .docx documents.

English · 简体中文 · Português (Brasil)

stella · npm · Issues · Discord

npm versionLicenseIssuesDiscord

folio

Browser editor and framework-neutral engine for .docx files. It opens, edits, and writes OOXML documents while preserving pagination, tables, headers and footers, tracked changes, and footnotes.

The core package is framework-neutral. React, Vue, Nuxt, and document-review packages build on top of it.

Part of stella, an open-source legal workspace.

See DOCX platform boundary for what belongs in folio and how editors, headless tools, agents, and hosts share one document model and operation contract.

Standards-first interoperability

Folio targets interoperable OOXML behavior through published standards, differential parsing, round-trip and interaction tests, and reproducible layout comparisons across independent implementations.

Comparison reports record the reference implementation, version, and relevant rendering environment so results remain explicit and reproducible.

See Interoperability references for the complete testing methodology and reference matrix.

Packages

This is a Bun workspace with these published packages:

PackageUse it for
@stll/folio-coreOOXML parsing, native Word redlining, document review, ProseMirror, and layout
@stll/folio-reactA React editor UI built on @stll/folio-core
@stll/folio-vueA Vue 3 editor and composables
@stll/folio-nuxtNuxt 3/4 registration for the Vue editor
@stll/folio-agentsLLM tools for reading .docx files and proposing comments or tracked changes

Install

# the React editor (pulls in @stll/folio-core)
bun add @stll/folio-react react react-dom use-intl
# the Vue editor
bun add @stll/folio-vue vue
# Nuxt integration
bun add @stll/folio-nuxt
# agent/review tooling
bun add @stll/folio-agents
# or just the headless engine
bun add @stll/folio-core

Quick Start

import{DocxEditor}from"@stll/folio-react";import"@stll/folio-react/standalone.css";exportfunctionEditor({ docx }: {docx: ArrayBuffer}){return<DocxEditordocumentBuffer={docx}onSave={(out)=>download(out)}/>;}

In SSR apps, load the editor with a client-only or dynamic import.

Native Word redlines

Folio writes OOXML tracked changes that Microsoft Word can review, accept, or reject. Apply explicit operations for deterministic edits, or compare two saved versions to produce a redlined .docx:

import{FolioDocxReviewer}from"@stll/folio-core/server";import{generateRedlineDocx}from"@stll/folio-core/redline";constreviewer=awaitFolioDocxReviewer.fromBuffer(sourceDocx,{author: "Reviewer",});constblock=reviewer.snapshot().blocks.at(0);if(!block)thrownewError("The document has no editable blocks");constresult=reviewer.applyOperations([{id: "replace-term",type: "replaceInBlock",blockId: block.id,find: "Supplier",replace: "Provider",},]);if(result.skipped.length>0)thrownewError(JSON.stringify(result.skipped));constreviewedDocx=awaitreviewer.toBuffer();const{buffer: comparisonRedline}=awaitgenerateRedlineDocx(sourceDocx,reviewedDocx,{author: "Reviewer",});

applyOperations defaults to native tracked changes. Use getChanges(), acceptChange(), rejectChange(), acceptAll(), and rejectAll() to manage pending revisions. The change census includes inline edits, formatting, paragraph marks, and paragraph, section, table, row, and cell property changes. For model-facing review tools, see @stll/folio-agents.

Styling

Pick one stylesheet.

Use standalone.css when your app does not run Tailwind or you want folio's styles isolated:

import"@stll/folio-react/standalone.css";

Override tokens on .folio-root:

.folio-root {
--background:#fdfdfc;
--foreground:#1c1c1a;
--primary:#3b5bdb;
/* ...only the tokens you want to change... */
}

For dark mode, add .dark to an ancestor such as <html>.

Use editor.css when your app already runs Tailwind. Add folio's distributed JS to Tailwind's sources, then import the stylesheet:

/* your app's Tailwind entry */@import"tailwindcss";
@source"../node_modules/@stll/folio-react/dist/**/*.js";
import"@stll/folio-react/editor.css";

Do not import both stylesheets. standalone.css already includes everything in editor.css.

Internationalization

The editor uses use-intl. Wrap it in an IntlProvider and pass folio's bundled messages:

import{IntlProvider}from"use-intl";import{DocxEditor}from"@stll/folio-react";import{FOLIO_LOCALES,getFolioMessages}from"@stll/folio-react/messages";import"@stll/folio-react/editor.css";exportfunctionEditor({ docx, locale }: {docx: ArrayBuffer;locale: string}){return(<IntlProviderlocale={locale}messages={getFolioMessages(locale)}><DocxEditordocumentBuffer={docx}/></IntlProvider>);}

@stll/folio-react/messages exports:

  • getFolioMessages(locale: string): FolioMessages
  • FOLIO_LOCALES
  • FolioLocale
  • isFolioLocale(locale: string): locale is FolioLocale

Bundled locales: en, de, fr, es, cs, ar, et, he, hi, hu, lt, lv, pl, pt-BR, sk, tr, zh-CN. Arabic (ar) and Hebrew (he) are right-to-left: set dir="rtl" on a container around the editor for those locales.

To merge folio messages with app messages, keep folio under its own folio.* namespace:

constmessages={ ...getFolioMessages(locale), ...appMessages[locale]};

Do not copy folio's folio.* keys into your app catalog.

Development

bun install
bun run build
bun run typecheck
bun run test
bun run lint
bun run validate-dist

Releasing

Releases use Changesets. Add a changeset to every PR that edits published package source under packages/{core,react,agents,vue,nuxt}/src:

bunx changeset

For source changes that do not need a release:

bunx changeset --empty

CI enforces this through the changeset-policy workflow. Merging the generated Version Packages PR publishes changed packages through publish.yml.

Acknowledgements

folio began as a private fork of Eigenpal's docx-editor by Jedr Blaszyk. The code has since been extended (mostly to match the needs of stella). After the upstream repository was taken down, we are publishing the folio fork as an independently maintained continuation. The original license and copyright are preserved in NOTICE.md.

License

Apache-2.0

About

Framework-neutral DOCX engine with React/Vue editors, Nuxt integration, and agent tooling

Topics

Resources

Code of conduct

Stars

20 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages