diff --git a/package.json b/package.json index 3c9b0bf..9bd5af4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chrome-dynamic-bookmarks", - "version": "2.2.5", + "version": "2.3.0", "description": "Chrome extension which dynamically updates bookmarks based on the specified regular expression.", "scripts": { "dev": "webpack --mode development", diff --git a/src/js/bookmarkManager/folderInfo.js b/src/js/bookmarkManager/folderInfo.js index 96f8227..f0c0561 100644 --- a/src/js/bookmarkManager/folderInfo.js +++ b/src/js/bookmarkManager/folderInfo.js @@ -4,7 +4,8 @@ import { displayBookmark } from './displayFunctions'; import { findLeafNodes, renderChildren, - createChildInfoId + createChildInfoId, + sortFolderInfoChildren } from '../utils/folderInfo'; import * as dynBookmarks from '../lib/dynBookmarks'; @@ -97,6 +98,7 @@ function initFolderInfo() { } }); } + sortFolderInfoChildren(); }); } }); @@ -141,7 +143,7 @@ function createFolderInfoChild( className: `truncate`, target: '_blank' }, - span({ className: `${color} text-darken-4` }, title), + span({ className: `${color} text-darken-4 child-info-title` }, title), span({ className: `${color} child-info-link` }, ` (${url})`) ), i( diff --git a/src/js/bookmarkManager/treeView.js b/src/js/bookmarkManager/treeView.js index 7433a60..5ba014e 100644 --- a/src/js/bookmarkManager/treeView.js +++ b/src/js/bookmarkManager/treeView.js @@ -1,7 +1,6 @@ import { section } from '../lib/react-clone'; import File from '../components/File'; import Folder from '../components/Folder'; -import options from '../config/config'; import { updateTreeColor } from '../utils/treeView'; import { createTree, @@ -14,13 +13,6 @@ import { import { displayFolderInfo, displayBookmark } from './displayFunctions'; import globalSelectHandler from './selectHandler'; -const { - defaultFileIconColor, - defaultFolderIconColor, - trackedFileIconColor, - trackedFolderIconColor -} = options; - document.addEventListener('DOMContentLoaded', () => { var sidenavs = document.querySelectorAll('.sidenav'); M.Sidenav.init(sidenavs); @@ -64,7 +56,8 @@ document.addEventListener('DOMContentLoaded', () => { onDragover: allowDrop, onDragstart: drag }); - parent.querySelector('ul').appendChild(newEl); + + appendSorted(parent.querySelector('ul'), newEl); // note: i wrapped this in timeout because storage is updated AFTER bookmark is created setTimeout(() => { @@ -90,6 +83,9 @@ document.addEventListener('DOMContentLoaded', () => { chrome.bookmarks.onChanged.addListener((id, changeInfo) => { if (changeInfo.title) { let elem = document.getElementById(id); + elem.setAttribute('name', changeInfo.title); + appendSorted(elem.parentElement, elem); + if (elem.classList.contains('folder')) { elem = elem.querySelector('.folder-header') || elem; } @@ -102,7 +98,7 @@ document.addEventListener('DOMContentLoaded', () => { const elem = document.getElementById(id); const parent = document.getElementById(moveInfo.parentId); if (parent.classList.contains('folder')) { - parent.querySelector('ul').appendChild(elem); + appendSorted(parent.querySelector('ul'), elem); } setTimeout(() => { if (elem.classList.contains('folder')) { @@ -115,3 +111,31 @@ document.addEventListener('DOMContentLoaded', () => { }, 100); }); }); + +function appendSorted(parent, element) { + if (!parent) return console.warn('parent in appendSorted is undefined'); + if (!element) return console.warn('element in appendSorted is undefined'); + let appended = false; + const elemName = element.getAttribute('name').toLowerCase(); + const isElemFolder = element.classList.contains('folder'); + for (let child of parent.children) { + try { + const childName = child.getAttribute('name').toLowerCase(); + const isChildFolder = child.classList.contains('folder'); + + if ( + (isElemFolder && !isChildFolder) || + (isElemFolder === isChildFolder && childName > elemName) + ) { + parent.insertBefore(element, child); + appended = true; + break; + } + } catch (err) { + console.warn(err); + } + } + if (!appended) { + parent.appendChild(element); + } +} diff --git a/src/js/bookmarkManager/treeViewComponents.js b/src/js/bookmarkManager/treeViewComponents.js index 881a3e5..798e320 100644 --- a/src/js/bookmarkManager/treeViewComponents.js +++ b/src/js/bookmarkManager/treeViewComponents.js @@ -28,6 +28,21 @@ export function createTree(node) { }); } else { let childEls = []; + node.children.sort((lhs, rhs) => { + let retVal = 0; + if (!lhs.children ^ !rhs.children) { + // only one is folder + retVal = !lhs.children ? 1 : -1; + } else { + // both or none are folders + if (lhs.title.toLowerCase() < rhs.title.toLowerCase()) { + retVal = -1; + } else { + retVal = 1; + } + } + return retVal; + }); for (let child of node.children) { let subTree = createTree(child); childEls.push(subTree); diff --git a/src/js/components/Folder.js b/src/js/components/Folder.js index ec19713..83cb4a7 100644 --- a/src/js/components/Folder.js +++ b/src/js/components/Folder.js @@ -17,7 +17,7 @@ const Folder = (props, ...children) => { const folderName = name || 'unknown'; const iconColor = folderIconColor || defaultFolderIconColor; return div( - { className: 'folder', ...(id && { id }) }, + { className: 'folder', name: folderName, ...(id && { id }) }, header( { ...headerParams, diff --git a/src/js/lib/sortList.js b/src/js/lib/sortList.js new file mode 100644 index 0000000..486b25a --- /dev/null +++ b/src/js/lib/sortList.js @@ -0,0 +1,41 @@ +/** + * Sorts children of html element with given `id` + * @param {string} id - id of html element whose children will be sorted + * @param {function(HTMLElement, HTMLElement)} callback - returns `true` if elements should swap, `false` if not (default: ascending) + */ +export default function sortList(id, callback) { + if (typeof id != 'string') { + return console.warn( + `failed to sort list ${id} (reason: invalid parameter listId)` + ); + } + var list, i, switching, b, shouldSwitch; + list = document.getElementById(id); + switching = true; + + while (switching) { + switching = false; + b = list.children; + for (i = 0; i < b.length - 1; i++) { + shouldSwitch = false; + /* Check if the next item should + switch place with the current item: */ + if (typeof callback == 'function') { + shouldSwitch = !!callback(b[i], b[i + 1]); + } else { + shouldSwitch = + b[i].innerHTML.toLowerCase() > b[i + 1].innerHTML.toLowerCase(); + } + if (shouldSwitch) { + break; + } + } + + if (shouldSwitch) { + /* If a switch has been marked, make the switch + and mark the switch as done: */ + b[i].parentNode.insertBefore(b[i + 1], b[i]); + switching = true; + } + } +} diff --git a/src/js/popup/index.js b/src/js/popup/index.js index 4de76ee..a2d5bba 100644 --- a/src/js/popup/index.js +++ b/src/js/popup/index.js @@ -19,9 +19,8 @@ document.addEventListener('DOMContentLoaded', function() { // extract values from form const title = event.target['bookmark_name'].value; let regExpString = event.target.regexp.value; - let regExp; try { - regExp = new RegExp(event.target.regexp.value); + new RegExp(event.target.regexp.value); } catch { formResponse.textContent = 'Invalid regular expression'; popupModalInstance.open(); diff --git a/src/js/utils/bookmarkInfo.js b/src/js/utils/bookmarkInfo.js index 3cc9065..dacf029 100644 --- a/src/js/utils/bookmarkInfo.js +++ b/src/js/utils/bookmarkInfo.js @@ -1,4 +1,5 @@ import { li, a } from '../lib/react-clone'; +import * as dbm from '../lib/dynBookmarks'; /** * Sets given properties to bookmarkInfo (undefined values will be ignored) * @param {object} props - { @@ -89,8 +90,8 @@ export function getBookmarkData(bookmarkId, done) { console.warn(chrome.runtime.lastError.message); } else { const bookmark = results[0]; - chrome.storage.sync.get(['dynBookmarks'], ({ dynBookmarks }) => { - let dynBook = dynBookmarks || {}; + dbm.findAll((err, dynBook) => { + if (err) console.warn(err); chrome.bookmarks.get(bookmark.parentId, (results) => { let parentTitle = null; if (chrome.runtime.lastError) { diff --git a/src/js/utils/folderInfo.js b/src/js/utils/folderInfo.js index d2fdf9c..8fd08af 100644 --- a/src/js/utils/folderInfo.js +++ b/src/js/utils/folderInfo.js @@ -1,3 +1,6 @@ +import * as dbm from '../lib/dynBookmarks'; +import sortList from '../lib/sortList'; + /* Show / Hide export functionality */ export function hideFolderInfo() { document.getElementById('folderInfo').classList.add('hide'); @@ -104,6 +107,7 @@ export function renderChildren(renderAll = false) { const folderId = renderAll === true ? '0' : childrenList.getAttribute('folderId'); + // extract search pattern from search-input let searchPattern; try { let searchInput = document.getElementById('search-input').value; @@ -117,35 +121,29 @@ export function renderChildren(renderAll = false) { const isUntrackedChecked = document.getElementById('untracked-checkbox') .checked; - chrome.bookmarks.getSubTree(folderId, (results) => { + chrome.bookmarks.getSubTree(folderId, (subTrees) => { if (chrome.runtime.lastError) { console.warn(chrome.runtime.lastError.message); } else { - chrome.storage.sync.get(['dynBookmarks'], ({ dynBookmarks }) => { - const dynBook = dynBookmarks || {}; + dbm.findAll((err, dynBook) => { + if (err) console.warn(err); hideFolderInfoChildren(); - for (let child of results) { - findLeafNodes(child, (node) => { + sortFolderInfoChildren(); + for (let tree of subTrees) { + findLeafNodes(tree, (node) => { const childEl = document.getElementById(`child-info-${node.id}`); + // filter by search pattern if (childEl && searchPattern.test(childEl.textContent)) { const spans = childEl.querySelectorAll('span'); + + // filter by tracked/untracked if (dynBook[node.id] && isTrackedChecked) { - childEl.parentElement.classList.remove('hide'); - for (let span of spans) { - span.classList.replace( - defaultFileIconColor, - trackedFileIconColor - ); - } + showTracked(childEl); } else if (!dynBook[node.id] && isUntrackedChecked) { - childEl.parentElement.classList.remove('hide'); - for (let span of spans) { - span.classList.replace( - trackedFileIconColor, - defaultFileIconColor - ); - } + showUntracked(childEl); } + + // update information if bookmark is changed if (spans[0].textContent !== node.title) { spans[0].textContent = node.title; } else if (childEl.getAttribute('href') !== node.url) { @@ -159,3 +157,40 @@ export function renderChildren(renderAll = false) { } }); } + +export function sortFolderInfoChildren() { + sortList('folder-children-info', (lhs, rhs) => { + const lhsUrl = lhs.querySelector('.child-info-link').textContent; + const rhsUrl = rhs.querySelector('.child-info-link').textContent; + + const lhsTitle = lhs + .querySelector('.child-info-title') + .textContent.toLowerCase(); + const rhsTitle = rhs + .querySelector('.child-info-title') + .textContent.toLowerCase(); + + if (lhsTitle === rhsTitle) { + return lhsUrl > rhsUrl; + } else { + return lhsTitle > rhsTitle; + } + }); +} + +/* Functions below are NOT exported, they are intended to make functions above more readable */ +function showTracked(childEl) { + const spans = childEl.querySelectorAll('span'); + childEl.parentElement.classList.remove('hide'); + replaceAllClass(spans, defaultFileIconColor, trackedFileIconColor); +} +function showUntracked(childEl) { + const spans = childEl.querySelectorAll('span'); + childEl.parentElement.classList.remove('hide'); + replaceAllClass(spans, trackedFileIconColor, defaultFileIconColor); +} +function replaceAllClass(nodesArray, oldClass, newClass) { + for (let node of nodesArray) { + node.classList.replace(oldClass, newClass); + } +} diff --git a/src/js/utils/treeView.js b/src/js/utils/treeView.js index 361448a..46e95db 100644 --- a/src/js/utils/treeView.js +++ b/src/js/utils/treeView.js @@ -1,4 +1,6 @@ import options from '../config/config'; +import * as dbm from '../lib/dynBookmarks'; + const { defaultFileIconColor, defaultFolderIconColor, @@ -7,8 +9,8 @@ const { } = options; export function updateTreeColor() { - chrome.storage.sync.get(['dynBookmarks'], ({ dynBookmarks }) => { - let dynBook = dynBookmarks || {}; + dbm.findAll((err, dynBook) => { + if (err) console.warn(err); chrome.bookmarks.getTree((results) => { const rootNode = results[0]; (function traverseTree(node) { diff --git a/src/manifest.json b/src/manifest.json index bd34f3f..bcb1421 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "Dynamic Bookmarks", "description": "Chrome extension which dynamically updates bookmarks based on the specified regular expression.", - "version": "2.2.5", + "version": "2.3.0", "permissions": ["tabs", "bookmarks", "storage"], "background": { "page": "background.html"