saltcorn-samba 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -50,6 +50,11 @@ const {
50
50
  } = require("./smb-client");
51
51
  const treeView = require("./tree-view");
52
52
  const fileManagerView = require("./filemanager-view");
53
+ const {
54
+ catalogFor,
55
+ resolveLocaleFromReq,
56
+ availableLocales,
57
+ } = require("./i18n");
53
58
  // pdf-view is intentionally NOT wired into the manifest (see note at bottom).
54
59
  // The file is kept in the package so the DB-linkage release can revive it.
55
60
 
@@ -1263,6 +1268,35 @@ code{background:#f4f4f4;padding:2px 6px;border-radius:3px;word-break:break-all}<
1263
1268
  },
1264
1269
  },
1265
1270
 
1271
+ {
1272
+ // GET /samba-i18n.json?locale=xx
1273
+ //
1274
+ // Liefert den vollen \u00dcbersetzungskatalog f\u00fcr die Client-Seite. Wird
1275
+ // aktuell NICHT vom Standard-Bootstrap benutzt (die View-Shells injizieren
1276
+ // den Katalog inline, spart einen Roundtrip). Die Route ist f\u00fcr
1277
+ // externe Consumer / Debugging / dynamisches Nachladen einer anderen
1278
+ // Sprache im Browser gedacht.
1279
+ //
1280
+ // Keine Authentifizierung: Katalog enth\u00e4lt nur \u00dcbersetzungstext,
1281
+ // keine Konfiguration und keine share-spezifischen Daten.
1282
+ url: "/samba-i18n.json",
1283
+ method: "get",
1284
+ callback: async (req, res) => {
1285
+ try {
1286
+ const locale = resolveLocaleFromReq(req, req.query && req.query.locale);
1287
+ const catalog = catalogFor(locale);
1288
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
1289
+ // Kurzer Cache: Katalog \u00e4ndert sich nur bei Plugin-Release.
1290
+ res.setHeader("Cache-Control", "public, max-age=300");
1291
+ res.setHeader("X-Samba-Locale", locale);
1292
+ res.setHeader("X-Samba-Available-Locales", availableLocales.join(","));
1293
+ res.json(catalog);
1294
+ } catch (e) {
1295
+ res.status(500).json({ error: "i18n: " + (e.message || String(e)) });
1296
+ }
1297
+ },
1298
+ },
1299
+
1266
1300
  {
1267
1301
  url: "/sambamkdir",
1268
1302
  method: "post",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "saltcorn-samba",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Saltcorn plugin: browse, upload, rename and delete files on a Samba/CIFS share via SMB 3.1.1 (AES-CMAC signing, optional encryption). File-manager view, directory tree, inline PDF viewer, external-app open (smb://).",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "node test/sanitize.test.js",
8
- "lint": "node --check index.js && node --check smb-client.js && node --check readdir-compat.js && node --check tree-view.js && node --check filemanager-view.js && node --check pdf-view.js && node --check public/samba-tree.js && node --check public/samba-filemanager.js",
8
+ "lint": "node --check index.js && node --check smb-client.js && node --check readdir-compat.js && node --check tree-view.js && node --check filemanager-view.js && node --check pdf-view.js && node --check i18n.js && node --check public/samba-tree.js && node --check public/samba-filemanager.js && node --check public/samba-common.js",
9
9
  "prepublishOnly": "npm run lint && npm test"
10
10
  },
11
11
  "keywords": [
@@ -43,6 +43,8 @@
43
43
  "tree-view.js",
44
44
  "filemanager-view.js",
45
45
  "pdf-view.js",
46
+ "i18n.js",
47
+ "i18n/",
46
48
  "public/",
47
49
  "tools/",
48
50
  "README.md",
package/pdf-view.js CHANGED
@@ -20,6 +20,7 @@ const {
20
20
  button,
21
21
  } = require("@saltcorn/markup/tags");
22
22
  const { text } = require("@saltcorn/markup");
23
+ const { tFor, resolveLocaleFromReq } = require("./i18n");
23
24
 
24
25
  /** Extract the file extension in lowercase, without the dot. */
25
26
  function extOf(name) {
@@ -88,7 +89,9 @@ const samba_pdf = {
88
89
  },
89
90
  ],
90
91
  run: (value, req, options = {}) => {
91
- if (!value) return span({ class: "text-muted" }, "");
92
+ // Locale aus dem Saltcorn-Request; bei fehlendem req greift automatisch "en".
93
+ const _t = tFor(resolveLocaleFromReq(req));
94
+ if (!value) return span({ class: "text-muted" }, _t("pdf.empty"));
92
95
  const safeVal = text(String(value));
93
96
  const height = Number(options.height) > 0 ? Number(options.height) : 700;
94
97
 
@@ -101,7 +104,7 @@ const samba_pdf = {
101
104
  class: "btn btn-sm btn-outline-secondary me-2",
102
105
  },
103
106
  i({ class: "fas fa-download me-1" }),
104
- "Download"
107
+ _t("pdf.download")
105
108
  )
106
109
  );
107
110
  }
@@ -111,10 +114,10 @@ const samba_pdf = {
111
114
  {
112
115
  href: smbUrl(safeVal),
113
116
  class: "btn btn-sm btn-outline-primary me-2",
114
- title: "Open in Nemo/Nautilus/Explorer",
117
+ title: _t("pdf.open_in_fm_title"),
115
118
  },
116
119
  i({ class: "fas fa-external-link-alt me-1" }),
117
- "Open in file manager"
120
+ _t("pdf.open_in_fm")
118
121
  )
119
122
  );
120
123
  }
@@ -126,7 +129,7 @@ const samba_pdf = {
126
129
  class: "btn btn-sm btn-outline-secondary",
127
130
  },
128
131
  i({ class: "fas fa-eye me-1" }),
129
- "Open in new tab"
132
+ _t("pdf.open_new_tab")
130
133
  )
131
134
  );
132
135
 
@@ -0,0 +1,175 @@
1
+ /**
2
+ * saltcorn-samba – Gemeinsame Client-Utilities für den Browser.
3
+ *
4
+ * Wird von samba-filemanager.js und samba-tree.js benutzt. Enthält:
5
+ * - iconFor(item) : Emoji-Icon für Verzeichnisse/Dateien
6
+ * (früher in beiden JS-Dateien dupliziert)
7
+ * - extOf(name) : Extension in lower-case, ohne Punkt
8
+ * - joinPath(a, b), parentOf : simple posix-artige Pfad-Helfer
9
+ * - fmtSize(bytes), fmtDate(v): menschenlesbare Formatierung
10
+ * - isViewable(name) : PDFs, Bilder, Text-artige Dateien
11
+ * - mediaTypeFor(item) : grober MIME-Typ (auch nur für File-Manager)
12
+ * - t(key, params) : i18n-Übersetzung. Katalog wird nach dem
13
+ * Laden asynchron nachgeschoben; solange nur
14
+ * der Key nicht auffindbar ist, gilt der Key
15
+ * selbst als Rückfalltext.
16
+ * - setCatalog(obj) : setzt den i18n-Katalog (aus dem Server-JSON)
17
+ * - loadCatalog(url) : lädt einen JSON-Katalog per fetch()
18
+ *
19
+ * Alles wird an `window.SambaCommon` gehängt. Die Datei ist absichtlich in
20
+ * ES5 gehalten (kein Bundler-Zwang, funktioniert in älteren Browsern).
21
+ */
22
+ (function () {
23
+ "use strict";
24
+
25
+ // ---- i18n-State ---------------------------------------------------------
26
+ var catalog = {};
27
+
28
+ function setCatalog(obj) {
29
+ catalog = obj && typeof obj === "object" ? obj : {};
30
+ }
31
+
32
+ /** Löst {name}-Platzhalter im Muster auf. */
33
+ function interpolate(msg, params) {
34
+ if (!params) return msg;
35
+ return String(msg).replace(/\{(\w+)\}/g, function (_, k) {
36
+ return Object.prototype.hasOwnProperty.call(params, k) ? String(params[k]) : "";
37
+ });
38
+ }
39
+
40
+ /** Übersetzt einen Key. Fällt auf den Key selbst zurück, falls kein Eintrag. */
41
+ function t(key, params) {
42
+ if (Object.prototype.hasOwnProperty.call(catalog, key)) {
43
+ return interpolate(catalog[key], params);
44
+ }
45
+ return interpolate(key, params);
46
+ }
47
+
48
+ /**
49
+ * Lädt den Katalog von der übergebenen URL (z. B. /samba-i18n.json?locale=de).
50
+ * Ergebnis wird gecacht; scheitert der Fetch, bleibt der aktuelle Katalog stehen.
51
+ */
52
+ function loadCatalog(url) {
53
+ return fetch(url, { credentials: "same-origin" })
54
+ .then(function (r) { return r.ok ? r.json() : {}; })
55
+ .then(function (data) { setCatalog(data); return data; })
56
+ .catch(function () { return {}; });
57
+ }
58
+
59
+ // ---- reine Utilities ---------------------------------------------------
60
+ /** Extension in lower-case, ohne führenden Punkt. */
61
+ function extOf(name) {
62
+ var s = String(name || "");
63
+ var dot = s.lastIndexOf(".");
64
+ return dot >= 0 ? s.slice(dot + 1).toLowerCase() : "";
65
+ }
66
+
67
+ /**
68
+ * Einheitliches Icon für Verzeichnisse und Dateien.
69
+ * Die Kategorien decken die häufigsten Office-, Bild-, Archiv-, Audio- und
70
+ * Video-Formate ab. Alles Unbekannte bekommt 📎.
71
+ */
72
+ function iconFor(item) {
73
+ if (item && item.isDir) return "📁";
74
+ var e = extOf(item && item.name);
75
+ if (e === "pdf") return "📄";
76
+ if (["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"].indexOf(e) >= 0) return "🖼️";
77
+ if (["doc", "docx", "odt", "rtf", "txt", "md"].indexOf(e) >= 0) return "📝";
78
+ if (["xls", "xlsx", "ods", "csv"].indexOf(e) >= 0) return "📊";
79
+ if (["ppt", "pptx", "odp"].indexOf(e) >= 0) return "📽️";
80
+ if (["zip", "tar", "gz", "7z", "rar"].indexOf(e) >= 0) return "🗜️";
81
+ if (["mp3", "wav", "ogg", "flac", "m4a"].indexOf(e) >= 0) return "🎵";
82
+ if (["mp4", "mkv", "mov", "avi", "webm"].indexOf(e) >= 0) return "🎬";
83
+ return "📎";
84
+ }
85
+
86
+ /** Erlaubt inline-Anzeige im integrierten Viewer (PDF, Bilder, Text). */
87
+ function isViewable(name) {
88
+ var n = String(name || "").toLowerCase();
89
+ return (
90
+ n.endsWith(".pdf") ||
91
+ /\.(png|jpe?g|gif|webp|svg|bmp)$/.test(n) ||
92
+ /\.(txt|md|json|xml|csv|html?)$/.test(n)
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Grober MIME-Typ nur anhand der Extension – ausreichend zum Sortieren und
98
+ * für die "Media type"-Spalte. Der Server liefert für den echten Stream
99
+ * seinen eigenen (genaueren) Content-Type.
100
+ */
101
+ function mediaTypeFor(item) {
102
+ if (item && item.isDir) return "folder";
103
+ var e = extOf(item && item.name);
104
+ var map = {
105
+ pdf: "application/pdf",
106
+ png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg",
107
+ gif: "image/gif", webp: "image/webp", svg: "image/svg+xml",
108
+ txt: "text/plain", md: "text/markdown", csv: "text/csv",
109
+ json: "application/json", xml: "application/xml",
110
+ html: "text/html", htm: "text/html",
111
+ zip: "application/zip", doc: "application/msword",
112
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
113
+ xls: "application/vnd.ms-excel",
114
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
115
+ ppt: "application/vnd.ms-powerpoint",
116
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
117
+ mp3: "audio/mpeg", mp4: "video/mp4", mkv: "video/x-matroska",
118
+ mov: "video/quicktime", avi: "video/x-msvideo",
119
+ };
120
+ return map[e] || (e ? "application/" + e : "application/octet-stream");
121
+ }
122
+
123
+ /** Größe in KiB/MiB/GiB. Kompakter Format-String für Tabellen und Bäume. */
124
+ function fmtSize(n) {
125
+ if (!n || n < 0) return "";
126
+ if (n < 1024) return n + " B";
127
+ var kib = n / 1024;
128
+ if (kib < 1024) return kib.toFixed(kib < 10 ? 1 : 0) + " KiB";
129
+ var mib = kib / 1024;
130
+ if (mib < 1024) return mib.toFixed(mib < 10 ? 1 : 0) + " MiB";
131
+ return (mib / 1024).toFixed(2) + " GiB";
132
+ }
133
+
134
+ /** ISO-artig, ohne Sekunden ("2026-07-08 22:31"). Leere/ungültige Werte → "". */
135
+ function fmtDate(v) {
136
+ if (!v) return "";
137
+ var d = new Date(v);
138
+ if (isNaN(d.getTime())) return "";
139
+ var pad = function (x) { return String(x).padStart(2, "0"); };
140
+ return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate()) +
141
+ " " + pad(d.getHours()) + ":" + pad(d.getMinutes());
142
+ }
143
+
144
+ /** Fügt zwei Pfad-Fragmente mit genau einem "/" zusammen. */
145
+ function joinPath(a, b) {
146
+ if (!a) return b;
147
+ if (!b) return a;
148
+ return (a.replace(/\/+$/, "") + "/" + b.replace(/^\/+/, "")).replace(/\/+/g, "/");
149
+ }
150
+
151
+ /** Übergeordneter Pfad. `parentOf("a/b/c")` = "a/b", `parentOf("a")` = "". */
152
+ function parentOf(p) {
153
+ if (!p) return "";
154
+ var i = p.lastIndexOf("/");
155
+ return i < 0 ? "" : p.slice(0, i);
156
+ }
157
+
158
+ // ---- Export -------------------------------------------------------------
159
+ window.SambaCommon = {
160
+ // i18n
161
+ t: t,
162
+ setCatalog: setCatalog,
163
+ loadCatalog: loadCatalog,
164
+ // icons / classification
165
+ iconFor: iconFor,
166
+ isViewable: isViewable,
167
+ mediaTypeFor: mediaTypeFor,
168
+ // strings & paths
169
+ extOf: extOf,
170
+ fmtSize: fmtSize,
171
+ fmtDate: fmtDate,
172
+ joinPath: joinPath,
173
+ parentOf: parentOf,
174
+ };
175
+ })();