@fengzhubao/bookmark-lens
v0.2.0
Published
Bookmark tree analysis core for BookmarkLens — flatten, sort, detect duplicates, find empty folders, group by domain.
Maintainers
Readme
@fengzhubao/bookmark-lens
Bookmark tree analysis core — flatten, sort, detect duplicates, find empty folders, group by domain.
A structured analysis library for browser bookmark trees (Chrome / Edge chrome.bookmarks tree). No DOM, no extension API, no UI framework dependencies — runs in Node, browser extensions, CLIs, or anywhere else JavaScript runs.
Powers the BookmarkLens browser extension as its pure-logic layer.
Install
npm install @fengzhubao/bookmark-lensRequires Node >= 20 or any modern browser (depends on the standard WHATWG URL).
Quick start
import {
flattenBookmarkTree,
findDuplicateBookmarks,
findEmptyFolders,
groupBookmarksByDomain
} from "@fengzhubao/bookmark-lens";
// Inside a Chrome/Edge extension
chrome.bookmarks.getTree((tree) => {
const rows = flattenBookmarkTree(tree);
// Find duplicate URLs
const { groups, duplicateIds } = findDuplicateBookmarks(rows);
console.log(`${groups.length} duplicate URL groups, ${duplicateIds.size} affected bookmarks`);
// Find empty folders
const empty = findEmptyFolders(rows);
console.log(`${empty.folders.length} empty folders`);
// Group by domain
const byDomain = groupBookmarksByDomain(rows, { minGroupSize: 2 });
console.log(`${byDomain.totalDomains} domains, ${byDomain.totalBookmarks} bookmarks`);
});All functions are pure — they never mutate their inputs.
API
Data model
| Function | Purpose |
|----------|---------|
| flattenBookmarkTree(nodes) | Flatten a nested bookmark tree into an array of rows. Each row contains id / title / url / domain / path / folderPath / dateAdded / depth / isFolder / childCount and more. |
| summarizeRows(rows) | Compute { bookmarkCount, folderCount, datedCount, label }. |
| parseBookmarkUrl(url) | Safely parse a URL, returning { href, protocol, hostname, origin, isValid, error }. |
| formatBookmarkTime(value) | Format a dateAdded millisecond timestamp into a human-readable string. |
Sorting and view state
| Function | Purpose |
|----------|---------|
| sortBookmarkRows(rows, sortSpec) | Stable sort by a given key, falling back to original tree order on ties. |
| getNextSortSpec(currentSort, key) | Three-state sort state machine: default → asc → desc → default. |
| createDefaultBookmarkViewState() | Create an empty view state (query / sort / domainScope / folderScopeId). |
| resetBookmarkViewState() | Reset the view state. |
| rowMatchesDomainScope(row, scope) | Domain-scope predicate. |
| rowMatchesFolderScope(row, id) | Folder-subtree predicate. |
Analysis
| Function | Purpose |
|----------|---------|
| normalizeBookmarkUrl(url, options?) | Normalize a URL: lowercase hostname, drop default ports, strip trailing slashes, remove tracking params (utm_*, fbclid, gclid, etc.). |
| findDuplicateBookmarks(rows, options?) | Group bookmarks by normalized URL, returning { groups, duplicateIds }. Options are forwarded to normalizeBookmarkUrl. |
| findEmptyFolders(rows) | Find leaf folders with no children (excluding system roots). Returns { folders, folderIds }. |
| groupBookmarksByDomain(rows, options?) | Group bookmarks by domain. minGroupSize filters out singletons. Returns { groups, totalDomains, totalBookmarks }. |
Operation plans (read-only)
Generate explainable, undo-able plan objects for write operations. The plans themselves never mutate any bookmarks — the caller is responsible for executing them.
| Function | Purpose |
|----------|---------|
| createRenamePlan(row, title) | Rename plan. |
| createEditUrlPlan(row, url) | Change URL plan. |
| createCreateBookmarkPlan(parentRow, data) | Create-bookmark plan. |
| createCreateFolderPlan(parentRow, data) | Create-folder plan. |
| createMovePlan(row, options) | Move plan. |
| createDeletePlan(row, options) | Delete plan (folders cascade automatically). |
| isProtectedBookmarkRow(row) | Is the row a protected root (id 0 / 1 / 2 or depth 0)? |
| canUndoHistoryEntry(entry) | Is the history entry undoable? |
| trimOperationHistory(history, limit?) | Truncate history (default 100 entries). |
| createHistoryEntry(plan, result?) | Build a history entry from an executed plan. |
Export
| Function | Purpose |
|----------|---------|
| exportRows(rows, format) | Export rows as "json" or "csv" string. |
| getBookmarkContextMenuItems(row, options?) | Context-menu item data (UI side renders). |
URL normalization defaults
normalizeBookmarkUrl applies these rules by default (each configurable via options):
- Lowercase scheme and hostname
- Drop default ports (
http:80/https:443/ws:80/wss:443/ftp:21) - Strip trailing slashes
- Remove tracking params:
utm_*,fbclid,gclid,mc_cid,mc_eid,ref,ref_src,_ga,igshid - Sort remaining query params by key
- Preserve hash fragment (many SPAs use hashes for routing)
Configurable options:
normalizeBookmarkUrl(url, {
stripHash: false, // default keeps hash
stripTrackingParams: true, // default strips tracking
stripTrailingSlash: true, // default removes trailing slash
additionalTrackingParams: [] // extra param names to strip
})License
GPL-3.0-or-later. Copyright © 2026 luppiter.
See LICENSE for the full text.
