├── img ├── icon.png ├── icon48.png ├── icon64.png ├── icon-red.png ├── icon128.png ├── icon-delete.png ├── icon-email.png ├── gradient-down.png ├── icon-facebook.png ├── icon-options.png ├── icon-twitter.png ├── icon-email-hover.png ├── icon-delete-hover.png ├── icon-facebook-hover.png ├── icon-options-hover.png └── icon-twitter-hover.png ├── assets ├── icon.png ├── Options.png ├── Hover Menu.png ├── Hover URL.png ├── screen-shot-1.png ├── promo-1400by560.png ├── promo-440by280.png └── promo-920by680.png ├── src ├── assets │ └── icons │ │ ├── icon.png │ │ ├── icon48.png │ │ ├── icon64.png │ │ ├── icon-red.png │ │ ├── icon128.png │ │ ├── icon-delete.png │ │ ├── icon-email.png │ │ ├── gradient-down.png │ │ ├── icon-facebook.png │ │ ├── icon-options.png │ │ ├── icon-twitter.png │ │ ├── icon-email-hover.png │ │ ├── icon-delete-hover.png │ │ ├── icon-options-hover.png │ │ ├── icon-twitter-hover.png │ │ └── icon-facebook-hover.png ├── options.css ├── background.ts ├── popup.css ├── popup.tsx └── options.tsx ├── .gitignore ├── tsconfig.json ├── .env.example ├── package.json ├── README.md └── LICENSE /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon.png -------------------------------------------------------------------------------- /img/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon48.png -------------------------------------------------------------------------------- /img/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon64.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/icon.png -------------------------------------------------------------------------------- /img/icon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-red.png -------------------------------------------------------------------------------- /img/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon128.png -------------------------------------------------------------------------------- /assets/Options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/Options.png -------------------------------------------------------------------------------- /img/icon-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-delete.png -------------------------------------------------------------------------------- /img/icon-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-email.png -------------------------------------------------------------------------------- /assets/Hover Menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/Hover Menu.png -------------------------------------------------------------------------------- /assets/Hover URL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/Hover URL.png -------------------------------------------------------------------------------- /img/gradient-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/gradient-down.png -------------------------------------------------------------------------------- /img/icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-facebook.png -------------------------------------------------------------------------------- /img/icon-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-options.png -------------------------------------------------------------------------------- /img/icon-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-twitter.png -------------------------------------------------------------------------------- /assets/screen-shot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/screen-shot-1.png -------------------------------------------------------------------------------- /img/icon-email-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-email-hover.png -------------------------------------------------------------------------------- /assets/promo-1400by560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/promo-1400by560.png -------------------------------------------------------------------------------- /assets/promo-440by280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/promo-440by280.png -------------------------------------------------------------------------------- /assets/promo-920by680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/assets/promo-920by680.png -------------------------------------------------------------------------------- /img/icon-delete-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-delete-hover.png -------------------------------------------------------------------------------- /img/icon-facebook-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-facebook-hover.png -------------------------------------------------------------------------------- /img/icon-options-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-options-hover.png -------------------------------------------------------------------------------- /img/icon-twitter-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/img/icon-twitter-hover.png -------------------------------------------------------------------------------- /src/assets/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon.png -------------------------------------------------------------------------------- /src/assets/icons/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon48.png -------------------------------------------------------------------------------- /src/assets/icons/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon64.png -------------------------------------------------------------------------------- /src/assets/icons/icon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-red.png -------------------------------------------------------------------------------- /src/assets/icons/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon128.png -------------------------------------------------------------------------------- /src/assets/icons/icon-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-delete.png -------------------------------------------------------------------------------- /src/assets/icons/icon-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-email.png -------------------------------------------------------------------------------- /src/assets/icons/gradient-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/gradient-down.png -------------------------------------------------------------------------------- /src/assets/icons/icon-facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-facebook.png -------------------------------------------------------------------------------- /src/assets/icons/icon-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-options.png -------------------------------------------------------------------------------- /src/assets/icons/icon-twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-twitter.png -------------------------------------------------------------------------------- /src/assets/icons/icon-email-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-email-hover.png -------------------------------------------------------------------------------- /src/assets/icons/icon-delete-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-delete-hover.png -------------------------------------------------------------------------------- /src/assets/icons/icon-options-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-options-hover.png -------------------------------------------------------------------------------- /src/assets/icons/icon-twitter-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-twitter-hover.png -------------------------------------------------------------------------------- /src/assets/icons/icon-facebook-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ProLoser/Find-My-Bookmarks/master/src/assets/icons/icon-facebook-hover.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build outputs 5 | build/ 6 | dist/ 7 | .plasmo/ 8 | 9 | # Environment variables 10 | .env 11 | .env.local 12 | .env.production 13 | 14 | # IDE files 15 | .vscode/ 16 | .idea/ 17 | *.swp 18 | *.swo 19 | 20 | # OS files 21 | .DS_Store 22 | Thumbs.db 23 | 24 | # Logs 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | 30 | # Temporary files 31 | .tmp/ 32 | temp/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": [ 5 | "ES2020", 6 | "DOM" 7 | ], 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "noEmit": true, 14 | "jsx": "react-jsx", 15 | "strict": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "types": [ 20 | "chrome" 21 | ] 22 | }, 23 | "include": [ 24 | ".plasmo/index.d.ts", 25 | "src/**/*" 26 | ], 27 | "exclude": [ 28 | "node_modules", 29 | "build", 30 | "dist" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Extension Configuration 2 | # Replace these placeholders with actual values 3 | 4 | # Chrome Extension ID (for Chrome Web Store) 5 | CHROME_EXTENSION_ID={CHROME_EXTENSION_ID_PLACEHOLDER} 6 | 7 | # Edge Extension ID (for Microsoft Edge Store) 8 | EDGE_EXTENSION_ID={EDGE_EXTENSION_ID_PLACEHOLDER} 9 | 10 | # Safari Extension ID (for Safari Extensions) 11 | SAFARI_EXTENSION_ID={SAFARI_EXTENSION_ID_PLACEHOLDER} 12 | 13 | # Opera Extension ID (for Opera Add-ons) 14 | OPERA_EXTENSION_ID={OPERA_EXTENSION_ID_PLACEHOLDER} 15 | 16 | # Brave Extension ID (uses Chrome store) 17 | BRAVE_EXTENSION_ID={BRAVE_EXTENSION_ID_PLACEHOLDER} 18 | 19 | # API Keys (if needed for future features) 20 | TWITTER_API_KEY={TWITTER_API_KEY_PLACEHOLDER} 21 | FACEBOOK_APP_ID={FACEBOOK_APP_ID_PLACEHOLDER} 22 | 23 | # Analytics (if needed) 24 | GOOGLE_ANALYTICS_ID={GOOGLE_ANALYTICS_ID_PLACEHOLDER} 25 | 26 | # Update URLs 27 | UPDATE_URL_CHROME={UPDATE_URL_CHROME_PLACEHOLDER} 28 | UPDATE_URL_FIREFOX={UPDATE_URL_FIREFOX_PLACEHOLDER} 29 | UPDATE_URL_EDGE={UPDATE_URL_EDGE_PLACEHOLDER} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmarked", 3 | "displayName": "Bookmarked", 4 | "version": "2.0.0", 5 | "description": "Find all your related bookmarks for the current website.", 6 | "author": "Dean Sofer", 7 | "license": "GPL-2.0", 8 | "keywords": [ 9 | "bookmarks", 10 | "browser-extension", 11 | "chrome", 12 | "firefox", 13 | "edge", 14 | "safari", 15 | "opera", 16 | "brave" 17 | ], 18 | "scripts": { 19 | "dev": "plasmo dev", 20 | "build": "yarn build:all", 21 | "build:all": "yarn build:chrome && yarn build:firefox && yarn build:edge && yarn build:safari && yarn build:opera && yarn build:brave", 22 | "build:chrome": "plasmo build --target=chrome-mv3", 23 | "build:firefox": "plasmo build --target=firefox-mv2", 24 | "build:edge": "plasmo build --target=edge-mv3", 25 | "build:safari": "plasmo build --target=safari", 26 | "build:opera": "plasmo build --target=opera", 27 | "build:brave": "plasmo build --target=brave", 28 | "package": "plasmo package", 29 | "package:all": "yarn package:chrome && yarn package:firefox && yarn package:edge && yarn package:safari", 30 | "package:chrome": "plasmo build --target=chrome-mv3 && plasmo package --target=chrome-mv3", 31 | "package:firefox": "plasmo build --target=firefox-mv2 && plasmo package --target=firefox-mv2", 32 | "package:edge": "plasmo build --target=edge-mv3 && plasmo package --target=edge-mv3", 33 | "package:safari": "plasmo build --target=safari && plasmo package --target=safari" 34 | }, 35 | "devDependencies": { 36 | "@types/chrome": "^0.1.4", 37 | "@types/react": "^19.1.11", 38 | "@types/react-dom": "^19.1.7", 39 | "plasmo": "^0.90.5", 40 | "typescript": "^5.9.2" 41 | }, 42 | "dependencies": { 43 | "react": "^19.1.1", 44 | "react-dom": "^19.1.1" 45 | }, 46 | "manifest": { 47 | "permissions": [ 48 | "bookmarks", 49 | "tabs", 50 | "activeTab", 51 | "storage" 52 | ], 53 | "browser_specific_settings": { 54 | "gecko": { 55 | "id": "@bookmarked", 56 | "strict_min_version": "109.0" 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/options.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Open Sans", sans-serif; 3 | font-size: 12px; 4 | padding: 20px; 5 | text-align: left; 6 | width: 700px; 7 | margin: 0 auto; 8 | background: #275786; 9 | } 10 | 11 | .container { 12 | padding: 0px 20px 20px 20px; 13 | background: #ffffff; 14 | box-shadow: 0px 0px 10px #333333; 15 | border: 4px solid #cccccc; 16 | } 17 | 18 | ul { 19 | text-align: left; 20 | list-style: square; 21 | padding-left: 25px; 22 | margin-top: 0; 23 | } 24 | 25 | h1 { 26 | margin: 0px; 27 | padding: 10px 0px; 28 | font-size: 24px; 29 | letter-spacing: -1px; 30 | color: #ffffff; 31 | font-weight: 800; 32 | text-shadow: #333333 0px -2px 1px; 33 | } 34 | 35 | h2 { 36 | border-top: 1px solid #cccccc; 37 | padding-top: 5px; 38 | line-height: 24px; 39 | font-size: 24px; 40 | font-weight: 300; 41 | color: #999999; 42 | margin: 20px -20px 0px -20px; 43 | padding: 15px 20px 15px 20px; 44 | background-image: linear-gradient(bottom, white 0%, #eeeeee 100%); 45 | } 46 | 47 | h3 { 48 | margin: 0; 49 | padding: 0; 50 | } 51 | 52 | h4 { 53 | margin: 0; 54 | } 55 | 56 | form { 57 | h2 { 58 | border: 0px; 59 | margin-top: 0px; 60 | } 61 | 62 | p { 63 | text-align: left; 64 | } 65 | 66 | label { 67 | display: block; 68 | font-weight: 700; 69 | margin-left: 26px; 70 | 71 | span { 72 | display: block; 73 | font-weight: normal; 74 | } 75 | } 76 | 77 | input { 78 | float: left; 79 | display: block; 80 | width: 20px; 81 | margin: 3px; 82 | } 83 | } 84 | 85 | #status { 86 | transition: all ease 0.2s; 87 | color: #ffffff; 88 | background: green; 89 | font-weight: bold; 90 | margin: 0px -20px; 91 | font-weight: bold; 92 | font-size: 14px; 93 | text-align: center; 94 | height: 0; 95 | opacity: 0; 96 | padding: 0; 97 | 98 | &.active { 99 | height: 20px; 100 | opacity: 1; 101 | padding: 10px; 102 | } 103 | } 104 | 105 | footer { 106 | background: #eee; 107 | margin: 30px -15px -15px -15px; 108 | padding: 15px; 109 | background: #333333; 110 | color: #ffffff; 111 | 112 | a { 113 | color: #ffffff; 114 | text-decoration: underline; 115 | } 116 | } -------------------------------------------------------------------------------- /src/background.ts: -------------------------------------------------------------------------------- 1 | // Background service worker for Bookmarked extension 2 | // Handles bookmark counting and page action display 3 | 4 | // Declare browser for cross-browser compatibility 5 | declare const browser: typeof chrome; 6 | 7 | // Cross-browser API compatibility 8 | const API = typeof chrome !== 'undefined' ? chrome : browser; 9 | 10 | const setBadge = (tabId: number, text: string, color: string) => { 11 | if (API.action) { 12 | // Manifest v3 (Chrome/Edge) 13 | API.action.setBadgeText({ text, tabId }); 14 | API.action.setBadgeBackgroundColor({ color, tabId }); 15 | } else { 16 | // Manifest v2 (Firefox) - consolidated type assertion in one place 17 | const browserAction = API.browserAction; 18 | if (browserAction) { 19 | browserAction.setBadgeText({ text, tabId }); 20 | browserAction.setBadgeBackgroundColor({ color, tabId }); 21 | } 22 | } 23 | }; 24 | 25 | let fullTree: chrome.bookmarks.BookmarkTreeNode[]; 26 | 27 | // Listen for tab updates and bookmark changes 28 | API.tabs.onUpdated.addListener(checkForValidUrl); 29 | API.bookmarks.onCreated.addListener(refreshTree); 30 | API.bookmarks.onChanged.addListener(refreshTree); 31 | API.bookmarks.onImportEnded.addListener(refreshTree); 32 | API.bookmarks.onRemoved.addListener(refreshTree); 33 | 34 | // Initialize bookmark tree 35 | refreshTree(); 36 | 37 | function refreshTree() { 38 | API.bookmarks.getTree(function(bookmarkTree) { 39 | fullTree = bookmarkTree; 40 | }); 41 | } 42 | 43 | // Called when the url of a tab changes 44 | function checkForValidUrl(tabId: number, _changeInfo: any, tab: chrome.tabs.Tab) { 45 | if (!tab.url || !fullTree) return; 46 | 47 | let domain: string; 48 | 49 | // Extract domain using URL constructor for reliability 50 | try { 51 | const url = new URL(tab.url); 52 | domain = url.hostname.replace(/^www\./, ''); 53 | } catch (error) { 54 | console.error('Invalid URL:', tab.url); 55 | return; 56 | } 57 | 58 | // Apply subdomain filtering and check ignore current page setting 59 | if (API.storage?.local) { 60 | API.storage.local.get(['ignore_subdomain', 'ignore_current_page'], function(result) { 61 | if (result.ignore_subdomain === 'true') { 62 | const pieces = domain.split('.'); 63 | if (pieces.length > 2 && (pieces.length !== 3 || pieces[1] !== 'co' || pieces[2] !== 'uk')) { 64 | pieces.shift(); 65 | domain = pieces.join('.'); 66 | } 67 | } 68 | searchBookmarks(tabId, domain, tab.url, result.ignore_current_page === 'true'); 69 | }); 70 | } else { 71 | // Fallback for compatibility 72 | searchBookmarks(tabId, domain, tab.url, false); 73 | } 74 | } 75 | 76 | function searchBookmarks(tabId: number, domain: string, currentUrl: string, ignoreCurrentPage: boolean) { 77 | let matches = 0; 78 | 79 | function iterator(tree: chrome.bookmarks.BookmarkTreeNode[]) { 80 | for (const node of tree) { 81 | if (!node.children && node.url) { 82 | if (node.url.includes(domain)) { 83 | // Check if we should ignore exact matches for the current page 84 | if (ignoreCurrentPage && node.url === currentUrl) { 85 | continue; // Skip this bookmark as it matches the current page exactly 86 | } 87 | 88 | matches++; 89 | if (matches > 9) { 90 | matches = 9; 91 | break; 92 | } 93 | } 94 | } else if (node.children) { 95 | iterator(node.children); 96 | if (matches > 9) break; 97 | } 98 | } 99 | } 100 | 101 | iterator(fullTree); 102 | 103 | if (matches > 0) { 104 | const badgeText = matches > 9 ? '9+' : matches.toString(); 105 | setBadge(tabId, badgeText, '#275786'); 106 | } else { 107 | // Clear the badge if no matches 108 | setBadge(tabId, '', '#275786'); 109 | } 110 | } 111 | 112 | export {}; -------------------------------------------------------------------------------- /src/popup.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | body { 7 | font-family: "Open Sans", "Lucida Sans Unicode", "Lucida Grande", sans-serif; 8 | font-size: 12px; 9 | padding: 5px; 10 | white-space: nowrap; 11 | max-width: 500px; 12 | min-width: 200px; 13 | } 14 | 15 | h2 { 16 | font-size: 14px; 17 | border-bottom: 1px solid #cccccc; 18 | padding-bottom: 5px; 19 | margin-bottom: 3px; 20 | font-weight: normal; 21 | } 22 | 23 | a { 24 | text-decoration: none; 25 | color: #333333; 26 | cursor: pointer; 27 | } 28 | 29 | /* List item */ 30 | 31 | li { 32 | list-style: none; 33 | display: block; 34 | font-size: 11px; 35 | color: #333; 36 | position: relative; 37 | overflow: hidden; 38 | line-height: 20px; 39 | 40 | a { 41 | color: #666; 42 | display: block; 43 | padding: 1px 4px 3px 4px; 44 | text-overflow: ellipsis; 45 | padding-right: 10px; 46 | overflow: hidden; 47 | 48 | &:hover { 49 | color: #ffffff; 50 | background-color: #aec2eb; 51 | } 52 | 53 | em { 54 | display: block; 55 | font-size: 10px; 56 | } 57 | } 58 | } 59 | 60 | .hoverUrl { 61 | .link { 62 | display: none; 63 | } 64 | 65 | li { 66 | overflow: hidden; 67 | 68 | &:hover { 69 | .title { 70 | display: none; 71 | } 72 | 73 | .link { 74 | display: inline; 75 | color: #333; 76 | white-space: nowrap; 77 | white-space: pre; 78 | } 79 | } 80 | } 81 | } 82 | 83 | li:hover menu, 84 | h2:hover menu { 85 | display: block; 86 | } 87 | 88 | /* Menu */ 89 | menu { 90 | float: right; 91 | padding-left: 5px; 92 | text-align: center; 93 | 94 | display: none; 95 | position: absolute; 96 | top: 0; 97 | right: 0; 98 | z-index: 100; 99 | border-left: 1px solid #ccc; 100 | 101 | li { 102 | float: right; 103 | } 104 | 105 | a { 106 | background: no-repeat right center #fff; 107 | width: 16px; 108 | height: 16px; 109 | text-indent: -5000px; 110 | padding: 4px; 111 | 112 | &:hover { 113 | background-color: #fff; 114 | } 115 | } 116 | } 117 | 118 | h2 menu { 119 | top: 4px; 120 | padding-right: 5px; 121 | } 122 | 123 | a { 124 | &.delete { 125 | background-image: url(../img/icon-delete.png); 126 | } 127 | 128 | &.twitter { 129 | background-image: url(../img/icon-twitter.png); 130 | } 131 | 132 | &.facebook { 133 | background-image: url(../img/icon-facebook.png); 134 | } 135 | 136 | &.email { 137 | background-image: url(../img/icon-email.png); 138 | } 139 | 140 | &.options { 141 | background-image: url(../img/icon-options.png); 142 | } 143 | 144 | &:hover { 145 | &.delete { 146 | background-image: url(../img/icon-delete-hover.png); 147 | } 148 | 149 | &.twitter { 150 | background-image: url(../img/icon-twitter-hover.png); 151 | } 152 | 153 | &.facebook { 154 | background-image: url(../img/icon-facebook-hover.png); 155 | } 156 | 157 | &.email { 158 | background-image: url(../img/icon-email-hover.png); 159 | } 160 | 161 | &.options { 162 | background-image: url(../img/icon-options-hover.png); 163 | } 164 | } 165 | } 166 | 167 | .folders { 168 | li { 169 | line-height: normal; 170 | } 171 | 172 | em { 173 | display: block; 174 | } 175 | 176 | menu { 177 | a { 178 | height: 25px; 179 | } 180 | } 181 | } 182 | 183 | .footer { 184 | padding: 7px 0px 0px 0px; 185 | border-top: 1px solid #cccccc; 186 | font-size: 10px; 187 | color: #666; 188 | line-height: 14px; 189 | 190 | a { 191 | color: #009; 192 | text-decoration: underline; 193 | } 194 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bookmarked 2 | 3 | A modern browser extension that helps you find all your related bookmarks for the current website. Now built with [Plasmo](https://plasmo.com) framework for multi-browser support. 4 | 5 | ## Features 6 | 7 | - 🔍 **Smart Bookmark Search**: Automatically finds bookmarks related to the current domain 8 | - 🌐 **Multi-Browser Support**: Works on Chrome, Firefox, Edge, Safari, Brave, and Opera 9 | - 📊 **Bookmark Counter**: Shows the number of matching bookmarks in the extension badge 10 | - ⚙️ **Customizable Settings**: Various options to customize behavior 11 | - 🔗 **Social Sharing**: Built-in sharing to Twitter, Facebook, and email 12 | - 📁 **Folder Display**: Optional display of bookmark parent folders 13 | - 🎯 **Subdomain Handling**: Option to ignore subdomains for broader matching 14 | 15 | ## Installation 16 | 17 | ### Development Setup 18 | 19 | 1. **Clone the repository** 20 | ```bash 21 | git clone https://github.com/ProLoser/Find-My-Bookmarks.git 22 | cd Find-My-Bookmarks 23 | ``` 24 | 25 | 2. **Install dependencies** 26 | ```bash 27 | yarn install 28 | ``` 29 | 30 | 3. **Set up environment variables** 31 | ```bash 32 | cp .env.example .env 33 | # Edit .env and replace placeholders with actual values 34 | ``` 35 | 36 | 4. **Build for development** 37 | ```bash 38 | yarn dev 39 | ``` 40 | 41 | ### Browser-Specific Builds 42 | 43 | ```bash 44 | # Chrome (Manifest V3) 45 | yarn build:chrome 46 | 47 | # Firefox (Manifest V2) 48 | yarn build:firefox 49 | 50 | # Edge (Manifest V3) 51 | yarn build:edge 52 | 53 | # Safari 54 | yarn build:safari 55 | 56 | # Opera 57 | yarn build:opera 58 | 59 | # Brave 60 | yarn build:brave 61 | 62 | # All browsers 63 | yarn build 64 | ``` 65 | 66 | ### Loading the Extension 67 | 68 | #### Chrome/Edge/Brave 69 | 1. Open `chrome://extensions/` (or equivalent) 70 | 2. Enable "Developer mode" 71 | 3. Click "Load unpacked" 72 | 4. Select the `build/chrome-mv3-prod` folder 73 | 74 | #### Firefox 75 | 1. Open `about:debugging` 76 | 2. Click "This Firefox" 77 | 3. Click "Load Temporary Add-on" 78 | 4. Select the `manifest.json` from `build/firefox-mv2-prod` folder 79 | 80 | ## Configuration 81 | 82 | ### Environment Variables 83 | 84 | Create a `.env` file based on `.env.example` and configure the following placeholders: 85 | 86 | ```env 87 | # Extension IDs for different browsers 88 | FIREFOX_EXTENSION_ID=your-firefox-extension-id 89 | CHROME_EXTENSION_ID=your-chrome-extension-id 90 | EDGE_EXTENSION_ID=your-edge-extension-id 91 | # ... etc 92 | ``` 93 | 94 | ### Extension Settings 95 | 96 | The extension provides several customizable options: 97 | 98 | - **Hide Share Links**: Disable social sharing buttons 99 | - **Hide Folders**: Don't display bookmark parent folders 100 | - **Ignore Subdomain**: Treat `subdomain.example.com` same as `example.com` 101 | - **Show URL on Hover**: Display full URLs when hovering over bookmarks 102 | - **Ignore Current Page**: Don't show results if current page is the only bookmark 103 | 104 | ## Architecture 105 | 106 | This extension is built using modern web technologies: 107 | 108 | - **Framework**: [Plasmo](https://plasmo.com) - Modern browser extension framework 109 | - **Language**: TypeScript for type safety 110 | - **UI**: React 19 for component-based UI 111 | - **Build Tool**: Yarn for package management 112 | - **Manifest**: V3 for Chrome/Edge, V2 for Firefox compatibility 113 | 114 | ### Project Structure 115 | 116 | ``` 117 | src/ 118 | ├── background/ # Service worker (background script) 119 | │ └── index.ts 120 | ├── popup.tsx # Main popup interface 121 | ├── options.tsx # Extension options page 122 | ├── popup.css # Popup styling 123 | ├── options.css # Options page styling 124 | └── assets/ 125 | └── icons/ # Extension icons and UI assets 126 | 127 | assets/ 128 | └── icon.png # Main extension icon 129 | 130 | build/ # Generated browser-specific builds 131 | ├── chrome-mv3-prod/ 132 | ├── firefox-mv2-prod/ 133 | ├── edge-mv3-prod/ 134 | └── ... 135 | ``` 136 | 137 | ## Development 138 | 139 | ### Adding New Features 140 | 141 | 1. Make changes to the source files in `src/` 142 | 2. Test with `yarn dev` for hot reloading 143 | 3. Build for specific browsers to test compatibility 144 | 4. Update documentation as needed 145 | 146 | ### API Compatibility 147 | 148 | The extension handles differences between browser APIs: 149 | 150 | - **Manifest V3** (Chrome, Edge): Uses `chrome.action` API 151 | - **Manifest V2** (Firefox): Uses `chrome.browserAction` API 152 | - **Storage**: Uses `chrome.storage.local` instead of localStorage for better compatibility 153 | 154 | ## Migration from v1.x 155 | 156 | This v2.0 represents a complete rewrite with: 157 | 158 | - ✅ Modern TypeScript/React codebase 159 | - ✅ Plasmo framework for better development experience 160 | - ✅ Multi-browser support out of the box 161 | - ✅ Manifest V3 compatibility 162 | - ✅ Better error handling and performance 163 | - ✅ Maintainable architecture 164 | 165 | ## Contributing 166 | 167 | 1. Fork the repository 168 | 2. Create a feature branch 169 | 3. Make your changes 170 | 4. Test across browsers 171 | 5. Submit a pull request 172 | 173 | ## License 174 | 175 | GPL-2.0 - See [LICENSE](LICENSE) file for details. 176 | 177 | ## Credits 178 | 179 | Created by [Dean Sofer](http://www.deansofer.com) and [Usability Counts](http://www.usabilitycounts.com). 180 | 181 | --- 182 | 183 | **Version 2.0.0** - Modernized with Plasmo framework and multi-browser support 184 | -------------------------------------------------------------------------------- /src/popup.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import "./popup.css" 3 | 4 | // Declare browser for cross-browser compatibility 5 | declare const browser: typeof chrome; 6 | 7 | const API = typeof chrome !== 'undefined' ? chrome : browser; 8 | 9 | interface Bookmark { 10 | id: string 11 | title: string 12 | url: string 13 | parentId?: string 14 | } 15 | 16 | interface Settings { 17 | ignore_subdomain?: string 18 | hover_url?: string 19 | no_folders?: string 20 | no_share?: string 21 | ignore_current_page?: string 22 | } 23 | 24 | function Popup() { 25 | const [bookmarks, setBookmarks] = useState([]) 26 | const [domain, setDomain] = useState("") 27 | const [settings, setSettings] = useState({}) 28 | const [loading, setLoading] = useState(true) 29 | 30 | useEffect(() => { 31 | loadBookmarks() 32 | }, []) 33 | 34 | const loadBookmarks = async () => { 35 | try { 36 | // Get current tab 37 | const [activeTab] = await API.tabs.query({ active: true, currentWindow: true }) 38 | if (!activeTab.url) return 39 | 40 | // Extract domain using URL constructor for reliability 41 | let currentDomain = "" 42 | try { 43 | const url = new URL(activeTab.url) 44 | currentDomain = url.hostname.replace(/^www\./, '') 45 | } catch (error) { 46 | console.error('Invalid URL:', activeTab.url) 47 | return 48 | } 49 | 50 | // Load settings 51 | const result = await API.storage.local.get([ 52 | 'ignore_subdomain', 53 | 'hover_url', 54 | 'no_folders', 55 | 'no_share', 56 | 'ignore_current_page' 57 | ]) 58 | setSettings(result) 59 | 60 | // Apply subdomain filtering 61 | if (result.ignore_subdomain === 'true') { 62 | const pieces = currentDomain.split('.') 63 | if (pieces.length > 2 && (pieces.length !== 3 || pieces[1] !== 'co' || pieces[2] !== 'uk')) { 64 | pieces.shift() 65 | currentDomain = pieces.join('.') 66 | } 67 | } 68 | 69 | setDomain(currentDomain) 70 | 71 | // Get bookmarks 72 | const bookmarkTree = await API.bookmarks.getTree() 73 | const matchingBookmarks: Bookmark[] = [] 74 | 75 | function searchBookmarks(tree: chrome.bookmarks.BookmarkTreeNode[], parentTitle = "") { 76 | for (const node of tree) { 77 | if (!node.children && node.url) { 78 | if (node.url.includes(currentDomain)) { 79 | // Check if we should ignore exact matches for the current page 80 | if (result.ignore_current_page === 'true' && node.url === activeTab.url) { 81 | continue; // Skip this bookmark as it matches the current page exactly 82 | } 83 | 84 | matchingBookmarks.push({ 85 | id: node.id, 86 | title: node.title, 87 | url: node.url, 88 | parentId: parentTitle 89 | }) 90 | } 91 | } else if (node.children) { 92 | searchBookmarks(node.children, node.title) 93 | } 94 | } 95 | } 96 | 97 | searchBookmarks(bookmarkTree) 98 | setBookmarks(matchingBookmarks) 99 | } catch (error) { 100 | console.error('Error loading bookmarks:', error) 101 | } finally { 102 | setLoading(false) 103 | } 104 | } 105 | 106 | const deleteBookmark = async (bookmarkId: string) => { 107 | try { 108 | await API.bookmarks.remove(bookmarkId) 109 | setBookmarks(prev => prev.filter(b => b.id !== bookmarkId)) 110 | } catch (error) { 111 | console.error('Error deleting bookmark:', error) 112 | } 113 | } 114 | 115 | const renderMenu = (url: string, title: string, id?: string) => { 116 | const items = []; 117 | if (id) { 118 | items.push( 119 | deleteBookmark(id)} 122 | title="Delete this bookmark" 123 | > 124 | Delete 125 | 126 | ); 127 | 128 | if (settings.no_share !== 'true') { 129 | items.push( 130 | 136 | Tweet 137 | , 138 | 144 | Share 145 | , 146 | 152 | Email 153 | 154 | ); 155 | } 156 | 157 | } else { 158 | items.push( 159 | 165 | Settings 166 | 167 | ); 168 | } 169 | return ( 170 | 171 | {items.map((item, index) =>
  • {item}
  • )} 172 |
    173 | ) 174 | } 175 | 176 | if (loading) { 177 | return
    Loading bookmarks...
    178 | } 179 | 180 | return ( 181 |
    182 |

    183 | {renderMenu(domain, domain)} 184 | {domain} Bookmarks 185 |

    186 |
    187 | {bookmarks.length === 0 ? ( 188 |
    No bookmarks found for this domain
    189 | ) : ( 190 |
      191 | {bookmarks.map((bookmark) => ( 192 |
    • 193 | {renderMenu(bookmark.url, bookmark.title, bookmark.id)} 194 | 195 | {bookmark.title} 196 | 197 | {settings.no_folders !== 'true' && bookmark.parentId && ( 198 | {bookmark.parentId} 199 | )} 200 |
    • 201 | ))} 202 |
    203 | )} 204 |
    205 |
    206 | ) 207 | } 208 | 209 | export default Popup -------------------------------------------------------------------------------- /src/options.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react" 2 | import "./options.css" 3 | 4 | // Declare browser for cross-browser compatibility 5 | declare const browser: typeof chrome; 6 | 7 | const API = typeof chrome !== 'undefined' ? chrome : browser; 8 | 9 | interface Settings { 10 | no_share: boolean 11 | no_folders: boolean 12 | ignore_subdomain: boolean 13 | hover_url: boolean 14 | ignore_current_page: boolean 15 | } 16 | 17 | function OptionsPage() { 18 | const [settings, setSettings] = useState({ 19 | no_share: false, 20 | no_folders: false, 21 | ignore_subdomain: false, 22 | hover_url: false, 23 | ignore_current_page: false 24 | }) 25 | const [showStatus, setShowStatus] = useState(false) 26 | 27 | useEffect(() => { 28 | loadSettings() 29 | }, []) 30 | 31 | const loadSettings = async () => { 32 | try { 33 | const result = await API.storage.local.get([ 34 | 'no_share', 35 | 'no_folders', 36 | 'ignore_subdomain', 37 | 'hover_url', 38 | 'ignore_current_page' 39 | ]) 40 | 41 | setSettings({ 42 | no_share: result.no_share === 'true', 43 | no_folders: result.no_folders === 'true', 44 | ignore_subdomain: result.ignore_subdomain === 'true', 45 | hover_url: result.hover_url === 'true', 46 | ignore_current_page: result.ignore_current_page === 'true' 47 | }) 48 | } catch (error) { 49 | console.error('Error loading settings:', error) 50 | } 51 | } 52 | 53 | const saveSettings = async (newSettings: Partial) => { 54 | try { 55 | const updatedSettings = { ...settings, ...newSettings } 56 | setSettings(updatedSettings) 57 | 58 | // Convert booleans to strings for storage (maintaining compatibility) 59 | const storageData: Record = {} 60 | Object.entries(updatedSettings).forEach(([key, value]) => { 61 | storageData[key] = value.toString() 62 | }) 63 | 64 | await API.storage.local.set(storageData) 65 | 66 | setShowStatus(true) 67 | setTimeout(() => setShowStatus(false), 2000) 68 | } catch (error) { 69 | console.error('Error saving settings:', error) 70 | } 71 | } 72 | 73 | const handleCheckboxChange = (key: keyof Settings) => { 74 | saveSettings({ [key]: !settings[key] }) 75 | } 76 | 77 | return ( 78 |
    79 |

    About Bookmarked

    80 |
    81 |

    82 | Settings were saved! 83 |

    84 | 85 |
    86 |

    Settings

    87 | 88 |

    89 | handleCheckboxChange('no_share')} 94 | /> 95 | 99 |

    100 | 101 |

    102 | handleCheckboxChange('no_folders')} 107 | /> 108 | 112 |

    113 | 114 |

    115 | handleCheckboxChange('ignore_subdomain')} 120 | /> 121 | 125 |

    126 | 127 |

    128 | handleCheckboxChange('hover_url')} 133 | /> 134 | 138 |

    139 | 140 |

    141 | handleCheckboxChange('ignore_current_page')} 146 | /> 147 | 151 |

    152 |
    153 | 154 |

    Found a Bug? Want a Feature?

    155 |

    156 | 157 | Send us a note 158 | -- we'll let you know if it's in an upcoming release! 159 |

    160 | 161 |

    Upcoming Features

    162 |
      163 |
    • Show the folder path to each bookmark
    • 164 |
    • Option to ignore subdomains (so that sites like en.wikipedia.org and wikipedia.org will share results)
    • 165 |
    • Hide the plugin if the current page is the only bookmark you have
    • 166 |
    • Image optimization for smaller filesizes
    • 167 |
    • Page titles included in sharing to Twitter, Facebook and Emails
    • 168 |
    • Sharing options permanent for any URL
    • 169 |
    170 | 171 |

    Extension Updates

    172 |

    v2.0.0

    173 |
      174 |
    • Migrated to Plasmo framework
    • 175 |
    • Updated to Manifest V3
    • 176 |
    • Added multi-browser support (Firefox, Edge, Safari, Brave, Opera)
    • 177 |
    • Modernized codebase with TypeScript and React
    • 178 |
    179 | 180 |

    v1.1.5

    181 |
      182 |
    • Add bookmark count to icon
    • 183 |
    • Set a minimum width of the popup in case social links are enabled
    • 184 |
    185 | 186 |

    v1.1.4

    187 |
      188 |
    • Fixed options page after upgrading
    • 189 |
    190 | 191 |

    v1.1.3

    192 |
      193 |
    • Upgrading for the new version of chrome
    • 194 |
    195 | 196 |

    v1.1.2

    197 |
      198 |
    • Enhanced URL detection, better support for 2-part suffixes (co.uk)
    • 199 |
    • Using dynamic protocol for CSS and JS assets (fixes encryption warnings)
    • 200 |
    201 | 202 | 225 |
    226 |
    227 | ) 228 | } 229 | 230 | export default OptionsPage -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 2013 Dean Sofer 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------