Skip to content

Commit

Permalink
- Added webnavigation event to support sticky tabs
Browse files Browse the repository at this point in the history
- Some error handling
- Bookmarker more responsive sizing
- handle &'s in page titles and in topic names
- added setting for stickyTabs
- gave BTNode an explicit topicName fn to stop re-using displayName or title (confusingly)
- updated popup to allow saving a navigated sticky node
- fixed bug saving notes to all tabs of the tg not just selected one
  • Loading branch information
tconfrey committed Jun 2, 2024
1 parent f238655 commit 316d2e6
Show file tree
Hide file tree
Showing 27 changed files with 452 additions and 170 deletions.
11 changes: 6 additions & 5 deletions app/BTAppNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -528,13 +528,14 @@ class BTAppNode extends BTNode {
});
window.postMessage({'function': 'groupAndPositionTabs', 'tabGroupId': this.tabGroupId,
'windowId': this.windowId, 'tabInfo': tabInfo,
'groupName': BTAppNode.displayNameFromTitle(this.displayTopic)});
'groupName': this.topicName(), // BTAppNode.displayNameFromTitle(this.displayTopic),
});
}

putInGroup() {
// wrap this one nodes tab in a group
if (!this.tabId || !this.windowId || (GroupingMode != 'TABGROUP')) return;
const groupName = this.isTopic() ? this.displayTopic : AllNodes[this.parentId]?.displayTopic;
const groupName = this.isTopic() ? this.topicName() : AllNodes[this.parentId]?.topicName();
const tgId = this.tabGroupId || AllNodes[this.parentId]?.tabGroupId;
window.postMessage({'function': 'groupAndPositionTabs', 'tabGroupId': tgId,
'windowId': this.windowId, 'tabInfo': [{'nodeId': this.id, 'tabId': this.tabId, 'tabIndex': this.tabIndex}],
Expand All @@ -556,7 +557,7 @@ class BTAppNode extends BTNode {
let rsp;
if (this.tabGroupId && this.isTopic())
rsp = callBackground({'function': 'updateGroup', 'tabGroupId': this.tabGroupId,
'collapsed': this.folded, 'title': this.title});
'collapsed': this.folded, 'title': this.topicName()});
return rsp;
}

Expand Down Expand Up @@ -774,7 +775,7 @@ class BTAppNode extends BTNode {
tabGroupTabs.push({'nodeId': id, 'url': node.URL});
});
const me = tabGroupTabs.length ?
{'tabGroupId': this.tabGroupId, 'windowId': this.windowId, 'groupName': this.displayTopic,
{'tabGroupId': this.tabGroupId, 'windowId': this.windowId, 'groupName': this.topicName(),
'tabGroupTabs': tabGroupTabs} : [];
const subtopics = this.childIds.flatMap(id => AllNodes[id].listOpenableTabGroups());
return [me, ...subtopics].flat();
Expand Down Expand Up @@ -891,7 +892,7 @@ class BTAppNode extends BTNode {
const topTopic = (components && components.length) ? components[0] : topic;

// Find or create top node
let topNode = AllNodes.find(node => node && node.displayTopic == topTopic);
let topNode = AllNodes.find(node => node && node.topicName() == topTopic);
if (!topNode) {
topNode = new BTAppNode(topTopic, null, "", 1);
topNode.createDisplayNode();
Expand Down
56 changes: 17 additions & 39 deletions app/BTNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class BTNode {
this._displayTopic = BTNode.displayNameFromTitle(_title);
this._childIds = [];
this._topicPath = '';
this.generateUniqueTopicPath();
if (parentId && AllNodes[parentId]) {
AllNodes[parentId].addChild(this._id, false, firstChild); // add to parent, index not passed, firstChild => front or back
}
Expand Down Expand Up @@ -99,7 +98,7 @@ class BTNode {

findChild(childTopic) {
// does this topic node have this sub topic
const childId = this.childIds.find(id => AllNodes[id].displayTopic == childTopic);
const childId = this.childIds.find(id => AllNodes[id].topicName() == childTopic);
return childId ? AllNodes[childId] : null;
}

Expand Down Expand Up @@ -133,6 +132,13 @@ class BTNode {
// Is this node used as a topic => has webLinked children
return (this.level == 1) || (!this.URL) || this.childIds.some(id => AllNodes[id]._hasWebLinks);
}

topicName () {
// return the topic name for this node
if (this.isTopic())
return (this.URL) ? BTNode.editableTopicFromTitle(this.title) : this.title;
return AllNodes[this.parentId].topicName();
}

isTopicTree() {
// Does this nodes url match a pointer to a web .org resource that can be loaded
Expand Down Expand Up @@ -287,42 +293,13 @@ class BTNode {

fullTopicPath() {
// distinguished name for this node
const myTopic = this.isTopic() ? this.title : '';
const myTopic = this.isTopic() ? this.topicName() : '';
if (this.parentId && AllNodes[this.parentId])
return AllNodes[this.parentId].fullTopicPath() + ':' + myTopic;
else
return myTopic;
}

generateUniqueTopicPath() {
// same topic can be under multiple parents, generate a unique topicPath
// only called from ctor. suplanted by below. can't really amke uniquee without looking at all topics

if (!this.isTopic()) {
if (this.parentId && AllNodes[this.parentId])
this._topicPath = AllNodes[this.parentId].topicPath;
else
this._topicPath = this._displayTopic;
return;
}

if (this.displayTopic == "") {
this._topicPath = this._displayTopic;
return;
}
const sameTopic = AllNodes.filter(nn => nn && nn.isTopic() && nn.displayTopic == this.displayTopic);
if (sameTopic.length == 1) {
// unique
this._topicPath = this._displayTopic;
return;
}
sameTopic.forEach(function(nn) {
const parentTag = AllNodes[nn.parentId] ? AllNodes[nn.parentId].displayTopic : "";
nn._topicPath = parentTag + ":" + nn.displayTopic;
});
return myTopic;
}


static generateUniqueTopicPaths() {
// same topic can be under multiple parents, generate a unique topic Path for each node

Expand All @@ -332,13 +309,14 @@ class BTNode {
let level = 1;
AllNodes.forEach((n) => {
if (!n) return;
const topicName = n.topicName();
if (n.isTopic()) {
if (topics[n.displayTopic]) {
topics[n.displayTopic].push(n.id);
if (topics[topicName]) {
topics[topicName].push(n.id);
flat = false;
}
else
topics[n.displayTopic] = Array(1).fill(n.id);
topics[topicName] = Array(1).fill(n.id);
}});

// !flat => dup topic names (<99 to prevent infinite loop
Expand All @@ -349,11 +327,11 @@ class BTNode {
// replace dups w DN of increasing levels until flat
delete topics[topic];
ids.forEach(id => {
let tpath = AllNodes[id].displayTopic;
let tpath = AllNodes[id].topicName();
let parent = AllNodes[id].parentId;
for (let i = 1; i < level; i++) {
if (parent && AllNodes[parent]) {
tpath = AllNodes[parent].displayTopic + ":" + tpath;
tpath = AllNodes[parent].topicName() + ":" + tpath;
parent = AllNodes[parent].parentId;
}
}
Expand All @@ -379,7 +357,7 @@ class BTNode {
if (node.parentId && AllNodes[node.parentId])
node._topicPath = AllNodes[node.parentId].topicPath;
else
node._topicPath = node._displayTopic;
node._topicPath = BTNode.editableTopicFromTitle(node.title); // no parent but not topic, use [[][title part]]
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion app/bt.css
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ textarea:focus, input[type="text"]:focus {
#youShallNotPass {
position: absolute;
width: 98%;
height: 400px;
height: 450px;
top: 242px;left: 3px;
z-index: 5;
background-color: #888;
Expand Down
71 changes: 55 additions & 16 deletions app/bt.js
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ function tabClosed(data) {
node.tabIndex = 0;
node.windowId = 0;
node.opening = false;
node.navigated = false;
tabActivated(data);

// update ui and animate parent to indicate change
Expand Down Expand Up @@ -790,17 +791,25 @@ function saveTabs(data) {

// Handle existing node case: update and return
const existingNode = BTAppNode.findFromTab(tab.tabId);
if (existingNode) {
if (existingNode && !existingNode.navigated) {
if (note) {
existingNode.text = note;
existingNode.redisplay();
}
if (close) existingNode.closeTab();
return; // already saved, ignore other than making any note update
}

// Deal with Topic
const [topicDN, keyword] = BTNode.processTopicString(tab.topic || "📝 Scratch");
const topicNode = BTAppNode.findOrCreateFromTopicDN(topicDN);
// Find or create topic, use existingNodes topic if it exists
let topicNode, keyword, topicDN;
if (existingNode) {
tabClosed({"tabId": tab.tabId}); // tab navigated away, clean up
topicNode = AllNodes[existingNode.parentId];
topicDN = topicNode.topicPath;
} else {
[topicDN, keyword] = BTNode.processTopicString(tab.topic || "📝 Scratch");
topicNode = BTAppNode.findOrCreateFromTopicDN(topicDN);
}
changedTopicNodes.add(topicNode);

// Create and populate node
Expand Down Expand Up @@ -832,7 +841,7 @@ function saveTabs(data) {
BTAppNode.generateTopics();
let lastTopicNode = Array.from(changedTopicNodes).pop();
window.postMessage({'function': 'localStore',
'data': { 'topics': Topics, 'mruTopics': MRUTopicPerWindow, 'currentTopic': lastTopicNode?.title || '', 'currentText': note}});
'data': { 'topics': Topics, 'mruTopics': MRUTopicPerWindow, 'currentTopic': lastTopicNode?.topicName() || '', 'currentText': note}});
window.postMessage({'function' : 'brainZoom', 'tabId' : data.tabs[0].tabId});

initializeUI();
Expand Down Expand Up @@ -863,14 +872,34 @@ function tabPositioned(data, highlight = false) {
}

function tabNavigated(data) {
// tab updated event, could be nav away or to a BT node
// tab updated event, could be nav away or to a BT node or even between two btnodes
// if tabs are sticky, tab sticks w original BTnode, as long as:
// 1) it was a result of a link click or server redirect (ie not a new use of the tab like typing in the address bar) or
// 2) its not a nav to a different btnode whose topic is open in the same window

function stickyTab() {
// Should the tab stay associated with the BT node
if (configManager.getProp('BTStickyTabs') =='NOTSTICKY') return false;
if (['link', 'reload'].includes(transitionType)) return true;
if (transitionQualifiers.length && !transitionQualifiers.includes('from_address_bar')) return true;
return false;
}
function closeAndUngroup() {
data['nodeId'] = tabNode.id;
tabClosed(data);
callBackground({'function' : 'ungroup', 'tabIds' : [tabId]});
}

const tabId = data.tabId;
const tabUrl = data.tabURL;
const groupId = data.groupId;
const windowId = data.windowId;
const tabNode = BTAppNode.findFromTab(tabId);
const urlNode = BTAppNode.findFromURLTGWin(tabUrl, groupId, windowId);
const parentsWindow = urlNode?.parentId ? AllNodes[urlNode.parentId]?.windowId : null;
const transitionType = data?.transitionData?.transitionType;
const transitionQualifiers = data?.transitionData?.transitionQualifiers || [];
const sticky = stickyTab();

if (tabNode) {
// activity was on managed active tab
Expand All @@ -883,18 +912,26 @@ function tabNavigated(data) {
tabNode.URL = tabUrl;
}
else {
// nav away from BT tab
data['nodeId'] = tabNode.id;
tabClosed(data);
callBackground({'function' : 'ungroup', 'tabIds' : [tabId]});
// Might be nav away from BT tab or maybe the tab sticks with the BT node
if (sticky) {
tabNode.navigated = true;
tabActivated(data); // handles updating localstorage/popup with current topic etc
}
else closeAndUngroup();
}
}
tabNode.opening = false;
}

const parentsWindow = urlNode?.parentId ? AllNodes[urlNode.parentId]?.windowId : null;
if (urlNode && (!parentsWindow || (parentsWindow == windowId))) {
// nav into a bt node from an open tab, ignore if parent/TG open elsewhere else - handle like tab open
if (urlNode && (parentsWindow == windowId)) {
// nav into a bt node from an open tab, ignore if parent/TG open elsewhere else handle like tab open
if (tabNode && sticky) closeAndUngroup(); // if sticky we won't have closed above but if urlnode is in same window we should
data['nodeId'] = urlNode.id;
tabOpened(data, true);
return;
}
if (urlNode && !parentsWindow && (!tabNode || !sticky)) {
// nav into a bt node from an open tab, set open if not open elsewhere and url has not stuck to stick tabnode
data['nodeId'] = urlNode.id;
tabOpened(data, true);
return;
Expand Down Expand Up @@ -924,12 +961,12 @@ function tabActivated(data) {
let m1, m2 = {'windowTopic': winNode ? winNode.topicPath : '',
'groupTopic': groupNode ? groupNode.topicPath : '', 'currentTabId' : tabId};
if (node) {
node.topicPath || node.generateUniqueTopicPath();
node.topicPath || BTNode.generateUniqueTopicPaths();
changeSelected(node); // select in tree
m1 = {'currentTopic': node.topicPath, 'currentText': node.text, 'currentTitle': node.displayTopic};
m1 = {'currentTopic': node.topicPath, 'currentText': node.text, 'currentTitle': node.displayTopic, 'tabNavigated': node.navigated};
}
else {
m1 = {'currentTopic': '', 'currentText': '', 'currentTitle': ''};
m1 = {'currentTopic': '', 'currentText': '', 'currentTitle': '', 'tabNavigated': false};
clearSelected();
}
window.postMessage({'function': 'localStore', 'data': {...m1, ...m2}});
Expand Down Expand Up @@ -993,7 +1030,9 @@ function tabJoinedTG(data) {
$("table.treetable").treetable("loadBranch", topicNode.getTTNode(), tabNode.HTML());
tabNode.populateFavicon();
initializeUI();
tabActivated(data); // handles setting topic etc into local storage for popup
changeSelected(tabNode);
setNodeOpen(tabNode);
positionInTopic(topicNode, tabNode, index, indices, winId);
}

Expand Down
14 changes: 13 additions & 1 deletion app/configManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const configManager = (() => {

const Properties = {
'keys': ['CLIENT_ID', 'API_KEY', 'FB_KEY', 'STRIPE_KEY'],
'localStorageProps': ['BTId', 'BTTimestamp', 'BTFileID', 'BTGDriveConnected', 'BTStats', 'BTLastShownMessageIndex', 'BTManagerHome',
'localStorageProps': ['BTId', 'BTTimestamp', 'BTFileID', 'BTGDriveConnected', 'BTStats', 'BTLastShownMessageIndex', 'BTManagerHome', 'BTStickyTabs',
'BTTheme', 'BTFavicons', 'BTNotes', 'BTDense', 'BTSize', 'BTTooltips', 'BTGroupingMode', 'BTDontShowIntro', 'BTExpiry'],
'orgProps': ['BTCohort', 'BTVersion', 'BTId'],
'stats': ['BTNumTabOperations', 'BTNumSaves', 'BTNumLaunches', 'BTInstallDate', 'BTSessionStartTime', 'BTLastActivityTime', 'BTSessionStartSaves', 'BTSessionStartOps', 'BTDaysOfUse'],
Expand Down Expand Up @@ -173,6 +173,12 @@ const configManager = (() => {
$radio.filter(`[value=${notes}]`).prop('checked', true);
checkCompactMode((notes == 'NONOTES')); // turn off if needed

// Sticky Tabs?
const sticky = configManager.getProp('BTStickyTabs') || 'STICKY';
$radio = $('#stickyToggle :radio[name=sticky]');
$radio.filter(`[value=${sticky}]`).prop('checked', true);
configManager.setProp('BTStickyTabs', sticky);

// Dense?
const dense = configManager.getProp('BTDense') || 'NOTDENSE';
$radio = $('#denseToggle :radio[name=dense]');
Expand Down Expand Up @@ -235,6 +241,12 @@ const configManager = (() => {
checkCompactMode((newN == 'NONOTES'));
saveBT();
});
$('#stickyToggle :radio').change(function () {
const newN = $(this).val();
configManager.setProp('BTStickyTabs', newN);
// No immediate action, take effect on next tabNavigated event
saveBT();
});
$('#denseToggle :radio').change(function () {
const newD = $(this).val();
configManager.setProp('BTDense', newD);
Expand Down
17 changes: 16 additions & 1 deletion app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
<label for="settingsLocationTab">Browser tab</label>
</span>
</div>
<div class="settingsFootnote">* Close and re-open the Topic Manager to apply this setting</div>
<div class="settingsFootnote">* Applies next time the Topic Manager is opened.</div>
<hr/>
</div>

Expand Down Expand Up @@ -156,6 +156,21 @@
<hr/>
</div>

<div id="settingsSticky">
<div class="settingsSubtitle">Sticky Tabs?</div>
<div id="stickyToggle" class="settingsInput">
<span>
<input type="radio" id="settingsStickyOn" name="sticky" value="STICKY" checked>
<label for="settingsStickyOn">Yes</label>
</span>
<span>
<input type="radio" id="settingsStickyOff" name="sticky" value="NOTSTICKY">
<label for="settingsStickyOff">No</label>
</span>
</div>
<hr/>
</div>

<div id="settingsTheme">
<div class="settingsSubtitle">Dark Mode?</div>
<div id="themeToggle" class="settingsInput">
Expand Down
Loading

0 comments on commit 316d2e6

Please sign in to comment.