Skip to content

Commit

Permalink
significant refactor to improve tabgroup handling. Combined tab/tg/wi…
Browse files Browse the repository at this point in the history
…ndow/session saves into a single saveTabs fn. Better handling of dragging tabs into and out of tgs in the browser
  • Loading branch information
tconfrey committed Feb 7, 2024
1 parent f1f6a01 commit 9affb69
Show file tree
Hide file tree
Showing 5 changed files with 309 additions and 130 deletions.
26 changes: 19 additions & 7 deletions app/BTAppNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ class BTAppNode extends BTNode {
});
}

storeFavicon() {
// store favicon in browser local storage
try {
const host = this.URL.split(/[?#]/)[0]; // strip off any query or hash
localFileManager.set(host, this.faviconUrl);
}
catch (e) {
console.warn(`Error storing favicon: ${e}`);
}
}

async populateFavicon() {
// add favicon icon either from local storage or goog
if (this.isTopic() || !this.URL) return;
Expand Down Expand Up @@ -527,7 +538,7 @@ class BTAppNode extends BTNode {
if (n.hasOpenChildren()) {
const openTabIds = n.childIds.flatMap(
c => AllNodes[c].tabId ? [AllNodes[c].tabId] :[]);
window.postMessage({'function': 'groupAll', 'groupName': n.displayTag,
window.postMessage({'function': 'moveOpenTabsToTG', 'groupName': n.displayTag,
'tabIds': openTabIds, 'windowId': n.windowId});
}
});
Expand Down Expand Up @@ -936,19 +947,20 @@ class BTLinkNode extends BTAppNode {
const Handlers = {
"launchApp": launchApp, // Kick the whole thing off
"loadBookmarks": loadBookmarks,
"tabActivated": tabActivated, // User nav to Existing tab
"tabGrouped": tabGrouped, //!!!reconcile with tabJOinedTG
"tabJoinedTG" : tabJoinedTG, // a tab was dragged into a TG
"tabActivated": tabActivated, // User nav to Existing tab
"tabJoinedTG" : tabJoinedTG, // a tab was dragged or moved into a TG
"tabLeftTG" : tabLeftTG, // a tab was dragged out of a TG
"tabNavigated": tabNavigated, // User navigated a tab to a new url
"tabOpened" : tabOpened, // New tab opened
"tabMoved" : tabMoved, // user moved a tab
"tabPositioned": tabPositioned // tab moved by extension
"tabPositioned": tabPositioned, // tab moved by extension
"tabClosed" : tabClosed, // tab closed
"storeTabs": storeTabs, // popup store operation - page, window or session
"importSession": importSession, //!!!reconcile with storeTabs
"saveTabs": saveTabs, // popup save operation - page, tg, window or session
"tabGroupCreated": tabGroupCreated,
"tabGroupUpdated": tabGroupUpdated,
//"tabGrouped": tabGrouped, // merged with tabJOinedTG
//"storeTabs": storeTabs, // replaced by saveTabs. popup store operation - page, window or session
//"importSession": importSession, // erplaced by saveTabs.
};

// Set handler for extension messaging
Expand Down
206 changes: 137 additions & 69 deletions app/bt.js
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,68 @@ function tabClosed(data) {

}

function saveTabs(data) {
// iterate thru array of tabsData and save each to BT
// data is of the form: {'function': 'saveTabs', 'saveType':Tab|TG|Window|Session, 'tabs': [], 'note': msg.note, 'close': msg.close}
// tabs: [{'tabId': t.id, 'groupId': t.groupId, 'windowId': t.windowId, 'url': t.url, 'topic': topic, 'title': msg.title, favIconUrl: t.favIconUrl}]
// topic is potentially topic-dn:window##:TGName:TODO
console.log('saveTabs: ', data);
const note = data.note;
const close = data.close;

// Iterate tabs and create btappNodes as needed
const changedTopicNodes = new Set();
data.tabs.reverse().forEach(tab=> {
const existingNode = BTAppNode.findFromTab(tab.tabId);
if (existingNode) {
if (note) {
existingNode.text = note;
existingNode.redisplay();
}
return; // already saved, ignore other than making any note update
}
const [topicDN, keyword] = BTNode.processTopicString(tab.topic || "📝 Scratch");
const topicNode = BTAppNode.findOrCreateFromTopicDN(topicDN);
changedTopicNodes.add(topicNode);
const title = cleanTitle(tab.title); // get rid of unprintable characters etc
// create and populate node
const node = new BTAppNode(`[[${tab.url}][${title}]]`, topicNode.id, note || "", topicNode.level + 1);
node.tabId = tab.tabId; node.windowId = tab.windowId;
topicNode.windowId = tab.windowId;
if (tab.groupId > 0) { // groupid = -1 if not set
node.tabGroupId = tab.groupId;
topicNode.tabGroupId = tab.groupId;
}
node.faviconUrl = tab.favIconUrl; node.tabIndex = tab.tabIndex;
if (keyword) node.keyword = keyword;
if (note) node.text = note;

// handle display aspects of single node
$("table.treetable").treetable("loadBranch", topicNode.getTTNode(), node.HTML());
if (close) node.closeTab(); else setNodeOpen(node); // save and close popup operation
node.storeFavicon(); node.populateFavicon();
MRUTopicPerWindow[node.windowId] = topicDN; // track mru topic per window for popup population
});

// update subtree of each changed topic node
changedTopicNodes.forEach(node => {
if (['TG', 'Session'].includes(data.saveType)) node.childIds.reverse(); // reverse to get back to order of insertion when whole tg added
node.redisplay();
node.groupAndPosition();
});

// update topic list, sync extension, reset ui and save changes.
BTAppNode.generateTags();
let lastTopic = Array.from(changedTopicNodes).pop();
window.postMessage({'function': 'localStore',
'data': { 'tags': Tags, 'mruTopics': MRUTopicPerWindow, 'currentTag': lastTopic, 'currentText': note}});
window.postMessage({'function' : 'brainZoom', 'tabId' : data.tabs[0].tabId});

initializeUI();
saveBT();
}

/* // StoreTabs and importSession replaced by more generic saveTabs above
function storeTabs(data) {
// put tab(s) under storage w given topic. tabsData is a list, could be one or all tabs in window
// NB topicString may be topic, topic:hierarchy:nodes etc and may have a terminating :TODO
Expand Down Expand Up @@ -833,6 +895,78 @@ function storeTabs(data) {
}
}
function importSession(msg) {
// handler for session sent via msg {winID: {tabs[], tgs:{tabs[]}}}
const wins = msg.windows;
const dateString = getDateString().replace(':', '∷'); // 12:15 => :15 is a sub topic
const sessionName = "Session Import (" + dateString + ")";
const parentName = msg.topic || "📝 Scratch";
const parent = BTAppNode.findOrCreateFromTopicDN(parentName);
const sessionNode = new BTAppNode(sessionName, parent.id, "", parent.level + 1);
const closeP = msg.close; // close tabs after saving?
const newTabNodes = [];
Object.values(wins).forEach(win => {
const wNode = new BTAppNode(win.windowName, sessionNode.id, "", sessionNode.level + 1);
wNode.windowId = win.windowId;
win.tabs.reverse().forEach(tab => {
// Find or create node for tab
let tabNode = BTAppNode.findFromTab(tab.id);
if (!tabNode) {
tabNode = new BTAppNode(`[[${tab.url}][${tab.title}]]`, wNode.id, "", wNode.level + 1);
tabNode.createDisplayNode();
}
newTabNodes.push(tabNode);
tabNode.windowId = tab.windowId;
tabNode.tabId = tab.id;
tabNode.tabIndex = tab.tabIndex;
tabNode.faviconUrl = tab.faviconUrl;
setNodeOpen(tabNode);
if (closeP) tabNode.closeTab();
});
Object.values(win.tabGroups).reverse().forEach(tg => {
if (BTAppNode.findFromGroup(tg.id)) return; // already managed
const tgNode = new BTAppNode(tg.title, wNode.id, "", wNode.level + 1);
tgNode.tabGroupId = tg.id;
tg.tabs.reverse().forEach(tab => {
let tabNode = BTAppNode.findFromTab(tab.id);
if (!tabNode) {
tabNode = new BTAppNode(`[[${tab.url}][${tab.title}]]`, tgNode.id, "", tgNode.level + 1);
tabNode.createDisplayNode();
}
newTabNodes.push(tabNode);
tabNode.windowId = win.windowId;
tabNode.tabGroupId = tg.id;
tabNode.tabId = tab.id;
tabNode.tabIndex = tab.tabIndex;
tabNode.faviconUrl = tab.faviconUrl;
setNodeOpen(tabNode);
if (closeP) tabNode.closeTab();
});
tabGroupUpdated({'tabGroupId': tg.id, 'tabGroupColor': tg.color,
'tabGroupName': tg.title, 'tabGroupCollapsed': tg.collapsed});
});
wNode.createTabGroup(); // since win is topic, create Tabgroup
});
// save the hostname<->favicon mapping locally for Favicon feature.
try {
newTabNodes.forEach(n => {
const u = n.URL.split(/[?#]/)[0]; // remove any params
n.faviconUrl && localFileManager.set(u, n.faviconUrl);
});
}
catch (e) {
console.warn('URL/Favicon storage error: ', e);
};
processImport(sessionNode.id);
}
*/


function tabPositioned(data, highlight = false) {
// handle tab move, currently as a result of an earlier groupAndPosition - see StoreTabs and tabOpened

Expand Down Expand Up @@ -923,6 +1057,7 @@ function tabActivated(data) {
window.postMessage({'function': 'localStore', 'data': {...m1, ...m2}});
}

/* merged with tabJoinedTG
function tabGrouped(data) {
// tab added to group at index
const node = BTAppNode.findFromTab(data.tabId);
Expand All @@ -932,6 +1067,7 @@ function tabGrouped(data) {
if (node?.parentId)
AllNodes[node.parentId].tabGroupId = data.tgId;
}
*/

function tabGroupCreated(data) {
// TG created update associated topic color as appropriate
Expand Down Expand Up @@ -1063,6 +1199,7 @@ function positionInTopic(topicNode, tabNode, index, indices, winId) {
}

function cleanTitle(text) {
if (!text) return "";
// NOTE: Regex is from https://stackoverflow.com/a/11598864
const clean_non_printable_chars_re = /[\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g;
// clean page title text of things that can screw up BT. Currently [] and non printable chars
Expand Down Expand Up @@ -1637,75 +1774,6 @@ function loadBookmarks(msg) {
processImport(importNode.id); // see above
}

function importSession(msg) {
// handler for session sent via msg {winID: {tabs[], tgs:{tabs[]}}}

const wins = msg.windows;
const dateString = getDateString().replace(':', '&#8759;'); // 12:15 => :15 is a sub topic
const sessionName = "Session Import (" + dateString + ")";
const parentName = msg.topic || "📝 Scratch";
const parent = BTAppNode.findOrCreateFromTopicDN(parentName);
const sessionNode = new BTAppNode(sessionName, parent.id, "", parent.level + 1);
const closeP = msg.close; // close tabs after saving?
const newTabNodes = [];

Object.values(wins).forEach(win => {
const wNode = new BTAppNode(win.windowName, sessionNode.id, "", sessionNode.level + 1);
wNode.windowId = win.windowId;
win.tabs.reverse().forEach(tab => {
// Find or create node for tab
let tabNode = BTAppNode.findFromTab(tab.id);
if (!tabNode) {
tabNode = new BTAppNode(`[[${tab.url}][${tab.title}]]`, wNode.id, "", wNode.level + 1);
tabNode.createDisplayNode();
}
newTabNodes.push(tabNode);
tabNode.windowId = tab.windowId;
tabNode.tabId = tab.id;
tabNode.tabIndex = tab.tabIndex;
tabNode.faviconUrl = tab.faviconUrl;
setNodeOpen(tabNode);
if (closeP) tabNode.closeTab();
});
Object.values(win.tabGroups).reverse().forEach(tg => {
if (BTAppNode.findFromGroup(tg.id)) return; // already managed
const tgNode = new BTAppNode(tg.title, wNode.id, "", wNode.level + 1);
tgNode.tabGroupId = tg.id;
tg.tabs.reverse().forEach(tab => {
let tabNode = BTAppNode.findFromTab(tab.id);
if (!tabNode) {
tabNode = new BTAppNode(`[[${tab.url}][${tab.title}]]`, tgNode.id, "", tgNode.level + 1);
tabNode.createDisplayNode();
}
newTabNodes.push(tabNode);
tabNode.windowId = win.windowId;
tabNode.tabGroupId = tg.id;
tabNode.tabId = tab.id;
tabNode.tabIndex = tab.tabIndex;
tabNode.faviconUrl = tab.faviconUrl;
setNodeOpen(tabNode);
if (closeP) tabNode.closeTab();
});
tabGroupUpdated({'tabGroupId': tg.id, 'tabGroupColor': tg.color,
'tabGroupName': tg.title, 'tabGroupCollapsed': tg.collapsed});
});
wNode.createTabGroup(); // since win is topic, create Tabgroup
});

// save the hostname<->favicon mapping locally for Favicon feature.
try {
newTabNodes.forEach(n => {
const u = n.URL.split(/[?#]/)[0]; // remove any params
n.faviconUrl && localFileManager.set(u, n.faviconUrl);
});
}
catch (e) {
console.warn('URL/Favicon storage error: ', e);
};

processImport(sessionNode.id);
}

function loadBookmarkNode(node, parent) {
// load a new node from bookmark export format as child of parent BTNode and recurse on children

Expand Down
Loading

0 comments on commit 9affb69

Please sign in to comment.