Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci-react.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jobs:
node-version-file: .nvmrc
cache: pnpm

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install dependencies
run: pnpm install

Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/dispatch-oss-push-to-orbit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: "SuperDoc: Dispatch OSS Push To Orbit"

on:
push:
branches: [main]

permissions:
contents: read

concurrency:
group: superdoc-oss-push-orbit-dispatch-${{ github.ref_name }}
cancel-in-progress: false

jobs:
dispatch:
name: Dispatch OSS push backflow to Orbit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Create Orbit dispatch App token
id: orbit-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.ORBIT_DISPATCH_APP_ID }}
private-key: ${{ secrets.ORBIT_DISPATCH_APP_PRIVATE_KEY }}
owner: superdoc
repositories: orbit

- name: Dispatch OSS backflow to Orbit
env:
AFTER_SHA: ${{ github.sha }}
GH_TOKEN: ${{ steps.orbit-token.outputs.token }}
run: |
set -euo pipefail
gh api repos/superdoc/orbit/dispatches \
-f event_type=superdoc-oss-backflow \
-F client_payload[repository]="$GITHUB_REPOSITORY" \
-F client_payload[ref]="$GITHUB_REF_NAME" \
-F client_payload[after]="$AFTER_SHA"

{
echo "### Orbit OSS push backflow dispatched"
echo ""
echo "- ref: \`$GITHUB_REF_NAME\`"
echo "- after: \`$AFTER_SHA\`"
} >> "$GITHUB_STEP_SUMMARY"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Check the [issue tracker](https://github.com/superdoc-dev/superdoc/issues) for o
## License

- Open source: [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html)
- Commercial: [Enterprise License](https://www.superdocportal.dev/get-in-touch)
- Commercial: [SuperDoc Commercial License](https://www.superdocportal.dev/superdoc-terms-of-service)

## Acknowledgments

Expand Down Expand Up @@ -186,6 +186,7 @@ Special thanks to these community members who have contributed code to SuperDoc:
<a href="https://github.com/wookieb"><img src="https://github.com/wookieb.png" width="50" height="50" alt="wookieb" title="Łukasz Kużyński" /></a>
<a href="https://github.com/xy200303"><img src="https://github.com/xy200303.png" width="50" height="50" alt="xy200303" title="小云" /></a>
<a href="https://github.com/garhm"><img src="https://github.com/garhm.png" width="50" height="50" alt="garhm" title="Yaroslav Zakharov" /></a>
<a href="https://github.com/MIt9"><img src="https://github.com/MIt9.png" width="50" height="50" alt="MIt9" title="Dmitro Bilukha" /></a>

Want to see your avatar here? Check the [Contributing Guide](CONTRIBUTING.md) to get started.

Expand Down
6 changes: 5 additions & 1 deletion apps/docs/guides/general/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ Cloud data is multi-region within the United States. Automatic backups replicate
Third-party monitoring software detects potential attacks and anomalous network behavior. Every user action is logged and fully auditable. GCP systems are regularly audited for ongoing security and compliance (e.g., SOC 2).

### Terms of service and privacy policy
See the [Terms of Service](https://www.harbourshare.com/terms-of-service) and [Privacy Policy](https://www.harbourshare.com/privacy-policy).
SuperDoc is dual-licensed. The open-source SuperDoc is available under the [GNU AGPLv3](https://www.gnu.org/licenses/agpl-3.0); proprietary and commercial deployments are licensed under the [SuperDoc Commercial License](https://www.superdocportal.dev/superdoc-terms-of-service).

Use of our websites is governed by our [Website Terms of Use](https://app.termly.io/policy-viewer/policy.html?policyUUID=0b852fc9-5d9c-4170-8c96-08b072a55a9f).

See our [Privacy Policy](https://www.harbourshare.com/privacy-policy) for how we handle data.

### Vulnerability disclosure program
Found a security bug? Report it at security@superdoc.dev. The security team investigates all reported issues promptly.
4 changes: 2 additions & 2 deletions apps/docs/resources/license.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SuperDoc is available under dual licensing:

- **Open Source**: [GNU Affero General Public License v3.0](https://www.gnu.org/licenses/agpl-3.0.html)
- **Commercial**: [Enterprise License](https://www.harbourshare.com/request-a-demo)
- **Open Source**: [GNU AGPLv3](https://www.gnu.org/licenses/agpl-3.0.html)
- **Commercial**: [SuperDoc Commercial License](https://www.superdocportal.dev/superdoc-terms-of-service)

For questions about licensing, please contact [q@superdoc.dev](mailto:q@superdoc.dev).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ describe('numberingPartDescriptor.ensurePart', () => {
expect(root.attributes['xmlns:mc']).toBe('http://schemas.openxmlformats.org/markup-compatibility/2006');
expect(root.attributes['mc:Ignorable']).toContain('w15');
});

it('declares xmlns:w16cid so freshly-created numbering parts are namespace-valid (GH #3773)', () => {
const part = numberingPartDescriptor.ensurePart() as {
elements: Array<{ attributes: Record<string, string> }>;
};
const root = part.elements[0];

expect(root.attributes['xmlns:w16cid']).toBe('http://schemas.microsoft.com/office/word/2016/wordml/cid');
expect(root.attributes['xmlns:w14']).toBe('http://schemas.microsoft.com/office/word/2010/wordml');
expect(root.attributes['xmlns:r']).toBe('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
expect(root.attributes['mc:Ignorable']).toContain('w16cid');
});
});

describe('syncNumberingToXmlTree', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,25 @@ import type { Editor } from '../../Editor.js';
import type { PartDescriptor } from '../types.js';
import { translator as wAbstractNumTranslator } from '../../super-converter/v3/handlers/w/abstractNum/index.js';
import { translator as wNumTranslator } from '../../super-converter/v3/handlers/w/num/index.js';
import { DEFAULT_DOCX_DEFS } from '../../super-converter/exporter-docx-defs.js';
import { isPartCacheStale, clearPartCacheStale } from '../cache-staleness.js';

const NUMBERING_PART_ID = 'word/numbering.xml' as const;

/**
* Namespace attributes for the `<w:numbering>` root element.
*
* Includes `xmlns:w15` because base list definitions use
* `w15:restartNumberingAfterBreak` — without this declaration the
* numbering part is namespace-invalid and Word shows a repair prompt.
* Reuses the same full namespace + `mc:Ignorable` map that document.xml,
* comments.xml, footnotes.xml, and people.xml apply unconditionally
* (`DEFAULT_DOCX_DEFS`), instead of hand-picking a subset. A prior version
* of this map declared only `xmlns:w`/`xmlns:w15`/`xmlns:mc`, which was
* enough for `w15:restartNumberingAfterBreak` but omitted `xmlns:w16cid`
* (used by the seeded base numbering definitions, e.g. `w16cid:durableId`
* on `w:num`) — Word flagged any docx where this part was freshly created
* (i.e. the source docx had no numbering.xml before the user added their
* first list) as unreadable and forced a repair pass. See GH #3773.
*/
const NUMBERING_ROOT_ATTRS: Record<string, string> = {
'xmlns:w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
'xmlns:w15': 'http://schemas.microsoft.com/office/word/2012/wordml',
'xmlns:mc': 'http://schemas.openxmlformats.org/markup-compatibility/2006',
'mc:Ignorable': 'w15',
};
const NUMBERING_ROOT_ATTRS: Record<string, string> = { ...DEFAULT_DOCX_DEFS };

// ---------------------------------------------------------------------------
// Converter shape (minimal interface to avoid importing SuperConverter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ describe('templates.apply adapter integration', () => {
expect(numberingXml).toContain('xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml"');
expect(numberingXml).toContain('xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid"');
expect(numberingXml).toContain('xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"');
expect(numberingXml).toContain('mc:Ignorable="w15 w14 w16cid"');
expect(numberingXml).toMatch(/mc:Ignorable="[^"]*\bw16cid\b/);
expect(numberingXml).toContain('w15:restartNumberingAfterBreak="0"');
expect(numberingXml).toContain('w16cid:durableId="123456789"');
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect } from 'vitest';
import { dirname, join } from 'path';
import { fileURLToPath } from 'node:url';
import { promises as fs } from 'fs';
import { Editor } from '@core/Editor.js';
import DocxZipper from '@core/DocxZipper.js';
import { parseXmlToJson } from '@converter/v2/docxHelper.js';
import { initTestEditor } from '../helpers/helpers.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

const findNumberingRoot = (json) => {
if (!json?.elements?.length) return null;
if (json.elements[0]?.name === 'w:numbering') return json.elements[0];
return json.elements.find((el) => el?.name === 'w:numbering') || null;
};

// GH #3773: a docx imported with no `word/numbering.xml` (blank-doc.docx has none —
// see the SD-2911 P2 sanity test) that then gets its first numbered list added via
// `toggleOrderedList` must still export a namespace-complete `<w:numbering>` root.
// The part is created on-the-fly by the parts system (numbering-part-descriptor.ts)
// mid-session, so the legacy `baseNumbering` export-time fallback never runs for it —
// the part descriptor's own namespace map has to be complete on its own.
describe('numbering.xml namespaces when the first list is added to a numbering-less doc (GH #3773)', () => {
it('emits the full namespace set (including xmlns:w16cid) on the freshly-created numbering part', async () => {
const docxPath = join(__dirname, '../data', 'blank-doc.docx');
const docxBuffer = await fs.readFile(docxPath);

const [docx, media, mediaFiles, fonts] = await Editor.loadXmlData(docxBuffer, true);
const { editor } = await initTestEditor({ content: docx, media, mediaFiles, fonts, isHeadless: true });

editor.commands.insertContent('First item');
editor.commands.toggleOrderedList();

const exportedBuffer = await editor.exportDocx({ isFinalDoc: false });
const exportedZipper = new DocxZipper();
const exportedFiles = await exportedZipper.getDocxData(exportedBuffer, true);
const exportedNumberingEntry = exportedFiles.find((entry) => entry.name === 'word/numbering.xml');

expect(exportedNumberingEntry, 'export must contain word/numbering.xml').toBeDefined();

const exportedRoot = findNumberingRoot(parseXmlToJson(exportedNumberingEntry.content));

expect(exportedRoot.attributes['xmlns:w16cid']).toBe('http://schemas.microsoft.com/office/word/2016/wordml/cid');
expect(exportedRoot.attributes['xmlns:w14']).toBe('http://schemas.microsoft.com/office/word/2010/wordml');
expect(exportedRoot.attributes['xmlns:r']).toBe(
'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
);
expect(exportedRoot.attributes['mc:Ignorable']).toContain('w16cid');
});
});
Loading