/
home
/
techb158
/
trello-import.abdallabala.com
/
public
/
js
/
/home/techb158/trello-import.abdallabala.com/public/js
mkdir
upload
Name
Size
Mode
Actions
config.js
415
0666
edit
dl
rm
importer.js
26616
0666
edit
dl
rm
settings.js
1919
0666
edit
dl
rm
Edit:
/home/techb158/trello-import.abdallabala.com/public/js/importer.js
(26616B)
(function () { "use strict"; var config = window.TRELLO_IMPORT_CONFIG || {}; var options = { appKey: config.appKey || "REPLACE_WITH_YOUR_TRELLO_POWERUP_API_KEY", appName: config.appName || "Import JSON/CSV", appAuthor: config.appAuthor || "Your Name or Company" }; var state = { t: null, board: null, token: null, lists: [], labels: [], members: [], rawRows: [], headers: [], mappedRows: [] }; var fields = [ { value: "ignore", label: "Ignore" }, { value: "name", label: "Card name", required: true, aliases: ["name", "title", "card", "card name", "summary", "task", "subject"] }, { value: "desc", label: "Description", aliases: ["desc", "description", "details", "body", "notes", "note"] }, { value: "list", label: "List name", aliases: ["list", "list name", "status", "stage", "lane", "column"] }, { value: "due", label: "Due date", aliases: ["due", "due date", "duedate", "deadline"] }, { value: "start", label: "Start date", aliases: ["start", "start date", "startdate"] }, { value: "labels", label: "Labels", aliases: ["labels", "label", "tags", "tag"] }, { value: "members", label: "Members", aliases: ["members", "member", "assignees", "assignee", "assigned to", "owners", "owner"] }, { value: "checklist", label: "Checklist items", aliases: ["checklist", "checklists", "check items", "checkitems", "todo", "todos", "subtasks"] }, { value: "comments", label: "Comments", aliases: ["comments", "comment", "activity"] }, { value: "urlSource", label: "Source URL", aliases: ["url", "source", "source url", "link", "attachment url"] }, { value: "pos", label: "Position", aliases: ["position", "pos"] } ]; var sampleCsv = "name,list,desc,due,labels,members,checklist\n" + "Write launch brief,To Do,Prepare first draft,2026-07-10,Marketing;Priority,alice,Outline|Draft|Review\n" + "QA importer,In Progress,Test CSV and JSON cases,2026-07-12,Engineering,bob,Parse CSV|Preview rows|Import cards\n" + "Publish docs,Done,Finalize internal usage notes,,Documentation,,README|Screenshots"; var sampleJson = JSON.stringify({ cards: [ { name: "Plan kickoff", list: "To Do", desc: "Schedule the kickoff and prepare agenda.", due: "2026-07-14", labels: ["Planning", "Priority"], members: ["alice"], checklist: ["Draft agenda", "Invite team", "Share notes"] }, { title: "Build prototype", status: "In Progress", description: "Create the first working prototype.", tags: "Engineering;Prototype", checklist: "Create branch|Implement UI|Request review" } ] }, null, 2); var $ = function (id) { return document.getElementById(id); }; function escapeHtml(value) { return String(value == null ? "" : value) .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function setStatus(message, type) { var el = $("status"); el.className = "status " + (type || "info"); el.textContent = message; } function setVisible(id, visible) { $(id).classList.toggle("hidden", !visible); } function isConfigured() { return options.appKey && options.appKey.indexOf("REPLACE_WITH_") !== 0; } function normalizeKey(value) { return String(value == null ? "" : value).trim().toLowerCase(); } function asArray(value) { if (value == null || value === "") return []; if (Array.isArray(value)) return value; if (typeof value === "object") return Object.values(value); var text = String(value).trim(); if (!text) return []; if ((text[0] === "[" && text[text.length - 1] === "]") || (text[0] === "{" && text[text.length - 1] === "}")) { try { var parsed = JSON.parse(text); if (Array.isArray(parsed)) return parsed; if (parsed && typeof parsed === "object") return Object.values(parsed); } catch (e) { // fall through to delimiter parsing } } return text .split(/\r?\n|[;|]/) .map(function (item) { return item.trim(); }) .filter(Boolean); } function parseChecklists(value) { if (value == null || value === "") return []; if (Array.isArray(value)) { return [ { name: "Imported checklist", items: value.map(function (item) { return String(item).trim(); }).filter(Boolean) } ].filter(function (list) { return list.items.length > 0; }); } if (typeof value === "object") { return Object.keys(value).map(function (name) { return { name: name, items: asArray(value[name]) }; }).filter(function (list) { return list.items.length > 0; }); } var text = String(value).trim(); if (!text) return []; if (text[0] === "[" || text[0] === "{") { try { return parseChecklists(JSON.parse(text)); } catch (e) { // fall through } } return [ { name: "Imported checklist", items: asArray(text) } ].filter(function (list) { return list.items.length > 0; }); } function parseCsv(text) { var delimiter = detectDelimiter(text); var rows = []; var row = []; var cell = ""; var inQuotes = false; for (var i = 0; i < text.length; i += 1) { var ch = text[i]; var next = text[i + 1]; if (inQuotes) { if (ch === '"' && next === '"') { cell += '"'; i += 1; } else if (ch === '"') { inQuotes = false; } else { cell += ch; } continue; } if (ch === '"') { inQuotes = true; } else if (ch === delimiter) { row.push(cell); cell = ""; } else if (ch === "\n") { row.push(cell); rows.push(row); row = []; cell = ""; } else if (ch === "\r") { if (next === "\n") i += 1; row.push(cell); rows.push(row); row = []; cell = ""; } else { cell += ch; } } row.push(cell); rows.push(row); rows = rows.filter(function (r) { return r.some(function (c) { return String(c).trim() !== ""; }); }); if (rows.length < 2) { throw new Error("CSV must include a header row and at least one data row."); } var headers = rows.shift().map(function (h, index) { var header = String(h).trim(); return header || "Column " + (index + 1); }); return rows.map(function (r) { var obj = {}; headers.forEach(function (header, index) { obj[header] = r[index] == null ? "" : String(r[index]).trim(); }); return obj; }); } function detectDelimiter(text) { var firstLine = text.split(/\r?\n/).find(function (line) { return line.trim(); }) || ""; var candidates = [",", "\t", ";"]; var best = ","; var bestCount = -1; candidates.forEach(function (candidate) { var count = 0; var inQuotes = false; for (var i = 0; i < firstLine.length; i += 1) { if (firstLine[i] === '"') inQuotes = !inQuotes; else if (!inQuotes && firstLine[i] === candidate) count += 1; } if (count > bestCount) { best = candidate; bestCount = count; } }); return best; } function parseInput(text, filename) { var trimmed = text.trim(); if (!trimmed) throw new Error("No data to parse."); var looksJson = trimmed[0] === "[" || trimmed[0] === "{" || /\.json$/i.test(filename || ""); if (looksJson) { var parsed = JSON.parse(trimmed); var rows = Array.isArray(parsed) ? parsed : parsed.cards || parsed.items || parsed.data; if (!Array.isArray(rows)) { throw new Error("JSON must be an array, or an object with a cards, items, or data array."); } if (!rows.length) throw new Error("JSON array is empty."); return rows.map(function (row, index) { if (row && typeof row === "object" && !Array.isArray(row)) return row; return { name: String(row), rowNumber: index + 1 }; }); } return parseCsv(trimmed); } function collectHeaders(rows) { var seen = {}; var headers = []; rows.forEach(function (row) { Object.keys(row).forEach(function (key) { if (!seen[key]) { seen[key] = true; headers.push(key); } }); }); return headers; } function guessField(header) { var normalized = normalizeKey(header).replace(/[_-]+/g, " "); for (var i = 0; i < fields.length; i += 1) { var field = fields[i]; if (!field.aliases) continue; if (field.aliases.indexOf(normalized) >= 0) return field.value; } return "ignore"; } function renderMapping() { var rowsEl = $("mappingRows"); rowsEl.innerHTML = state.headers.map(function (header) { var guess = guessField(header); var optionsHtml = fields.map(function (field) { return "<option value=\"" + escapeHtml(field.value) + "\"" + (field.value === guess ? " selected" : "") + ">" + escapeHtml(field.label) + "</option>"; }).join(""); return "<div class=\"mapping-row\"><div><strong>" + escapeHtml(header) + "</strong><div class=\"small muted\">Detected input field</div></div><div><select data-header=\"" + escapeHtml(header) + "\">" + optionsHtml + "</select></div></div>"; }).join(""); renderDefaultLists(); setVisible("mappingSection", true); } function renderDefaultLists() { var select = $("defaultListSelect"); select.innerHTML = state.lists.map(function (list) { return "<option value=\"" + escapeHtml(list.id) + "\">" + escapeHtml(list.name) + "</option>"; }).join(""); } function getMappings() { var mapping = {}; Array.prototype.forEach.call($("mappingRows").querySelectorAll("select[data-header]"), function (select) { mapping[select.getAttribute("data-header")] = select.value; }); return mapping; } function mapRows() { var mapping = getMappings(); var defaultList = state.lists.find(function (list) { return list.id === $("defaultListSelect").value; }); var defaultListName = defaultList ? defaultList.name : ""; var position = $("positionSelect").value || "bottom"; return state.rawRows.map(function (row, index) { var card = { sourceRow: index + 1, name: "", desc: "", listName: defaultListName, due: "", start: "", labels: [], members: [], checklists: [], comments: [], urlSource: "", pos: position, errors: [], warnings: [] }; Object.keys(mapping).forEach(function (header) { var target = mapping[header]; if (!target || target === "ignore") return; var value = row[header]; if (target === "name") card.name = String(value == null ? "" : value).trim(); if (target === "desc") card.desc = String(value == null ? "" : value).trim(); if (target === "list" && String(value || "").trim()) card.listName = String(value).trim(); if (target === "due") card.due = normalizeDate(value); if (target === "start") card.start = normalizeDate(value); if (target === "labels") card.labels = asArray(value); if (target === "members") card.members = asArray(value); if (target === "checklist") card.checklists = parseChecklists(value); if (target === "comments") card.comments = asArray(value); if (target === "urlSource") card.urlSource = String(value == null ? "" : value).trim(); if (target === "pos" && String(value || "").trim()) card.pos = String(value).trim(); }); if (!card.name) card.errors.push("Missing card name."); if (!card.listName) card.errors.push("Missing list name and no default list is available."); if (card.due === null) card.errors.push("Invalid due date."); if (card.start === null) card.errors.push("Invalid start date."); if (card.urlSource && !/^https?:\/\//i.test(card.urlSource)) card.warnings.push("Source URL is not an HTTP URL and will be ignored."); return card; }); } function normalizeDate(value) { if (value == null || value === "") return ""; var text = String(value).trim(); if (!text) return ""; var date = new Date(text); if (Number.isNaN(date.getTime())) return null; return date.toISOString(); } function renderPreview() { state.mappedRows = mapRows(); enrichPreviewResolution(state.mappedRows); var errorCount = state.mappedRows.reduce(function (sum, row) { return sum + row.errors.length; }, 0); var warningCount = state.mappedRows.reduce(function (sum, row) { return sum + row.warnings.length; }, 0); var strict = $("strictCheckbox").checked; $("previewSummary").innerHTML = "<p>" + "<span class=\"badge success\">" + state.mappedRows.length + " rows</span>" + "<span class=\"badge error\">" + errorCount + " errors</span>" + "<span class=\"badge\">" + warningCount + " warnings</span>" + (strict ? "<span class=\"badge\">Strict mode on</span>" : "") + "</p>"; var previewRows = state.mappedRows.slice(0, 50); $("previewTable").innerHTML = "<table><thead><tr>" + "<th>Row</th><th>Name</th><th>List</th><th>Due</th><th>Labels</th><th>Members</th><th>Checklist items</th><th>Issues</th>" + "</tr></thead><tbody>" + previewRows.map(function (row) { var issues = row.errors.concat(row.warnings).map(function (issue) { return "<span class=\"badge " + (row.errors.indexOf(issue) >= 0 ? "error" : "") + "\">" + escapeHtml(issue) + "</span>"; }).join(""); return "<tr>" + "<td>" + row.sourceRow + "</td>" + "<td>" + escapeHtml(row.name) + "</td>" + "<td>" + escapeHtml(row.listName) + "</td>" + "<td>" + escapeHtml(row.due ? row.due.slice(0, 10) : "") + "</td>" + "<td>" + escapeHtml(row.labels.join(", ")) + "</td>" + "<td>" + escapeHtml(row.members.join(", ")) + "</td>" + "<td>" + row.checklists.reduce(function (sum, list) { return sum + list.items.length; }, 0) + "</td>" + "<td>" + (issues || "") + "</td>" + "</tr>"; }).join("") + "</tbody></table>" + (state.mappedRows.length > 50 ? "<p class=\"small muted\">Showing first 50 rows only.</p>" : ""); $("importButton").disabled = errorCount > 0; setVisible("previewSection", true); setStatus(errorCount ? "Preview has errors. Fix mappings or source data before importing." : "Preview ready. Review the rows before importing.", errorCount ? "error" : "success"); } function enrichPreviewResolution(rows) { var strict = $("strictCheckbox").checked; var createLists = $("createListsCheckbox").checked; var createLabels = $("createLabelsCheckbox").checked; rows.forEach(function (row) { if (row.listName && !findList(row.listName) && !createLists) { var message = "List does not exist: " + row.listName; (strict ? row.errors : row.warnings).push(message); } row.labels.forEach(function (label) { if (label && !findLabel(label) && !createLabels) { var labelMessage = "Label does not exist: " + label; (strict ? row.errors : row.warnings).push(labelMessage); } }); row.members.forEach(function (member) { if (member && !findMember(member)) { var memberMessage = "Member not found on board: " + member; (strict ? row.errors : row.warnings).push(memberMessage); } }); }); } function findList(name) { var key = normalizeKey(name); return state.lists.find(function (list) { return normalizeKey(list.name) === key || list.id === name; }); } function findLabel(name) { var key = normalizeKey(name); return state.labels.find(function (label) { return normalizeKey(label.name) === key || normalizeKey(label.color) === key || label.id === name; }); } function findMember(name) { var key = normalizeKey(String(name).replace(/^@/, "")); return state.members.find(function (member) { return normalizeKey(member.username) === key || normalizeKey(member.fullName) === key || normalizeKey(member.id) === key; }); } async function init() { state.t = window.TrelloPowerUp.iframe(options); bindEvents(); if (!isConfigured()) { setStatus("Set your Trello Power-Up API key in public/js/config.js before using the importer.", "error"); disableMainControls(true); return; } await state.t.render(async function () { await refreshAuthAndBoardData(false); }); } function bindEvents() { $("authorizeButton").addEventListener("click", authorize); $("reloadButton").addEventListener("click", function () { refreshAuthAndBoardData(true); }); $("parseButton").addEventListener("click", parseDataFromUi); $("previewButton").addEventListener("click", renderPreview); $("importButton").addEventListener("click", importCards); $("closeButton").addEventListener("click", function () { state.t.closeModal(); }); $("sampleCsvButton").addEventListener("click", function () { $("pasteInput").value = sampleCsv; }); $("sampleJsonButton").addEventListener("click", function () { $("pasteInput").value = sampleJson; }); } function disableMainControls(disabled) { ["authorizeButton", "reloadButton", "parseButton", "previewButton", "importButton"].forEach(function (id) { $(id).disabled = disabled; }); } async function authorize() { try { setStatus("Opening Trello authorization.", "info"); var client = await state.t.getRestApi(); await client.authorize({ scope: "read,write", expiration: "never" }); setStatus("Authorized. Loading board data.", "success"); await refreshAuthAndBoardData(true); } catch (error) { setStatus("Authorization was not completed: " + errorMessage(error), "error"); } } async function refreshAuthAndBoardData(forceMessage) { try { var client = await state.t.getRestApi(); var authorized = await client.isAuthorized(); $("authorizeButton").textContent = authorized ? "Reauthorize" : "Authorize Trello"; if (!authorized) { setStatus("Authorize the Power-Up before importing. The importer needs read and write access to create cards on this board.", "warning"); return; } state.token = await client.getToken(); state.board = await state.t.board("id", "name", "url"); await loadBoardData(); if (forceMessage) setStatus("Loaded board data for “" + state.board.name + "”.", "success"); else setStatus("Authorized and ready. Choose a JSON or CSV file to begin.", "success"); } catch (error) { setStatus("Could not load board data: " + errorMessage(error), "error"); } } async function loadBoardData() { if (!state.board || !state.board.id) throw new Error("Could not determine the current Trello board."); var boardId = state.board.id; var lists = await api("boards/" + boardId + "/lists", "GET", { filter: "open", fields: "name" }); var labels = await api("boards/" + boardId + "/labels", "GET", { fields: "name,color" }); var members = await api("boards/" + boardId + "/members", "GET", { fields: "fullName,username,initials" }); state.lists = lists || []; state.labels = labels || []; state.members = members || []; renderDefaultLists(); } async function parseDataFromUi() { try { var file = $("fileInput").files[0]; var pasted = $("pasteInput").value; var text = pasted.trim() ? pasted : file ? await readFile(file) : ""; state.rawRows = parseInput(text, file && file.name); state.headers = collectHeaders(state.rawRows); setStatus("Parsed " + state.rawRows.length + " rows. Confirm the field mapping.", "success"); renderMapping(); setVisible("previewSection", false); setVisible("progressSection", false); } catch (error) { setStatus("Parse failed: " + errorMessage(error), "error"); } } function readFile(file) { return new Promise(function (resolve, reject) { var reader = new FileReader(); reader.onload = function () { resolve(String(reader.result || "")); }; reader.onerror = function () { reject(reader.error || new Error("Could not read file.")); }; reader.readAsText(file); }); } async function importCards() { var rows = state.mappedRows.length ? state.mappedRows : mapRows(); enrichPreviewResolution(rows); var errorRows = rows.filter(function (row) { return row.errors.length > 0; }); if (errorRows.length) { setStatus("Import blocked because preview still has errors.", "error"); return; } $("importButton").disabled = true; setVisible("progressSection", true); clearLog(); updateProgress(0, rows.length); log("Starting import of " + rows.length + " cards."); var imported = 0; var failed = 0; var delayMs = Math.max(0, parseInt($("rateLimitInput").value, 10) || 0); for (var i = 0; i < rows.length; i += 1) { var row = rows[i]; try { await importSingleCard(row); imported += 1; log("✓ Row " + row.sourceRow + ": " + row.name); } catch (error) { failed += 1; log("✗ Row " + row.sourceRow + ": " + row.name + ", " + errorMessage(error)); } updateProgress(i + 1, rows.length); if (delayMs && i < rows.length - 1) await sleep(delayMs); } setStatus("Import finished. " + imported + " cards created, " + failed + " failed.", failed ? "warning" : "success"); log("Finished. Created: " + imported + ". Failed: " + failed + "."); $("importButton").disabled = false; } async function importSingleCard(row) { var list = await resolveList(row.listName); var labelIds = await resolveLabels(row.labels); var memberIds = resolveMembers(row.members); var payload = { idList: list.id, name: row.name, desc: row.desc, pos: row.pos || "bottom" }; if (row.due) payload.due = row.due; if (row.start) payload.start = row.start; if (labelIds.length) payload.idLabels = labelIds.join(","); if (memberIds.length) payload.idMembers = memberIds.join(","); if (row.urlSource && /^https?:\/\//i.test(row.urlSource)) payload.urlSource = row.urlSource; var card = await api("cards", "POST", payload); for (var c = 0; c < row.comments.length; c += 1) { await api("cards/" + card.id + "/actions/comments", "POST", { text: row.comments[c] }); } for (var l = 0; l < row.checklists.length; l += 1) { var checklist = row.checklists[l]; var created = await api("cards/" + card.id + "/checklists", "POST", { name: checklist.name || "Imported checklist", pos: "bottom" }); for (var itemIndex = 0; itemIndex < checklist.items.length; itemIndex += 1) { await api("checklists/" + created.id + "/checkItems", "POST", { name: checklist.items[itemIndex], checked: "false", pos: "bottom" }); } } return card; } async function resolveList(name) { var existing = findList(name); if (existing) return existing; if (!$("createListsCheckbox").checked) throw new Error("List does not exist: " + name); var list = await api("lists", "POST", { name: name, idBoard: state.board.id, pos: "bottom" }); state.lists.push(list); renderDefaultLists(); return list; } async function resolveLabels(names) { var ids = []; for (var i = 0; i < names.length; i += 1) { var name = names[i]; var existing = findLabel(name); if (existing) { ids.push(existing.id); continue; } if (!$("createLabelsCheckbox").checked) throw new Error("Label does not exist: " + name); var label = await api("labels", "POST", { name: name, color: "null", idBoard: state.board.id }); state.labels.push(label); ids.push(label.id); } return ids; } function resolveMembers(names) { return names.map(function (name) { return findMember(name); }).filter(Boolean).map(function (member) { return member.id; }); } async function api(path, method, params) { if (!state.token) throw new Error("Missing Trello token. Reauthorize and try again."); var url = new URL("https://api.trello.com/1/" + path.replace(/^\//, "")); url.searchParams.set("key", options.appKey); url.searchParams.set("token", state.token); var fetchOptions = { method: method || "GET" }; var data = params || {}; if ((method || "GET").toUpperCase() === "GET") { Object.keys(data).forEach(function (key) { if (data[key] !== undefined && data[key] !== null && data[key] !== "") url.searchParams.set(key, data[key]); }); } else { fetchOptions.headers = { "Content-Type": "application/x-www-form-urlencoded" }; var body = new URLSearchParams(); Object.keys(data).forEach(function (key) { if (data[key] !== undefined && data[key] !== null && data[key] !== "") body.set(key, data[key]); }); fetchOptions.body = body.toString(); } var response = await fetch(url.toString(), fetchOptions); var text = await response.text(); var payload = null; try { payload = text ? JSON.parse(text) : null; } catch (e) { payload = text; } if (!response.ok) { throw new Error(typeof payload === "string" ? payload : JSON.stringify(payload)); } return payload; } function updateProgress(done, total) { var percent = total ? Math.round((done / total) * 100) : 0; $("progressBar").style.width = percent + "%"; $("progressText").textContent = done + " of " + total + " rows processed."; } function log(message) { var el = $("log"); el.textContent += message + "\n"; el.scrollTop = el.scrollHeight; } function clearLog() { $("log").textContent = ""; } function sleep(ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); } function errorMessage(error) { if (!error) return "Unknown error"; return error.message || String(error); } document.addEventListener("DOMContentLoaded", function () { init().catch(function (error) { setStatus("Could not initialize importer: " + errorMessage(error), "error"); }); }); })();
Save
cmd:
run