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
107 changes: 91 additions & 16 deletions DocDiff.html

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
</picture>
</p>

## Your documents stay on your device

- **No upload.** Files are read and compared inside the browser tab. The page's Content Security Policy (`connect-src 'none'`) makes the browser block every network connection from the page.
- **Works offline.** The whole app is one HTML file. Download it, disconnect, keep working.
- **Nothing stored, nothing shared.** No account, no cookies, no tracking, no browser storage. Close the tab and everything is gone.
- **Verifiable.** The code is open (MIT), and the browser's Network tab shows no request while you compare.

This makes it suitable for confidential contracts and documents with personal data (GDPR): the documents reach no server, neither ours nor anyone else's. Details and limits are under [Privacy](#privacy).

## What it is

Doc Diff is a small tool for one job: you have an old and a new version of a document, typically a contract or a tender, and you want to know exactly what changed. It is free, has no accounts, no ads and no tracking, and the whole app is a single HTML file you can also run offline.
Expand Down
4 changes: 2 additions & 2 deletions docs/launch/0-cover.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@
</g>
</svg><b>Doc Diff</b><span class="url">docdiff.app</span></div>
<h1>Compare<br>documents<br><del>by hand</del> <ins>easily</ins>.</h1>
<p class="sub">PDF, Word or text: every change is marked word by word. Everything runs locally in your browser.</p>
<div class="chips"><span>Free &amp; open source</span><span>No uploads</span><span>No tracking</span></div>
<p class="sub">PDF, Word or text, marked word by word. Your files never leave your device.</p>
<div class="chips"><span>No upload</span><span>Works offline</span><span>Open source</span></div>
</div>
<div class="win">
<div class="bar">
Expand Down
Binary file modified docs/launch/0-cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/og-image-en.html
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
</g>
</svg><b>Doc Diff</b><span class="url">docdiff.app</span></div>
<h1>Compare<br>documents<br><del>by hand</del> <ins>easily</ins>.</h1>
<p class="sub">PDF, Word or text: every change is marked word by word. Everything runs locally in your browser.</p>
<div class="chips"><span>Free &amp; open source</span><span>No uploads</span><span>No tracking</span></div>
<p class="sub">PDF, Word or text, marked word by word. Your files never leave your device.</p>
<div class="chips"><span>No upload</span><span>Works offline</span><span>Open source</span></div>
</div>
<div class="win">
<div class="bar">
Expand Down
4 changes: 2 additions & 2 deletions docs/og-image.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@
</g>
</svg><b>Doc Diff</b><span class="url">docdiff.app</span></div>
<h1>Dokumente<br><del>mühsam</del> <ins>einfach</ins><br>vergleichen.</h1>
<p class="sub">PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Alles läuft lokal im Browser.</p>
<div class="chips"><span>Kostenlos &amp; Open Source</span><span>Keine Uploads</span><span>Kein Tracking</span></div>
<p class="sub">PDF, Word oder Text, Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.</p>
<div class="chips"><span>Kein Upload</span><span>Funktioniert offline</span><span>Open Source</span></div>
</div>
<div class="win">
<div class="bar">
Expand Down
9 changes: 9 additions & 0 deletions docs/releases/v1.6.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# v1.6.4 — Your documents stay with you

- **Privacy up front.** A new section on the start page, "Your documents stay with you", sums up what sets Doc Diff apart from online comparison services: no upload (the browser blocks every network connection from the page), works offline as a single HTML file, nothing stored or shared, and you can check all of this yourself. The line below the buttons now starts with "No upload · Works offline".
- **Clearer search results and link previews.** The page title, description and link preview images now say that Doc Diff runs locally, without uploading, and works offline.
- **No light flash in dark mode.** The dark theme is now set before the page first appears. Before, the start page could briefly show light drop zones and buttons that then faded to dark.

## Offline version

Download `DocDiff.html` below and open it from disk. It works without an internet connection.
2 changes: 1 addition & 1 deletion scripts/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def json_ld(template: str, lang: str, strings: dict, version: str) -> str:
page = PAGES[lang]
license_url = REPO_URL + "/blob/main/LICENSE"
author = {"@type": "Person", "name": "Tobias Schnorpfeil", "url": "https://github.com/tschnorpfeil"}
features = re.findall(r'data-i18n="(scopeCan\d+)"', template)
features = re.findall(r'data-i18n="(local\dTitle|scopeCan\d+)"', template)
faq_keys = re.findall(r'<summary data-i18n="(faq\w+)Q"', template)
app = {
"@type": "WebApplication",
Expand Down
6 changes: 4 additions & 2 deletions scripts/check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ for (const page of [
const strings = I18N[lang];
const bodyStart = html.indexOf('<body');
const head = html.slice(0, bodyStart);
const firstScript = html.indexOf('<script>');
const firstScript = html.indexOf('<script>', bodyStart);
const staticMarkup = html.slice(bodyStart, firstScript);

// Security invariants hold for every page, with the same CSP.
Expand Down Expand Up @@ -213,7 +213,9 @@ for (const page of [
}

// Performance: the landing markup comes before the ~2.5 MB of inline scripts
assert.ok(!/<script>/.test(head), `${where}: executable scripts must not sit in <head>`);
// The one exception is the tiny theme switch that sets dark mode before the first paint.
const headScripts = head.match(/<script>[\s\S]*?<\/script>/g) || [];
assert.ok(headScripts.length <= 1 && headScripts.every(s => s.length < 300), `${where}: only the small theme script may sit in <head>`);
assert.ok(firstScript > html.indexOf('id="faq-title"'), `${where}: scripts must follow the landing content`);
assert.ok(html.indexOf('id="pdfjs-worker-source"') > bodyStart, `${where}: PDF.js worker source must be present`);
// PDF.js and Mammoth are embedded but only started when a PDF or Word file is first read.
Expand Down
Loading
Loading