From 6d1876857beae1aed754528e8c096e0b92a54963 Mon Sep 17 00:00:00 2001 From: Colin Devroe Date: Thu, 19 Feb 2026 09:11:12 -0500 Subject: [PATCH 1/5] Upgraded Electron. --- package-lock.json | 24 ++++++++++++------------ package.json | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fcff5d..0b358dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "dotenv": "^17.2.2", - "electron": "^31.0.0", + "electron": "^40.6.0", "electron-builder": "^26.0.12" } }, @@ -945,13 +945,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", - "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "version": "24.10.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.13.tgz", + "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/plist": { @@ -2235,15 +2235,15 @@ } }, "node_modules/electron": { - "version": "31.7.7", - "resolved": "https://registry.npmjs.org/electron/-/electron-31.7.7.tgz", - "integrity": "sha512-HZtZg8EHsDGnswFt0QeV8If8B+et63uD6RJ7I4/xhcXqmTIbI08GoubX/wm+HdY0DwcuPe1/xsgqpmYvjdjRoA==", + "version": "40.6.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-40.6.0.tgz", + "integrity": "sha512-ett8W+yOFGDuM0vhJMamYSkrbV3LoaffzJd9GfjI96zRAxyrNqUSKqBpf/WGbQCweDxX2pkUCUfrv4wwKpsFZA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^20.9.0", + "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { @@ -4817,9 +4817,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 9aab43f..ac74434 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ }, "devDependencies": { "dotenv": "^17.2.2", - "electron": "^31.0.0", + "electron": "^40.6.0", "electron-builder": "^26.0.12" }, "dependencies": { From 7bbceb9e8cf455cc0b5a8697e55457749464173f Mon Sep 17 00:00:00 2001 From: Colin Devroe Date: Thu, 19 Feb 2026 10:49:58 -0500 Subject: [PATCH 2/5] Initial startup is faster and it is more clear what to do from an empty state. --- app/board/boardTabs.js | 32 +- app/board/openBoard.js | 12 +- app/board/renderBoard.js | 135 +++- app/init.js | 32 +- index.html | 20 +- package-lock.json | 1499 ++++++++++++++++++-------------------- package.json | 8 +- static/styles.css | 81 ++ 8 files changed, 951 insertions(+), 868 deletions(-) diff --git a/app/board/boardTabs.js b/app/board/boardTabs.js index c18c817..c21ddda 100644 --- a/app/board/boardTabs.js +++ b/app/board/boardTabs.js @@ -95,15 +95,19 @@ function ensureBoardInTabs(boardPath) { } function clearRenderedBoard() { + if (typeof setBoardChromeState === 'function') { + setBoardChromeState(false); + } + + if (typeof renderBoardEmptyState === 'function') { + renderBoardEmptyState(); + return; + } + const boardEl = document.getElementById('board'); if (boardEl) { boardEl.innerHTML = ''; } - - const boardNameEl = document.getElementById('boardName'); - if (boardNameEl) { - boardNameEl.textContent = 'No Board Open'; - } } async function closeBoardTab(boardPath) { @@ -151,17 +155,13 @@ async function closeBoardTab(boardPath) { await renderBoard(); } -function initializeBoardTabsSortable(tabsEl) { - if (!tabsEl || typeof Sortable !== 'function') { - return; - } - - if (boardTabsSortable && boardTabsSortable.el !== tabsEl) { +function initializeBoardTabsSortable(tabsEl, canSortTabs = true) { + if (boardTabsSortable && (!tabsEl || boardTabsSortable.el !== tabsEl || !canSortTabs)) { boardTabsSortable.destroy(); boardTabsSortable = null; } - if (boardTabsSortable) { + if (!canSortTabs || !tabsEl || typeof Sortable !== 'function' || boardTabsSortable) { return; } @@ -245,6 +245,12 @@ function renderBoardTabs() { const openBoards = getStoredOpenBoards(); tabsEl.innerHTML = ''; + if (openBoards.length === 0) { + tabsWrapper.classList.add('hidden'); + initializeBoardTabsSortable(null, false); + return; + } + tabsWrapper.classList.remove('hidden'); const activeBoard = normalizeBoardPath(window.boardRoot || getStoredActiveBoard()); @@ -329,5 +335,5 @@ function renderBoardTabs() { addBoardTab.appendChild(addBoardButton); tabsEl.appendChild(addBoardTab); - initializeBoardTabsSortable(tabsEl); + initializeBoardTabsSortable(tabsEl, openBoards.length > 1); } diff --git a/app/board/openBoard.js b/app/board/openBoard.js index 59eb67e..ea86459 100644 --- a/app/board/openBoard.js +++ b/app/board/openBoard.js @@ -39,11 +39,13 @@ async function openBoard( dir ) { const directories = await window.board.listDirectories( boardPath ); if ( directories.length == 0 ) { - await window.board.createList( boardPath + '000-To-do-stock'); - await window.board.createList( boardPath + '001-Doing-stock'); - await window.board.createList( boardPath + '002-Done-stock'); - await window.board.createList( boardPath + '003-On-hold-stock'); - await window.board.createList( boardPath + 'XXX-Archive'); + await Promise.all([ + window.board.createList(boardPath + '000-To-do-stock'), + window.board.createList(boardPath + '001-Doing-stock'), + window.board.createList(boardPath + '002-Done-stock'), + window.board.createList(boardPath + '003-On-hold-stock'), + window.board.createList(boardPath + 'XXX-Archive'), + ]); await window.board.createCard( boardPath + '000-To-do-stock/000-hello-stock.md', `👋 Hello diff --git a/app/board/renderBoard.js b/app/board/renderBoard.js index c732ae3..f22a702 100644 --- a/app/board/renderBoard.js +++ b/app/board/renderBoard.js @@ -1,20 +1,94 @@ +function setBoardChromeState(hasOpenBoard) { + const body = document.body; + if (body) { + body.classList.toggle('board-empty', !hasOpenBoard); + } + + if (hasOpenBoard) { + return; + } + + const boardNameEl = document.getElementById('boardName'); + if (boardNameEl) { + boardNameEl.textContent = 'Signboard'; + } +} + +async function handleEmptyBoardCallToActionClick(buttonEl) { + if (!buttonEl || buttonEl.disabled) { + return; + } + + buttonEl.disabled = true; + try { + await promptAndOpenBoardFromTabs(); + } finally { + buttonEl.disabled = false; + } +} + +function createEmptyBoardCallToAction() { + const buttonEl = document.createElement('button'); + buttonEl.type = 'button'; + buttonEl.id = 'emptyBoardCallToAction'; + buttonEl.className = 'empty-board-cta'; + buttonEl.setAttribute('aria-label', 'Select a directory to create a board'); + buttonEl.innerHTML = ` + + Create your first board + Select an empty directory. Signboard will use the directory name as the board name. + `; + buttonEl.addEventListener('click', async () => { + await handleEmptyBoardCallToActionClick(buttonEl); + }); + return buttonEl; +} + +function renderBoardEmptyState() { + const boardEl = document.getElementById('board'); + if (!boardEl) { + return; + } + + boardEl.innerHTML = ''; + boardEl.appendChild(createEmptyBoardCallToAction()); +} + async function renderBoard() { - const boardRoot = window.boardRoot; // set in the drop‑zone handler - await ensureBoardLabelsLoaded(); + const boardRoot = window.boardRoot; // set in the drop-zone handler if (!boardRoot) { + setBoardChromeState(false); renderBoardTabs(); + renderBoardEmptyState(); return; } closeCardLabelPopover(); + setBoardChromeState(true); - const boardName = document.getElementById('boardName'); - boardName.textContent = await window.board.getBoardName( boardRoot ); + const boardNameEl = document.getElementById('boardName'); + const [boardName, lists] = await Promise.all([ + window.board.getBoardName(boardRoot), + window.board.listLists(boardRoot), + ensureBoardLabelsLoaded(), + ]); + + if (boardNameEl) { + boardNameEl.textContent = boardName; + } renderBoardTabs(); - const lists = await window.board.listLists(boardRoot); const boardEl = document.getElementById('board'); + if (!boardEl) { + return; + } boardEl.innerHTML = ''; const listsWithCards = await Promise.all( @@ -25,41 +99,44 @@ async function renderBoard() { }) ); - for (const { listName, listPath, cards } of listsWithCards) { - const listEl = await createListElement(listName, listPath, cards); + const listElements = await Promise.all( + listsWithCards.map(({ listName, listPath, cards }) => createListElement(listName, listPath, cards)) + ); + + for (const listEl of listElements) { boardEl.appendChild(listEl); } - // Enable SortableJS on this column - new Sortable(boardEl, { - group: 'lists', - animation: 150, - onEnd: async (evt) => { - - const finalOrder = [...evt.to.querySelectorAll('.list')].map(list => + if (typeof Sortable === 'function') { + // Enable SortableJS on this column + new Sortable(boardEl, { + group: 'lists', + animation: 150, + onEnd: async (evt) => { + const finalOrder = [...evt.to.querySelectorAll('.list')].map((list) => list.getAttribute('data-path') - ); + ); - let directoryCounter = 0; - for (const directoryPath of finalOrder) { - - let directoryNumber = (directoryCounter).toLocaleString('en-US', { - minimumIntegerDigits: 3, - useGrouping: false + let directoryCounter = 0; + for (const directoryPath of finalOrder) { + const directoryNumber = (directoryCounter).toLocaleString('en-US', { + minimumIntegerDigits: 3, + useGrouping: false }); - let newDirectoryName = window.boardRoot + directoryNumber + await window.board.getListDirectoryName(directoryPath).slice(3); + const newDirectoryName = window.boardRoot + directoryNumber + await window.board.getListDirectoryName(directoryPath).slice(3); await window.board.moveCard(directoryPath, newDirectoryName); directoryCounter++; - } - await renderBoard(); - - } - }); - feather.replace(); - return; + await renderBoard(); + } + }); + } + + if (typeof feather !== 'undefined' && feather && typeof feather.replace === 'function') { + feather.replace(); + } } diff --git a/app/init.js b/app/init.js index b13f378..7332e64 100644 --- a/app/init.js +++ b/app/init.js @@ -1,11 +1,31 @@ -var turndown = new TurndownService(); -const renderMarkdown = (md) => marked.parse(md); - async function init() { - initializeBoardLabelControls(); - initializeBoardSearchControls(); - const restoredBoard = restoreBoardTabs(); + const initializeHeaderControls = () => { + initializeBoardLabelControls(); + initializeBoardSearchControls(); + }; + + if (!restoredBoard) { + window.boardRoot = ''; + if (typeof setBoardChromeState === 'function') { + setBoardChromeState(false); + } + + const emptyBoardCallToAction = document.getElementById('emptyBoardCallToAction'); + if (emptyBoardCallToAction) { + emptyBoardCallToAction.addEventListener('click', async () => { + if (typeof handleEmptyBoardCallToActionClick === 'function') { + await handleEmptyBoardCallToActionClick(emptyBoardCallToAction); + return; + } + await promptAndOpenBoardFromTabs(); + }); + } + + window.setTimeout(initializeHeaderControls, 0); + } else { + initializeHeaderControls(); + } if (restoredBoard) { window.boardRoot = restoredBoard; diff --git a/index.html b/index.html index 23f9aa8..4eceeb8 100644 --- a/index.html +++ b/index.html @@ -5,18 +5,16 @@ Signboard - - - +
-

BoardName

+

Signboard

@@ -41,7 +39,19 @@

BoardName

-
+
+ +