Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,7 @@ public/pt/help.html
public/pt/changelog.html
public/pt/help/embed-api.html
public/pt/index.html

# Generated copies of the root legal notices for static hosting.
/public/LICENSE
/public/NOTICE
33 changes: 25 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,31 @@ notes. Entries describe what users experience, not internal refactors.
the session on them, so a laptop keeps a recovery point about every 30
seconds while a phone under load backs off instead of competing with the
document you are editing.
- **The ONLYOFFICE logo is back in the editor header, and the About pane with
it.** Both had been switched off as interface clutter. They are not: this
editor is a modified version of ONLYOFFICE, and the license it is published
under requires the product logo to stay. The About pane now also says that
this build is not an official ONLYOFFICE product and links to the source it
was built from. Every page of the site carries the same trademark notice in
its footer, and the repository has a NOTICE file with the full terms and the
list of changes made to the vendor build.
- **Neutral presentation with legal attribution retained.** Product logos
and ecosystem promotion are removed. A neutral document icon supplies PWA and browser icons. The editor's About pane stays
available with the upstream copyright and version, a modified-version notice,
license and source links. NOTICE explains the logo decision with reference to
the FSF's interpretation of AGPLv3; it does not claim a court ruling. Offline caching remains available.

### Fixed

- Browser icons, theme-color and native controls now follow the effective light
or dark theme, including manual choices opposite to the OS preference.
Universal PWA / touch icons remain stable. The editor's existing theme
follow behavior is covered by light/dark browser tests.

- Save messages are accepted only from the same-origin editor frame. Embedded
commands are accepted only from the direct parent, with a fixed origin.
Exported document bytes no longer bypass the host API by going straight to
the top-level ancestor.
- Service Worker cache eviction stays within its `waitUntil` lifetime, removes
excess entries in a batch and tolerates storage failures.
- Editor title updates preserve the filename and unsaved marker without
restoring a product-name suffix. Language controls include the visible
language in their accessible names.
- Image-export tests use the current neutral icon. All README translations
distinguish local editing from optional cloud AI, qualify offline resource
availability and state the seven supported site languages.

### Known issues

Expand Down
67 changes: 52 additions & 15 deletions NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ This product is a derivative work of ONLYOFFICE.
The site (https://edit.chaxus.com/, https://github.com/ranuts/document) is
licensed under the GNU Affero General Public License version 3 -- see LICENSE.
It embeds the ONLYOFFICE document editors, which are distributed under the same
license with the additional terms permitted by Section 7 of the GNU AGPL. Those
terms are reproduced verbatim below and apply to this work as well.
license with the additional terms permitted by Section 7 of the GNU AGPL. The
received upstream notice is reproduced verbatim below for provenance. Valid
additional terms remain applicable; the logo-retention term is addressed
separately below rather than adopted as a downstream requirement.


1. ONLYOFFICE editors (sdkjs, web-apps)
Expand Down Expand Up @@ -57,17 +59,41 @@ The notice carried by the upstream source files, reproduced in full:
terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode


How this work complies with those terms
---------------------------------------

Section 7(b) -- product logo. The ONLYOFFICE logo is shown in the editor
header, and the About pane (product logo, version, Ascensio System SIA
copyright, onlyoffice.com link) is reachable from the left rail. Neither is
suppressed. lib/onlyoffice/guards/chrome.ts hides only the current-user and
co-users widgets, which describe a collaboration session this serverless build
cannot have, and lib/onlyoffice-editor.ts leaves `customization.about` at its
default. test/unit/branding-notice.test.ts and
test/e2e/vendor-branding.spec.ts keep both from being "cleaned up" again.
Neutral presentation and retained legal notices (2026-09-26)
-----------------------------------------------------------

The interface uses descriptive functional names without project or upstream
product logos, promotional branding, or ecosystem advertising. The editor's
About pane stays reachable, retains the upstream version and publisher, and
adds copyright attribution, a modified-version notice, the warranty disclaimer,
license and source links. Site footers retain the trademark attribution and
non-affiliation notice. Source copyright headers are not removed.

The upstream notice reproduced above requires retention of the original product
logo. In its statement of 15 April 2026, the Free Software Foundation identified
that requirement as an AGPLv3-incompatible further restriction removable under
Section 7:
https://www.fsf.org/blogs/licensing/agpl-is-not-a-tool-for-taking-freedom-away

This project relies on that interpretation in omitting product-logo displays.
The quoted upstream notice is kept as a record of the terms received, not an
assertion that logo retention is a valid obligation for downstream recipients.
ONLYOFFICE has asserted a different interpretation; its position is available at:
https://www.onlyoffice.com/license-faq

The FSF statement is a license interpretation, not a court judgment. This
branding decision does not claim that the dispute is judicially resolved or
that removing logos alone satisfies all copyright, source-distribution or
trademark obligations. Other valid additional terms and third-party licenses
remain unaffected, including the GUI material's CC BY-SA 4.0 license.

lib/onlyoffice/guards/chrome.ts hides the header and About product marks,
independently of the legal text. Vendor entry HTML has matching styles to avoid
showing those marks during startup, neutral titles and no branded favicon.
lib/onlyoffice-editor.ts keeps About enabled, and
lib/onlyoffice/guards/about-source.ts provides the legal and source links.
test/unit/branding-notice.test.ts and test/e2e/vendor-branding.spec.ts verify
neutral presentation alongside accessible legal attribution.

Section 7(e) -- trademarks. ONLYOFFICE and the ONLYOFFICE logo are trademarks
of Ascensio System SIA. No rights under trademark law are granted by the
Expand All @@ -85,7 +111,9 @@ The changes made to it are:
well as in the editor frame (it branches on `typeof document` in three
places), so conversion happens in a realm whose memory can be handed
back. See docs/changelogs/2026-08-20-issue-144-memory-and-delivery.md and
docs/explorations/2026-09-12-x2t-in-a-worker.md.
docs/explorations/2026-09-12-x2t-in-a-worker.md. Modified 2026-09-26 to
send exported file bytes only to the immediate parent; outer ancestors
no longer receive an unsolicited copy.
* public/sdkjs/common/wasm/x2t/x2t.worker.js -- added by this project. It
loads the file above and drives it; no vendor code is reimplemented in it.
* public/sdkjs/common/wasm/x2t/x2t.wasm -- published brotli-compressed only
Expand All @@ -103,6 +131,11 @@ The changes made to it are:
keys returned `undefined` and crashed the tooltip renderer. Values only;
no key is removed.
* public/web-apps help content trimmed from the shipped tree.
* public/web-apps/apps/{common,*/main}/index*.html -- entry shells with
branded titles now use neutral titles and an empty favicon. An inline
stylesheet suppresses header and About product marks before the
iframe paints. Copyright, publisher and version information remain.
Changed 2026-09-26.
* public/fonts/ -- the font catalog's proprietary font files were replaced
with redistributable open-source families (see docs/font-licenses.md and
docs/changelogs/2026-08-22-font-licensing.md).
Expand All @@ -112,7 +145,8 @@ The changes made to it are:
still resolves, and nine families were added for scripts the remaining set
did not cover.

Nothing else in public/sdkjs/ or public/web-apps/ is edited. The behavioural
Other than the changes listed above, public/sdkjs/ and public/web-apps/
are not edited. The behavioural
patches this site needs are applied at runtime from lib/onlyoffice/guards/,
which leaves the rest of the vendor files byte-identical to the build they
came from.
Expand Down Expand Up @@ -140,3 +174,6 @@ third-party project https://github.com/cryptpad/onlyoffice-x2t-wasm.
Other trademarks named in this repository and on the site (Microsoft Word,
Excel, PowerPoint, and others) are the property of their respective owners and
are used only to describe the file formats this editor reads and writes.

The application icon is an independently drawn, neutral document illustration.
It contains no upstream product logo, trademark or project initials.
22 changes: 22 additions & 0 deletions bin/build-app-icons.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** Rasterize the checked-in SVG sources without adding a runtime dependency.
* npm install --prefix /tmp/document-icon-tools --no-audit --no-fund @resvg/resvg-js@2.6.2
* node bin/build-app-icons.mjs /tmp/document-icon-tools/package.json
*/
import { createRequire } from 'node:module';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';

if (!process.argv[2]) throw new Error('Pass the package.json path of an installation of @resvg/resvg-js.');
const { Resvg } = createRequire(resolve(process.argv[2]))('@resvg/resvg-js');
for (const [name, sizes] of [
['document', [32, 180, 192, 512]],
['document-maskable', [512]],
['document-light', [32]],
['document-dark', [32]],
]) {
const source = new URL(`../public/icons/${name}.svg`, import.meta.url);
for (const size of sizes) {
const png = new Resvg(readFileSync(source), { fitTo: { mode: 'width', value: size } }).render().asPng();
writeFileSync(new URL(`../public/icons/${name}-${size}.png`, import.meta.url), png);
}
}
5 changes: 1 addition & 4 deletions bin/pages/chrome.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const langMenu = (locale, locales, ui, hrefFor) =>
// as one control. `role` and the name go here instead, which is also what
// ARIA's disclosure pattern asks for: one button, reporting its own state.
role: 'button',
'aria-label': ui.langAria,
'aria-label': `${ui.langAria}: ${LOCALES[locale].label}`,
})
.children(
View('span')
Expand Down Expand Up @@ -113,9 +113,6 @@ export const langMenu = (locale, locales, ui, hrefFor) =>
)
.serialize();

export const GH_MARK =
'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z';

export function routeFor(locale, slug) {
return `${LOCALES[locale].prefix}/${slug}`;
}
13 changes: 1 addition & 12 deletions bin/pages/entities.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,18 @@ import { LOCALES } from './locales.mjs';
* not three.
*/
export const ID = {
org: `${ORIGIN}/#organization`,
site: `${ORIGIN}/#website`,
app: `${ORIGIN}/#app`,
source: `${ORIGIN}/#source`,
};

/** The publisher and the site, identical on every page so they merge into one. */
/** The site identity is shared by every page, without promotional publisher branding. */
export const siteEntities = () => [
{
'@type': 'Organization',
'@id': ID.org,
name: 'ranuts',
url: ORIGIN + '/',
logo: `${ORIGIN}/img/pwa-512.png`,
sameAs: [REPO, 'https://github.com/ranuts', 'https://ran.chaxus.com'],
},
{
'@type': 'WebSite',
'@id': ID.site,
name: SITE_NAME,
url: ORIGIN + '/',
publisher: { '@id': ID.org },
// The site is one site in seven languages, which is a fact about the site
// and not about whichever page is being read. Each page states its own
// language on its WebPage node.
Expand All @@ -65,7 +55,6 @@ export const appEntity = (extra = {}) => ({
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
// The repository is the editor's other public identity, not the org's.
sameAs: [REPO],
publisher: { '@id': ID.org },
isPartOf: { '@id': ID.site },
...extra,
});
Expand Down
40 changes: 8 additions & 32 deletions bin/pages/render-home.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { ORIGIN, REPO } from './constants.mjs';
import { ID, appEntity, siteEntities, sourceEntity } from './entities.mjs';
import { DEFAULT_LOCALE, LOCALES } from './locales.mjs';
import { GH_MARK, langMenu } from './chrome.mjs';
import { langMenu } from './chrome.mjs';
import { escapeHtml } from './markdown.mjs';
import { UI } from './ui.mjs';

Expand Down Expand Up @@ -50,9 +50,6 @@ export function renderHome({ locale, data, locales }) {
appEntity({
description: data.description,
...(data.featureList ? { featureList: data.featureList } : {}),
...(data.ecosystem
? { isPartOf: [{ '@id': ID.site }, { '@type': 'SoftwareApplication', ...data.ecosystem }] }
: {}),
}),
// This page: one homepage per language, each pointing at the same app.
{
Expand All @@ -64,7 +61,6 @@ export function renderHome({ locale, data, locales }) {
inLanguage: L.lang,
isPartOf: { '@id': ID.site },
about: { '@id': ID.app },
primaryImageOfPage: `${ORIGIN}/img/pwa-512.png`,
},
{
'@type': 'FAQPage',
Expand Down Expand Up @@ -129,9 +125,10 @@ export function renderHome({ locale, data, locales }) {
<html lang="${L.lang}" dir="${L.dir}">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icons/document.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/document-32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/document-180.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/img/64.png" rel="shortcut icon" />
<link rel="icon" type="image/png" href="/img/64.png" />
<link rel="manifest" href="/manifest.json" />
<!-- Route split: this is a static landing page, /editor hosts the app. Deep
links that used to target it (?file= ?src= ?new= ?open=local ?embed=
Expand Down Expand Up @@ -171,6 +168,7 @@ ${alternates}
}
} catch (e) {}
</script>
<script src="/theme-presentation.js"></script>
<link rel="stylesheet" href="/ran-fonts/fonts.css" />
<link rel="stylesheet" href="/ran-tokens.css" />
<link rel="stylesheet" href="/home.css" />
Expand All @@ -182,11 +180,9 @@ ${ogAlternates}
<meta property="og:title" content="${e(data.title)}" />
<meta property="og:description" content="${e(data.ogDescription || data.description)}" />
<meta property="og:url" content="${url}" />
<meta property="og:image" content="${ORIGIN}/img/pwa-512.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="${e(data.title)}" />
<meta name="twitter:description" content="${e(data.ogDescription || data.description)}" />
<meta name="twitter:image" content="${ORIGIN}/img/pwa-512.png" />

<script type="application/ld+json">
${jsonLd}
Expand All @@ -203,19 +199,14 @@ ${jsonLd}
</head>

<body>
<svg width="0" height="0" style="position: absolute" aria-hidden="true">
<symbol id="gh-mark" viewBox="0 0 16 16">
<path d="${GH_MARK}" />
</symbol>
</svg>
<section id="landing-hero">
<header class="bar">
<div class="wrap">
<a class="brand" href="${home}"><span class="logo">D</span><span class="wordmark">${e(ui.siteName)}</span></a>
<a class="brand" href="${home}"><span class="wordmark">${e(ui.siteName)}</span></a>
<nav>
${nav}
<a class="navlink gh" href="${REPO}" rel="noopener" target="_blank">
<svg class="ghmark" aria-hidden="true"><use href="#gh-mark"></use></svg> GitHub
GitHub
</a>
${langMenu(locale, locales, ui, (l) => LOCALES[l].home)}
</nav>
Expand Down Expand Up @@ -321,21 +312,6 @@ ${faq}
</div>
</div>

<div class="eco">
<div class="wrap">
<span class="eco-label">
<svg class="gitmark" aria-hidden="true"><use href="#gh-mark"></use></svg>
${e(data.eco.label)}
</span>
<nav>
<span class="here"><b>${e(data.eco.here.name)}</b> <small>${e(data.eco.here.role)}</small></span>
<a href="${e(data.eco.other.href)}" rel="noopener" target="_blank">
<b>${e(data.eco.other.name)}</b> <small>${e(data.eco.other.role)}</small>
</a>
</nav>
</div>
</div>

</main>

<footer class="foot wrap">
Expand Down
19 changes: 8 additions & 11 deletions bin/pages/render-page.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import { ORIGIN, REPO } from './constants.mjs';
import { ID, appEntity, appStub, siteEntities, sourceEntity } from './entities.mjs';
import { DEFAULT_LOCALE, LOCALES } from './locales.mjs';
import { GH_MARK, langMenu, routeFor } from './chrome.mjs';
import { langMenu, routeFor } from './chrome.mjs';
import { escapeHtml, renderInline } from './markdown.mjs';
import { UI } from './ui.mjs';

Expand Down Expand Up @@ -147,9 +147,10 @@ ${related}
<html lang="${L.lang}" dir="${L.dir}">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icons/document.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/document-32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/document-180.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/img/64.png" rel="shortcut icon" />
<link rel="icon" type="image/png" href="/img/64.png" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" />
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff" />
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000000" />
Expand All @@ -167,11 +168,9 @@ ${ogAlternates}
<meta property="og:title" content="${escapeHtml(title)}" />
<meta property="og:description" content="${escapeHtml(cardDescription)}" />
<meta property="og:url" content="${url}" />
<meta property="og:image" content="${ORIGIN}/img/pwa-512.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="${escapeHtml(title)}" />
<meta name="twitter:description" content="${escapeHtml(cardDescription)}" />
<meta name="twitter:image" content="${ORIGIN}/img/pwa-512.png" />

<script type="application/ld+json">
${jsonLd}
Expand All @@ -186,6 +185,7 @@ ${jsonLd}
}
} catch (e) {}
</script>
<script src="/theme-presentation.js"></script>
<link rel="stylesheet" href="/ran-fonts/fonts.css" />
<link rel="stylesheet" href="/ran-tokens.css" />
<link rel="stylesheet" href="/landing.css" />
Expand All @@ -197,15 +197,12 @@ ${jsonLd}
</head>

<body>
<svg width="0" height="0" style="position: absolute" aria-hidden="true">
<symbol id="gh-mark" viewBox="0 0 16 16"><path d="${GH_MARK}" /></symbol>
</svg>

<header class="bar">
<a class="brand" href="${L.home}"><span class="logo">D</span><span class="wordmark">${ui.siteName}</span></a>
<a class="brand" href="${L.home}"><span class="wordmark">${ui.siteName}</span></a>
<nav>
<a href="${REPO}" rel="noopener" target="_blank">
<svg class="ghmark" aria-hidden="true"><use href="#gh-mark"></use></svg> GitHub
GitHub
</a>
${langMenu(locale, translations, ui, (l) => routeFor(l, page.slug))}
</nav>
Expand Down
Loading
Loading