Compare commits
3 Commits
8d09f0ef16
...
f1c6d43613
| Author | SHA1 | Date | |
|---|---|---|---|
| f1c6d43613 | |||
| 23909dceeb | |||
| dad16d6ba2 |
43
README.md
43
README.md
@ -74,11 +74,15 @@ Gegen den **Test-Schrank `LL_TEST_BELEGE`** ablegen, dann per DocuWare-MCP gegen
|
|||||||
- Anhänge erscheinen als eigene Dokumente
|
- Anhänge erscheinen als eigene Dokumente
|
||||||
- Aufräumen: `docuware_delete_document` (nur im Test-Schrank!)
|
- Aufräumen: `docuware_delete_document` (nur im Test-Schrank!)
|
||||||
|
|
||||||
## Verteilung & automatische Updates (eigenes Gitea, nicht öffentlich)
|
## Verteilung & automatische Updates (eigenes Gitea)
|
||||||
|
|
||||||
Das Add-on wird **nicht** öffentlich auf addons.thunderbird.net gelistet, sondern
|
Das Add-on wird **nicht** auf addons.thunderbird.net gelistet, sondern als **unsigniertes
|
||||||
über AMO als **„Selbstständig" (self-distribution)** signiert und über dein eigenes
|
XPI** über dein eigenes Gitea verteilt + aktualisiert.
|
||||||
Gitea verteilt + aktualisiert.
|
|
||||||
|
> **Kein Signieren nötig.** Thunderbird verlangt für self-distributed Add-ons keine
|
||||||
|
> Mozilla-Signatur, und addons.thunderbird.net (ATN) zeichnet Thunderbird-Add-ons ohnehin
|
||||||
|
> nicht gegen (jede hochgeladene Version bleibt `signed=false`, `web-ext sign` schlägt mit
|
||||||
|
> „no signed files were found" fehl). Wir bauen das XPI also lokal und verteilen es direkt.
|
||||||
|
|
||||||
> Im XPI und in `updates.json` stehen **keine** Zugangsdaten. Das DocuWare-Passwort
|
> Im XPI und in `updates.json` stehen **keine** Zugangsdaten. Das DocuWare-Passwort
|
||||||
> wird ohnehin nirgends gespeichert (nur Sitzungs-Token im Speicher) und nie mitverteilt.
|
> wird ohnehin nirgends gespeichert (nur Sitzungs-Token im Speicher) und nie mitverteilt.
|
||||||
@ -87,21 +91,34 @@ Gitea verteilt + aktualisiert.
|
|||||||
|
|
||||||
1. In `manifest.json` → `applications.gecko.update_url` die Domain `GITEA.EXAMPLE.DE`
|
1. In `manifest.json` → `applications.gecko.update_url` die Domain `GITEA.EXAMPLE.DE`
|
||||||
durch deine (HTTPS-)Gitea-Adresse ersetzen. **HTTPS ist Pflicht** (TB lehnt HTTP ab).
|
durch deine (HTTPS-)Gitea-Adresse ersetzen. **HTTPS ist Pflicht** (TB lehnt HTTP ab).
|
||||||
2. `updates.json` ebenfalls auf deine Gitea-URLs anpassen und in dein Repo committen
|
2. `updates.json` ebenfalls auf deine Gitea-URLs anpassen und in dein Repo committen.
|
||||||
(erreichbar unter der `update_url`, z.B. via *raw*-Link auf `main`).
|
3. ⚠️ **`update_url` und Release-Assets müssen ANONYM (ohne Login) erreichbar sein** —
|
||||||
|
sonst bekommt Thunderbird beim Update-Check nur die Login-Seite und aktualisiert nie.
|
||||||
|
Bei Gitea: Repo öffentlich **und** instanzweit `REQUIRE_SIGNIN_VIEW = false` (in
|
||||||
|
`app.ini`, Abschnitt `[service]`).
|
||||||
|
|
||||||
**Bei jedem neuen Release:**
|
**Bei jedem neuen Release** – am einfachsten mit `scripts/release.sh` (baut sauberes XPI,
|
||||||
|
patcht `updates.json`, committet/pusht, legt Gitea-Release an + lädt das Asset hoch):
|
||||||
|
|
||||||
1. `version` in `manifest.json` erhöhen (z.B. `0.8.0` → `0.8.1`).
|
```bash
|
||||||
2. XPI bauen (`manifest.json` muss im Archiv-Wurzelverzeichnis liegen).
|
scripts/release.sh 0.10.2 # Version setzen + alles erledigen
|
||||||
3. XPI bei AMO hochladen → **„Selbstständig"** wählen → **signiertes** XPI herunterladen.
|
```
|
||||||
4. Signiertes XPI als Release-Anhang in Gitea hochladen (passend zum `update_link`).
|
|
||||||
5. In `updates.json` einen neuen Eintrag mit `version` + `update_link` ergänzen,
|
Manuell wären das die Schritte: `version` in `manifest.json` erhöhen → sauberes XPI bauen
|
||||||
committen/pushen.
|
(`manifest.json` im Archiv-Wurzelverzeichnis; `scripts/`, `docs/`, `.env` **nicht** mit
|
||||||
|
einpacken) → XPI als Release-Anhang in Gitea hochladen → Eintrag (`version` + `update_link`)
|
||||||
|
in `updates.json` ergänzen, committen/pushen.
|
||||||
|
|
||||||
Thunderbird prüft die `update_url` periodisch und aktualisiert automatisch auf die
|
Thunderbird prüft die `update_url` periodisch und aktualisiert automatisch auf die
|
||||||
neueste dort gelistete Version.
|
neueste dort gelistete Version.
|
||||||
|
|
||||||
|
**Nach dem Release verifizieren** (anonym, genau wie Thunderbird):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -L .../raw/branch/main/updates.json # muss JSON liefern, nicht die Login-Seite
|
||||||
|
curl -sI -L .../releases/download/vX/...xpi # Content-Type: application/octet-stream
|
||||||
|
```
|
||||||
|
|
||||||
## Bekannte Grenzen / nächste Schritte
|
## Bekannte Grenzen / nächste Schritte
|
||||||
|
|
||||||
- **PDF** ist v1 ein einfaches Text-PDF (Kopf + Klartext-Body) ohne HTML-Layout/Bilder.
|
- **PDF** ist v1 ein einfaches Text-PDF (Kopf + Klartext-Body) ohne HTML-Layout/Bilder.
|
||||||
|
|||||||
47
lib/mail.js
47
lib/mail.js
@ -72,10 +72,55 @@ const Mail = {
|
|||||||
};
|
};
|
||||||
walk(fullPart);
|
walk(fullPart);
|
||||||
if (plain.trim()) return plain;
|
if (plain.trim()) return plain;
|
||||||
if (html.trim()) return html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
if (html.trim()) return this._htmlToText(html);
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Macht aus HTML lesbaren Klartext: entfernt style/script/head-INHALTE komplett,
|
||||||
|
* leitet aus Block-Elementen Zeilenumbrüche ab, dekodiert Entities und glättet
|
||||||
|
* Whitespace – ohne Absatzstruktur platt zu walzen.
|
||||||
|
*/
|
||||||
|
_htmlToText(html) {
|
||||||
|
let s = String(html);
|
||||||
|
s = s.replace(/<!--[\s\S]*?-->/g, " ");
|
||||||
|
// style/script/head/title inkl. Inhalt raus (sonst landet CSS/JS im Text).
|
||||||
|
s = s.replace(/<(style|script|head|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, " ");
|
||||||
|
// Block-Grenzen -> Zeilenumbruch; Listenpunkte -> "- ".
|
||||||
|
s = s.replace(/<\s*br\s*\/?>/gi, "\n");
|
||||||
|
s = s.replace(/<\s*li\b[^>]*>/gi, "\n- ");
|
||||||
|
s = s.replace(/<\/\s*(p|div|li|tr|h[1-6]|ul|ol|table|blockquote)\s*>/gi, "\n");
|
||||||
|
s = s.replace(/<\/\s*td\s*>/gi, " ");
|
||||||
|
// Restliche Tags entfernen, Entities dekodieren.
|
||||||
|
s = s.replace(/<[^>]+>/g, " ");
|
||||||
|
s = this._decodeEntities(s);
|
||||||
|
// Whitespace glätten: Spaces/Tabs/NBSP zusammenfassen, Leerzeilen begrenzen.
|
||||||
|
s = s.replace(/[ \t ]+/g, " ");
|
||||||
|
s = s.replace(/ *\n */g, "\n");
|
||||||
|
s = s.replace(/\n{3,}/g, "\n\n");
|
||||||
|
return s.trim();
|
||||||
|
},
|
||||||
|
|
||||||
|
_decodeEntities(s) {
|
||||||
|
const named = {
|
||||||
|
amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", nbsp: " ",
|
||||||
|
auml: "ä", ouml: "ö", uuml: "ü", Auml: "Ä", Ouml: "Ö", Uuml: "Ü", szlig: "ß",
|
||||||
|
euro: "€", copy: "©", reg: "®", trade: "™", hellip: "…", middot: "·", bull: "•",
|
||||||
|
ndash: "–", mdash: "—", laquo: "«", raquo: "»", deg: "°", eacute: "é", agrave: "à",
|
||||||
|
ldquo: "“", rdquo: "”", lsquo: "‘", rsquo: "’",
|
||||||
|
bdquo: "„", sbquo: "‚",
|
||||||
|
};
|
||||||
|
return String(s)
|
||||||
|
.replace(/&#x([0-9a-fA-F]+);/g, (_, h) => this._fromCp(parseInt(h, 16)))
|
||||||
|
.replace(/&#(\d+);/g, (_, d) => this._fromCp(parseInt(d, 10)))
|
||||||
|
.replace(/&([a-zA-Z][a-zA-Z0-9]*);/g, (m, n) => (named[n] != null ? named[n] : m));
|
||||||
|
},
|
||||||
|
|
||||||
|
_fromCp(cp) {
|
||||||
|
try { return Number.isFinite(cp) ? String.fromCodePoint(cp) : ""; }
|
||||||
|
catch (_) { return ""; }
|
||||||
|
},
|
||||||
|
|
||||||
_parseAddress(str) {
|
_parseAddress(str) {
|
||||||
if (!str) return { name: "", email: "" };
|
if (!str) return { name: "", email: "" };
|
||||||
const m = str.match(/^\s*"?([^"<]*)"?\s*<([^>]+)>\s*$/);
|
const m = str.match(/^\s*"?([^"<]*)"?\s*<([^>]+)>\s*$/);
|
||||||
|
|||||||
22
lib/pdf.js
22
lib/pdf.js
@ -129,9 +129,27 @@ const Pdf = {
|
|||||||
return this._toLatin1(s).replace(/[\\()]/g, "\\$&");
|
return this._toLatin1(s).replace(/[\\()]/g, "\\$&");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Unicode-Sonderzeichen, die NICHT in Latin-1, aber in WinAnsiEncoding liegen
|
||||||
|
// (0x80–0x9F). Da die Schrift /WinAnsiEncoding nutzt und Bytes per charCodeAt&0xFF
|
||||||
|
// geschrieben werden, rendern diese so als echte Typografie-Zeichen statt als '?'.
|
||||||
|
_WINANSI: {
|
||||||
|
"€": 0x80, "‚": 0x82, "ƒ": 0x83, "„": 0x84, "…": 0x85,
|
||||||
|
"†": 0x86, "‡": 0x87, "ˆ": 0x88, "‰": 0x89, "Š": 0x8A,
|
||||||
|
"‹": 0x8B, "Œ": 0x8C, "Ž": 0x8E, "‘": 0x91, "’": 0x92,
|
||||||
|
"“": 0x93, "”": 0x94, "•": 0x95, "–": 0x96, "—": 0x97,
|
||||||
|
"˜": 0x98, "™": 0x99, "š": 0x9A, "›": 0x9B, "œ": 0x9C,
|
||||||
|
"ž": 0x9E, "Ÿ": 0x9F,
|
||||||
|
},
|
||||||
|
|
||||||
_toLatin1(s) {
|
_toLatin1(s) {
|
||||||
// Zeichen außerhalb Latin-1 durch '?' ersetzen.
|
let out = "";
|
||||||
return (s || "").replace(/[^\x00-\xFF]/g, "?");
|
for (const ch of (s || "")) {
|
||||||
|
const cp = ch.codePointAt(0);
|
||||||
|
if (cp <= 0xff) out += ch; // Latin-1 direkt
|
||||||
|
else if (this._WINANSI[ch] != null) out += String.fromCharCode(this._WINANSI[ch]);
|
||||||
|
else out += "?"; // nicht darstellbar
|
||||||
|
}
|
||||||
|
return out;
|
||||||
},
|
},
|
||||||
|
|
||||||
_latin1Bytes(str) {
|
_latin1Bytes(str) {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "DocuWare Ablage",
|
"name": "DocuWare Ablage",
|
||||||
"description": "Legt markierte E-Mails (als .eml, PDF und mit Anhängen) in DocuWare ab.",
|
"description": "Legt markierte E-Mails (als .eml, PDF und mit Anhängen) in DocuWare ab.",
|
||||||
"version": "0.10.1",
|
"version": "0.10.2",
|
||||||
"author": "l.lingler",
|
"author": "l.lingler",
|
||||||
"applications": {
|
"applications": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
|||||||
@ -1,29 +1,36 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
#
|
#
|
||||||
# release.sh – signiert die Extension bei addons.thunderbird.net (Kanal "unlisted",
|
# release.sh – baut ein sauberes XPI, lädt es als Gitea-Release hoch und trägt es
|
||||||
# also signiert ABER nicht öffentlich gelistet), lädt das signierte XPI als
|
# in updates.json fürs Auto-Update ein.
|
||||||
# Gitea-Release hoch und trägt es in updates.json fürs Auto-Update ein.
|
|
||||||
#
|
#
|
||||||
# Benötigt: bash, node, npx, curl, git. web-ext wird genutzt, wenn global
|
# KEIN Signieren: Thunderbird verlangt für self-distributed Add-ons keine
|
||||||
# installiert; sonst automatisch via "npx web-ext" (kein globales Install nötig).
|
# Mozilla-Signatur, und addons.thunderbird.net (ATN) zeichnet Thunderbird-Add-ons
|
||||||
|
# ohnehin nicht gegen (jede Version bleibt `signed=false`; "web-ext sign" scheitert
|
||||||
|
# mit "no signed files were found"). Wir verteilen also das unsignierte XPI direkt
|
||||||
|
# über Gitea – genau wie schon 0.8.0, das nie bei ATN war.
|
||||||
|
#
|
||||||
|
# WICHTIG fürs Auto-Update: update_url und Release-Asset müssen ANONYM (ohne Login)
|
||||||
|
# erreichbar sein. Bei Gitea heißt das: Repo öffentlich UND instanzweit
|
||||||
|
# REQUIRE_SIGNIN_VIEW=false (in app.ini). Sonst bekommt Thunderbird beim Update-Check
|
||||||
|
# nur die Login-Seite und aktualisiert nie.
|
||||||
|
#
|
||||||
|
# Benötigt: bash, node, curl, git. Zum Bauen zusätzlich npx (web-ext); im --xpi-Modus
|
||||||
|
# nicht nötig.
|
||||||
#
|
#
|
||||||
# Secrets kommen aus der Umgebung bzw. aus scripts/.env (gitignored, NIE committen):
|
# Secrets kommen aus der Umgebung bzw. aus scripts/.env (gitignored, NIE committen):
|
||||||
# ATN_API_KEY – API-Schlüssel von addons.thunderbird.net (Entwicklerbereich)
|
|
||||||
# ATN_API_SECRET – zugehöriges Secret
|
|
||||||
# GITEA_TOKEN – Gitea Personal Access Token (Scope: write:repository)
|
# GITEA_TOKEN – Gitea Personal Access Token (Scope: write:repository)
|
||||||
# Optionale Overrides (sonst aus dem git-Remote "origin" abgeleitet):
|
# Optionale Overrides (sonst aus dem git-Remote "origin" abgeleitet):
|
||||||
# GITEA_BASE, GITEA_OWNER, GITEA_REPO, REPO_DIR
|
# GITEA_BASE, GITEA_OWNER, GITEA_REPO, REPO_DIR
|
||||||
#
|
#
|
||||||
# Aufruf:
|
# Aufruf:
|
||||||
# scripts/release.sh # signiert via ATN, nutzt Version aus manifest.json
|
# scripts/release.sh # baut sauberes XPI, Version aus manifest.json
|
||||||
# scripts/release.sh 0.9.0 # setzt zuerst diese Version, dann signieren
|
# scripts/release.sh 0.9.0 # setzt zuerst diese Version, dann bauen
|
||||||
# scripts/release.sh --xpi <datei.xpi> # bereits signiertes XPI veröffentlichen
|
# scripts/release.sh --xpi <datei.xpi> # fertiges XPI/ZIP veröffentlichen (kein Build)
|
||||||
# scripts/release.sh --xpi <datei.xpi> 0.9.0
|
# scripts/release.sh --xpi <datei.xpi> 0.9.0
|
||||||
#
|
#
|
||||||
# Hinweis ATN: addons.thunderbird.net gibt das signierte XPI nicht zuverlässig über
|
# Verifikation nach jedem Release (anonym, wie Thunderbird):
|
||||||
# die API zurück. In der Praxis: einmal mit dem Skript hochladen (legt die Version an),
|
# curl -L .../raw/branch/main/updates.json → muss JSON liefern, nicht die Login-Seite
|
||||||
# das signierte XPI aus dem ATN-Entwicklerbereich herunterladen, dann mit --xpi
|
# curl -L .../releases/download/vX/...xpi → Content-Type application/octet-stream
|
||||||
# veröffentlichen. Der --xpi-Modus überspringt das Signieren komplett.
|
|
||||||
#
|
#
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@ -57,13 +64,10 @@ need node; need curl; need git
|
|||||||
[ -f "$UPDATES" ] || die "updates.json nicht gefunden ($UPDATES)."
|
[ -f "$UPDATES" ] || die "updates.json nicht gefunden ($UPDATES)."
|
||||||
: "${GITEA_TOKEN:?GITEA_TOKEN fehlt (scripts/.env oder Umgebung)}"
|
: "${GITEA_TOKEN:?GITEA_TOKEN fehlt (scripts/.env oder Umgebung)}"
|
||||||
if [ -z "$PRESIGNED" ]; then
|
if [ -z "$PRESIGNED" ]; then
|
||||||
# web-ext: ATN nutzt die Signing-API v4. Das geht nur mit web-ext 7.x – ab 8.x
|
# Sauberes Paket wird mit "web-ext build" erzeugt (kein Signieren, daher
|
||||||
# spricht web-ext ausschließlich die AMO-v5-API und ATN antwortet mit 404.
|
# versionsunabhängig). Per WEBEXT_CMD überschreibbar.
|
||||||
# Daher fest auf 7.x via npx (kein globales Install nötig); per WEBEXT_CMD überschreibbar.
|
|
||||||
need npx
|
need npx
|
||||||
WEBEXT="${WEBEXT_CMD:-npx --yes web-ext@^7}"
|
WEBEXT="${WEBEXT_CMD:-npx --yes web-ext}"
|
||||||
: "${ATN_API_KEY:?ATN_API_KEY fehlt (scripts/.env oder Umgebung)}"
|
|
||||||
: "${ATN_API_SECRET:?ATN_API_SECRET fehlt}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Version setzen / lesen ---------------------------------------------------
|
# --- Version setzen / lesen ---------------------------------------------------
|
||||||
@ -100,26 +104,25 @@ echo "Release $VERSION (Add-on $ADDON_ID)"
|
|||||||
echo " Gitea : $GITEA_BASE/$GITEA_OWNER/$GITEA_REPO (Branch $BRANCH)"
|
echo " Gitea : $GITEA_BASE/$GITEA_OWNER/$GITEA_REPO (Branch $BRANCH)"
|
||||||
echo " Asset : $UPDATE_LINK"
|
echo " Asset : $UPDATE_LINK"
|
||||||
|
|
||||||
# --- 1) XPI besorgen: entweder bereits signiert (--xpi) oder via ATN signieren --
|
# --- 1) XPI besorgen: fertiges Paket (--xpi) oder sauber selbst bauen -----------
|
||||||
mkdir -p "$ARTIFACTS"
|
mkdir -p "$ARTIFACTS"
|
||||||
if [ -n "$PRESIGNED" ]; then
|
if [ -n "$PRESIGNED" ]; then
|
||||||
echo "→ Nutze vorsigniertes XPI: $PRESIGNED"
|
echo "→ Nutze fertiges Paket: $PRESIGNED"
|
||||||
cp -f "$PRESIGNED" "$ARTIFACTS/$XPI_NAME"
|
cp -f "$PRESIGNED" "$ARTIFACTS/$XPI_NAME"
|
||||||
else
|
else
|
||||||
echo "→ Signiere bei addons.thunderbird.net (unlisted) …"
|
echo "→ Baue sauberes XPI (web-ext build) …"
|
||||||
rm -f "$ARTIFACTS"/*.xpi 2>/dev/null || true
|
rm -f "$ARTIFACTS"/*.zip "$ARTIFACTS"/*.xpi 2>/dev/null || true
|
||||||
$WEBEXT sign \
|
# --ignore-files hält Secrets/Build-Kram raus: scripts/ (mit .env!), docs/,
|
||||||
--channel=unlisted \
|
# web-ext-artifacts/, *.md, *.xpi, git-Kram und updates.json (gehört NICHT ins XPI).
|
||||||
--api-url-prefix="https://addons.thunderbird.net/api/v4" \
|
$WEBEXT build \
|
||||||
--api-key="$ATN_API_KEY" \
|
|
||||||
--api-secret="$ATN_API_SECRET" \
|
|
||||||
--source-dir="$REPO_DIR" \
|
--source-dir="$REPO_DIR" \
|
||||||
--artifacts-dir="$ARTIFACTS" \
|
--artifacts-dir="$ARTIFACTS" \
|
||||||
|
--overwrite-dest \
|
||||||
--ignore-files "web-ext-artifacts/**" "scripts/**" "docs/**" "*.md" "*.xpi" \
|
--ignore-files "web-ext-artifacts/**" "scripts/**" "docs/**" "*.md" "*.xpi" \
|
||||||
".git/**" ".gitignore" "*.env" ".env"
|
".git/**" ".gitignore" "*.env" ".env" "updates.json"
|
||||||
SIGNED="$(ls -t "$ARTIFACTS"/*.xpi 2>/dev/null | head -1)"
|
BUILT="$(ls -t "$ARTIFACTS"/*.zip 2>/dev/null | head -1)"
|
||||||
[ -n "${SIGNED:-}" ] && [ -f "$SIGNED" ] || die "Kein signiertes XPI in $ARTIFACTS gefunden."
|
[ -n "${BUILT:-}" ] && [ -f "$BUILT" ] || die "Kein Paket in $ARTIFACTS gebaut."
|
||||||
cp -f "$SIGNED" "$ARTIFACTS/$XPI_NAME"
|
cp -f "$BUILT" "$ARTIFACTS/$XPI_NAME"
|
||||||
fi
|
fi
|
||||||
echo "✓ XPI bereit: $ARTIFACTS/$XPI_NAME"
|
echo "✓ XPI bereit: $ARTIFACTS/$XPI_NAME"
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,10 @@
|
|||||||
{
|
{
|
||||||
"version": "0.10.1",
|
"version": "0.10.1",
|
||||||
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.1/docuware-ablage-0.10.1.xpi"
|
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.1/docuware-ablage-0.10.1.xpi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.10.2",
|
||||||
|
"update_link": "https://sylyx.xyz/sylyx/thunderbird2docuware/releases/download/v0.10.2/docuware-ablage-0.10.2.xpi"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user