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/CHANGELOG.md +96 -0
- package/README.md +64 -0
- package/filemanager-view.js +52 -10
- package/i18n/de.json +93 -0
- package/i18n/en.json +93 -0
- package/i18n.js +155 -0
- package/index.js +34 -0
- package/package.json +4 -2
- package/pdf-view.js +8 -5
- package/public/samba-common.js +175 -0
- package/public/samba-filemanager.js +373 -381
- package/public/samba-tree.js +126 -137
- package/tree-view.js +49 -11
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.
|
|
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
|
-
|
|
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
|
-
"
|
|
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: "
|
|
117
|
+
title: _t("pdf.open_in_fm_title"),
|
|
115
118
|
},
|
|
116
119
|
i({ class: "fas fa-external-link-alt me-1" }),
|
|
117
|
-
"
|
|
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
|
-
"
|
|
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
|
+
})();
|