Add obscure.lst manifest filter for folder selection
This commit is contained in:
Vendored
+215
-3
@@ -727,6 +727,209 @@ function onFilesChange(state, cfg, elements) {
|
||||
};
|
||||
}
|
||||
|
||||
// upload/frontend/table/lst_filter.js
|
||||
var MANIFEST_NAME = "obscure.lst";
|
||||
function extensionAllowed2(name, allowedExt) {
|
||||
const lowerName = String(name || "").toLowerCase();
|
||||
return allowedExt.some((extension) => lowerName.endsWith(String(extension).toLowerCase()));
|
||||
}
|
||||
function relativePathOf(file) {
|
||||
const raw = String(file.webkitRelativePath || file.name || "").replace(/\\/g, "/");
|
||||
const parts = raw.split("/");
|
||||
return parts.length > 1 ? parts.slice(1).join("/") : parts[0] || "";
|
||||
}
|
||||
function findManifest(files) {
|
||||
return files.find((file) => {
|
||||
const raw = String(file.webkitRelativePath || file.name || "").replace(/\\/g, "/");
|
||||
const parts = raw.split("/");
|
||||
return parts.length === 2 && parts[1].toLowerCase() === MANIFEST_NAME;
|
||||
}) || null;
|
||||
}
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
function findClosingBracket(glob, openIndex) {
|
||||
let index = openIndex + 1;
|
||||
if (glob[index] === "!" || glob[index] === "^") index += 1;
|
||||
if (glob[index] === "]") index += 1;
|
||||
for (; index < glob.length; index += 1) {
|
||||
if (glob[index] === "]") return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function compileGlob(glob) {
|
||||
let pattern = "^";
|
||||
let index = 0;
|
||||
while (index < glob.length) {
|
||||
const character = glob[index];
|
||||
if (character === "*") {
|
||||
const isDouble = glob[index + 1] === "*";
|
||||
if (!isDouble) {
|
||||
pattern += "[^/]*";
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const atSegmentStart = index === 0 || glob[index - 1] === "/";
|
||||
const atSegmentEnd = index + 2 === glob.length || glob[index + 2] === "/";
|
||||
if (atSegmentStart && atSegmentEnd) {
|
||||
if (glob[index + 2] === "/") {
|
||||
pattern += "(?:[^/]+/)*";
|
||||
index += 3;
|
||||
} else {
|
||||
pattern += ".*";
|
||||
index += 2;
|
||||
}
|
||||
} else {
|
||||
pattern += "[^/]*";
|
||||
index += 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (character === "?") {
|
||||
pattern += "[^/]";
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (character === "[") {
|
||||
const close = findClosingBracket(glob, index);
|
||||
if (close === -1) return null;
|
||||
let body = glob.slice(index + 1, close);
|
||||
if (body.startsWith("!")) body = `^${body.slice(1)}`;
|
||||
pattern += `[${body.replace(/[\\\]]/g, (symbol) => `\\${symbol}`)}]`;
|
||||
index = close + 1;
|
||||
continue;
|
||||
}
|
||||
pattern += escapeRegExp(character);
|
||||
index += 1;
|
||||
}
|
||||
pattern += "$";
|
||||
try {
|
||||
return new RegExp(pattern, "i");
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function normalizeRule(line) {
|
||||
let text = line.trim();
|
||||
if (!text || text.startsWith("#")) return null;
|
||||
let negative = false;
|
||||
if (text.startsWith("!")) {
|
||||
negative = true;
|
||||
text = text.slice(1).trim();
|
||||
if (!text) return { error: '\u043F\u0443\u0441\u0442\u043E\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043F\u043E\u0441\u043B\u0435 "!"' };
|
||||
}
|
||||
text = text.replace(/\\/g, "/");
|
||||
text = text.replace(/\/{2,}/g, "/");
|
||||
text = text.replace(/^(?:\.\/)+/, "");
|
||||
if (text.startsWith("/") || /^[a-zA-Z]:\//.test(text)) {
|
||||
return { error: "\u0430\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F" };
|
||||
}
|
||||
if (text.split("/").some((segment) => segment === "..")) {
|
||||
return { error: '\u0441\u0435\u0433\u043C\u0435\u043D\u0442 ".." \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F' };
|
||||
}
|
||||
if (!text) return { error: "\u043F\u0443\u0441\u0442\u043E\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E" };
|
||||
if (text.endsWith("/")) text += "**";
|
||||
return { path: text, negative };
|
||||
}
|
||||
function parseRules(text, diagnostics) {
|
||||
const lines = text.split(/\r\n|\r|\n/);
|
||||
const rules = [];
|
||||
lines.forEach((line, index) => {
|
||||
const lineNumber = index + 1;
|
||||
const normalized = normalizeRule(line);
|
||||
if (normalized === null) return;
|
||||
if (normalized.error) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: lineNumber,
|
||||
message: `${normalized.error}: "${line.trim()}"`
|
||||
});
|
||||
return;
|
||||
}
|
||||
const regex = compileGlob(normalized.path);
|
||||
if (!regex) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: lineNumber,
|
||||
message: `\u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 glob: "${line.trim()}"`
|
||||
});
|
||||
return;
|
||||
}
|
||||
rules.push({
|
||||
line: lineNumber,
|
||||
source: line.trim(),
|
||||
negative: normalized.negative,
|
||||
regex
|
||||
});
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
async function lstFilter(files, allowedExt) {
|
||||
const list = Array.from(files || []);
|
||||
const manifest = findManifest(list);
|
||||
if (!manifest) {
|
||||
return { manifest: null, files: list, diagnostics: [] };
|
||||
}
|
||||
const diagnostics = [];
|
||||
let text;
|
||||
try {
|
||||
const buffer = await manifest.arrayBuffer();
|
||||
text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
||||
} catch (error) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: 0,
|
||||
message: `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${MANIFEST_NAME}: ${error.message}`
|
||||
});
|
||||
return { manifest, files: [], diagnostics };
|
||||
}
|
||||
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
||||
const rules = parseRules(text, diagnostics);
|
||||
const positives = rules.filter((rule) => !rule.negative);
|
||||
const negatives = rules.filter((rule) => rule.negative);
|
||||
if (positives.length === 0) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: 0,
|
||||
message: `\u0432 ${MANIFEST_NAME} \u043D\u0435\u0442 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0445 \u0432\u043A\u043B\u044E\u0447\u0430\u044E\u0449\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B`
|
||||
});
|
||||
return { manifest, files: [], diagnostics };
|
||||
}
|
||||
const candidates = list.filter((file) => file !== manifest && extensionAllowed2(file.name, allowedExt));
|
||||
const matched = /* @__PURE__ */ new Set();
|
||||
const selected = [];
|
||||
for (const file of candidates) {
|
||||
const path = relativePathOf(file);
|
||||
let included = false;
|
||||
for (const rule of positives) {
|
||||
if (rule.regex.test(path)) {
|
||||
matched.add(rule);
|
||||
included = true;
|
||||
}
|
||||
}
|
||||
if (!included) continue;
|
||||
const excluded = negatives.some((rule) => rule.regex.test(path));
|
||||
if (!excluded) selected.push(file);
|
||||
}
|
||||
for (const rule of positives) {
|
||||
if (!matched.has(rule)) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: rule.line,
|
||||
message: `\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u043D\u0438 \u0441 \u043E\u0434\u043D\u0438\u043C \u0444\u0430\u0439\u043B\u043E\u043C: "${rule.source}"`
|
||||
});
|
||||
}
|
||||
}
|
||||
if (selected.length === 0) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: 0,
|
||||
message: "manifest \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043B \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430"
|
||||
});
|
||||
}
|
||||
return { manifest, files: selected, diagnostics };
|
||||
}
|
||||
|
||||
// upload/frontend/table/on_folder_change.js
|
||||
function onFolderChange(state, cfg, elements) {
|
||||
return async () => {
|
||||
@@ -735,7 +938,14 @@ function onFolderChange(state, cfg, elements) {
|
||||
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||
try {
|
||||
const roots = /* @__PURE__ */ new Map();
|
||||
for (const file of Array.from(elements.folderInputEl.files)) {
|
||||
const filtered = await lstFilter(elements.folderInputEl.files, [...cfg.allowedExt, ".zip"]);
|
||||
if (filtered.manifest) {
|
||||
const messages = filtered.diagnostics.map((item) => item.line ? `\u0441\u0442\u0440\u043E\u043A\u0430 ${item.line}: ${item.message}` : item.message);
|
||||
if (messages.length && elements.statusEl) {
|
||||
elements.statusEl.textContent = messages.join("; ");
|
||||
}
|
||||
}
|
||||
for (const file of filtered.files) {
|
||||
const parts = (file.webkitRelativePath || file.name).split("/");
|
||||
const relativePath = parts.slice(1).join("/") || file.name;
|
||||
const lowerPath = relativePath.toLowerCase();
|
||||
@@ -1073,11 +1283,13 @@ function buildMarkup(root, cfg) {
|
||||
const control = (name, id, label, className = "") => controls.includes(name) ? `<button id="${id}" type="button" class="${className}">${esc(label)}</button>` : "";
|
||||
const columns = cfg.layout.columns?.length ? cfg.layout.columns : Object.keys(cfg.labels.columns);
|
||||
const headings = columns.map((column) => `<th>${esc(cfg.labels.columns[column] || column)}</th>`).join("");
|
||||
const fileAccept = esc([.../* @__PURE__ */ new Set([...cfg.allowedExt, ".zip"])].join(","));
|
||||
const folderAccept = esc([.../* @__PURE__ */ new Set([...cfg.allowedExt, ".zip", ".lst"])].join(","));
|
||||
root.innerHTML = `
|
||||
<div class="file-picker${cfg.layout.theme ? ` ${esc(cfg.layout.theme)}` : ""}">
|
||||
<div class="fp-toolbar" aria-label="${esc(cfg.labels.pickFiles)}">
|
||||
<input class="fp-file-input" type="file" accept="${esc([...cfg.allowedExt, ".zip"].join(","))}" multiple hidden>
|
||||
<input class="fp-folder-input" type="file" accept="${esc([...cfg.allowedExt, ".zip"].join(","))}" webkitdirectory directory multiple hidden>
|
||||
<input class="fp-file-input" type="file" accept="${fileAccept}" multiple hidden>
|
||||
<input class="fp-folder-input" type="file" accept="${folderAccept}" webkitdirectory directory multiple hidden>
|
||||
${control("files", "fp-files-btn", cfg.labels.pickFiles)}
|
||||
${control("folder", "fp-folder-btn", cfg.labels.pickFolder, "secondary")}
|
||||
${control("clear", "fp-clear-btn", cfg.labels.clear, "quiet")}
|
||||
|
||||
Vendored
+215
-3
@@ -755,6 +755,209 @@ var FilePicker = (() => {
|
||||
};
|
||||
}
|
||||
|
||||
// upload/frontend/table/lst_filter.js
|
||||
var MANIFEST_NAME = "obscure.lst";
|
||||
function extensionAllowed2(name, allowedExt) {
|
||||
const lowerName = String(name || "").toLowerCase();
|
||||
return allowedExt.some((extension) => lowerName.endsWith(String(extension).toLowerCase()));
|
||||
}
|
||||
function relativePathOf(file) {
|
||||
const raw = String(file.webkitRelativePath || file.name || "").replace(/\\/g, "/");
|
||||
const parts = raw.split("/");
|
||||
return parts.length > 1 ? parts.slice(1).join("/") : parts[0] || "";
|
||||
}
|
||||
function findManifest(files) {
|
||||
return files.find((file) => {
|
||||
const raw = String(file.webkitRelativePath || file.name || "").replace(/\\/g, "/");
|
||||
const parts = raw.split("/");
|
||||
return parts.length === 2 && parts[1].toLowerCase() === MANIFEST_NAME;
|
||||
}) || null;
|
||||
}
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
function findClosingBracket(glob, openIndex) {
|
||||
let index = openIndex + 1;
|
||||
if (glob[index] === "!" || glob[index] === "^") index += 1;
|
||||
if (glob[index] === "]") index += 1;
|
||||
for (; index < glob.length; index += 1) {
|
||||
if (glob[index] === "]") return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
function compileGlob(glob) {
|
||||
let pattern = "^";
|
||||
let index = 0;
|
||||
while (index < glob.length) {
|
||||
const character = glob[index];
|
||||
if (character === "*") {
|
||||
const isDouble = glob[index + 1] === "*";
|
||||
if (!isDouble) {
|
||||
pattern += "[^/]*";
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
const atSegmentStart = index === 0 || glob[index - 1] === "/";
|
||||
const atSegmentEnd = index + 2 === glob.length || glob[index + 2] === "/";
|
||||
if (atSegmentStart && atSegmentEnd) {
|
||||
if (glob[index + 2] === "/") {
|
||||
pattern += "(?:[^/]+/)*";
|
||||
index += 3;
|
||||
} else {
|
||||
pattern += ".*";
|
||||
index += 2;
|
||||
}
|
||||
} else {
|
||||
pattern += "[^/]*";
|
||||
index += 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (character === "?") {
|
||||
pattern += "[^/]";
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (character === "[") {
|
||||
const close = findClosingBracket(glob, index);
|
||||
if (close === -1) return null;
|
||||
let body = glob.slice(index + 1, close);
|
||||
if (body.startsWith("!")) body = `^${body.slice(1)}`;
|
||||
pattern += `[${body.replace(/[\\\]]/g, (symbol) => `\\${symbol}`)}]`;
|
||||
index = close + 1;
|
||||
continue;
|
||||
}
|
||||
pattern += escapeRegExp(character);
|
||||
index += 1;
|
||||
}
|
||||
pattern += "$";
|
||||
try {
|
||||
return new RegExp(pattern, "i");
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function normalizeRule(line) {
|
||||
let text = line.trim();
|
||||
if (!text || text.startsWith("#")) return null;
|
||||
let negative = false;
|
||||
if (text.startsWith("!")) {
|
||||
negative = true;
|
||||
text = text.slice(1).trim();
|
||||
if (!text) return { error: '\u043F\u0443\u0441\u0442\u043E\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043F\u043E\u0441\u043B\u0435 "!"' };
|
||||
}
|
||||
text = text.replace(/\\/g, "/");
|
||||
text = text.replace(/\/{2,}/g, "/");
|
||||
text = text.replace(/^(?:\.\/)+/, "");
|
||||
if (text.startsWith("/") || /^[a-zA-Z]:\//.test(text)) {
|
||||
return { error: "\u0430\u0431\u0441\u043E\u043B\u044E\u0442\u043D\u044B\u0439 \u043F\u0443\u0442\u044C \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F" };
|
||||
}
|
||||
if (text.split("/").some((segment) => segment === "..")) {
|
||||
return { error: '\u0441\u0435\u0433\u043C\u0435\u043D\u0442 ".." \u043D\u0435 \u0434\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F' };
|
||||
}
|
||||
if (!text) return { error: "\u043F\u0443\u0441\u0442\u043E\u0435 \u043F\u0440\u0430\u0432\u0438\u043B\u043E" };
|
||||
if (text.endsWith("/")) text += "**";
|
||||
return { path: text, negative };
|
||||
}
|
||||
function parseRules(text, diagnostics) {
|
||||
const lines = text.split(/\r\n|\r|\n/);
|
||||
const rules = [];
|
||||
lines.forEach((line, index) => {
|
||||
const lineNumber = index + 1;
|
||||
const normalized = normalizeRule(line);
|
||||
if (normalized === null) return;
|
||||
if (normalized.error) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: lineNumber,
|
||||
message: `${normalized.error}: "${line.trim()}"`
|
||||
});
|
||||
return;
|
||||
}
|
||||
const regex = compileGlob(normalized.path);
|
||||
if (!regex) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: lineNumber,
|
||||
message: `\u043D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 glob: "${line.trim()}"`
|
||||
});
|
||||
return;
|
||||
}
|
||||
rules.push({
|
||||
line: lineNumber,
|
||||
source: line.trim(),
|
||||
negative: normalized.negative,
|
||||
regex
|
||||
});
|
||||
});
|
||||
return rules;
|
||||
}
|
||||
async function lstFilter(files, allowedExt) {
|
||||
const list = Array.from(files || []);
|
||||
const manifest = findManifest(list);
|
||||
if (!manifest) {
|
||||
return { manifest: null, files: list, diagnostics: [] };
|
||||
}
|
||||
const diagnostics = [];
|
||||
let text;
|
||||
try {
|
||||
const buffer = await manifest.arrayBuffer();
|
||||
text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
||||
} catch (error) {
|
||||
diagnostics.push({
|
||||
level: "error",
|
||||
line: 0,
|
||||
message: `\u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C ${MANIFEST_NAME}: ${error.message}`
|
||||
});
|
||||
return { manifest, files: [], diagnostics };
|
||||
}
|
||||
if (text.charCodeAt(0) === 65279) text = text.slice(1);
|
||||
const rules = parseRules(text, diagnostics);
|
||||
const positives = rules.filter((rule) => !rule.negative);
|
||||
const negatives = rules.filter((rule) => rule.negative);
|
||||
if (positives.length === 0) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: 0,
|
||||
message: `\u0432 ${MANIFEST_NAME} \u043D\u0435\u0442 \u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0445 \u0432\u043A\u043B\u044E\u0447\u0430\u044E\u0449\u0438\u0445 \u043F\u0440\u0430\u0432\u0438\u043B`
|
||||
});
|
||||
return { manifest, files: [], diagnostics };
|
||||
}
|
||||
const candidates = list.filter((file) => file !== manifest && extensionAllowed2(file.name, allowedExt));
|
||||
const matched = /* @__PURE__ */ new Set();
|
||||
const selected = [];
|
||||
for (const file of candidates) {
|
||||
const path = relativePathOf(file);
|
||||
let included = false;
|
||||
for (const rule of positives) {
|
||||
if (rule.regex.test(path)) {
|
||||
matched.add(rule);
|
||||
included = true;
|
||||
}
|
||||
}
|
||||
if (!included) continue;
|
||||
const excluded = negatives.some((rule) => rule.regex.test(path));
|
||||
if (!excluded) selected.push(file);
|
||||
}
|
||||
for (const rule of positives) {
|
||||
if (!matched.has(rule)) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: rule.line,
|
||||
message: `\u043F\u0440\u0430\u0432\u0438\u043B\u043E \u043D\u0435 \u0441\u043E\u0432\u043F\u0430\u043B\u043E \u043D\u0438 \u0441 \u043E\u0434\u043D\u0438\u043C \u0444\u0430\u0439\u043B\u043E\u043C: "${rule.source}"`
|
||||
});
|
||||
}
|
||||
}
|
||||
if (selected.length === 0) {
|
||||
diagnostics.push({
|
||||
level: "warning",
|
||||
line: 0,
|
||||
message: "manifest \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043B \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0444\u0430\u0439\u043B\u0430"
|
||||
});
|
||||
}
|
||||
return { manifest, files: selected, diagnostics };
|
||||
}
|
||||
|
||||
// upload/frontend/table/on_folder_change.js
|
||||
function onFolderChange(state, cfg, elements) {
|
||||
return async () => {
|
||||
@@ -763,7 +966,14 @@ var FilePicker = (() => {
|
||||
if (elements.statusEl) elements.statusEl.textContent = "";
|
||||
try {
|
||||
const roots = /* @__PURE__ */ new Map();
|
||||
for (const file of Array.from(elements.folderInputEl.files)) {
|
||||
const filtered = await lstFilter(elements.folderInputEl.files, [...cfg.allowedExt, ".zip"]);
|
||||
if (filtered.manifest) {
|
||||
const messages = filtered.diagnostics.map((item) => item.line ? `\u0441\u0442\u0440\u043E\u043A\u0430 ${item.line}: ${item.message}` : item.message);
|
||||
if (messages.length && elements.statusEl) {
|
||||
elements.statusEl.textContent = messages.join("; ");
|
||||
}
|
||||
}
|
||||
for (const file of filtered.files) {
|
||||
const parts = (file.webkitRelativePath || file.name).split("/");
|
||||
const relativePath = parts.slice(1).join("/") || file.name;
|
||||
const lowerPath = relativePath.toLowerCase();
|
||||
@@ -1101,11 +1311,13 @@ var FilePicker = (() => {
|
||||
const control = (name, id, label, className = "") => controls.includes(name) ? `<button id="${id}" type="button" class="${className}">${esc(label)}</button>` : "";
|
||||
const columns = cfg.layout.columns?.length ? cfg.layout.columns : Object.keys(cfg.labels.columns);
|
||||
const headings = columns.map((column) => `<th>${esc(cfg.labels.columns[column] || column)}</th>`).join("");
|
||||
const fileAccept = esc([.../* @__PURE__ */ new Set([...cfg.allowedExt, ".zip"])].join(","));
|
||||
const folderAccept = esc([.../* @__PURE__ */ new Set([...cfg.allowedExt, ".zip", ".lst"])].join(","));
|
||||
root.innerHTML = `
|
||||
<div class="file-picker${cfg.layout.theme ? ` ${esc(cfg.layout.theme)}` : ""}">
|
||||
<div class="fp-toolbar" aria-label="${esc(cfg.labels.pickFiles)}">
|
||||
<input class="fp-file-input" type="file" accept="${esc([...cfg.allowedExt, ".zip"].join(","))}" multiple hidden>
|
||||
<input class="fp-folder-input" type="file" accept="${esc([...cfg.allowedExt, ".zip"].join(","))}" webkitdirectory directory multiple hidden>
|
||||
<input class="fp-file-input" type="file" accept="${fileAccept}" multiple hidden>
|
||||
<input class="fp-folder-input" type="file" accept="${folderAccept}" webkitdirectory directory multiple hidden>
|
||||
${control("files", "fp-files-btn", cfg.labels.pickFiles)}
|
||||
${control("folder", "fp-folder-btn", cfg.labels.pickFolder, "secondary")}
|
||||
${control("clear", "fp-clear-btn", cfg.labels.clear, "quiet")}
|
||||
|
||||
Reference in New Issue
Block a user