diff --git a/.github/workflows/ci-react.yml b/.github/workflows/ci-react.yml index 19dc952bd9..dc5e884c9a 100644 --- a/.github/workflows/ci-react.yml +++ b/.github/workflows/ci-react.yml @@ -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 diff --git a/.github/workflows/dispatch-oss-push-to-orbit.yml b/.github/workflows/dispatch-oss-push-to-orbit.yml new file mode 100644 index 0000000000..7c1318d7a0 --- /dev/null +++ b/.github/workflows/dispatch-oss-push-to-orbit.yml @@ -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" diff --git a/README.md b/README.md index 1247e22d5a..68d7d71efc 100644 --- a/README.md +++ b/README.md @@ -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 @@ -186,6 +186,7 @@ Special thanks to these community members who have contributed code to SuperDoc: wookieb xy200303 garhm +MIt9 Want to see your avatar here? Check the [Contributing Guide](CONTRIBUTING.md) to get started. diff --git a/apps/docs/guides/general/security.mdx b/apps/docs/guides/general/security.mdx index 318c239519..abbc248025 100644 --- a/apps/docs/guides/general/security.mdx +++ b/apps/docs/guides/general/security.mdx @@ -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. \ No newline at end of file diff --git a/apps/docs/resources/license.mdx b/apps/docs/resources/license.mdx index 729a9cc0fa..ad68cf0548 100644 --- a/apps/docs/resources/license.mdx +++ b/apps/docs/resources/license.mdx @@ -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). diff --git a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.test.ts b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.test.ts index 0c96028ea6..407815b93a 100644 --- a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.test.ts +++ b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.test.ts @@ -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 }>; + }; + 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', () => { diff --git a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.ts b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.ts index 8805691c80..9a50e27cce 100644 --- a/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.ts +++ b/packages/super-editor/src/editors/v1/core/parts/adapters/numbering-part-descriptor.ts @@ -13,6 +13,7 @@ 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; @@ -20,16 +21,17 @@ const NUMBERING_PART_ID = 'word/numbering.xml' as const; /** * Namespace attributes for the `` 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 = { - '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 = { ...DEFAULT_DOCX_DEFS }; // --------------------------------------------------------------------------- // Converter shape (minimal interface to avoid importing SuperConverter) diff --git a/packages/super-editor/src/editors/v1/document-api-adapters/templates/templates-adapter.integration.test.ts b/packages/super-editor/src/editors/v1/document-api-adapters/templates/templates-adapter.integration.test.ts index 7f7a657625..cda30a3e8d 100644 --- a/packages/super-editor/src/editors/v1/document-api-adapters/templates/templates-adapter.integration.test.ts +++ b/packages/super-editor/src/editors/v1/document-api-adapters/templates/templates-adapter.integration.test.ts @@ -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"'); }); diff --git a/packages/super-editor/src/editors/v1/tests/import-export/numbering-namespace-on-first-list.test.js b/packages/super-editor/src/editors/v1/tests/import-export/numbering-namespace-on-first-list.test.js new file mode 100644 index 0000000000..4767759ff8 --- /dev/null +++ b/packages/super-editor/src/editors/v1/tests/import-export/numbering-namespace-on-first-list.test.js @@ -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 `` 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'); + }); +});