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
21 changes: 11 additions & 10 deletions .github/workflows/preview-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ jobs:
# preview clones ~616 MiB, checks out ~625 MB and uploads ~697 MB of
# output. The build script itself runs in about seven seconds.
# Fork heads need a maintainer to mirror the exact SHA into a preview
# branch here; waiting longer cannot create a missing deployment. Only
# install dependencies and browsers once there is a preview to test.
# branch here; waiting longer cannot create a missing deployment. Limit
# their missing-check grace window, but let same-repository checks use
# the full deployment window: Pages can queue before publishing a check.
# Install dependencies and browsers only once there is a preview to test.
- name: Wait for the Cloudflare Pages preview of this commit
id: preview
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.event.pull_request.head.sha }}
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
WAIT_MINUTES: "45"
CHECK_WAIT_MINUTES: "5"
run: |
Expand All @@ -56,20 +59,18 @@ jobs:
--jq '.check_runs | map(select(.name=="Cloudflare Pages")) | max_by(.id) | if . == null then empty else {status, conclusion, summary: .output.summary} end')
STATUS=$(printf '%s' "$RUN" | jq -r '.status // empty' 2>/dev/null | head -1)
if [ -n "$STATUS" ]; then SEEN_CHECK=true; fi
if [ "$SEEN_CHECK" = false ] && [ "$i" -ge "$CHECK_ATTEMPTS" ]; then
if [ "$SEEN_CHECK" = false ] && [ "$HEAD_REPOSITORY" != "$GITHUB_REPOSITORY" ] && [ "$i" -ge "$CHECK_ATTEMPTS" ]; then
echo "::error::Cloudflare Pages check never appeared for $SHA within $CHECK_WAIT_MINUTES minutes. For a fork PR, a maintainer must push the exact head SHA to a preview branch in this repository, then rerun this workflow. For same-repository PRs, check Pages branch deployment controls and the GitHub integration."
exit 1
fi
if [ "$STATUS" = "completed" ]; then
CONCLUSION=$(printf '%s' "$RUN" | jq -r '.conclusion' | head -1)
if [ "$CONCLUSION" != "success" ]; then
# Cloudflare builds this repository close to its limits, so a
# failure here is not automatically a defect in the commit. Check
# whether `E2E (Cloudflare Pages semantics)` passed: it runs the
# same bin/build.sh, so green there and red here means the build
# is fine and Cloudflare fell over. There is no API to retry a
# Git-integration deployment -- pushing again is the way.
echo "::error::Cloudflare Pages preview deployment concluded: $CONCLUSION. If the 'E2E (Cloudflare Pages semantics)' checks are green, the build itself is fine (they run the same bin/build.sh) and this was Cloudflare-side; push again to trigger a new deployment. See docs/explorations/2026-08-21-cloudflare-preview-flake.md"
# The Pages-semantics suite uses the same build script, but its
# success cannot diagnose a failed provider deployment. Inspect
# the actual Pages logs to distinguish clone, build and upload
# failures. Pushing again triggers a new Git-integration deploy.
echo "::error::Cloudflare Pages preview deployment concluded: $CONCLUSION. Inspect the Pages deployment logs and compare the 'E2E (Cloudflare Pages semantics)' checks to identify the failed stage. For a transient failure, retry in the Pages dashboard or push again to trigger a new deployment. See docs/explorations/2026-08-21-cloudflare-preview-flake.md"
exit 1
fi
URL=$(printf '%s' "$RUN" | jq -r '.summary' | grep -oE 'https://[a-z0-9-]+\.document-[a-z0-9]+\.pages\.dev' | head -1)
Expand Down
42 changes: 42 additions & 0 deletions bin/build-social-image.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/** Render the product sharing artwork without a runtime dependency.
* npm install --prefix <tools-dir> --no-audit --no-fund @resvg/resvg-js@2.6.2
* node bin/build-social-image.mjs <tools-dir>/package.json
*/
import { createRequire } from 'node:module';
import { mkdirSync, 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');
const brand = readFileSync(new URL('../public/icons/document-light.svg', import.meta.url), 'utf8').match(
/<g fill="#171717">([\s\S]*?)<\/g>/,
)[1];

for (const [name, width, height] of [
['document-editor-v1', 1200, 630],
['document-editor-github-v1', 1280, 640],
]) {
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
<title>Online Document Editor</title>
<desc>Open-source document editing in your browser. Word, Excel, PowerPoint and PDF. Local editing, no signup, optional AI (Experimental Beta), WebMCP and PWA.</desc>
<rect width="${width}" height="${height}" fill="#f7f7f4"/>
<g transform="translate(${(width - 1200) / 2} ${(height - 630) / 2})">
<g transform="translate(76 58) scale(.135)">${brand}</g>
<g font-family="Arial, Helvetica, sans-serif" fill="#171717">
<text x="160" y="103" font-size="28" font-weight="700">Online Document Editor</text>
<text x="80" y="243" font-size="66" font-weight="700" letter-spacing="-2">Your documents.</text>
<text x="80" y="322" font-size="66" font-weight="700" letter-spacing="-2">Right in your browser.</text>
<text x="83" y="384" font-size="26" fill="#525252">Word · Excel · PowerPoint · PDF</text>
<text x="83" y="443" font-size="22" fill="#525252">Local editing. Open source. No signup.</text>
<path d="M80 480H1120" stroke="#d8d8d2"/>
<text x="83" y="526" font-size="19" fill="#525252">AI (Experimental Beta) · WebMCP · PWA</text>
<text x="83" y="568" font-size="20" font-weight="700">edit.chaxus.com</text>
<text x="1120" y="568" text-anchor="end" font-size="18" fill="#737373">github.com/ranuts/document</text>
</g>
</g>
</svg>`;
const folder = new URL('../public/social/', import.meta.url);
mkdirSync(folder, { recursive: true });
writeFileSync(new URL(`${name}.svg`, folder), svg);
writeFileSync(new URL(`${name}.png`, folder), new Resvg(svg).render().asPng());
}
7 changes: 4 additions & 3 deletions bin/pages/render-home.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DEFAULT_LOCALE, LOCALES } from './locales.mjs';
import { langMenu } from './chrome.mjs';
import { escapeHtml } from './markdown.mjs';
import { UI } from './ui.mjs';
import { socialImageTags } from './social-image.mjs';

/**
* The homepage.
Expand Down Expand Up @@ -177,11 +178,11 @@ ${alternates}
<meta property="og:site_name" content="Online Document Editor" />
<meta property="og:locale" content="${L.og}" />
${ogAlternates}
<meta property="og:title" content="${e(data.title)}" />
<meta property="og:title" content="${e(data.ogTitle || data.title)}" />
<meta property="og:description" content="${e(data.ogDescription || data.description)}" />
<meta property="og:url" content="${url}" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="${e(data.title)}" />
${socialImageTags()}
<meta name="twitter:title" content="${e(data.ogTitle || data.title)}" />
<meta name="twitter:description" content="${e(data.ogDescription || data.description)}" />

<script type="application/ld+json">
Expand Down
3 changes: 2 additions & 1 deletion bin/pages/render-page.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DEFAULT_LOCALE, LOCALES } from './locales.mjs';
import { langMenu, routeFor } from './chrome.mjs';
import { escapeHtml, renderInline } from './markdown.mjs';
import { UI } from './ui.mjs';
import { socialImageTags } from './social-image.mjs';

export function renderPage({ page, locale, meta, body, headings, faq, steps, source }) {
const L = LOCALES[locale];
Expand Down Expand Up @@ -168,7 +169,7 @@ ${ogAlternates}
<meta property="og:title" content="${escapeHtml(title)}" />
<meta property="og:description" content="${escapeHtml(cardDescription)}" />
<meta property="og:url" content="${url}" />
<meta name="twitter:card" content="summary" />
${socialImageTags()}
<meta name="twitter:title" content="${escapeHtml(title)}" />
<meta name="twitter:description" content="${escapeHtml(cardDescription)}" />

Expand Down
15 changes: 15 additions & 0 deletions bin/pages/social-image.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ORIGIN } from './constants.mjs';

/** One public product image shared by all static page cards. */
export function socialImageTags() {
const image = `${ORIGIN}/social/document-editor-v1.png`;
const alt = 'Online Document Editor: Word, Excel, PowerPoint and PDF; local editing, open source and no signup.';
return ` <meta property="og:image" content="${image}" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="${alt}" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="${image}" />
<meta name="twitter:image:alt" content="${alt}" />`;
}
5 changes: 3 additions & 2 deletions content/de/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "DOCX, XLSX & PPTX im Browser öffnen — kostenloser Dokumentenbetrachter und -editor",
"description": "DOCX, XLSX, PPTX und CSV ohne Office oder Konto im Browser öffnen, ansehen und bearbeiten. Der Kerneditor benötigt keinen Dokument-Upload; Offline-Nutzung hängt von zwischengespeicherten Ressourcen ab.",
"ogDescription": "DOCX, XLSX, PPTX und CSV ohne Office oder Konto im Browser öffnen, ansehen und bearbeiten. Der Kerneditor benötigt keinen Dokument-Upload; Offline-Nutzung hängt von zwischengespeicherten Ressourcen ab.",
"ogDescription": "Word, Excel und PowerPoint lokal bearbeiten, PDF kommentieren. Ohne Konto oder Office-Installation. Optionale KI (Experimental Beta), WebMCP und PWA.",
"chip": "Local-first · <b>Open Source</b>",
"h1": {
"plain": "Word-, Excel- und PowerPoint-Dateien öffnen,",
Expand Down Expand Up @@ -246,5 +246,6 @@
"Offline mit zwischengespeicherten Ressourcen",
"Über die postMessage-API einbettbar",
"Kostenlos und quelloffen (AGPL-3.0)"
]
],
"ogTitle": "Open-Source-Dokumenteditor für dich und AI-Agenten"
}
5 changes: 3 additions & 2 deletions content/en/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "Open DOCX, XLSX & PPTX in Your Browser — Free Document Viewer & Editor",
"description": "Open, view and edit DOCX, XLSX, PPTX and CSV in your browser without Office or an account. Core editing requires no document upload; offline use depends on cached resources.",
"ogDescription": "Open, view and edit DOCX, XLSX, PPTX and CSV in your browser without Office or an account. Core editing requires no document upload; offline use depends on cached resources.",
"ogDescription": "Edit Word, Excel and PowerPoint locally; annotate PDFs. No signup or Office installation. Optional AI (Experimental Beta), WebMCP and PWA support.",
"chip": "Local-first · <b>open source</b>",
"h1": {
"plain": "Open Word, Excel & PowerPoint files,",
Expand Down Expand Up @@ -245,5 +245,6 @@
"Offline with cached resources",
"Embeddable via postMessage API",
"Free and open source (AGPL-3.0)"
]
],
"ogTitle": "Open-source document editor for you and AI agents"
}
5 changes: 3 additions & 2 deletions content/es/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "Abrir DOCX, XLSX y PPTX en el navegador — visor y editor de documentos gratis",
"description": "Abre, visualiza y edita DOCX, XLSX, PPTX y CSV en el navegador sin Office ni cuenta. La edición básica no exige subir documentos; el uso sin conexión depende de los recursos en caché.",
"ogDescription": "Abre, visualiza y edita DOCX, XLSX, PPTX y CSV en el navegador sin Office ni cuenta. La edición básica no exige subir documentos; el uso sin conexión depende de los recursos en caché.",
"ogDescription": "Edita Word, Excel y PowerPoint localmente; anota PDF. Sin registro ni instalar Office. IA opcional (Experimental Beta), WebMCP y PWA.",
"chip": "Local primero · <b>código abierto</b>",
"h1": {
"plain": "Abre archivos de Word, Excel y PowerPoint,",
Expand Down Expand Up @@ -241,5 +241,6 @@
"Sin conexión con recursos en caché",
"Integrable mediante la API postMessage",
"Gratis y de código abierto (AGPL-3.0)"
]
],
"ogTitle": "Editor de documentos de código abierto para ti y agentes de IA"
}
5 changes: 3 additions & 2 deletions content/ja/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "ブラウザで DOCX・XLSX・PPTX を開く — 無料のドキュメントビューア&エディタ",
"description": "Office やアカウントなしで DOCX、XLSX、PPTX、CSV をブラウザで開き、表示・編集できます。基本編集にアップロードは不要で、オフライン利用はキャッシュ済みリソースに依存します。",
"ogDescription": "Office やアカウントなしで DOCX、XLSX、PPTX、CSV をブラウザで開き、表示・編集できます。基本編集にアップロードは不要で、オフライン利用はキャッシュ済みリソースに依存します。",
"ogDescription": "ブラウザーで Word、Excel、PowerPoint をローカル編集し、PDF に注釈を追加。登録や Office のインストールは不要。任意の AI アシスタント (Experimental Beta)、WebMCP、PWA に対応。",
"chip": "ローカル優先 · <b>オープンソース</b>",
"h1": {
"plain": "Word・Excel・PowerPoint のファイルを、",
Expand Down Expand Up @@ -241,5 +241,6 @@
"キャッシュ済みリソースでオフライン利用",
"postMessage API で埋め込み可能",
"無料でオープンソース(AGPL-3.0)"
]
],
"ogTitle": "あなたと AI エージェントのためのオープンソース文書エディター"
}
5 changes: 3 additions & 2 deletions content/ko/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "브라우저에서 DOCX, XLSX, PPTX 열기 — 무료 문서 뷰어 및 편집기",
"description": "Office나 계정 없이 브라우저에서 DOCX, XLSX, PPTX, CSV를 열고 보고 편집하세요. 기본 편집에는 문서 업로드가 필수가 아니며 오프라인 사용은 캐시된 리소스에 달려 있습니다.",
"ogDescription": "Office나 계정 없이 브라우저에서 DOCX, XLSX, PPTX, CSV를 열고 보고 편집하세요. 기본 편집에는 문서 업로드가 필수가 아니며 오프라인 사용은 캐시된 리소스에 달려 있습니다.",
"ogDescription": "브라우저에서 Word, Excel, PowerPoint를 로컬로 편집하고 PDF에 주석을 추가하세요. 가입이나 Office 설치 없이 사용하며, 선택적 AI (Experimental Beta), WebMCP, PWA를 지원합니다.",
"chip": "로컬 우선 · <b>오픈 소스</b>",
"h1": {
"plain": "Word, Excel, PowerPoint 파일을",
Expand Down Expand Up @@ -241,5 +241,6 @@
"캐시된 리소스로 오프라인 사용",
"postMessage API로 임베드 가능",
"무료이며 오픈 소스(AGPL-3.0)"
]
],
"ogTitle": "사용자와 AI 에이전트를 위한 오픈 소스 문서 편집기"
}
5 changes: 3 additions & 2 deletions content/pt/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "Abrir DOCX, XLSX e PPTX no navegador — visualizador e editor de documentos grátis",
"description": "Abra, veja e edite DOCX, XLSX, PPTX e CSV no navegador sem Office ou conta. A edição básica não exige enviar documentos; o uso offline depende dos recursos em cache.",
"ogDescription": "Abra, veja e edite DOCX, XLSX, PPTX e CSV no navegador sem Office ou conta. A edição básica não exige enviar documentos; o uso offline depende dos recursos em cache.",
"ogDescription": "Edite Word, Excel e PowerPoint localmente; anote PDF. Sem cadastro nem instalar o Office. IA opcional (Experimental Beta), WebMCP e PWA.",
"chip": "Local primeiro · <b>código aberto</b>",
"h1": {
"plain": "Abra arquivos do Word, Excel e PowerPoint,",
Expand Down Expand Up @@ -241,5 +241,6 @@
"Offline com recursos em cache",
"Incorporável pela API postMessage",
"Grátis e de código aberto (AGPL-3.0)"
]
],
"ogTitle": "Editor de documentos open source para você e agentes de IA"
}
5 changes: 3 additions & 2 deletions content/zh-CN/home.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"title": "在浏览器打开 DOCX、XLSX、PPT 文件——免装 Office 的在线查看与编辑器",
"description": "无需 Office 或账号,即可在浏览器中打开、查看和编辑 DOCX、XLSX、PPTX 和 CSV。核心编辑不要求上传文档;离线使用取决于已缓存的资源。",
"ogDescription": "无需 Office 或账号,即可在浏览器中打开、查看和编辑 DOCX、XLSX、PPTX 和 CSV。核心编辑不要求上传文档;离线使用取决于已缓存的资源。",
"ogDescription": "浏览器内本地编辑 Word、Excel、PowerPoint,为 PDF 添加批注。无需注册或安装 Office,支持可选 AI 助手 (Experimental Beta)、WebMCP 和 PWA。",
"chip": "本地优先 · <b>开源</b>",
"h1": {
"plain": "在浏览器里直接打开 Word、Excel 和 PPT,",
Expand Down Expand Up @@ -245,5 +245,6 @@
"已缓存资源可离线使用",
"可通过 postMessage API 嵌入",
"免费且开源(AGPL-3.0)"
]
],
"ogTitle": "为你和 AI Agent 准备的开源文档编辑器"
}
Loading
Loading