├── .eslintignore ├── .stylelintrc.json ├── src ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg ├── images │ ├── icon_16.png │ ├── icon_32.png │ ├── icon_48.png │ ├── icon_64.png │ ├── icon_128.png │ ├── icon_300.png │ ├── icon_red.png │ ├── GitHub-Mark-32px.png │ ├── GitHub-Mark-64px.png │ ├── icon_green_dark_128.png │ ├── icon_green_dark_16.png │ ├── icon_green_dark_300.png │ ├── icon_green_dark_32.png │ ├── icon_green_dark_48.png │ ├── icon_green_dark_64.png │ ├── icon_green_light_128.png │ ├── icon_green_light_16.png │ ├── icon_green_light_300.png │ ├── icon_green_light_32.png │ ├── icon_green_light_48.png │ ├── icon_green_light_64.png │ ├── GitHub-Mark-120px-plus.png │ ├── buy-me-a-coffee-default-yellow.png │ ├── icon_green_dark.svg │ ├── icon_red.svg │ └── icon_green_light.svg ├── js │ ├── configuration.js │ ├── constants.js │ ├── Browser.js │ ├── utils │ │ └── index.js │ ├── pages │ │ ├── pageAbout.js │ │ └── pageSettings.js │ ├── Bookmarks.js │ └── popup.js ├── css │ ├── generated_theme │ │ ├── theme.css │ │ ├── theme.dark.css │ │ ├── theme.light.css │ │ ├── colors.module.css │ │ ├── typography.module.css │ │ └── tokens.css │ ├── loader.css │ ├── styles.scss │ └── styles.css ├── templates │ ├── loader.mustache │ ├── search-results.mustache │ ├── about-page.mustache │ ├── all-bookmarks.mustache │ ├── search-page.mustache │ ├── bookmark-card.mustache │ └── settings-page.mustache ├── popup.html ├── _locales │ ├── en │ │ └── messages.json │ └── uk │ │ └── messages.json └── lib │ └── js │ └── mustache.min.js ├── store_images ├── badges │ ├── ms_badge.png │ ├── amo_badge.png │ ├── opera_badge.png │ ├── cws_badge_large_border.png │ └── cws_badge_small_border.png ├── screenshots │ ├── ext_screenshot_1.png │ ├── ext_screenshot_2.png │ ├── ext_screenshot_3.png │ ├── chrome_screenshot_1.png │ ├── chrome_screenshot_1_D.png │ ├── chrome_screenshot_1_L.png │ ├── chrome_screenshot_2.png │ ├── chrome_screenshot_2_D.png │ ├── chrome_screenshot_2_L.png │ ├── chrome_screenshot_3.png │ ├── chrome_screenshot_3_D.png │ ├── chrome_screenshot_3_L.png │ ├── chrome_screenshot_4.png │ ├── chrome_screenshot_4_D.png │ ├── chrome_screenshot_4_L.png │ ├── ext_screenshor_edge_3.png │ ├── ext_screenshot_edge_1.png │ ├── ext_screenshot_edge_2.png │ ├── firefox_screenshot_1_D.png │ ├── firefox_screenshot_1_L.png │ ├── firefox_screenshot_2_D.png │ ├── firefox_screenshot_2_L.png │ ├── firefox_screenshot_3_D.png │ ├── firefox_screenshot_3_L.png │ ├── firefox_screenshot_4_D.png │ ├── firefox_screenshot_4_L.png │ ├── extension_screenshot_ff_1.png │ └── extension_screenshot_ff_2.png └── promo_tiles │ ├── promo_tile_large.afphoto │ ├── promo_tile_small.afphoto │ ├── promo_tile_marquee.afphoto │ ├── promo_tile_large_920x680.png │ ├── promo_tile_large_green.afphoto │ ├── promo_tile_small_440x280.png │ ├── promo_tile_small_green.afphoto │ ├── promo_tile_marquee_1400x560.png │ ├── promo_tile_marquee_green.afphoto │ ├── promo_tile_large_green_920x680.png │ ├── promo_tile_small_green_440x280.png │ └── promo_tile_marquee_green_1400x560.png ├── .gitignore ├── .eslintrc.json ├── set-version.sh ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── package.json ├── LICENSE ├── platform ├── chromium │ └── manifest.json └── firefox │ └── manifest.json ├── pack.sh ├── CHANGELOG.md ├── README.md └── test_data └── bookmarks_example.html /.eslintignore: -------------------------------------------------------------------------------- 1 | src/lib/* 2 | node_modules/* 3 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard", "stylelint-config-prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /src/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/fonts/icomoon.eot -------------------------------------------------------------------------------- /src/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/fonts/icomoon.ttf -------------------------------------------------------------------------------- /src/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/fonts/icomoon.woff -------------------------------------------------------------------------------- /src/images/icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_16.png -------------------------------------------------------------------------------- /src/images/icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_32.png -------------------------------------------------------------------------------- /src/images/icon_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_48.png -------------------------------------------------------------------------------- /src/images/icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_64.png -------------------------------------------------------------------------------- /src/images/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_128.png -------------------------------------------------------------------------------- /src/images/icon_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_300.png -------------------------------------------------------------------------------- /src/images/icon_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_red.png -------------------------------------------------------------------------------- /src/images/GitHub-Mark-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/GitHub-Mark-32px.png -------------------------------------------------------------------------------- /src/images/GitHub-Mark-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/GitHub-Mark-64px.png -------------------------------------------------------------------------------- /store_images/badges/ms_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/badges/ms_badge.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_128.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_16.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_300.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_32.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_48.png -------------------------------------------------------------------------------- /src/images/icon_green_dark_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_dark_64.png -------------------------------------------------------------------------------- /src/images/icon_green_light_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_128.png -------------------------------------------------------------------------------- /src/images/icon_green_light_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_16.png -------------------------------------------------------------------------------- /src/images/icon_green_light_300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_300.png -------------------------------------------------------------------------------- /src/images/icon_green_light_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_32.png -------------------------------------------------------------------------------- /src/images/icon_green_light_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_48.png -------------------------------------------------------------------------------- /src/images/icon_green_light_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/icon_green_light_64.png -------------------------------------------------------------------------------- /store_images/badges/amo_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/badges/amo_badge.png -------------------------------------------------------------------------------- /store_images/badges/opera_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/badges/opera_badge.png -------------------------------------------------------------------------------- /src/images/GitHub-Mark-120px-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/GitHub-Mark-120px-plus.png -------------------------------------------------------------------------------- /src/images/buy-me-a-coffee-default-yellow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/src/images/buy-me-a-coffee-default-yellow.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshot_1.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshot_2.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshot_3.png -------------------------------------------------------------------------------- /store_images/badges/cws_badge_large_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/badges/cws_badge_large_border.png -------------------------------------------------------------------------------- /store_images/badges/cws_badge_small_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/badges/cws_badge_small_border.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_large.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_large.afphoto -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_small.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_small.afphoto -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_1.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_1_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_1_D.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_1_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_1_L.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_2.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_2_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_2_D.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_2_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_2_L.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_3.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_3_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_3_D.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_3_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_3_L.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_4.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_4_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_4_D.png -------------------------------------------------------------------------------- /store_images/screenshots/chrome_screenshot_4_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/chrome_screenshot_4_L.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshor_edge_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshor_edge_3.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshot_edge_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshot_edge_1.png -------------------------------------------------------------------------------- /store_images/screenshots/ext_screenshot_edge_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/ext_screenshot_edge_2.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_marquee.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_marquee.afphoto -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_1_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_1_D.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_1_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_1_L.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_2_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_2_D.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_2_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_2_L.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_3_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_3_D.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_3_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_3_L.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_4_D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_4_D.png -------------------------------------------------------------------------------- /store_images/screenshots/firefox_screenshot_4_L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/firefox_screenshot_4_L.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_large_920x680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_large_920x680.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_large_green.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_large_green.afphoto -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_small_440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_small_440x280.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_small_green.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_small_green.afphoto -------------------------------------------------------------------------------- /store_images/screenshots/extension_screenshot_ff_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/extension_screenshot_ff_1.png -------------------------------------------------------------------------------- /store_images/screenshots/extension_screenshot_ff_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/screenshots/extension_screenshot_ff_2.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_marquee_1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_marquee_1400x560.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_marquee_green.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_marquee_green.afphoto -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_large_green_920x680.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_large_green_920x680.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_small_green_440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_small_green_440x280.png -------------------------------------------------------------------------------- /store_images/promo_tiles/promo_tile_marquee_green_1400x560.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zaksid/ext-duplicate-bookmarks-finder/HEAD/store_images/promo_tiles/promo_tile_marquee_green_1400x560.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .vscode/ 4 | node_modules/ 5 | build/ 6 | *.crx 7 | *.zip 8 | 9 | src/css/material-components-web.css 10 | src/css/styles.css 11 | src/css/styles.css.map 12 | -------------------------------------------------------------------------------- /src/js/configuration.js: -------------------------------------------------------------------------------- 1 | const Config = { 2 | buyMeACoffeeUrl: 'https://www.buymeacoffee.com/devzaksid', 3 | githubUrl: 'https://github.com/zaksid/ext-duplicate-bookmarks-finder', 4 | }; 5 | 6 | export default Config; 7 | -------------------------------------------------------------------------------- /src/css/generated_theme/theme.css: -------------------------------------------------------------------------------- 1 | @import url(tokens.css); 2 | @import url(colors.module.css); 3 | @import url(typography.module.css); 4 | @import url(theme.light.css) (prefers-color-scheme: light); 5 | @import url(theme.dark.css) (prefers-color-scheme: dark); 6 | -------------------------------------------------------------------------------- /src/templates/loader.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/js/constants.js: -------------------------------------------------------------------------------- 1 | export const MainNavBtnIcons = { 2 | APP_MAIN: '', 3 | BACK: 'arrow_back', 4 | }; 5 | 6 | export const MainNavBtnClasses = { 7 | IS_MENU_CONTENT: 'is-menu-content', 8 | }; 9 | 10 | export const Theme = { 11 | DARK: 'dark', 12 | LIGHT: 'light', 13 | SYSTEM: 'system', 14 | }; 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "airbnb-base" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": "latest", 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "import/extensions": ["error", "always"], 15 | "indent": ["error", 4], 16 | "max-len": ["error", { "code": 100, "comments": 150 }], 17 | "no-plusplus": "off", 18 | "no-use-before-define": "off" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /set-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | version="" 4 | 5 | while getopts ":v:" o; do 6 | case "${o}" in 7 | v) 8 | version=${OPTARG} 9 | ;; 10 | *) 11 | ;; 12 | esac 13 | done 14 | 15 | if [[ "$version" == "" ]] 16 | then 17 | echo "⚠️ Please provide version number!" 18 | exit 1 19 | fi 20 | 21 | sed -i '' -r -E "s/(\"version\": \")([0-9]+\.[0-9]+\.?[0-9]?)(\")/\1$version\3/g" platform/chromium/manifest.json 22 | sed -i '' -r -E "s/(\"version\": \")([0-9]+\.[0-9]+\.?[0-9]?)(\")/\1$version\3/g" platform/firefox/manifest.json 23 | sed -i '' -r -E "s/(version-)([0-9]+\.[0-9]+\.?[0-9]?)(\.*)/\1$version\3/g" README.md 24 | -------------------------------------------------------------------------------- /src/js/Browser.js: -------------------------------------------------------------------------------- 1 | export default class Browser { 2 | constructor(browser) { 3 | this.browser = browser; 4 | this.i18n = browser.i18n; 5 | } 6 | 7 | getBookmarksTree() { 8 | return this.browser.bookmarks.getTree(); 9 | } 10 | 11 | getExtensionVersion() { 12 | return this.browser.runtime.getManifest().version; 13 | } 14 | 15 | removeBookmarks(ids = []) { 16 | ids.forEach(async (id) => { 17 | try { 18 | await this.browser.bookmarks.remove(id); 19 | } catch (e) { 20 | console.error(`Cannot delete bookmark with id ${id}: ${e}`); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/js/utils/index.js: -------------------------------------------------------------------------------- 1 | export function getDefaultExtensionSettings() { 2 | return { 3 | showSeparators: false, 4 | theme: 'system', 5 | ignoredUrls: [], 6 | }; 7 | } 8 | 9 | export async function getExtensionSettings(browserInstance) { 10 | const defaults = getDefaultExtensionSettings(); 11 | const userSettings = await browserInstance.browser.storage.local.get(); 12 | 13 | return Object.keys(userSettings).length ? userSettings : defaults; 14 | } 15 | 16 | export async function setExtensionSettings(browserInstance, userSettings) { 17 | await browserInstance.browser.storage.local.set({ 18 | ...userSettings, 19 | }); 20 | } 21 | 22 | export function isSeparator(url) { 23 | return /data:/.test(url); 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help me improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - Browser [e.g. chrome, firefox] 27 | - Browser version 28 | - Extension version 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /src/templates/search-results.mustache: -------------------------------------------------------------------------------- 1 | {{# isLoader }} 2 |
3 | {{> loader }} 4 |
5 | {{/ isLoader }} 6 | 7 | {{^ isLoader }} 8 |
9 | 12 |
13 | 14 |
15 | {{#hasResults}} 16 | 21 | {{/hasResults}} 22 | 23 |
24 | {{#cards}} 25 | {{> card}} 26 | {{/cards}} 27 |
28 |
29 | {{/ isLoader }} 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "duplicate-bookmarks-finder", 3 | "version": "1.0.1", 4 | "description": "Find and delete duplicate bookmarks in browser", 5 | "author": "Oleksandr Mahdybor", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "conventional-changelog-cli": "^2.2.2", 9 | "eslint": "^8.21.0", 10 | "eslint-config-airbnb": "^19.0.4", 11 | "eslint-config-airbnb-base": "^15.0.0", 12 | "eslint-plugin-import": "^2.26.0", 13 | "prettier": "^1.19.1", 14 | "prettier-airbnb-config": "^1.0.0", 15 | "sass": "^1.63.6", 16 | "stylelint": "^14.9.1", 17 | "stylelint-config-prettier": "^9.0.3", 18 | "stylelint-config-standard": "^26.0.0" 19 | }, 20 | "prettier": "prettier-airbnb-config", 21 | "scripts": { 22 | "lint": "npm run lint:css & npm run lint:js", 23 | "lint:js": "eslint ./src", 24 | "lint:css": "stylelint \"./src/**/*.css\"", 25 | "sass": "sass --watch ./src/css/styles.scss ./src/css/styles.css", 26 | "version": "conventional-changelog -i CHANGELOG.md -s && git add CHANGELOG.md" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/templates/about-page.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{i18n.alt_extIcon}} 5 |
6 |
7 |

{{extensionName}}

8 |

{{i18n.txt_version}} {{version}}

9 |
10 |
11 |
12 | 18 | 19 | 20 |
21 | 27 | Buy Me A Coffee 28 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Oleksandr Mahdybor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/images/icon_green_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /src/templates/all-bookmarks.mustache: -------------------------------------------------------------------------------- 1 |
Found {{bookmarks.length}} bookmarks
2 | 3 | 39 | -------------------------------------------------------------------------------- /src/css/loader.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable number-max-precision */ 2 | 3 | /* generated by https://loading.io/ */ 4 | 5 | @keyframes ldio-5y59izw6pev { 6 | 0% { transform: translate(2.37px,2.37px) } 7 | 33.33% { transform: translate(120.87px,2.37px) } 8 | 66.66% { transform: translate(49.77px,120.87px) } 9 | 100% { transform: translate(2.37px,2.37px) } 10 | } 11 | 12 | .ldio-5y59izw6pev div { 13 | box-sizing: content-box; 14 | } 15 | 16 | .ldio-5y59izw6pev > div { 17 | transform: scale(0.42); 18 | transform-origin: 118.5px 118.5px; 19 | } 20 | 21 | .ldio-5y59izw6pev > div > div { 22 | animation: ldio-5y59izw6pev 1.282051282051282s linear infinite; 23 | position: absolute; 24 | } 25 | 26 | .ldio-5y59izw6pev > div > div div:nth-child(1) { 27 | width: 85.32000000000001px; 28 | height: 85.32000000000001px; 29 | border-radius: 50%; 30 | border: 14.22px solid var(--mdc-theme-primary); 31 | } 32 | 33 | .ldio-5y59izw6pev > div > div div:nth-child(2) { 34 | width: 20.145px; 35 | height: 60.435px; 36 | transform: rotate(-45deg); 37 | background: var(--mdc-theme-primary); 38 | border-radius: 0 0 9.48px 9.48px; 39 | position: absolute; 40 | top: 80.58px; 41 | left: 100.72500000000001px; 42 | } 43 | 44 | .loadingio-spinner-magnify-y6n6336d04m { 45 | width: 237px; 46 | height: 237px; 47 | display: inline-block; 48 | overflow: hidden; 49 | background: none; 50 | } 51 | 52 | .ldio-5y59izw6pev { 53 | width: 100%; 54 | height: 100%; 55 | position: relative; 56 | transform: translateZ(0) scale(1); 57 | backface-visibility: hidden; 58 | transform-origin: 0 0; /* see note above */ 59 | } 60 | 61 | #loader-wrapper { 62 | display: flex; 63 | align-content: center; 64 | justify-content: center; 65 | } 66 | -------------------------------------------------------------------------------- /platform/chromium/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_extensionName__", 4 | "description": "__MSG_extensionDescription__", 5 | "version": "1.0.1", 6 | "author": "Oleksandr Mahdybor", 7 | "homepage_url": "https://github.com/zaksid/ext-duplicate-bookmarks-finder", 8 | "permissions": [ 9 | "bookmarks", 10 | "storage" 11 | ], 12 | "action": { 13 | "browser_style": true, 14 | "default_popup": "popup.html", 15 | "default_title": "__MSG_extensionName__", 16 | "default_icon": { 17 | "16": "/images/icon_green_dark_16.png", 18 | "32": "/images/icon_green_dark_32.png", 19 | "48": "/images/icon_green_dark_48.png", 20 | "128": "/images/icon_green_dark_128.png" 21 | }, 22 | "theme_icons": [{ 23 | "dark": "/images/icon_green_light_16.png", 24 | "light": "/images/icon_green_dark_16.png", 25 | "size": 16 26 | }, { 27 | "dark": "/images/icon_green_light_32.png", 28 | "light": "/images/icon_green_dark_32.png", 29 | "size": 32 30 | }, { 31 | "dark": "/images/icon_green_light_48.png", 32 | "light": "/images/icon_green_dark_48.png", 33 | "size": 48 34 | }, { 35 | "dark": "/images/icon_green_light_128.png", 36 | "light": "/images/icon_green_dark_128.png", 37 | "size": 128 38 | }] 39 | }, 40 | "icons": { 41 | "16": "/images/icon_green_dark_16.png", 42 | "32": "/images/icon_green_dark_32.png", 43 | "48": "/images/icon_green_dark_48.png", 44 | "128": "/images/icon_green_dark_128.png" 45 | }, 46 | "default_locale": "en" 47 | } 48 | -------------------------------------------------------------------------------- /platform/firefox/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "__MSG_extensionName__", 4 | "description": "__MSG_extensionDescription__", 5 | "version": "1.0.1", 6 | "author": "Oleksandr Mahdybor", 7 | "homepage_url": "https://github.com/zaksid/ext-duplicate-bookmarks-finder", 8 | "permissions": [ 9 | "bookmarks", 10 | "storage" 11 | ], 12 | "browser_specific_settings": { 13 | "gecko": { 14 | "id": "{8dc9d389-4f2c-4f53-909a-7041d9442fa9}" 15 | } 16 | }, 17 | "action": { 18 | "default_popup": "popup.html", 19 | "default_title": "__MSG_extensionName__", 20 | "default_icon": { 21 | "16": "/images/icon_green_dark_16.png", 22 | "32": "/images/icon_green_dark_32.png", 23 | "48": "/images/icon_green_dark_48.png", 24 | "128": "/images/icon_green_dark_128.png" 25 | }, 26 | "theme_icons": [{ 27 | "dark": "/images/icon_green_light_16.png", 28 | "light": "/images/icon_green_dark_16.png", 29 | "size": 16 30 | }, { 31 | "dark": "/images/icon_green_light_32.png", 32 | "light": "/images/icon_green_dark_32.png", 33 | "size": 32 34 | }, { 35 | "dark": "/images/icon_green_light_48.png", 36 | "light": "/images/icon_green_dark_48.png", 37 | "size": 48 38 | }, { 39 | "dark": "/images/icon_green_light_128.png", 40 | "light": "/images/icon_green_dark_128.png", 41 | "size": 128 42 | }] 43 | }, 44 | "icons": { 45 | "16": "/images/icon_green_dark_16.png", 46 | "32": "/images/icon_green_dark_32.png", 47 | "48": "/images/icon_green_dark_48.png", 48 | "128": "/images/icon_green_dark_128.png" 49 | }, 50 | "default_locale": "en" 51 | } 52 | -------------------------------------------------------------------------------- /src/js/pages/pageAbout.js: -------------------------------------------------------------------------------- 1 | /* global Mustache */ 2 | 3 | import Config from '../configuration.js'; 4 | import { MainNavBtnIcons, MainNavBtnClasses } from '../constants.js'; 5 | 6 | export default async function initAboutPage(browserInstance) { 7 | const appBarNavBtn = document.querySelector('#app-bar-nav-btn'); 8 | const appBarTitle = document.querySelector('#app-bar-title'); 9 | 10 | appBarNavBtn.innerHTML = MainNavBtnIcons.BACK; 11 | appBarNavBtn.classList.add(MainNavBtnClasses.IS_MENU_CONTENT); 12 | 13 | appBarTitle.innerHTML = browserInstance.i18n.getMessage('menu_about'); 14 | 15 | const aboutTemplateResponse = await fetch('templates/about-page.mustache'); 16 | const aboutTemplate = await aboutTemplateResponse.text(); 17 | 18 | document.querySelector('#main-content').innerHTML = Mustache.render(aboutTemplate, { 19 | extensionName: browserInstance.i18n.getMessage('extensionName'), 20 | version: browserInstance.getExtensionVersion(), 21 | buyMeACoffeeUrl: Config.buyMeACoffeeUrl, 22 | githubUrl: Config.githubUrl, 23 | i18n: { 24 | alt_extIcon: browserInstance.i18n.getMessage('alt_extIcon'), 25 | txt_version: browserInstance.i18n.getMessage('txt_version'), 26 | }, 27 | }); 28 | 29 | const isFirefox = navigator.userAgent.includes('Firefox'); 30 | if (isFirefox) { 31 | // For some reason Firefox doesn't close pop-up when navigating via external links. 32 | // When using just `window.close()` the pop-up is closed but the link is opened in a new window instead af a new tab. 33 | // Need to manually open the link and then close the pop-up. 34 | document.querySelectorAll('.external-link').forEach((elem) => { 35 | elem.addEventListener('click', (event) => { 36 | event.preventDefault(); 37 | 38 | const { currentTarget } = event; 39 | 40 | window.open(currentTarget.href, currentTarget.target, 'noopener,noreferrer'); 41 | window.close(); 42 | }); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/css/generated_theme/theme.dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-sys-color-primary: var(--md-sys-color-primary-dark); 3 | --md-sys-color-on-primary: var(--md-sys-color-on-primary-dark); 4 | --md-sys-color-primary-container: var(--md-sys-color-primary-container-dark); 5 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-dark); 6 | --md-sys-color-secondary: var(--md-sys-color-secondary-dark); 7 | --md-sys-color-on-secondary: var(--md-sys-color-on-secondary-dark); 8 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-dark); 9 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-dark); 10 | --md-sys-color-tertiary: var(--md-sys-color-tertiary-dark); 11 | --md-sys-color-on-tertiary: var(--md-sys-color-on-tertiary-dark); 12 | --md-sys-color-tertiary-container: var(--md-sys-color-tertiary-container-dark); 13 | --md-sys-color-on-tertiary-container: var(--md-sys-color-on-tertiary-container-dark); 14 | --md-sys-color-error: var(--md-sys-color-error-dark); 15 | --md-sys-color-error-container: var(--md-sys-color-error-container-dark); 16 | --md-sys-color-on-error: var(--md-sys-color-on-error-dark); 17 | --md-sys-color-on-error-container: var(--md-sys-color-on-error-container-dark); 18 | --md-sys-color-background: var(--md-sys-color-background-dark); 19 | --md-sys-color-on-background: var(--md-sys-color-on-background-dark); 20 | --md-sys-color-surface: var(--md-sys-color-surface-dark); 21 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); 22 | --md-sys-color-surface-variant: var(--md-sys-color-surface-variant-dark); 23 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 24 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 25 | --md-sys-color-inverse-on-surface: var(--md-sys-color-inverse-on-surface-dark); 26 | --md-sys-color-inverse-surface: var(--md-sys-color-inverse-surface-dark); 27 | --md-sys-color-inverse-primary: var(--md-sys-color-inverse-primary-dark); 28 | --md-sys-color-shadow: var(--md-sys-color-shadow-dark); 29 | --md-sys-color-surface-tint: var(--md-sys-color-surface-tint-dark); 30 | --md-sys-color-outline-variant: var(--md-sys-color-outline-variant-dark); 31 | --md-sys-color-scrim: var(--md-sys-color-scrim-dark); 32 | } 33 | -------------------------------------------------------------------------------- /src/css/generated_theme/theme.light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-sys-color-primary: var(--md-sys-color-primary-light); 3 | --md-sys-color-on-primary: var(--md-sys-color-on-primary-light); 4 | --md-sys-color-primary-container: var(--md-sys-color-primary-container-light); 5 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-light); 6 | --md-sys-color-secondary: var(--md-sys-color-secondary-light); 7 | --md-sys-color-on-secondary: var(--md-sys-color-on-secondary-light); 8 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-light); 9 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-light); 10 | --md-sys-color-tertiary: var(--md-sys-color-tertiary-light); 11 | --md-sys-color-on-tertiary: var(--md-sys-color-on-tertiary-light); 12 | --md-sys-color-tertiary-container: var(--md-sys-color-tertiary-container-light); 13 | --md-sys-color-on-tertiary-container: var(--md-sys-color-on-tertiary-container-light); 14 | --md-sys-color-error: var(--md-sys-color-error-light); 15 | --md-sys-color-error-container: var(--md-sys-color-error-container-light); 16 | --md-sys-color-on-error: var(--md-sys-color-on-error-light); 17 | --md-sys-color-on-error-container: var(--md-sys-color-on-error-container-light); 18 | --md-sys-color-background: var(--md-sys-color-background-light); 19 | --md-sys-color-on-background: var(--md-sys-color-on-background-light); 20 | --md-sys-color-surface: var(--md-sys-color-surface-light); 21 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); 22 | --md-sys-color-surface-variant: var(--md-sys-color-surface-variant-light); 23 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 24 | --md-sys-color-outline: var(--md-sys-color-outline-light); 25 | --md-sys-color-inverse-on-surface: var(--md-sys-color-inverse-on-surface-light); 26 | --md-sys-color-inverse-surface: var(--md-sys-color-inverse-surface-light); 27 | --md-sys-color-inverse-primary: var(--md-sys-color-inverse-primary-light); 28 | --md-sys-color-shadow: var(--md-sys-color-shadow-light); 29 | --md-sys-color-surface-tint: var(--md-sys-color-surface-tint-light); 30 | --md-sys-color-outline-variant: var(--md-sys-color-outline-variant-light); 31 | --md-sys-color-scrim: var(--md-sys-color-scrim-light); 32 | } 33 | -------------------------------------------------------------------------------- /src/templates/search-page.mustache: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 17 |
18 | 19 | 26 | 27 | 34 | 35 |
36 |
37 |
43 |
44 | {{i18n.bookmarksDeleteConfirmationTitle}} 45 |
46 |
{{i18n.bookmarksDeleteConfirmation}}
47 |
48 | 52 | 56 |
57 |
58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /pack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | help=" 4 | \n 5 | Usage \n 6 | \t bash pack.sh \n\n 7 | Example \n 8 | \t bash pack.sh -p firefox -m dev \n\n 9 | Options \n 10 | \t -p \t\t platform - Platform to build for. \n 11 | \t\t\t Possible values: 'firefox', 'opera'. Default (param not provided) 'chromium'. \n\n 12 | \t -m \t\t mode - Packing mode. \n 13 | \t\t\t If 'dev' is specified - development mode. Create only build folder, don't zip. \n 14 | \t\t\t Otherwise mode=publishing (create zip, remove build folder). 15 | " 16 | 17 | platform="chromium" 18 | mode="publishing" 19 | is_help=false 20 | 21 | while getopts ":p:m:h" o; do 22 | case "${o}" in 23 | p) 24 | arg=${OPTARG} 25 | 26 | if [[ "$arg" == "firefox" ]]; then 27 | platform="firefox" 28 | elif [[ "$arg" == "opera" ]]; then 29 | platform="opera" 30 | fi 31 | 32 | echo -e "> Platform: ${platform}" 33 | ;; 34 | m) 35 | arg=${OPTARG} 36 | 37 | if [[ "$arg" == "dev" ]]; then 38 | mode="development" 39 | fi 40 | 41 | echo -e "> Mode: ${mode}" 42 | ;; 43 | h) 44 | is_help=true 45 | ;; 46 | *) 47 | ;; 48 | esac 49 | done 50 | 51 | if [[ "$is_help" = true ]]; then 52 | echo -e ${help} 53 | exit 0 54 | fi 55 | 56 | if [[ -d "build" ]]; then 57 | rm -rf build 58 | fi 59 | 60 | mkdir build 61 | cp -r ./src/* build/ 62 | rm -f build/manifest*.* 63 | rm build/css/*.scss 64 | rm build/css/*.map 65 | 66 | if [[ "$platform" == "opera" ]]; then 67 | cp ./platform/chromium/manifest.json build 68 | else 69 | cp ./platform/${platform}/manifest.json build 70 | fi 71 | 72 | if [[ "$platform" == "opera" ]]; then 73 | echo -e "> Renaming mustashe templates for Opera..." 74 | find build/templates/ -name '*.mustache' -exec sh -c 'mv "$0" "${0%.mustache}.mustache.html"' {} \; 75 | sed -i '' -r -E "s/\.mustache/\.mustache\.html/g" build/js/popup.js 76 | echo -e "> ...done" 77 | fi 78 | 79 | #if [[ "$mode" == "development" ]]; then 80 | cd build 81 | zip -r extension-${platform}.zip * 82 | mv extension-${platform}.zip ../ 83 | cd ../ 84 | 85 | if [[ "$platform" == "opera" ]]; then 86 | mv extension-${platform}.zip extension-${platform}.crx 87 | fi 88 | #fi 89 | 90 | if [[ "$mode" == "publishing" ]]; then 91 | rm -rf build 92 | fi 93 | -------------------------------------------------------------------------------- /src/images/icon_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 34 | 38 | 39 | -------------------------------------------------------------------------------- /src/templates/bookmark-card.mustache: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{url}}
4 |
{{i18n.txt_cardMatchNo}}
5 |
6 |
7 |
8 |
    9 | {{#items}} 10 | 11 | 46 | {{/items}} 47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /src/images/icon_green_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 | 32 | 33 | Duplicate bookmarks finder 34 |
35 | 54 |
55 |
56 | 57 |
58 | 59 |
60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/js/Bookmarks.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-continue */ 2 | 3 | import { getExtensionSettings, isSeparator } from './utils/index.js'; 4 | 5 | export default class Bookmarks { 6 | constructor(bookmarksTree) { 7 | this.processedBookmarks = []; 8 | this.#prepareBookmarks(bookmarksTree); 9 | } 10 | 11 | #prepareBookmarks(bookmarksInput, path = '') { 12 | // Make a copy of an array to work with 13 | const bookmarks = bookmarksInput.slice(); 14 | 15 | for (let i = 0; i < bookmarks.length; i++) { 16 | const bookmark = bookmarks[i]; 17 | 18 | if (bookmark.url) { 19 | if (path) { 20 | if (!('myPath' in bookmark)) { 21 | bookmark.myPath = ''; 22 | } 23 | bookmark.myPath += `${path}/`; 24 | } 25 | this.processedBookmarks.push(bookmark); 26 | } 27 | 28 | if (bookmark.children) { 29 | this.#prepareBookmarks(bookmark.children, `${path ? `${path}/` : ''}${bookmark.title}`); 30 | } 31 | } 32 | } 33 | 34 | getBookmarks() { 35 | return this.processedBookmarks; 36 | } 37 | 38 | async getDuplicates(browserInstance) { 39 | const array = this.processedBookmarks.slice(); 40 | const matches = {}; 41 | const httpsRegex = /http(s)?/; 42 | const settings = await getExtensionSettings(browserInstance); 43 | 44 | const isIgnored = (url) => { 45 | if ('ignoredUrls' in settings && settings.ignoredUrls.length) { 46 | const matchesIgnoredPattern = settings.ignoredUrls.some((pattern) => { 47 | const regex = new RegExp(pattern); 48 | return regex.test(url); 49 | }); 50 | 51 | if (matchesIgnoredPattern) { 52 | return true; 53 | } 54 | } 55 | 56 | if (settings && 'showSeparators' in settings) { 57 | if (settings.showSeparators) { 58 | return false; 59 | } 60 | return isSeparator(url); 61 | } 62 | 63 | return false; 64 | }; 65 | 66 | for (let i = 0; i < array.length; i++) { 67 | const currentElem = array[i]; 68 | let wasMatched = false; 69 | 70 | if (isIgnored(currentElem.url)) { 71 | continue; 72 | } 73 | 74 | for (let j = i + 1; j < array.length; j++) { 75 | const url1 = currentElem.url.replace(httpsRegex); 76 | const url2 = array[j].url.replace(httpsRegex); 77 | 78 | if (url1 === url2) { 79 | if (!(i in matches)) { 80 | matches[i] = []; 81 | } 82 | matches[i].push(array[j]); 83 | array.splice(j, 1); 84 | wasMatched = true; 85 | j--; // adjust due to deleted element 86 | } 87 | } 88 | 89 | if (wasMatched) { 90 | matches[i].unshift(currentElem); 91 | } 92 | } 93 | 94 | return Object.values(matches); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { 3 | "message": "Duplicate Bookmarks Finder", 4 | "description": "Name of the extension." 5 | }, 6 | "extensionDescription": { 7 | "message": "Find and delete duplicate bookmarks.", 8 | "description": "Description of the extension." 9 | }, 10 | "extensionDescriptionLong": { 11 | "message": "This extension allows to find and delete duplicate bookmarks.", 12 | "description": "Long description of the extension." 13 | }, 14 | "btn_add": { 15 | "message": "Add" 16 | }, 17 | "btn_findDuplicates": { 18 | "message": "Find" 19 | }, 20 | "btn_cancel": { 21 | "message": "Cancel" 22 | }, 23 | "btn_delete": { 24 | "message": "Delete" 25 | }, 26 | "btn_deleteSelected": { 27 | "message": "Delete selected" 28 | }, 29 | "btn_resetSettings": { 30 | "message": "Reset to defaults" 31 | }, 32 | "bookmarksDeleteConfirmationTitle": { 33 | "message": "Delete selected bookmarks?" 34 | }, 35 | "bookmarksDeleteConfirmation": { 36 | "message": "This action cannot be undone!" 37 | }, 38 | "bookmarksDeletedMsg": { 39 | "message": "Selected bookmarks have been deleted." 40 | }, 41 | "input_hint_ignoredUrlRegex": { 42 | "message": "Ignored URLs regex" 43 | }, 44 | "menu_about": { 45 | "message": "About" 46 | }, 47 | "menu_settings": { 48 | "message": "Settings" 49 | }, 50 | "msgFoundQty": { 51 | "message": "Found $QTY$ duplicates", 52 | "placeholders": { 53 | "qty": { 54 | "content": "$1", 55 | "example": "0" 56 | } 57 | } 58 | }, 59 | "msgFoundNone": { 60 | "message": "🥳 No duplicates found!" 61 | }, 62 | "settingTheme": { 63 | "message": "Theme" 64 | }, 65 | "settingThemeDark": { 66 | "message": "Dark" 67 | }, 68 | "settingThemeLight": { 69 | "message": "Light" 70 | }, 71 | "settingThemeSystem": { 72 | "message": "OS Default" 73 | }, 74 | "settingIgnoredPatterns": { 75 | "message": "Ignored URL patterns" 76 | }, 77 | "settingIgnoredPatternsDescription": { 78 | "message": "Patterns of URLs that should be ignored (RegExp)" 79 | }, 80 | "settingShowSeparators": { 81 | "message": "Show separators" 82 | }, 83 | "settingShowSeparatorsDescription": { 84 | "message": "Show bookmarks separators in search results" 85 | }, 86 | "txt_cardMatchNo": { 87 | "message": "Match $NO$", 88 | "placeholders": { 89 | "no": { 90 | "content": "$1", 91 | "example": "1" 92 | } 93 | } 94 | }, 95 | "alt_extIcon": { 96 | "message": "Extension icon" 97 | }, 98 | "txt_path": { 99 | "message": "Path:" 100 | }, 101 | "txt_separator": { 102 | "message": "Separator" 103 | }, 104 | "txt_separators": { 105 | "message": "Separators" 106 | }, 107 | "txt_version": { 108 | "message": "Version: " 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.1 (2024-01-25) 2 | 3 | * Fixes #49: Remove "Buy Me A Coffee" script to Comply with Chrome Web Store Policy (#51) ([dc4cc3d](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/dc4cc3d)), closes [#49](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/49) [#51](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/51) 4 | * Fixes #50: Firefox doesn't close pop-up when navigating via external link (#52) ([5833b4e](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/5833b4e)), closes [#50](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/50) [#52](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/52) 5 | 6 | 7 | ## 1.0.0 (2023-06-30) 8 | 9 | * Added dark mode. Restyled with Material You ([ed1039a](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/ed1039a)) 10 | 11 | 12 | ## 0.3.0 (2022-12-09) 13 | 14 | * 🛠 Fixes #24: Top App Bar is not fixed (#25) ([85f7dc1](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/85f7dc1)), closes [#24](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/24) 15 | * Resolve #38 Add extension settings. Fixes #37 Bookmark separators are included in duplicates ([5e1606d](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/5e1606d)), closes [#38](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/38) [#37](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/37) 16 | 17 | ## 0.2.0 (2022-08-09) 18 | * 🛠 Updated project structure and build process ([c51869f](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/c51869f)) 19 | 20 | ## 0.1.4 (2022-08-07) 21 | * 📦 Resolve #9: Create sh script to zip necessary files for publishing (#11) ([67ee736](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/67ee736)), closes [#9](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/9) [#11](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/11) 22 | * 🚑 Resolve #7: Add eslint / stylelint (#10) ([9fa2558](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/9fa2558)), closes [#7](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/7) [#10](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/10) 23 | 24 | ## 0.1.3 (2022-08-07) 25 | * 🛠 Fixes #2: Remove empty card after deleting bookmarks (#6) ([7cf2120](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/7cf2120)), closes [#2](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/2) [#6](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/6) 26 | 27 | ## 0.1.2 (2022-08-07) 28 | * Fixes #3: Menu toggle on menu icon click (#5) ([829d3a5](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/829d3a5)), closes [#3](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/3) [#5](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/5) 29 | 30 | ## 0.1.1 (2022-08-07) 31 | * Refactor (move classes to modules). Added loader (#4) ([5e05056](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/5e05056)), closes [#4](https://github.com/zaksid/ext-duplicate-bookmarks-finder/issues/4) 32 | 33 | ## 0.1 (2022-08-01) 34 | * 🎉 Initial commit ([0d2cbd1](https://github.com/zaksid/ext-duplicate-bookmarks-finder/commit/0d2cbd1)) 35 | -------------------------------------------------------------------------------- /src/_locales/uk/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { 3 | "message": "Пошук дублікатів закладок", 4 | "description": "Name of the extension." 5 | }, 6 | "extensionDescription": { 7 | "message": "Пошук і видалення дублікатів закладок.", 8 | "description": "Description of the extension." 9 | }, 10 | "extensionDescriptionLong": { 11 | "message": "Це розширення дозволяє легко знайти та видалити дублікати закладок у браузер.", 12 | "description": "Long description of the extension." 13 | }, 14 | "btn_add": { 15 | "message": "Додати" 16 | }, 17 | "btn_findDuplicates": { 18 | "message": "Пошук" 19 | }, 20 | "btn_cancel": { 21 | "message": "Скасувати" 22 | }, 23 | "btn_delete": { 24 | "message": "Видалити" 25 | }, 26 | "btn_deleteSelected": { 27 | "message": "Видалити вибрані" 28 | }, 29 | "btn_resetSettings": { 30 | "message": "Скинути до стандартних налаштуваннь" 31 | }, 32 | "bookmarksDeleteConfirmationTitle": { 33 | "message": "Видалити вибрані закладки?" 34 | }, 35 | "bookmarksDeleteConfirmation": { 36 | "message": "Цю дію не можна відмінити!" 37 | }, 38 | "bookmarksDeletedMsg": { 39 | "message": "Вибрані закладки було видалено." 40 | }, 41 | "input_hint_ignoredUrlRegex": { 42 | "message": "Регулярка для ігнорування закладок" 43 | }, 44 | "menu_about": { 45 | "message": "Про розширення" 46 | }, 47 | "menu_settings": { 48 | "message": "Налаштування" 49 | }, 50 | "msgFoundQty": { 51 | "message": "Знайдено дублікатів: $QTY$", 52 | "placeholders": { 53 | "qty": { 54 | "content": "$1", 55 | "example": "0" 56 | } 57 | } 58 | }, 59 | "msgFoundNone": { 60 | "message": "🥳 Дублікатів не знайдено!" 61 | }, 62 | "settingTheme": { 63 | "message": "Тема" 64 | }, 65 | "settingThemeDark": { 66 | "message": "Темна" 67 | }, 68 | "settingThemeLight": { 69 | "message": "Світла" 70 | }, 71 | "settingThemeSystem": { 72 | "message": "Як на пристрої" 73 | }, 74 | "settingIgnoredPatterns": { 75 | "message": "Ігноровані URL адреси" 76 | }, 77 | "settingIgnoredPatternsDescription": { 78 | "message": "Зразки URLs адрес (регулярки) які ігноруватимуться при пошуку" 79 | }, 80 | "settingShowSeparators": { 81 | "message": "Показувати роздільники" 82 | }, 83 | "settingShowSeparatorsDescription": { 84 | "message": "Показувати роздільники закладок під час пошуку" 85 | }, 86 | "txt_cardMatchNo": { 87 | "message": "Співпадіння $NO$", 88 | "placeholders": { 89 | "no": { 90 | "content": "$1", 91 | "example": "1" 92 | } 93 | } 94 | }, 95 | "alt_extIcon": { 96 | "message": "Іконка розширення" 97 | }, 98 | "txt_path": { 99 | "message": "Шлях:" 100 | }, 101 | "txt_separator": { 102 | "message": "Роздільник" 103 | }, 104 | "txt_separators": { 105 | "message": "Роздільники" 106 | }, 107 | "txt_version": { 108 | "message": "Версія:" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Duplicate Bookmarks Finder 2 | 3 | ![version](https://img.shields.io/badge/version-1.0.1-blue) 4 | ![GitHub](https://img.shields.io/github/license/zaksid/ext-duplicate-bookmarks-finder) 5 | 6 | Find and delete duplicate bookmarks. 7 | 8 | Available for the following browsers: 9 | 10 |

11 | 12 | Get Duplicate Bookmarks Finder for Chrome 13 | 14 | 15 | Get Duplicate Bookmarks Finder for Firefox 16 | 17 | 18 | Get Duplicate Bookmarks Finder for Edge 19 | 20 | 21 | Get Duplicate Bookmarks Finder for Opera 22 | 23 |

24 | 25 | This extension is a browser pop-up that allows to find and delete duplicate bookmarks. 26 | 27 | ### Features: 28 | - List all duplicated bookmarks 29 | - Delete selected duplicates 30 | - Dark/Light mode 31 | - Use RegEx to ignore particular URL patterns 32 | 33 |

34 | screenshot image 1 35 | screenshot image 2 36 | screenshot image 3 37 |

38 | 39 |

40 | screenshot image 1 41 | screenshot image 2 42 | screenshot image 3 43 |

44 | 45 | --- 46 | 47 | ## Developer section 48 | 49 | ### Dev dependencies 50 | 51 | To install dev dependencies run: 52 | ``` 53 | npm i 54 | ``` 55 | 56 | Available npm scripts: 57 | - `lint:js` - run eslint on project. 58 | - `lint:css` - run stylelint on project. 59 | - `lint` - lint both js and css. 60 | - `sass` - start Sass watch task. 61 | 62 | ### Updating extension version 63 | 64 | To update extension version in all places where it's contained (manifests, README...), run 65 | ``` 66 | bash set-version.sh -v X.Y.Z 67 | ``` 68 | 69 | ### Packing extension for store 70 | 71 | To prepare zip archive for publishing in browser app store, run 72 | ``` 73 | bash pack.sh 74 | ``` 75 | Options: 76 | * `-p` - platform - Platform to build for. Possible values: `firefox`, `opera`. Default (param not provided) `chromium`. 77 | * `-m` - mode - Packing mode. If `dev` is specified - development mode.Create only build folder, don't zip. Otherwise mode=publishing (create zip, remove build folder). 78 | 79 | ### Recommended workflow 80 | 1. Make changes. 81 | - Run `npm run sass` to start watch task to monitor scss changes. 82 | 2. Run `bash set-version.sh -v X.Y.Z`. 83 | 3. Commit changes. 84 | 4. Update version in `package.json` + run `npm i`. 85 | 5. Run `npm run version`. 86 | 6. Commit `package.json`, `package-lock.json` and `CHANGELOG.md` files. 87 | 7. Tag (`git tag vX.Y.Z`). 88 | 8. Push. 89 | 90 | ## Possible further enhancements 91 | 92 | * [ ] Find -> new search/find again 93 | * [ ] Rewrite with modules + Webpack? 94 | * [ ] Sort by folder path 95 | * [ ] Delete folder if empty after deletion duplicates 96 | -------------------------------------------------------------------------------- /src/css/generated_theme/colors.module.css: -------------------------------------------------------------------------------- 1 | .primary { 2 | background-color: var(--md-sys-color-primary); 3 | } 4 | .primary-text { 5 | color: var(--md-sys-color-primary); 6 | } 7 | .on-primary { 8 | background-color: var(--md-sys-color-on-primary); 9 | } 10 | .on-primary-text { 11 | color: var(--md-sys-color-on-primary); 12 | } 13 | .primary-container { 14 | background-color: var(--md-sys-color-primary-container); 15 | } 16 | .primary-container-text { 17 | color: var(--md-sys-color-primary-container); 18 | } 19 | .on-primary-container { 20 | background-color: var(--md-sys-color-on-primary-container); 21 | } 22 | .on-primary-container-text { 23 | color: var(--md-sys-color-on-primary-container); 24 | } 25 | .secondary { 26 | background-color: var(--md-sys-color-secondary); 27 | } 28 | .secondary-text { 29 | color: var(--md-sys-color-secondary); 30 | } 31 | .on-secondary { 32 | background-color: var(--md-sys-color-on-secondary); 33 | } 34 | .on-secondary-text { 35 | color: var(--md-sys-color-on-secondary); 36 | } 37 | .secondary-container { 38 | background-color: var(--md-sys-color-secondary-container); 39 | } 40 | .secondary-container-text { 41 | color: var(--md-sys-color-secondary-container); 42 | } 43 | .on-secondary-container { 44 | background-color: var(--md-sys-color-on-secondary-container); 45 | } 46 | .on-secondary-container-text { 47 | color: var(--md-sys-color-on-secondary-container); 48 | } 49 | .tertiary { 50 | background-color: var(--md-sys-color-tertiary); 51 | } 52 | .tertiary-text { 53 | color: var(--md-sys-color-tertiary); 54 | } 55 | .on-tertiary { 56 | background-color: var(--md-sys-color-on-tertiary); 57 | } 58 | .on-tertiary-text { 59 | color: var(--md-sys-color-on-tertiary); 60 | } 61 | .tertiary-container { 62 | background-color: var(--md-sys-color-tertiary-container); 63 | } 64 | .tertiary-container-text { 65 | color: var(--md-sys-color-tertiary-container); 66 | } 67 | .on-tertiary-container { 68 | background-color: var(--md-sys-color-on-tertiary-container); 69 | } 70 | .on-tertiary-container-text { 71 | color: var(--md-sys-color-on-tertiary-container); 72 | } 73 | .error { 74 | background-color: var(--md-sys-color-error); 75 | } 76 | .error-text { 77 | color: var(--md-sys-color-error); 78 | } 79 | .error-container { 80 | background-color: var(--md-sys-color-error-container); 81 | } 82 | .error-container-text { 83 | color: var(--md-sys-color-error-container); 84 | } 85 | .on-error { 86 | background-color: var(--md-sys-color-on-error); 87 | } 88 | .on-error-text { 89 | color: var(--md-sys-color-on-error); 90 | } 91 | .on-error-container { 92 | background-color: var(--md-sys-color-on-error-container); 93 | } 94 | .on-error-container-text { 95 | color: var(--md-sys-color-on-error-container); 96 | } 97 | .background { 98 | background-color: var(--md-sys-color-background); 99 | } 100 | .background-text { 101 | color: var(--md-sys-color-background); 102 | } 103 | .on-background { 104 | background-color: var(--md-sys-color-on-background); 105 | } 106 | .on-background-text { 107 | color: var(--md-sys-color-on-background); 108 | } 109 | .surface { 110 | background-color: var(--md-sys-color-surface); 111 | } 112 | .surface-text { 113 | color: var(--md-sys-color-surface); 114 | } 115 | .on-surface { 116 | background-color: var(--md-sys-color-on-surface); 117 | } 118 | .on-surface-text { 119 | color: var(--md-sys-color-on-surface); 120 | } 121 | .surface-variant { 122 | background-color: var(--md-sys-color-surface-variant); 123 | } 124 | .surface-variant-text { 125 | color: var(--md-sys-color-surface-variant); 126 | } 127 | .on-surface-variant { 128 | background-color: var(--md-sys-color-on-surface-variant); 129 | } 130 | .on-surface-variant-text { 131 | color: var(--md-sys-color-on-surface-variant); 132 | } 133 | .outline { 134 | background-color: var(--md-sys-color-outline); 135 | } 136 | .outline-text { 137 | color: var(--md-sys-color-outline); 138 | } 139 | .inverse-on-surface { 140 | background-color: var(--md-sys-color-inverse-on-surface); 141 | } 142 | .inverse-on-surface-text { 143 | color: var(--md-sys-color-inverse-on-surface); 144 | } 145 | .inverse-surface { 146 | background-color: var(--md-sys-color-inverse-surface); 147 | } 148 | .inverse-surface-text { 149 | color: var(--md-sys-color-inverse-surface); 150 | } 151 | .inverse-primary { 152 | background-color: var(--md-sys-color-inverse-primary); 153 | } 154 | .inverse-primary-text { 155 | color: var(--md-sys-color-inverse-primary); 156 | } 157 | .shadow { 158 | background-color: var(--md-sys-color-shadow); 159 | } 160 | .shadow-text { 161 | color: var(--md-sys-color-shadow); 162 | } 163 | .surface-tint { 164 | background-color: var(--md-sys-color-surface-tint); 165 | } 166 | .surface-tint-text { 167 | color: var(--md-sys-color-surface-tint); 168 | } 169 | .outline-variant { 170 | background-color: var(--md-sys-color-outline-variant); 171 | } 172 | .outline-variant-text { 173 | color: var(--md-sys-color-outline-variant); 174 | } 175 | .scrim { 176 | background-color: var(--md-sys-color-scrim); 177 | } 178 | .scrim-text { 179 | color: var(--md-sys-color-scrim); 180 | } 181 | -------------------------------------------------------------------------------- /test_data/bookmarks_example.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Bookmarks 7 |

Bookmarks

8 |

9 |

Bookmarks bar

10 |

11 |

zaksid (Alexander Mahdybor) · GitHub 12 |

test

13 |

14 |

gh test 15 |
gh test 2 16 |
JavaScript modules - JavaScript | MDN 17 |

subfolder

18 |

19 |

gh sub 20 |

21 |

gh test 3 22 |

23 |

mdn 24 |

25 |

26 | -------------------------------------------------------------------------------- /src/js/pages/pageSettings.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable func-names, prefer-destructuring */ 2 | /* global mdc, Mustache */ 3 | 4 | import { getDefaultExtensionSettings, getExtensionSettings, setExtensionSettings } from '../utils/index.js'; 5 | import { MainNavBtnIcons, MainNavBtnClasses, Theme } from '../constants.js'; 6 | 7 | const { MDCSwitch } = mdc.switchControl; 8 | const { MDCTextField } = mdc.textField; 9 | 10 | export default async function initSettingsPage(browserInstance) { 11 | const appBarNavBtn = document.querySelector('#app-bar-nav-btn'); 12 | const appBarTitle = document.querySelector('#app-bar-title'); 13 | 14 | appBarNavBtn.innerHTML = MainNavBtnIcons.BACK; 15 | appBarNavBtn.classList.add(MainNavBtnClasses.IS_MENU_CONTENT); 16 | 17 | appBarTitle.innerHTML = browserInstance.i18n.getMessage('menu_settings'); 18 | 19 | const isFirefox = navigator.userAgent.includes('Firefox'); 20 | const userSettings = await getExtensionSettings(browserInstance); 21 | const onOffSettings = []; 22 | 23 | if (isFirefox) { 24 | onOffSettings.push({ 25 | id: 'showSeparators', 26 | name: browserInstance.i18n.getMessage('settingShowSeparators'), 27 | description: browserInstance.i18n.getMessage('settingShowSeparatorsDescription'), 28 | value: userSettings.showSeparators, 29 | }); 30 | } 31 | 32 | const settingsTemplateResponse = await fetch('templates/settings-page.mustache'); 33 | const settingsTemplate = await settingsTemplateResponse.text(); 34 | 35 | document.querySelector('#main-content').innerHTML = await Mustache.render(settingsTemplate, { 36 | onOffSettings, 37 | ignoredUrls: userSettings.ignoredUrls, 38 | selectedTheme: userSettings.theme, 39 | theme: { 40 | isDark: userSettings.theme === Theme.DARK, 41 | isLight: userSettings.theme === Theme.LIGHT, 42 | isSystem: userSettings.theme === Theme.SYSTEM, 43 | }, 44 | i18n: { 45 | btn_add: browserInstance.i18n.getMessage('btn_add'), 46 | btn_resetSettings: browserInstance.i18n.getMessage('btn_resetSettings'), 47 | input_hint_ignoredUrlRegex: browserInstance.i18n.getMessage('input_hint_ignoredUrlRegex'), 48 | settingIgnoredPatterns: browserInstance.i18n.getMessage('settingIgnoredPatterns'), 49 | settingIgnoredPatternsDescription: browserInstance.i18n.getMessage('settingIgnoredPatternsDescription'), 50 | settingTheme: browserInstance.i18n.getMessage('settingTheme'), 51 | settingThemeDark: browserInstance.i18n.getMessage('settingThemeDark'), 52 | settingThemeLight: browserInstance.i18n.getMessage('settingThemeLight'), 53 | settingThemeSystem: browserInstance.i18n.getMessage('settingThemeSystem'), 54 | }, 55 | }); 56 | 57 | const settingsPage = document.querySelector('#settings-page'); 58 | settingsPage.querySelectorAll('.mdc-text-field').forEach((inputElement) => { 59 | // eslint-disable-next-line no-new 60 | new MDCTextField(inputElement); 61 | }); 62 | 63 | const form = settingsPage.querySelector('#ignored-patterns-form'); 64 | form.addEventListener('submit', async function (event) { 65 | event.preventDefault(); 66 | 67 | const formData = new FormData(this); 68 | const ignoredUrls = formData.get('ignoredUrls'); 69 | // TODO: Move to mustache template to avoid duplication? 70 | const chipMarkup = ` 71 | 72 | 73 | 80 | 81 | `; 82 | 83 | const newChip = document.createElement('span'); 84 | newChip.innerHTML = chipMarkup; 85 | document.querySelector('.mdc-chip-set__chips').append(newChip); 86 | 87 | userSettings.ignoredUrls = [...userSettings.ignoredUrls, ignoredUrls]; 88 | await setExtensionSettings(browserInstance, userSettings); 89 | 90 | this.reset(); 91 | }); 92 | 93 | settingsPage.querySelectorAll('.mdc-switch').forEach((switchElem) => { 94 | const switchControl = new MDCSwitch(switchElem); 95 | switchControl.selected = userSettings[switchControl.root.name]; 96 | 97 | switchElem.addEventListener('click', async (event) => { 98 | const settingsElem = event.currentTarget; 99 | if (settingsElem.name in userSettings) { 100 | userSettings[settingsElem.name] = settingsElem.attributes['aria-checked'].value === 'true'; 101 | await setExtensionSettings(browserInstance, userSettings); 102 | } 103 | }); 104 | }); 105 | 106 | const themeButtons = settingsPage.querySelectorAll('.theme-btn'); 107 | 108 | // Handle theme switching 109 | themeButtons.forEach((elem) => { 110 | elem.addEventListener('click', async (event) => { 111 | const currentButton = event.currentTarget; 112 | const val = currentButton.dataset.theme; 113 | 114 | userSettings.theme = val; 115 | document.documentElement.className = val === Theme.SYSTEM ? '' : val; 116 | 117 | await setExtensionSettings(browserInstance, userSettings); 118 | themeButtons.forEach((btn) => btn.classList.remove('selected')); 119 | currentButton.classList.add('selected'); 120 | }); 121 | }); 122 | 123 | const removeIgnoredUrl = async function () { 124 | const urlPattern = this.dataset.val; 125 | const index = userSettings.ignoredUrls.indexOf(urlPattern); 126 | 127 | userSettings.ignoredUrls.splice(index, 1); 128 | await setExtensionSettings(browserInstance, userSettings); 129 | 130 | this.closest('.mdc-chip').remove(); 131 | }; 132 | 133 | settingsPage.addEventListener('click', function (event) { 134 | // loop parent nodes from the target to the delegation node 135 | for (let target = event.target; target && target !== this; target = target.parentNode) { 136 | if (target.classList.contains('remove-ignored-url')) { 137 | removeIgnoredUrl.call(target, event); 138 | break; 139 | } 140 | } 141 | }, false); 142 | 143 | settingsPage.querySelector('#reset-settings').addEventListener('click', async () => { 144 | await setExtensionSettings(browserInstance, getDefaultExtensionSettings()); 145 | await initSettingsPage(browserInstance); 146 | document.documentElement.className = ''; 147 | }); 148 | } 149 | -------------------------------------------------------------------------------- /src/templates/settings-page.mustache: -------------------------------------------------------------------------------- 1 |

2 | 7 | 128 |
-------------------------------------------------------------------------------- /src/css/generated_theme/typography.module.css: -------------------------------------------------------------------------------- 1 | .display-large{ 2 | font-family: var(--md-sys-typescale-display-large-font-family-name); 3 | font-style: var(--md-sys-typescale-display-large-font-family-style); 4 | font-weight: var(--md-sys-typescale-display-large-font-weight); 5 | font-size: var(--md-sys-typescale-display-large-font-size); 6 | letter-spacing: var(--md-sys-typescale-display-large-tracking); 7 | line-height: var(--md-sys-typescale-display-large-height); 8 | text-transform: var(--md-sys-typescale-display-large-text-transform); 9 | text-decoration: var(--md-sys-typescale-display-large-text-decoration); 10 | } 11 | .display-medium{ 12 | font-family: var(--md-sys-typescale-display-medium-font-family-name); 13 | font-style: var(--md-sys-typescale-display-medium-font-family-style); 14 | font-weight: var(--md-sys-typescale-display-medium-font-weight); 15 | font-size: var(--md-sys-typescale-display-medium-font-size); 16 | letter-spacing: var(--md-sys-typescale-display-medium-tracking); 17 | line-height: var(--md-sys-typescale-display-medium-height); 18 | text-transform: var(--md-sys-typescale-display-medium-text-transform); 19 | text-decoration: var(--md-sys-typescale-display-medium-text-decoration); 20 | } 21 | .display-small{ 22 | font-family: var(--md-sys-typescale-display-small-font-family-name); 23 | font-style: var(--md-sys-typescale-display-small-font-family-style); 24 | font-weight: var(--md-sys-typescale-display-small-font-weight); 25 | font-size: var(--md-sys-typescale-display-small-font-size); 26 | letter-spacing: var(--md-sys-typescale-display-small-tracking); 27 | line-height: var(--md-sys-typescale-display-small-height); 28 | text-transform: var(--md-sys-typescale-display-small-text-transform); 29 | text-decoration: var(--md-sys-typescale-display-small-text-decoration); 30 | } 31 | .headline-large{ 32 | font-family: var(--md-sys-typescale-headline-large-font-family-name); 33 | font-style: var(--md-sys-typescale-headline-large-font-family-style); 34 | font-weight: var(--md-sys-typescale-headline-large-font-weight); 35 | font-size: var(--md-sys-typescale-headline-large-font-size); 36 | letter-spacing: var(--md-sys-typescale-headline-large-tracking); 37 | line-height: var(--md-sys-typescale-headline-large-height); 38 | text-transform: var(--md-sys-typescale-headline-large-text-transform); 39 | text-decoration: var(--md-sys-typescale-headline-large-text-decoration); 40 | } 41 | .headline-medium{ 42 | font-family: var(--md-sys-typescale-headline-medium-font-family-name); 43 | font-style: var(--md-sys-typescale-headline-medium-font-family-style); 44 | font-weight: var(--md-sys-typescale-headline-medium-font-weight); 45 | font-size: var(--md-sys-typescale-headline-medium-font-size); 46 | letter-spacing: var(--md-sys-typescale-headline-medium-tracking); 47 | line-height: var(--md-sys-typescale-headline-medium-height); 48 | text-transform: var(--md-sys-typescale-headline-medium-text-transform); 49 | text-decoration: var(--md-sys-typescale-headline-medium-text-decoration); 50 | } 51 | .headline-small{ 52 | font-family: var(--md-sys-typescale-headline-small-font-family-name); 53 | font-style: var(--md-sys-typescale-headline-small-font-family-style); 54 | font-weight: var(--md-sys-typescale-headline-small-font-weight); 55 | font-size: var(--md-sys-typescale-headline-small-font-size); 56 | letter-spacing: var(--md-sys-typescale-headline-small-tracking); 57 | line-height: var(--md-sys-typescale-headline-small-height); 58 | text-transform: var(--md-sys-typescale-headline-small-text-transform); 59 | text-decoration: var(--md-sys-typescale-headline-small-text-decoration); 60 | } 61 | .body-large{ 62 | font-family: var(--md-sys-typescale-body-large-font-family-name); 63 | font-style: var(--md-sys-typescale-body-large-font-family-style); 64 | font-weight: var(--md-sys-typescale-body-large-font-weight); 65 | font-size: var(--md-sys-typescale-body-large-font-size); 66 | letter-spacing: var(--md-sys-typescale-body-large-tracking); 67 | line-height: var(--md-sys-typescale-body-large-height); 68 | text-transform: var(--md-sys-typescale-body-large-text-transform); 69 | text-decoration: var(--md-sys-typescale-body-large-text-decoration); 70 | } 71 | .body-medium{ 72 | font-family: var(--md-sys-typescale-body-medium-font-family-name); 73 | font-style: var(--md-sys-typescale-body-medium-font-family-style); 74 | font-weight: var(--md-sys-typescale-body-medium-font-weight); 75 | font-size: var(--md-sys-typescale-body-medium-font-size); 76 | letter-spacing: var(--md-sys-typescale-body-medium-tracking); 77 | line-height: var(--md-sys-typescale-body-medium-height); 78 | text-transform: var(--md-sys-typescale-body-medium-text-transform); 79 | text-decoration: var(--md-sys-typescale-body-medium-text-decoration); 80 | } 81 | .body-small{ 82 | font-family: var(--md-sys-typescale-body-small-font-family-name); 83 | font-style: var(--md-sys-typescale-body-small-font-family-style); 84 | font-weight: var(--md-sys-typescale-body-small-font-weight); 85 | font-size: var(--md-sys-typescale-body-small-font-size); 86 | letter-spacing: var(--md-sys-typescale-body-small-tracking); 87 | line-height: var(--md-sys-typescale-body-small-height); 88 | text-transform: var(--md-sys-typescale-body-small-text-transform); 89 | text-decoration: var(--md-sys-typescale-body-small-text-decoration); 90 | } 91 | .label-large{ 92 | font-family: var(--md-sys-typescale-label-large-font-family-name); 93 | font-style: var(--md-sys-typescale-label-large-font-family-style); 94 | font-weight: var(--md-sys-typescale-label-large-font-weight); 95 | font-size: var(--md-sys-typescale-label-large-font-size); 96 | letter-spacing: var(--md-sys-typescale-label-large-tracking); 97 | line-height: var(--md-sys-typescale-label-large-height); 98 | text-transform: var(--md-sys-typescale-label-large-text-transform); 99 | text-decoration: var(--md-sys-typescale-label-large-text-decoration); 100 | } 101 | .label-medium{ 102 | font-family: var(--md-sys-typescale-label-medium-font-family-name); 103 | font-style: var(--md-sys-typescale-label-medium-font-family-style); 104 | font-weight: var(--md-sys-typescale-label-medium-font-weight); 105 | font-size: var(--md-sys-typescale-label-medium-font-size); 106 | letter-spacing: var(--md-sys-typescale-label-medium-tracking); 107 | line-height: var(--md-sys-typescale-label-medium-height); 108 | text-transform: var(--md-sys-typescale-label-medium-text-transform); 109 | text-decoration: var(--md-sys-typescale-label-medium-text-decoration); 110 | } 111 | .label-small{ 112 | font-family: var(--md-sys-typescale-label-small-font-family-name); 113 | font-style: var(--md-sys-typescale-label-small-font-family-style); 114 | font-weight: var(--md-sys-typescale-label-small-font-weight); 115 | font-size: var(--md-sys-typescale-label-small-font-size); 116 | letter-spacing: var(--md-sys-typescale-label-small-tracking); 117 | line-height: var(--md-sys-typescale-label-small-height); 118 | text-transform: var(--md-sys-typescale-label-small-text-transform); 119 | text-decoration: var(--md-sys-typescale-label-small-text-decoration); 120 | } 121 | .title-large{ 122 | font-family: var(--md-sys-typescale-title-large-font-family-name); 123 | font-style: var(--md-sys-typescale-title-large-font-family-style); 124 | font-weight: var(--md-sys-typescale-title-large-font-weight); 125 | font-size: var(--md-sys-typescale-title-large-font-size); 126 | letter-spacing: var(--md-sys-typescale-title-large-tracking); 127 | line-height: var(--md-sys-typescale-title-large-height); 128 | text-transform: var(--md-sys-typescale-title-large-text-transform); 129 | text-decoration: var(--md-sys-typescale-title-large-text-decoration); 130 | } 131 | .title-medium{ 132 | font-family: var(--md-sys-typescale-title-medium-font-family-name); 133 | font-style: var(--md-sys-typescale-title-medium-font-family-style); 134 | font-weight: var(--md-sys-typescale-title-medium-font-weight); 135 | font-size: var(--md-sys-typescale-title-medium-font-size); 136 | letter-spacing: var(--md-sys-typescale-title-medium-tracking); 137 | line-height: var(--md-sys-typescale-title-medium-height); 138 | text-transform: var(--md-sys-typescale-title-medium-text-transform); 139 | text-decoration: var(--md-sys-typescale-title-medium-text-decoration); 140 | } 141 | .title-small{ 142 | font-family: var(--md-sys-typescale-title-small-font-family-name); 143 | font-style: var(--md-sys-typescale-title-small-font-family-style); 144 | font-weight: var(--md-sys-typescale-title-small-font-weight); 145 | font-size: var(--md-sys-typescale-title-small-font-size); 146 | letter-spacing: var(--md-sys-typescale-title-small-tracking); 147 | line-height: var(--md-sys-typescale-title-small-height); 148 | text-transform: var(--md-sys-typescale-title-small-text-transform); 149 | text-decoration: var(--md-sys-typescale-title-small-text-decoration); 150 | } 151 | -------------------------------------------------------------------------------- /src/lib/js/mustache.min.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory():typeof define==="function"&&define.amd?define(factory):(global=global||self,global.Mustache=factory())})(this,function(){"use strict";var objectToString=Object.prototype.toString;var isArray=Array.isArray||function isArrayPolyfill(object){return objectToString.call(object)==="[object Array]"};function isFunction(object){return typeof object==="function"}function typeStr(obj){return isArray(obj)?"array":typeof obj}function escapeRegExp(string){return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&")}function hasProperty(obj,propName){return obj!=null&&typeof obj==="object"&&propName in obj}function primitiveHasOwnProperty(primitive,propName){return primitive!=null&&typeof primitive!=="object"&&primitive.hasOwnProperty&&primitive.hasOwnProperty(propName)}var regExpTest=RegExp.prototype.test;function testRegExp(re,string){return regExpTest.call(re,string)}var nonSpaceRe=/\S/;function isWhitespace(string){return!testRegExp(nonSpaceRe,string)}var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/","`":"`","=":"="};function escapeHtml(string){return String(string).replace(/[&<>"'`=\/]/g,function fromEntityMap(s){return entityMap[s]})}var whiteRe=/\s*/;var spaceRe=/\s+/;var equalsRe=/\s*=/;var curlyRe=/\s*\}/;var tagRe=/#|\^|\/|>|\{|&|=|!/;function parseTemplate(template,tags){if(!template)return[];var lineHasNonSpace=false;var sections=[];var tokens=[];var spaces=[];var hasTag=false;var nonSpace=false;var indentation="";var tagIndex=0;function stripSpace(){if(hasTag&&!nonSpace){while(spaces.length)delete tokens[spaces.pop()]}else{spaces=[]}hasTag=false;nonSpace=false}var openingTagRe,closingTagRe,closingCurlyRe;function compileTags(tagsToCompile){if(typeof tagsToCompile==="string")tagsToCompile=tagsToCompile.split(spaceRe,2);if(!isArray(tagsToCompile)||tagsToCompile.length!==2)throw new Error("Invalid tags: "+tagsToCompile);openingTagRe=new RegExp(escapeRegExp(tagsToCompile[0])+"\\s*");closingTagRe=new RegExp("\\s*"+escapeRegExp(tagsToCompile[1]));closingCurlyRe=new RegExp("\\s*"+escapeRegExp("}"+tagsToCompile[1]))}compileTags(tags||mustache.tags);var scanner=new Scanner(template);var start,type,value,chr,token,openSection;while(!scanner.eos()){start=scanner.pos;value=scanner.scanUntil(openingTagRe);if(value){for(var i=0,valueLength=value.length;i"){token=[type,value,start,scanner.pos,indentation,tagIndex,lineHasNonSpace]}else{token=[type,value,start,scanner.pos]}tagIndex++;tokens.push(token);if(type==="#"||type==="^"){sections.push(token)}else if(type==="/"){openSection=sections.pop();if(!openSection)throw new Error('Unopened section "'+value+'" at '+start);if(openSection[1]!==value)throw new Error('Unclosed section "'+openSection[1]+'" at '+start)}else if(type==="name"||type==="{"||type==="&"){nonSpace=true}else if(type==="="){compileTags(value)}}stripSpace();openSection=sections.pop();if(openSection)throw new Error('Unclosed section "'+openSection[1]+'" at '+scanner.pos);return nestTokens(squashTokens(tokens))}function squashTokens(tokens){var squashedTokens=[];var token,lastToken;for(var i=0,numTokens=tokens.length;i0?sections[sections.length-1][4]:nestedTokens;break;default:collector.push(token)}}return nestedTokens}function Scanner(string){this.string=string;this.tail=string;this.pos=0}Scanner.prototype.eos=function eos(){return this.tail===""};Scanner.prototype.scan=function scan(re){var match=this.tail.match(re);if(!match||match.index!==0)return"";var string=match[0];this.tail=this.tail.substring(string.length);this.pos+=string.length;return string};Scanner.prototype.scanUntil=function scanUntil(re){var index=this.tail.search(re),match;switch(index){case-1:match=this.tail;this.tail="";break;case 0:match="";break;default:match=this.tail.substring(0,index);this.tail=this.tail.substring(index)}this.pos+=match.length;return match};function Context(view,parentContext){this.view=view;this.cache={".":this.view};this.parent=parentContext}Context.prototype.push=function push(view){return new Context(view,this)};Context.prototype.lookup=function lookup(name){var cache=this.cache;var value;if(cache.hasOwnProperty(name)){value=cache[name]}else{var context=this,intermediateValue,names,index,lookupHit=false;while(context){if(name.indexOf(".")>0){intermediateValue=context.view;names=name.split(".");index=0;while(intermediateValue!=null&&index")value=this.renderPartial(token,context,partials,config);else if(symbol==="&")value=this.unescapedValue(token,context);else if(symbol==="name")value=this.escapedValue(token,context,config);else if(symbol==="text")value=this.rawValue(token);if(value!==undefined)buffer+=value}return buffer};Writer.prototype.renderSection=function renderSection(token,context,partials,originalTemplate,config){var self=this;var buffer="";var value=context.lookup(token[1]);function subRender(template){return self.render(template,context,partials,config)}if(!value)return;if(isArray(value)){for(var j=0,valueLength=value.length;j0||!lineHasNonSpace)){partialByNl[i]=filteredIndentation+partialByNl[i]}}return partialByNl.join("\n")};Writer.prototype.renderPartial=function renderPartial(token,context,partials,config){if(!partials)return;var tags=this.getConfigTags(config);var value=isFunction(partials)?partials(token[1]):partials[token[1]];if(value!=null){var lineHasNonSpace=token[6];var tagIndex=token[5];var indentation=token[4];var indentedValue=value;if(tagIndex==0&&indentation){indentedValue=this.indentPartial(value,indentation,lineHasNonSpace)}var tokens=this.parse(indentedValue,tags);return this.renderTokens(tokens,context,partials,indentedValue,config)}};Writer.prototype.unescapedValue=function unescapedValue(token,context){var value=context.lookup(token[1]);if(value!=null)return value};Writer.prototype.escapedValue=function escapedValue(token,context,config){var escape=this.getConfigEscape(config)||mustache.escape;var value=context.lookup(token[1]);if(value!=null)return typeof value==="number"&&escape===mustache.escape?String(value):escape(value)};Writer.prototype.rawValue=function rawValue(token){return token[1]};Writer.prototype.getConfigTags=function getConfigTags(config){if(isArray(config)){return config}else if(config&&typeof config==="object"){return config.tags}else{return undefined}};Writer.prototype.getConfigEscape=function getConfigEscape(config){if(config&&typeof config==="object"&&!isArray(config)){return config.escape}else{return undefined}};var mustache={name:"mustache.js",version:"4.2.0",tags:["{{","}}"],clearCache:undefined,escape:undefined,parse:undefined,render:undefined,Scanner:undefined,Context:undefined,Writer:undefined,set templateCache(cache){defaultWriter.templateCache=cache},get templateCache(){return defaultWriter.templateCache}};var defaultWriter=new Writer;mustache.clearCache=function clearCache(){return defaultWriter.clearCache()};mustache.parse=function parse(template,tags){return defaultWriter.parse(template,tags)};mustache.render=function render(template,view,partials,config){if(typeof template!=="string"){throw new TypeError('Invalid template! Template should be a "string" '+'but "'+typeStr(template)+'" was given as the first '+"argument for mustache#render(template, view, partials)")}return defaultWriter.render(template,view,partials,config)};mustache.escape=escapeHtml;mustache.Scanner=Scanner;mustache.Context=Context;mustache.Writer=Writer;return mustache}); 2 | -------------------------------------------------------------------------------- /src/css/generated_theme/tokens.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --md-source: #496500; 3 | /* primary */ 4 | --md-ref-palette-primary0: #000000; 5 | --md-ref-palette-primary10: #141f00; 6 | --md-ref-palette-primary20: #253600; 7 | --md-ref-palette-primary25: #2e4100; 8 | --md-ref-palette-primary30: #374e00; 9 | --md-ref-palette-primary35: #415a00; 10 | --md-ref-palette-primary40: #4b6703; 11 | --md-ref-palette-primary50: #638120; 12 | --md-ref-palette-primary60: #7c9b39; 13 | --md-ref-palette-primary70: #96b751; 14 | --md-ref-palette-primary80: #b0d369; 15 | --md-ref-palette-primary90: #ccef82; 16 | --md-ref-palette-primary95: #dafe8e; 17 | --md-ref-palette-primary98: #f2ffd0; 18 | --md-ref-palette-primary99: #faffe5; 19 | --md-ref-palette-primary100: #ffffff; 20 | /* secondary */ 21 | --md-ref-palette-secondary0: #000000; 22 | --md-ref-palette-secondary10: #171e09; 23 | --md-ref-palette-secondary20: #2c331d; 24 | --md-ref-palette-secondary25: #373e27; 25 | --md-ref-palette-secondary30: #424a31; 26 | --md-ref-palette-secondary35: #4e553c; 27 | --md-ref-palette-secondary40: #5a6148; 28 | --md-ref-palette-secondary50: #727a5f; 29 | --md-ref-palette-secondary60: #8c9477; 30 | --md-ref-palette-secondary70: #a7af90; 31 | --md-ref-palette-secondary80: #c2caaa; 32 | --md-ref-palette-secondary90: #dee6c5; 33 | --md-ref-palette-secondary95: #ecf5d3; 34 | --md-ref-palette-secondary98: #f5fddb; 35 | --md-ref-palette-secondary99: #faffe5; 36 | --md-ref-palette-secondary100: #ffffff; 37 | /* tertiary */ 38 | --md-ref-palette-tertiary0: #000000; 39 | --md-ref-palette-tertiary10: #00201d; 40 | --md-ref-palette-tertiary20: #013732; 41 | --md-ref-palette-tertiary25: #12423d; 42 | --md-ref-palette-tertiary30: #204e48; 43 | --md-ref-palette-tertiary35: #2c5a54; 44 | --md-ref-palette-tertiary40: #396660; 45 | --md-ref-palette-tertiary50: #527f79; 46 | --md-ref-palette-tertiary60: #6b9992; 47 | --md-ref-palette-tertiary70: #86b4ad; 48 | --md-ref-palette-tertiary80: #a0d0c8; 49 | --md-ref-palette-tertiary90: #bcece4; 50 | --md-ref-palette-tertiary95: #cafaf2; 51 | --md-ref-palette-tertiary98: #e4fffa; 52 | --md-ref-palette-tertiary99: #f2fffc; 53 | --md-ref-palette-tertiary100: #ffffff; 54 | /* neutral */ 55 | --md-ref-palette-neutral0: #000000; 56 | --md-ref-palette-neutral10: #1b1c17; 57 | --md-ref-palette-neutral20: #30312c; 58 | --md-ref-palette-neutral25: #3b3c36; 59 | --md-ref-palette-neutral30: #474742; 60 | --md-ref-palette-neutral35: #52534d; 61 | --md-ref-palette-neutral40: #5e5f59; 62 | --md-ref-palette-neutral50: #777771; 63 | --md-ref-palette-neutral60: #91918a; 64 | --md-ref-palette-neutral70: #acaba4; 65 | --md-ref-palette-neutral80: #c8c7bf; 66 | --md-ref-palette-neutral90: #e4e3db; 67 | --md-ref-palette-neutral95: #f2f1e9; 68 | --md-ref-palette-neutral98: #fbf9f1; 69 | --md-ref-palette-neutral99: #fefcf4; 70 | --md-ref-palette-neutral100: #ffffff; 71 | /* neutral-variant */ 72 | --md-ref-palette-neutral-variant0: #000000; 73 | --md-ref-palette-neutral-variant10: #1a1d13; 74 | --md-ref-palette-neutral-variant20: #2f3227; 75 | --md-ref-palette-neutral-variant25: #3a3d32; 76 | --md-ref-palette-neutral-variant30: #45483c; 77 | --md-ref-palette-neutral-variant35: #515448; 78 | --md-ref-palette-neutral-variant40: #5d6053; 79 | --md-ref-palette-neutral-variant50: #76786b; 80 | --md-ref-palette-neutral-variant60: #909284; 81 | --md-ref-palette-neutral-variant70: #aaad9e; 82 | --md-ref-palette-neutral-variant80: #c6c8b9; 83 | --md-ref-palette-neutral-variant90: #e2e4d4; 84 | --md-ref-palette-neutral-variant95: #f0f2e2; 85 | --md-ref-palette-neutral-variant98: #f9fbea; 86 | --md-ref-palette-neutral-variant99: #fcfeed; 87 | --md-ref-palette-neutral-variant100: #ffffff; 88 | /* error */ 89 | --md-ref-palette-error0: #000000; 90 | --md-ref-palette-error10: #410002; 91 | --md-ref-palette-error20: #690005; 92 | --md-ref-palette-error25: #7e0007; 93 | --md-ref-palette-error30: #93000a; 94 | --md-ref-palette-error35: #a80710; 95 | --md-ref-palette-error40: #ba1a1a; 96 | --md-ref-palette-error50: #de3730; 97 | --md-ref-palette-error60: #ff5449; 98 | --md-ref-palette-error70: #ff897d; 99 | --md-ref-palette-error80: #ffb4ab; 100 | --md-ref-palette-error90: #ffdad6; 101 | --md-ref-palette-error95: #ffedea; 102 | --md-ref-palette-error98: #fff8f7; 103 | --md-ref-palette-error99: #fffbff; 104 | --md-ref-palette-error100: #ffffff; 105 | /* light */ 106 | --md-sys-color-primary-light: #4b6703; 107 | --md-sys-color-on-primary-light: #ffffff; 108 | --md-sys-color-primary-container-light: #ccef82; 109 | --md-sys-color-on-primary-container-light: #141f00; 110 | --md-sys-color-secondary-light: #5a6148; 111 | --md-sys-color-on-secondary-light: #ffffff; 112 | --md-sys-color-secondary-container-light: #dee6c5; 113 | --md-sys-color-on-secondary-container-light: #171e09; 114 | --md-sys-color-tertiary-light: #396660; 115 | --md-sys-color-on-tertiary-light: #ffffff; 116 | --md-sys-color-tertiary-container-light: #bcece4; 117 | --md-sys-color-on-tertiary-container-light: #00201d; 118 | --md-sys-color-error-light: #ba1a1a; 119 | --md-sys-color-error-container-light: #ffdad6; 120 | --md-sys-color-on-error-light: #ffffff; 121 | --md-sys-color-on-error-container-light: #410002; 122 | --md-sys-color-background-light: #fefcf4; 123 | --md-sys-color-on-background-light: #1b1c17; 124 | --md-sys-color-surface-light: #fefcf4; 125 | --md-sys-color-on-surface-light: #1b1c17; 126 | --md-sys-color-surface-variant-light: #e2e4d4; 127 | --md-sys-color-on-surface-variant-light: #45483c; 128 | --md-sys-color-outline-light: #76786b; 129 | --md-sys-color-inverse-on-surface-light: #f2f1e9; 130 | --md-sys-color-inverse-surface-light: #30312c; 131 | --md-sys-color-inverse-primary-light: #b0d369; 132 | --md-sys-color-shadow-light: #000000; 133 | --md-sys-color-surface-tint-light: #4b6703; 134 | --md-sys-color-outline-variant-light: #c6c8b9; 135 | --md-sys-color-scrim-light: #000000; 136 | /* dark */ 137 | --md-sys-color-primary-dark: #b0d369; 138 | --md-sys-color-on-primary-dark: #253600; 139 | --md-sys-color-primary-container-dark: #374e00; 140 | --md-sys-color-on-primary-container-dark: #ccef82; 141 | --md-sys-color-secondary-dark: #c2caaa; 142 | --md-sys-color-on-secondary-dark: #2c331d; 143 | --md-sys-color-secondary-container-dark: #424a31; 144 | --md-sys-color-on-secondary-container-dark: #dee6c5; 145 | --md-sys-color-tertiary-dark: #a0d0c8; 146 | --md-sys-color-on-tertiary-dark: #013732; 147 | --md-sys-color-tertiary-container-dark: #204e48; 148 | --md-sys-color-on-tertiary-container-dark: #bcece4; 149 | --md-sys-color-error-dark: #ffb4ab; 150 | --md-sys-color-error-container-dark: #93000a; 151 | --md-sys-color-on-error-dark: #690005; 152 | --md-sys-color-on-error-container-dark: #ffdad6; 153 | --md-sys-color-background-dark: #1b1c17; 154 | --md-sys-color-on-background-dark: #e4e3db; 155 | --md-sys-color-surface-dark: #1b1c17; 156 | --md-sys-color-on-surface-dark: #e4e3db; 157 | --md-sys-color-surface-variant-dark: #45483c; 158 | --md-sys-color-on-surface-variant-dark: #c6c8b9; 159 | --md-sys-color-outline-dark: #909284; 160 | --md-sys-color-inverse-on-surface-dark: #1b1c17; 161 | --md-sys-color-inverse-surface-dark: #e4e3db; 162 | --md-sys-color-inverse-primary-dark: #4b6703; 163 | --md-sys-color-shadow-dark: #000000; 164 | --md-sys-color-surface-tint-dark: #b0d369; 165 | --md-sys-color-outline-variant-dark: #45483c; 166 | --md-sys-color-scrim-dark: #000000; 167 | /* display - large */ 168 | --md-sys-typescale-display-large-font-family-name: Roboto; 169 | --md-sys-typescale-display-large-font-family-style: Regular; 170 | --md-sys-typescale-display-large-font-weight: 400px; 171 | --md-sys-typescale-display-large-font-size: 57px; 172 | --md-sys-typescale-display-large-line-height: 64px; 173 | --md-sys-typescale-display-large-letter-spacing: -0.25px; 174 | /* display - medium */ 175 | --md-sys-typescale-display-medium-font-family-name: Roboto; 176 | --md-sys-typescale-display-medium-font-family-style: Regular; 177 | --md-sys-typescale-display-medium-font-weight: 400px; 178 | --md-sys-typescale-display-medium-font-size: 45px; 179 | --md-sys-typescale-display-medium-line-height: 52px; 180 | --md-sys-typescale-display-medium-letter-spacing: 0px; 181 | /* display - small */ 182 | --md-sys-typescale-display-small-font-family-name: Roboto; 183 | --md-sys-typescale-display-small-font-family-style: Regular; 184 | --md-sys-typescale-display-small-font-weight: 400px; 185 | --md-sys-typescale-display-small-font-size: 36px; 186 | --md-sys-typescale-display-small-line-height: 44px; 187 | --md-sys-typescale-display-small-letter-spacing: 0px; 188 | /* headline - large */ 189 | --md-sys-typescale-headline-large-font-family-name: Roboto; 190 | --md-sys-typescale-headline-large-font-family-style: Regular; 191 | --md-sys-typescale-headline-large-font-weight: 400px; 192 | --md-sys-typescale-headline-large-font-size: 32px; 193 | --md-sys-typescale-headline-large-line-height: 40px; 194 | --md-sys-typescale-headline-large-letter-spacing: 0px; 195 | /* headline - medium */ 196 | --md-sys-typescale-headline-medium-font-family-name: Roboto; 197 | --md-sys-typescale-headline-medium-font-family-style: Regular; 198 | --md-sys-typescale-headline-medium-font-weight: 400px; 199 | --md-sys-typescale-headline-medium-font-size: 28px; 200 | --md-sys-typescale-headline-medium-line-height: 36px; 201 | --md-sys-typescale-headline-medium-letter-spacing: 0px; 202 | /* headline - small */ 203 | --md-sys-typescale-headline-small-font-family-name: Roboto; 204 | --md-sys-typescale-headline-small-font-family-style: Regular; 205 | --md-sys-typescale-headline-small-font-weight: 400px; 206 | --md-sys-typescale-headline-small-font-size: 24px; 207 | --md-sys-typescale-headline-small-line-height: 32px; 208 | --md-sys-typescale-headline-small-letter-spacing: 0px; 209 | /* body - large */ 210 | --md-sys-typescale-body-large-font-family-name: Roboto; 211 | --md-sys-typescale-body-large-font-family-style: Regular; 212 | --md-sys-typescale-body-large-font-weight: 400px; 213 | --md-sys-typescale-body-large-font-size: 16px; 214 | --md-sys-typescale-body-large-line-height: 24px; 215 | --md-sys-typescale-body-large-letter-spacing: 0.50px; 216 | /* body - medium */ 217 | --md-sys-typescale-body-medium-font-family-name: Roboto; 218 | --md-sys-typescale-body-medium-font-family-style: Regular; 219 | --md-sys-typescale-body-medium-font-weight: 400px; 220 | --md-sys-typescale-body-medium-font-size: 14px; 221 | --md-sys-typescale-body-medium-line-height: 20px; 222 | --md-sys-typescale-body-medium-letter-spacing: 0.25px; 223 | /* body - small */ 224 | --md-sys-typescale-body-small-font-family-name: Roboto; 225 | --md-sys-typescale-body-small-font-family-style: Regular; 226 | --md-sys-typescale-body-small-font-weight: 400px; 227 | --md-sys-typescale-body-small-font-size: 12px; 228 | --md-sys-typescale-body-small-line-height: 16px; 229 | --md-sys-typescale-body-small-letter-spacing: 0.40px; 230 | /* label - large */ 231 | --md-sys-typescale-label-large-font-family-name: Roboto; 232 | --md-sys-typescale-label-large-font-family-style: Medium; 233 | --md-sys-typescale-label-large-font-weight: 500px; 234 | --md-sys-typescale-label-large-font-size: 14px; 235 | --md-sys-typescale-label-large-line-height: 20px; 236 | --md-sys-typescale-label-large-letter-spacing: 0.10px; 237 | /* label - medium */ 238 | --md-sys-typescale-label-medium-font-family-name: Roboto; 239 | --md-sys-typescale-label-medium-font-family-style: Medium; 240 | --md-sys-typescale-label-medium-font-weight: 500px; 241 | --md-sys-typescale-label-medium-font-size: 12px; 242 | --md-sys-typescale-label-medium-line-height: 16px; 243 | --md-sys-typescale-label-medium-letter-spacing: 0.50px; 244 | /* label - small */ 245 | --md-sys-typescale-label-small-font-family-name: Roboto; 246 | --md-sys-typescale-label-small-font-family-style: Medium; 247 | --md-sys-typescale-label-small-font-weight: 500px; 248 | --md-sys-typescale-label-small-font-size: 11px; 249 | --md-sys-typescale-label-small-line-height: 16px; 250 | --md-sys-typescale-label-small-letter-spacing: 0.50px; 251 | /* title - large */ 252 | --md-sys-typescale-title-large-font-family-name: Roboto; 253 | --md-sys-typescale-title-large-font-family-style: Regular; 254 | --md-sys-typescale-title-large-font-weight: 400px; 255 | --md-sys-typescale-title-large-font-size: 22px; 256 | --md-sys-typescale-title-large-line-height: 28px; 257 | --md-sys-typescale-title-large-letter-spacing: 0px; 258 | /* title - medium */ 259 | --md-sys-typescale-title-medium-font-family-name: Roboto; 260 | --md-sys-typescale-title-medium-font-family-style: Medium; 261 | --md-sys-typescale-title-medium-font-weight: 500px; 262 | --md-sys-typescale-title-medium-font-size: 16px; 263 | --md-sys-typescale-title-medium-line-height: 24px; 264 | --md-sys-typescale-title-medium-letter-spacing: 0.15px; 265 | /* title - small */ 266 | --md-sys-typescale-title-small-font-family-name: Roboto; 267 | --md-sys-typescale-title-small-font-family-style: Medium; 268 | --md-sys-typescale-title-small-font-weight: 500px; 269 | --md-sys-typescale-title-small-font-size: 14px; 270 | --md-sys-typescale-title-small-line-height: 20px; 271 | --md-sys-typescale-title-small-letter-spacing: 0.10px; 272 | } 273 | -------------------------------------------------------------------------------- /src/js/popup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/extensions, no-new */ 2 | /* global mdc, chrome, browser, Mustache */ 3 | 4 | import Browser from './Browser.js'; 5 | import Bookmarks from './Bookmarks.js'; 6 | import initSettingsPage from './pages/pageSettings.js'; 7 | import initAboutPage from './pages/pageAbout.js'; 8 | import { isSeparator, getExtensionSettings } from './utils/index.js'; 9 | import { MainNavBtnIcons, MainNavBtnClasses, Theme } from './constants.js'; 10 | 11 | const { MDCDialog } = mdc.dialog; 12 | const { MDCMenu } = mdc.menu; 13 | const { MDCMenuSurface } = mdc.menuSurface; 14 | const { MDCRipple } = mdc.ripple; 15 | const { MDCSnackbar } = mdc.snackbar; 16 | const { MDCTopAppBar } = mdc.topAppBar; 17 | 18 | const Classes = { 19 | MENU_OPENED: 'menu-opened', // also used for CSS rotate 20 | }; 21 | 22 | const state = { 23 | duplicatesSearchResult: null, 24 | selectedDuplicates: null, 25 | }; 26 | 27 | const browserInstance = new Browser(navigator.userAgent.includes('Chrome') ? chrome : browser); 28 | 29 | document.addEventListener('DOMContentLoaded', init.bind(this, false)); 30 | 31 | async function init(isReInit) { 32 | await initSearchTemplate(); 33 | initMDCComponents(isReInit); 34 | 35 | const appBarTitle = document.querySelector('#app-bar-title'); 36 | appBarTitle.innerHTML = browserInstance.i18n.getMessage('extensionName'); 37 | 38 | const menuSettingsText = document.querySelector('#menuitem-settings .mdc-list-item__text'); 39 | menuSettingsText.innerHTML = browserInstance.i18n.getMessage('menu_settings'); 40 | 41 | const menuAboutText = document.querySelector('#menuitem-about .mdc-list-item__text'); 42 | menuAboutText.innerHTML = browserInstance.i18n.getMessage('menu_about'); 43 | 44 | const findBookmarksForm = document.querySelector('#find-bookmarks'); 45 | findBookmarksForm.addEventListener('submit', findBookmarksHandler); 46 | 47 | // TODO 48 | // const showAllBtn = document.querySelector('#showAll'); 49 | // showAllBtn.addEventListener('click', showAllHandler) 50 | 51 | const appBarNavBtn = document.querySelector('#app-bar-nav-btn'); 52 | appBarNavBtn.addEventListener('click', mainNavBtnClickHandler); 53 | 54 | if (state.duplicatesSearchResult) { 55 | await renderSearchResults(state.duplicatesSearchResult); 56 | 57 | if (state.selectedDuplicates) { 58 | state.selectedDuplicates.forEach((id) => { 59 | document.querySelector(`#${id}`).click(); 60 | }); 61 | } 62 | } 63 | 64 | const userSettings = await getExtensionSettings(browserInstance); 65 | document.documentElement.className = userSettings.theme === Theme.SYSTEM ? '' : userSettings.theme; 66 | } 67 | 68 | // TODO 69 | // eslint-disable-next-line no-unused-vars 70 | async function showAllHandler() { 71 | const searchResultsContainer = document.querySelector('#search-results'); 72 | 73 | searchResultsContainer.hidden = false; 74 | 75 | const bookmarksTree = await browserInstance.getBookmarksTree(); 76 | const bookmarksInstance = new Bookmarks(bookmarksTree); 77 | const bookmarks = bookmarksInstance.getBookmarks(); 78 | 79 | const allBookmarksTemplateResponse = await fetch('templates/all-bookmarks.mustache'); 80 | const allBookmarksTemplate = await allBookmarksTemplateResponse.text(); 81 | 82 | searchResultsContainer.innerHTML = Mustache.render(allBookmarksTemplate, { 83 | bookmarks, 84 | }); 85 | 86 | console.log(bookmarks); 87 | } 88 | 89 | async function initSearchTemplate() { 90 | const searchTemplateResponse = await fetch('templates/search-page.mustache'); 91 | const searchCardTemplate = await searchTemplateResponse.text(); 92 | 93 | const loaderTemplateResponse = await fetch('templates/loader.mustache'); 94 | const loaderTemplate = await loaderTemplateResponse.text(); 95 | 96 | document.querySelector('#main-content').innerHTML = Mustache.render(searchCardTemplate, { 97 | i18n: { 98 | btn_findDuplicates: browserInstance.i18n.getMessage('btn_findDuplicates'), 99 | btn_cancel: browserInstance.i18n.getMessage('btn_cancel'), 100 | btn_delete: browserInstance.i18n.getMessage('btn_delete'), 101 | bookmarksDeleteConfirmationTitle: browserInstance.i18n.getMessage('bookmarksDeleteConfirmationTitle'), 102 | bookmarksDeleteConfirmation: browserInstance.i18n.getMessage('bookmarksDeleteConfirmation'), 103 | bookmarksDeletedMsg: browserInstance.i18n.getMessage('bookmarksDeletedMsg'), 104 | }, 105 | }, { 106 | loader: loaderTemplate, 107 | }); 108 | } 109 | 110 | function initMDCComponents(isReInit) { 111 | const findButton = document.querySelector('.mdc-button'); 112 | const topAppBarElement = document.querySelector('.mdc-top-app-bar'); 113 | 114 | new MDCRipple(findButton); 115 | new MDCTopAppBar(topAppBarElement); 116 | 117 | if (!isReInit) { 118 | const menu = new MDCMenu(document.querySelector('.mdc-menu')); 119 | const menuBtn = document.querySelector('#app-bar-menu-btn'); 120 | const menuSurface = new MDCMenuSurface(document.querySelector('.mdc-menu-surface')); 121 | menuSurface.listen('MDCMenuSurface:closed', () => menuBtn.classList.remove(Classes.MENU_OPENED)); 122 | menuBtn.addEventListener('click', openMenuHandler.bind(this, menu, menuBtn)); 123 | } 124 | } 125 | 126 | function mainNavBtnClickHandler() { 127 | if (this.classList.contains(MainNavBtnClasses.IS_MENU_CONTENT)) { 128 | const appBarNavBtn = document.querySelector('#app-bar-nav-btn'); 129 | appBarNavBtn.innerHTML = MainNavBtnIcons.APP_MAIN; 130 | 131 | init(true); 132 | } 133 | } 134 | 135 | function openMenuHandler(menu, menuBtn) { 136 | const classes = menuBtn.classList; 137 | const classMenuOpened = Classes.MENU_OPENED; 138 | 139 | if (classes.contains(classMenuOpened)) { 140 | classes.remove(classMenuOpened); 141 | return; 142 | } 143 | 144 | classes.add(classMenuOpened); 145 | // eslint-disable-next-line no-param-reassign 146 | menu.open = true; 147 | 148 | const menuItemAbout = document.querySelector('#menuitem-about'); 149 | menuItemAbout.addEventListener('click', initAboutPage.bind(this, browserInstance)); 150 | 151 | const menuItemSettings = document.querySelector('#menuitem-settings'); 152 | menuItemSettings.addEventListener('click', initSettingsPage.bind(this, browserInstance)); 153 | } 154 | 155 | async function findBookmarksHandler(event) { 156 | event.preventDefault(); 157 | 158 | const shouldClear = !!state.duplicatesSearchResult; 159 | 160 | state.duplicatesSearchResult = null; 161 | 162 | const searchResultsContainer = document.querySelector('#search-results'); 163 | searchResultsContainer.hidden = false; 164 | 165 | if (shouldClear) { 166 | const searchResultsTemplate = await (await fetch('templates/search-results.mustache')).text(); 167 | const loaderTemplate = await (await fetch('templates/loader.mustache')).text(); 168 | 169 | searchResultsContainer.innerHTML = Mustache.render(searchResultsTemplate, { 170 | isLoader: true, 171 | }, { 172 | loader: loaderTemplate, 173 | }); 174 | } 175 | 176 | const bookmarksTree = await browserInstance.getBookmarksTree(); 177 | const bookmarksInstance = new Bookmarks(bookmarksTree); 178 | const bookmarks = await bookmarksInstance.getDuplicates(browserInstance); 179 | 180 | state.duplicatesSearchResult = bookmarks; 181 | 182 | await renderSearchResults(bookmarks); 183 | } 184 | 185 | async function renderSearchResults(array) { 186 | const searchResultsTemplateResponse = await fetch('templates/search-results.mustache'); 187 | const searchResultsTemplate = await searchResultsTemplateResponse.text(); 188 | 189 | const bookmarkCardTemplateResponse = await fetch('templates/bookmark-card.mustache'); 190 | const bookmarkCardTemplate = await bookmarkCardTemplateResponse.text(); 191 | 192 | const duplicatesQty = array.length; 193 | const data = { 194 | hasResults: !!duplicatesQty, 195 | cards: array.map((matches, ind) => { 196 | const isSeparatorrr = isSeparator(matches[0]?.url); 197 | 198 | return { 199 | url: isSeparatorrr 200 | ? browserInstance.i18n.getMessage('txt_separators') 201 | : prepareCardTitleUrl(matches[0]?.url), 202 | items: matches.map((match) => { 203 | const obj = { ...match }; 204 | if (isSeparatorrr) { 205 | obj.title = browserInstance.i18n.getMessage('txt_separator'); 206 | obj.url = ''; 207 | } 208 | 209 | return obj; 210 | }), 211 | i18n: { 212 | txt_cardMatchNo: browserInstance.i18n.getMessage('txt_cardMatchNo', (ind + 1).toString()), 213 | txt_path: browserInstance.i18n.getMessage('txt_path'), 214 | }, 215 | }; 216 | }), 217 | i18n: { 218 | btn_deleteSelected: browserInstance.i18n.getMessage('btn_deleteSelected'), 219 | msg_FoundQty: duplicatesQty 220 | ? browserInstance.i18n.getMessage('msgFoundQty', duplicatesQty.toString()) 221 | : browserInstance.i18n.getMessage('msgFoundNone'), 222 | }, 223 | }; 224 | const partial = { 225 | card: bookmarkCardTemplate, 226 | }; 227 | 228 | const searchResultsContainer = document.querySelector('#search-results'); 229 | searchResultsContainer.innerHTML = Mustache.render(searchResultsTemplate, data, partial); 230 | searchResultsContainer.hidden = false; 231 | 232 | const duplicateBookmarksForm = document.querySelector('#bookmarks-list'); 233 | duplicateBookmarksForm.addEventListener('submit', deleteDuplicatesSubmitHandler); 234 | 235 | const deleteButton = duplicateBookmarksForm.querySelector('.mdc-button'); 236 | new MDCRipple(deleteButton); 237 | 238 | const elementSelector = 'duplicate-checkbox'; 239 | const handler = () => { 240 | const selectedCheckboxes = duplicateBookmarksForm.querySelectorAll('input[type="checkbox"]:checked'); 241 | deleteButton.disabled = !selectedCheckboxes.length; 242 | 243 | state.selectedDuplicates = []; 244 | selectedCheckboxes.forEach((checkbox) => { 245 | state.selectedDuplicates.push(checkbox.id); 246 | }); 247 | }; 248 | searchResultsContainer.addEventListener('change', (e) => { 249 | // loop parent nodes from the target to the delegation node 250 | // eslint-disable-next-line prefer-destructuring 251 | for (let target = e.target; target && target !== this; target = target.parentNode) { 252 | if (target.classList.contains(elementSelector)) { 253 | handler.call(target, e); 254 | break; 255 | } 256 | } 257 | }, false); 258 | 259 | searchResultsContainer.querySelectorAll('.bookmark-url').forEach((elem) => { 260 | elem.addEventListener('click', (event) => { 261 | browserInstance.browser.tabs.update({ 262 | url: event.target.href, 263 | }); 264 | }); 265 | }); 266 | } 267 | 268 | function prepareCardTitleUrl(url = '') { 269 | const httpsRegex = /http(s)?:\/\//; 270 | return url.replace(httpsRegex, ''); 271 | } 272 | 273 | function deleteDuplicatesSubmitHandler(event) { 274 | event.preventDefault(); 275 | 276 | const form = this; 277 | const formData = new FormData(form); 278 | const selectedBookmarks = formData.getAll('duplicate'); 279 | 280 | const dialog = new MDCDialog(document.querySelector('.mdc-dialog')); 281 | dialog.open(); 282 | dialog.listen('MDCDialog:closed', (action) => { 283 | if (action.detail.action === 'confirm') { 284 | browserInstance.removeBookmarks(selectedBookmarks); 285 | 286 | // Hide removed bookmarks 287 | const selectedCheckboxes = form.querySelectorAll('input[type="checkbox"]:checked'); 288 | selectedCheckboxes.forEach(async (checkbox) => { 289 | const parent = checkbox.closest('.duplicate-li'); 290 | await hideElementWithAnimation(parent); 291 | 292 | // Hide li divider 293 | const prevSibling = parent.previousElementSibling; 294 | if (prevSibling && prevSibling.classList.contains('mdc-list-divider')) { 295 | prevSibling.style.display = 'none'; 296 | } 297 | }); 298 | 299 | // Hide empty cards 300 | document.querySelectorAll('.bookmark-card-content').forEach((card) => { 301 | const removedQty = card.querySelectorAll('.duplicate-li.removed').length; 302 | const allQty = card.querySelectorAll('.duplicate-li').length; 303 | 304 | if (removedQty === allQty) { 305 | hideElementWithAnimation(card); 306 | } 307 | }); 308 | 309 | // Disable "Delete selected" button 310 | const deleteButton = form.querySelector('.mdc-button'); 311 | deleteButton.disabled = true; 312 | 313 | // Hide alert 314 | const alert = document.querySelector('.alert-warning'); 315 | if (alert) { 316 | hideElementWithAnimation(alert); 317 | } 318 | 319 | const snackbar = new MDCSnackbar(document.querySelector('.mdc-snackbar')); 320 | snackbar.open(); 321 | } 322 | }); 323 | } 324 | 325 | function hideElementWithAnimation(element) { 326 | element.classList.add('removed'); 327 | element.addEventListener('transitionend', () => { 328 | // eslint-disable-next-line no-param-reassign 329 | element.style.display = 'none'; 330 | }); 331 | 332 | return Promise.resolve(); 333 | } 334 | -------------------------------------------------------------------------------- /src/css/styles.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | 3 | @import "generated_theme/tokens.css"; 4 | @import "./loader.css"; 5 | 6 | @mixin light-theme { 7 | --md-sys-color-background: var(--md-sys-color-background-light); 8 | --md-sys-color-surface-light: #F5F4E9; 9 | --mdc-theme-primary: var(--md-sys-color-primary-light); 10 | --mdc-theme-secondary: var(--md-sys-color-secondary-light); 11 | --mdc-theme-background: var(--md-sys-color-background-light); 12 | --mdc-theme-surface: var(--md-sys-color-surface-light); 13 | --mdc-theme-error: var(--md-sys-color-error-light); 14 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-light); 15 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-light); 16 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-light); 17 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 18 | --mdc-theme-on-error: var(--md-sys-color-on-error-light); 19 | --md-sys-color-outline: var(--md-sys-color-outline-light); 20 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); 21 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-light); 22 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-light); 23 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-light); 24 | 25 | /* Override switch track */ 26 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-light); 27 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-light); 28 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-light); 29 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-light); 30 | 31 | /* Override switch handle button */ 32 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 33 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 34 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 35 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 36 | 37 | /* Alerts */ 38 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-light); 39 | --bs-alert-bg: var(--md-sys-color-tertiary-container-light); 40 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-light); 41 | 42 | 43 | /* Override checkbox */ 44 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-light); 45 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-light); 46 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-light); 47 | 48 | /* Button override */ 49 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-light); 50 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-light); 51 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-light); 52 | 53 | --md-sys-color-outline: var(--md-sys-color-outline-light); 54 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 55 | } 56 | 57 | @mixin dark-theme { 58 | --md-sys-color-background: var(--md-sys-color-background-dark); 59 | --md-sys-color-surface-dark: #23261C; 60 | --mdc-theme-primary: var(--md-sys-color-primary-dark); 61 | --mdc-theme-secondary: var(--md-sys-color-secondary-dark); 62 | 63 | --mdc-theme-background: var(--md-sys-color-background-dark); 64 | --mdc-theme-surface: var(--md-sys-color-surface-dark); 65 | --mdc-theme-error: var(--md-sys-color-error-dark); 66 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-dark); 67 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-dark); 68 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-dark); 69 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 70 | --mdc-theme-on-error: var(--md-sys-color-on-error-dark); 71 | --mdc-theme-text-primary-on-background: var(--md-sys-color-on-surface-dark); 72 | --mdc-theme-text-secondary-on-background: var(--md-sys-color-inverse-surface-dark); 73 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 74 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); 75 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-dark); 76 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-dark); 77 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-dark); 78 | 79 | /* Override switch track */ 80 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-dark); 81 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-dark); 82 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-dark); 83 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-dark); 84 | 85 | --mdc-switch-unselected-track-color: var(--md-sys-color-outline-dark); 86 | --mdc-switch-unselected-hover-track-color: var(--md-sys-color-outline-dark); 87 | --mdc-switch-unselected-focus-track-color: var(--md-sys-color-outline-dark); 88 | --mdc-switch-unselected-pressed-track-color: var(--md-sys-color-outline-dark); 89 | 90 | --mdc-switch-unselected-hover-handle-color: var(--md-sys-color-surface-variant-dark); 91 | /* --mdc-switch-unselected-focus-handle-color: var(--md-sys-color-surface-variant-dark); 92 | --mdc-switch-unselected-pressed-handle-color: var(--md-sys-color-surface-variant-dark); */ 93 | 94 | 95 | --mdc-switch-unselected-icon-color: var(--md-sys-color-on-primary-container-dark); 96 | /* Override switch handle button */ 97 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 98 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 99 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 100 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 101 | 102 | /* Alerts */ 103 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-dark); 104 | --bs-alert-bg: var(--md-sys-color-tertiary-container-dark); 105 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-dark); 106 | 107 | /* Override checkbox */ 108 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-dark); 109 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-dark); 110 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-dark); 111 | 112 | /* Button override */ 113 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-dark); 114 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-dark); 115 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-dark); 116 | 117 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 118 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 119 | } 120 | 121 | // Theme: Light 122 | .light { 123 | @include light-theme; 124 | } 125 | 126 | // Theme: Dark 127 | .dark { 128 | @include dark-theme; 129 | } 130 | 131 | // Theme: OS Default 132 | :root:not(.light):not(.dark) { 133 | @media (prefers-color-scheme: light) { 134 | @include light-theme; 135 | } 136 | 137 | @media (prefers-color-scheme: dark) { 138 | @include dark-theme; 139 | } 140 | } 141 | 142 | /* Icons font */ 143 | @font-face { 144 | font-family: icomoon; 145 | src: url("../fonts/icomoon.eot?46olml"); 146 | src: url("../fonts/icomoon.eot?46olml#iefix") format("embedded-opentype"), 147 | url("../fonts/icomoon.ttf?46olml") format("truetype"), 148 | url("../fonts/icomoon.woff?46olml") format("woff"), 149 | url("../fonts/icomoon.svg?46olml#icomoon") format("svg"); 150 | font-weight: normal; 151 | font-style: normal; 152 | font-display: block; 153 | } 154 | 155 | * { 156 | width: auto; 157 | } 158 | 159 | .icon-github, 160 | .app_icon-icon_bw { 161 | /* use !important to prevent issues with browser extensions that change fonts */ 162 | /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ 163 | font-family: icomoon !important; 164 | font-style: normal; 165 | font-weight: normal; 166 | font-variant: normal; 167 | font-size: xx-large; 168 | text-transform: none; 169 | line-height: 1; 170 | -moz-osx-font-smoothing: grayscale; 171 | } 172 | 173 | .app_icon-icon_bw::before { 174 | content: "\e901"; 175 | } 176 | 177 | .icon-github::before { 178 | content: "\eab0"; 179 | } 180 | 181 | /* Override Material styles */ 182 | .mdc-top-app-bar { 183 | color: var(--mdc-theme-on-primary); 184 | } 185 | 186 | .mdc-text-field:not(.mdc-text-field--disabled) .mdc-floating-label { 187 | color: var(--md-sys-color-on-surface-variant); 188 | } 189 | 190 | .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input { 191 | color: var(--md-sys-color-on-surface); 192 | } 193 | 194 | .mdc-card { 195 | box-shadow: none; 196 | border-radius: 12px; 197 | } 198 | 199 | .mdc-list-divider { 200 | background-color: var(--md-sys-color-outline); 201 | } 202 | 203 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, 204 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, 205 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { 206 | border-color: var(--md-sys-color-on-surface-variant); 207 | } 208 | 209 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading, 210 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch, 211 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing { 212 | border-color: var(--md-sys-color-on-surface); 213 | } 214 | 215 | .mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above, 216 | .mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above { 217 | color: var(--mdc-theme-primary); 218 | } 219 | 220 | .mdc-dialog .mdc-dialog__title { 221 | color: var(--mdc-theme-on-surface); 222 | } 223 | 224 | .mdc-dialog .mdc-dialog__content { 225 | color: var(--mdc-theme-on-surface-variant); 226 | } 227 | 228 | .mdc-dialog .mdc-dialog__close { 229 | color: #000; 230 | /* @alternate */ 231 | color: var(--mdc-theme-on-surface, #000); 232 | } 233 | 234 | /* Custom styles */ 235 | body { 236 | margin: 0; 237 | min-width: 560px; 238 | max-width: 700px; 239 | background-color: var(--mdc-theme-background); 240 | } 241 | 242 | main { 243 | margin: 0 8px; 244 | } 245 | 246 | .bookmarks-list-wrapper { 247 | overflow: hidden; 248 | } 249 | 250 | .bookmark-card__title { 251 | font-family: Roboto, sans-serif; 252 | -moz-osx-font-smoothing: grayscale; 253 | -webkit-font-smoothing: antialiased; 254 | font-size: 1.25rem; 255 | line-height: 2rem; 256 | color:var(--mdc-theme-on-surface); 257 | } 258 | 259 | .bookmark-card__subhead { 260 | font-family: Roboto, sans-serif; 261 | -moz-osx-font-smoothing: grayscale; 262 | -webkit-font-smoothing: antialiased; 263 | font-size: 0.875rem; 264 | line-height: 1.25rem; 265 | font-weight: 400; 266 | opacity: 0.6; 267 | color:var(--mdc-theme-on-surface); 268 | } 269 | 270 | .bookmark-card-content { 271 | padding: 8px 16px 0; 272 | margin: 8px 0; 273 | transition: opacity 0.3s; 274 | } 275 | 276 | .bookmark-card-content.removed { 277 | opacity: 0; 278 | } 279 | 280 | .bookmark-url { 281 | color: var(--md-sys-color-on-primary-container); 282 | } 283 | 284 | .duplicate-li { 285 | padding: 8px 0; 286 | transition: opacity 0.3s; 287 | /* overflow-x: auto; */ 288 | } 289 | 290 | .duplicate-li.removed { 291 | opacity: 0; 292 | } 293 | 294 | .menu-li { 295 | height: 32px; 296 | width: auto; 297 | align-items: center; 298 | } 299 | 300 | .mdc-list-item, 301 | .mdc-list-item__secondary-text { 302 | cursor: default; 303 | } 304 | .mdc-list-item__primary-text label { 305 | cursor: pointer; 306 | } 307 | 308 | #find-bookmarks { 309 | margin: 8px 0; 310 | padding-top: 5px; 311 | } 312 | 313 | #find-bookmarks > * { 314 | margin: 4px; 315 | } 316 | 317 | #find-bookmarks .mdc-text-field__input { 318 | width: 300px; 319 | } 320 | 321 | .alert { 322 | --bs-alert-padding-x: 1rem; 323 | --bs-alert-padding-y: 1rem; 324 | --bs-alert-margin-bottom: 1rem; 325 | --bs-alert-color: inherit; 326 | --bs-alert-border-color: transparent; 327 | --bs-alert-border: 1px solid var(--bs-alert-border-color); 328 | --bs-alert-border-radius: 12px; 329 | 330 | font-family: Roboto, sans-serif; 331 | -moz-osx-font-smoothing: grayscale; 332 | -webkit-font-smoothing: antialiased; 333 | position: relative; 334 | padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); 335 | margin-bottom: var(--bs-alert-margin-bottom); 336 | color: var(--bs-alert-color); 337 | background-color: var(--bs-alert-bg); 338 | border: var(--bs-alert-border); 339 | border-radius: var(--bs-alert-border-radius); 340 | transition: opacity 0.3s; 341 | font-size: 1rem; 342 | } 343 | 344 | .alert.removed { 345 | opacity: 0; 346 | } 347 | 348 | .about-extension__main-container { 349 | color: var(--mdc-theme-on-surface); 350 | display: flex; 351 | flex-direction: row; 352 | justify-content: space-around; 353 | align-items: center; 354 | margin: 16px; 355 | } 356 | 357 | .about-extension__additional-container { 358 | display: flex; 359 | flex-direction: row; 360 | justify-content: space-around; 361 | align-items: center; 362 | padding: 16px; 363 | } 364 | 365 | .about-extension__source-img { 366 | width: 32px; 367 | height: auto; 368 | } 369 | 370 | .icon-link, 371 | .icon-link:visited { 372 | color: var(--mdc-theme-on-surface); 373 | text-decoration: none; 374 | } 375 | 376 | #app-bar-menu-btn { 377 | transition: transform .3s; 378 | } 379 | 380 | /* #app-bar-menu-btn.menu-opened { 381 | transform: rotate(90deg); 382 | } */ 383 | 384 | #bookmarks-list { 385 | margin: 8px; 386 | } 387 | 388 | .mdc-list-item__text { 389 | width: 100%; 390 | } 391 | 392 | .setting-item { 393 | height: 64px; 394 | display: flex; 395 | flex-direction: row; 396 | justify-content: space-between; 397 | align-items: center; 398 | } 399 | 400 | .mdc-list-item__secondary-text::before { 401 | height: 0; 402 | } 403 | 404 | .mdc-chip { 405 | --mdc-ripple-hover-opacity: 0; 406 | 407 | border: 1px solid var(--md-sys-color-outline); 408 | border-radius: 8px; 409 | background-color: var(--mdc-theme-surface); 410 | } 411 | 412 | .mdc-chip .mdc-chip__ripple { 413 | border-radius: 8px; 414 | } 415 | 416 | .mdc-chip:hover { 417 | color: var(--mdc-theme-surface); 418 | } 419 | 420 | .mdc-chip__action { 421 | border: none; 422 | background: none; 423 | color: var(--mdc-theme-on-surface); 424 | padding: 1px 0; 425 | } 426 | 427 | .mdc-chip__icon--trailing { 428 | color: var(--mdc-theme-on-surface-variant); 429 | } 430 | 431 | .mdc-chip__icon--trailing:hover { 432 | color: var(--mdc-theme-on-surface); 433 | } 434 | 435 | .mdc-chip__icon--trailing:focus { 436 | color: var(--mdc-theme-on-surface-variant); 437 | } 438 | 439 | .mdc-button { 440 | --mdc-shape-small: 20px; 441 | --mdc-outlined-button-container-shape: 20px; 442 | --mdc-text-button-container-shape: 20px; 443 | --mdc-text-button-container-height: 40px; 444 | } 445 | 446 | .mdc-button--outlined:disabled { 447 | opacity: 0.12; 448 | } 449 | 450 | .list-item-chips .setting-item { 451 | flex-direction: column; 452 | align-items: flex-start; 453 | height: max-content; 454 | } 455 | 456 | .mdc-chip-set__chips { 457 | display: flex; 458 | width: 90vw; 459 | flex-direction: row; 460 | flex-wrap: wrap; 461 | } 462 | 463 | .mdc-menu { 464 | max-height: fit-content !important; 465 | top: 16px !important; 466 | width: max-content; 467 | } 468 | 469 | #reset-settings { 470 | margin: 1em; 471 | } 472 | 473 | .remove-ignored-url { 474 | cursor: pointer; 475 | } 476 | 477 | .settings-text-field { 478 | height: 45px; 479 | width: 320px; 480 | } 481 | 482 | .settings-button-add { 483 | height: 40px; 484 | } 485 | 486 | .settings-form { 487 | display: flex; 488 | align-items: center; 489 | justify-content: space-between; 490 | width: 100%; 491 | margin: 24px 0 5px; 492 | } 493 | 494 | // Settings Page - Segmented Buttons (for Theme) 495 | .mdc-segmented-buttons { 496 | color: var(--mdc-theme-on-surface); 497 | padding: 0; 498 | display: flex; 499 | } 500 | 501 | .mdc-button--segmented { 502 | border-radius: 0; 503 | border: 1px solid var(--md-sys-color-outline); 504 | border-left: none; 505 | padding-left: 12px; 506 | padding-right: 12px; 507 | } 508 | 509 | .mdc-button--segmented:first-child { 510 | border-left: 1px solid var(--md-sys-color-outline); 511 | border-radius: 24px 0 0 24px; 512 | } 513 | 514 | .mdc-button--segmented:last-child { 515 | border-radius: 0 24px 24px 0; 516 | } 517 | 518 | .mdc-button--segmented.selected { 519 | color: var(--md-sys-color-on-secondary-container); 520 | background-color: var(--md-sys-color-secondary-container); 521 | } 522 | 523 | .mdc-button--segmented .mdc-button__ripple { 524 | border-radius: 0; 525 | } 526 | 527 | .mdc-button--segmented:first-child .mdc-button__ripple { 528 | border-radius: 24px 0 0 24px; 529 | } 530 | 531 | .mdc-button--segmented:last-child .mdc-button__ripple { 532 | border-radius: 0 24px 24px 0; 533 | } 534 | // --------------------------------------------- 535 | -------------------------------------------------------------------------------- /src/css/styles.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | @import "generated_theme/tokens.css"; 3 | @import "./loader.css"; 4 | .light { 5 | --md-sys-color-background: var(--md-sys-color-background-light); 6 | --md-sys-color-surface-light: #F5F4E9; 7 | --mdc-theme-primary: var(--md-sys-color-primary-light); 8 | --mdc-theme-secondary: var(--md-sys-color-secondary-light); 9 | --mdc-theme-background: var(--md-sys-color-background-light); 10 | --mdc-theme-surface: var(--md-sys-color-surface-light); 11 | --mdc-theme-error: var(--md-sys-color-error-light); 12 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-light); 13 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-light); 14 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-light); 15 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 16 | --mdc-theme-on-error: var(--md-sys-color-on-error-light); 17 | --md-sys-color-outline: var(--md-sys-color-outline-light); 18 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); 19 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-light); 20 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-light); 21 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-light); 22 | /* Override switch track */ 23 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-light); 24 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-light); 25 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-light); 26 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-light); 27 | /* Override switch handle button */ 28 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 29 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 30 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 31 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 32 | /* Alerts */ 33 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-light); 34 | --bs-alert-bg: var(--md-sys-color-tertiary-container-light); 35 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-light); 36 | /* Override checkbox */ 37 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-light); 38 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-light); 39 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-light); 40 | /* Button override */ 41 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-light); 42 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-light); 43 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-light); 44 | --md-sys-color-outline: var(--md-sys-color-outline-light); 45 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 46 | } 47 | 48 | .dark { 49 | --md-sys-color-background: var(--md-sys-color-background-dark); 50 | --md-sys-color-surface-dark: #23261C; 51 | --mdc-theme-primary: var(--md-sys-color-primary-dark); 52 | --mdc-theme-secondary: var(--md-sys-color-secondary-dark); 53 | --mdc-theme-background: var(--md-sys-color-background-dark); 54 | --mdc-theme-surface: var(--md-sys-color-surface-dark); 55 | --mdc-theme-error: var(--md-sys-color-error-dark); 56 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-dark); 57 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-dark); 58 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-dark); 59 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 60 | --mdc-theme-on-error: var(--md-sys-color-on-error-dark); 61 | --mdc-theme-text-primary-on-background: var(--md-sys-color-on-surface-dark); 62 | --mdc-theme-text-secondary-on-background: var(--md-sys-color-inverse-surface-dark); 63 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 64 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); 65 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-dark); 66 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-dark); 67 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-dark); 68 | /* Override switch track */ 69 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-dark); 70 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-dark); 71 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-dark); 72 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-dark); 73 | --mdc-switch-unselected-track-color: var(--md-sys-color-outline-dark); 74 | --mdc-switch-unselected-hover-track-color: var(--md-sys-color-outline-dark); 75 | --mdc-switch-unselected-focus-track-color: var(--md-sys-color-outline-dark); 76 | --mdc-switch-unselected-pressed-track-color: var(--md-sys-color-outline-dark); 77 | --mdc-switch-unselected-hover-handle-color: var(--md-sys-color-surface-variant-dark); 78 | /* --mdc-switch-unselected-focus-handle-color: var(--md-sys-color-surface-variant-dark); 79 | --mdc-switch-unselected-pressed-handle-color: var(--md-sys-color-surface-variant-dark); */ 80 | --mdc-switch-unselected-icon-color: var(--md-sys-color-on-primary-container-dark); 81 | /* Override switch handle button */ 82 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 83 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 84 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 85 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 86 | /* Alerts */ 87 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-dark); 88 | --bs-alert-bg: var(--md-sys-color-tertiary-container-dark); 89 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-dark); 90 | /* Override checkbox */ 91 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-dark); 92 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-dark); 93 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-dark); 94 | /* Button override */ 95 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-dark); 96 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-dark); 97 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-dark); 98 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 99 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 100 | } 101 | 102 | @media (prefers-color-scheme: light) { 103 | :root:not(.light):not(.dark) { 104 | --md-sys-color-background: var(--md-sys-color-background-light); 105 | --md-sys-color-surface-light: #F5F4E9; 106 | --mdc-theme-primary: var(--md-sys-color-primary-light); 107 | --mdc-theme-secondary: var(--md-sys-color-secondary-light); 108 | --mdc-theme-background: var(--md-sys-color-background-light); 109 | --mdc-theme-surface: var(--md-sys-color-surface-light); 110 | --mdc-theme-error: var(--md-sys-color-error-light); 111 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-light); 112 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-light); 113 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-light); 114 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 115 | --mdc-theme-on-error: var(--md-sys-color-on-error-light); 116 | --md-sys-color-outline: var(--md-sys-color-outline-light); 117 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-light); 118 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-light); 119 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-light); 120 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-light); 121 | /* Override switch track */ 122 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-light); 123 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-light); 124 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-light); 125 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-light); 126 | /* Override switch handle button */ 127 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 128 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 129 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 130 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 131 | /* Alerts */ 132 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-light); 133 | --bs-alert-bg: var(--md-sys-color-tertiary-container-light); 134 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-light); 135 | /* Override checkbox */ 136 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-light); 137 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-light); 138 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-light); 139 | /* Button override */ 140 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-light); 141 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-light); 142 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-light); 143 | --md-sys-color-outline: var(--md-sys-color-outline-light); 144 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-light); 145 | } 146 | } 147 | @media (prefers-color-scheme: dark) { 148 | :root:not(.light):not(.dark) { 149 | --md-sys-color-background: var(--md-sys-color-background-dark); 150 | --md-sys-color-surface-dark: #23261C; 151 | --mdc-theme-primary: var(--md-sys-color-primary-dark); 152 | --mdc-theme-secondary: var(--md-sys-color-secondary-dark); 153 | --mdc-theme-background: var(--md-sys-color-background-dark); 154 | --mdc-theme-surface: var(--md-sys-color-surface-dark); 155 | --mdc-theme-error: var(--md-sys-color-error-dark); 156 | --mdc-theme-on-primary: var(--md-sys-color-on-primary-dark); 157 | --mdc-theme-on-secondary: var(--md-sys-color-on-secondary-dark); 158 | --mdc-theme-on-surface: var(--md-sys-color-on-surface-dark); 159 | --mdc-theme-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 160 | --mdc-theme-on-error: var(--md-sys-color-on-error-dark); 161 | --mdc-theme-text-primary-on-background: var(--md-sys-color-on-surface-dark); 162 | --mdc-theme-text-secondary-on-background: var(--md-sys-color-inverse-surface-dark); 163 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 164 | --md-sys-color-on-surface: var(--md-sys-color-on-surface-dark); 165 | --md-sys-color-on-primary-container: var(--md-sys-color-on-primary-container-dark); 166 | --md-sys-color-on-secondary-container: var(--md-sys-color-on-secondary-container-dark); 167 | --md-sys-color-secondary-container: var(--md-sys-color-secondary-container-dark); 168 | /* Override switch track */ 169 | --mdc-switch-selected-track-color: var(--md-sys-color-primary-container-dark); 170 | --mdc-switch-selected-hover-track-color: var(--md-sys-color-primary-container-dark); 171 | --mdc-switch-selected-focus-track-color: var(--md-sys-color-primary-container-dark); 172 | --mdc-switch-selected-pressed-track-color: var(--md-sys-color-primary-container-dark); 173 | --mdc-switch-unselected-track-color: var(--md-sys-color-outline-dark); 174 | --mdc-switch-unselected-hover-track-color: var(--md-sys-color-outline-dark); 175 | --mdc-switch-unselected-focus-track-color: var(--md-sys-color-outline-dark); 176 | --mdc-switch-unselected-pressed-track-color: var(--md-sys-color-outline-dark); 177 | --mdc-switch-unselected-hover-handle-color: var(--md-sys-color-surface-variant-dark); 178 | /* --mdc-switch-unselected-focus-handle-color: var(--md-sys-color-surface-variant-dark); 179 | --mdc-switch-unselected-pressed-handle-color: var(--md-sys-color-surface-variant-dark); */ 180 | --mdc-switch-unselected-icon-color: var(--md-sys-color-on-primary-container-dark); 181 | /* Override switch handle button */ 182 | --mdc-switch-selected-handle-color: var(--mdc-theme-primary); 183 | --mdc-switch-selected-hover-handle-color: var(--mdc-theme-primary); 184 | --mdc-switch-selected-focus-handle-color: var(--mdc-theme-primary); 185 | --mdc-switch-selected-pressed-handle-color: var(--mdc-theme-primary); 186 | /* Alerts */ 187 | --bs-alert-color: var(--md-sys-color-on-tertiary-container-dark); 188 | --bs-alert-bg: var(--md-sys-color-tertiary-container-dark); 189 | --bs-alert-border-color: var(--md-sys-color-tertiary-container-dark); 190 | /* Override checkbox */ 191 | --mdc-checkbox-checked-color: var(--md-sys-color-on-primary-container-dark); 192 | --mdc-checkbox-unchecked-color: var(--md-sys-color-on-primary-container-dark); 193 | --mdc-checkbox-ink-color: var(--md-sys-color-surface-dark); 194 | /* Button override */ 195 | --mdc-outlined-button-disabled-label-text-color: var(--md-sys-color-on-surface-dark); 196 | --mdc-outlined-button-disabled-outline-color: var(--md-sys-color-on-surface-dark); 197 | --mdc-outlined-button-outline-color: var(--md-sys-color-outline-dark); 198 | --md-sys-color-outline: var(--md-sys-color-outline-dark); 199 | --md-sys-color-on-surface-variant: var(--md-sys-color-on-surface-variant-dark); 200 | } 201 | } 202 | 203 | /* Icons font */ 204 | @font-face { 205 | font-family: icomoon; 206 | src: url("../fonts/icomoon.eot?46olml"); 207 | src: url("../fonts/icomoon.eot?46olml#iefix") format("embedded-opentype"), url("../fonts/icomoon.ttf?46olml") format("truetype"), url("../fonts/icomoon.woff?46olml") format("woff"), url("../fonts/icomoon.svg?46olml#icomoon") format("svg"); 208 | font-weight: normal; 209 | font-style: normal; 210 | font-display: block; 211 | } 212 | * { 213 | width: auto; 214 | } 215 | 216 | .icon-github, 217 | .app_icon-icon_bw { 218 | /* use !important to prevent issues with browser extensions that change fonts */ 219 | /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */ 220 | font-family: icomoon !important; 221 | font-style: normal; 222 | font-weight: normal; 223 | font-variant: normal; 224 | font-size: xx-large; 225 | text-transform: none; 226 | line-height: 1; 227 | -moz-osx-font-smoothing: grayscale; 228 | } 229 | 230 | .app_icon-icon_bw::before { 231 | content: "\e901"; 232 | } 233 | 234 | .icon-github::before { 235 | content: "\eab0"; 236 | } 237 | 238 | /* Override Material styles */ 239 | .mdc-top-app-bar { 240 | color: var(--mdc-theme-on-primary); 241 | } 242 | 243 | .mdc-text-field:not(.mdc-text-field--disabled) .mdc-floating-label { 244 | color: var(--md-sys-color-on-surface-variant); 245 | } 246 | 247 | .mdc-text-field:not(.mdc-text-field--disabled) .mdc-text-field__input { 248 | color: var(--md-sys-color-on-surface); 249 | } 250 | 251 | .mdc-card { 252 | box-shadow: none; 253 | border-radius: 12px; 254 | } 255 | 256 | .mdc-list-divider { 257 | background-color: var(--md-sys-color-outline); 258 | } 259 | 260 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__leading, 261 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__notch, 262 | .mdc-text-field--outlined:not(.mdc-text-field--disabled) .mdc-notched-outline__trailing { 263 | border-color: var(--md-sys-color-on-surface-variant); 264 | } 265 | 266 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__leading, 267 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__notch, 268 | .mdc-text-field--outlined:not(.mdc-text-field--disabled):not(.mdc-text-field--focused):hover .mdc-notched-outline .mdc-notched-outline__trailing { 269 | border-color: var(--md-sys-color-on-surface); 270 | } 271 | 272 | .mdc-text-field--outlined.mdc-notched-outline--upgraded .mdc-floating-label--float-above, 273 | .mdc-text-field--outlined .mdc-notched-outline--upgraded .mdc-floating-label--float-above { 274 | color: var(--mdc-theme-primary); 275 | } 276 | 277 | .mdc-dialog .mdc-dialog__title { 278 | color: var(--mdc-theme-on-surface); 279 | } 280 | 281 | .mdc-dialog .mdc-dialog__content { 282 | color: var(--mdc-theme-on-surface-variant); 283 | } 284 | 285 | .mdc-dialog .mdc-dialog__close { 286 | color: #000; 287 | /* @alternate */ 288 | color: var(--mdc-theme-on-surface, #000); 289 | } 290 | 291 | /* Custom styles */ 292 | body { 293 | margin: 0; 294 | min-width: 560px; 295 | max-width: 700px; 296 | background-color: var(--mdc-theme-background); 297 | } 298 | 299 | main { 300 | margin: 0 8px; 301 | } 302 | 303 | .bookmarks-list-wrapper { 304 | overflow: hidden; 305 | } 306 | 307 | .bookmark-card__title { 308 | font-family: Roboto, sans-serif; 309 | -moz-osx-font-smoothing: grayscale; 310 | -webkit-font-smoothing: antialiased; 311 | font-size: 1.25rem; 312 | line-height: 2rem; 313 | color: var(--mdc-theme-on-surface); 314 | } 315 | 316 | .bookmark-card__subhead { 317 | font-family: Roboto, sans-serif; 318 | -moz-osx-font-smoothing: grayscale; 319 | -webkit-font-smoothing: antialiased; 320 | font-size: 0.875rem; 321 | line-height: 1.25rem; 322 | font-weight: 400; 323 | opacity: 0.6; 324 | color: var(--mdc-theme-on-surface); 325 | } 326 | 327 | .bookmark-card-content { 328 | padding: 8px 16px 0; 329 | margin: 8px 0; 330 | transition: opacity 0.3s; 331 | } 332 | 333 | .bookmark-card-content.removed { 334 | opacity: 0; 335 | } 336 | 337 | .bookmark-url { 338 | color: var(--md-sys-color-on-primary-container); 339 | } 340 | 341 | .duplicate-li { 342 | padding: 8px 0; 343 | transition: opacity 0.3s; 344 | /* overflow-x: auto; */ 345 | } 346 | 347 | .duplicate-li.removed { 348 | opacity: 0; 349 | } 350 | 351 | .menu-li { 352 | height: 32px; 353 | width: auto; 354 | align-items: center; 355 | } 356 | 357 | .mdc-list-item, 358 | .mdc-list-item__secondary-text { 359 | cursor: default; 360 | } 361 | 362 | .mdc-list-item__primary-text label { 363 | cursor: pointer; 364 | } 365 | 366 | #find-bookmarks { 367 | margin: 8px 0; 368 | padding-top: 5px; 369 | } 370 | 371 | #find-bookmarks > * { 372 | margin: 4px; 373 | } 374 | 375 | #find-bookmarks .mdc-text-field__input { 376 | width: 300px; 377 | } 378 | 379 | .alert { 380 | --bs-alert-padding-x: 1rem; 381 | --bs-alert-padding-y: 1rem; 382 | --bs-alert-margin-bottom: 1rem; 383 | --bs-alert-color: inherit; 384 | --bs-alert-border-color: transparent; 385 | --bs-alert-border: 1px solid var(--bs-alert-border-color); 386 | --bs-alert-border-radius: 12px; 387 | font-family: Roboto, sans-serif; 388 | -moz-osx-font-smoothing: grayscale; 389 | -webkit-font-smoothing: antialiased; 390 | position: relative; 391 | padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); 392 | margin-bottom: var(--bs-alert-margin-bottom); 393 | color: var(--bs-alert-color); 394 | background-color: var(--bs-alert-bg); 395 | border: var(--bs-alert-border); 396 | border-radius: var(--bs-alert-border-radius); 397 | transition: opacity 0.3s; 398 | font-size: 1rem; 399 | } 400 | 401 | .alert.removed { 402 | opacity: 0; 403 | } 404 | 405 | .about-extension__main-container { 406 | color: var(--mdc-theme-on-surface); 407 | display: flex; 408 | flex-direction: row; 409 | justify-content: space-around; 410 | align-items: center; 411 | margin: 16px; 412 | } 413 | 414 | .about-extension__additional-container { 415 | display: flex; 416 | flex-direction: row; 417 | justify-content: space-around; 418 | align-items: center; 419 | padding: 16px; 420 | } 421 | 422 | .about-extension__source-img { 423 | width: 32px; 424 | height: auto; 425 | } 426 | 427 | .icon-link, 428 | .icon-link:visited { 429 | color: var(--mdc-theme-on-surface); 430 | text-decoration: none; 431 | } 432 | 433 | #app-bar-menu-btn { 434 | transition: transform 0.3s; 435 | } 436 | 437 | /* #app-bar-menu-btn.menu-opened { 438 | transform: rotate(90deg); 439 | } */ 440 | #bookmarks-list { 441 | margin: 8px; 442 | } 443 | 444 | .mdc-list-item__text { 445 | width: 100%; 446 | } 447 | 448 | .setting-item { 449 | height: 64px; 450 | display: flex; 451 | flex-direction: row; 452 | justify-content: space-between; 453 | align-items: center; 454 | } 455 | 456 | .mdc-list-item__secondary-text::before { 457 | height: 0; 458 | } 459 | 460 | .mdc-chip { 461 | --mdc-ripple-hover-opacity: 0; 462 | border: 1px solid var(--md-sys-color-outline); 463 | border-radius: 8px; 464 | background-color: var(--mdc-theme-surface); 465 | } 466 | 467 | .mdc-chip .mdc-chip__ripple { 468 | border-radius: 8px; 469 | } 470 | 471 | .mdc-chip:hover { 472 | color: var(--mdc-theme-surface); 473 | } 474 | 475 | .mdc-chip__action { 476 | border: none; 477 | background: none; 478 | color: var(--mdc-theme-on-surface); 479 | padding: 1px 0; 480 | } 481 | 482 | .mdc-chip__icon--trailing { 483 | color: var(--mdc-theme-on-surface-variant); 484 | } 485 | 486 | .mdc-chip__icon--trailing:hover { 487 | color: var(--mdc-theme-on-surface); 488 | } 489 | 490 | .mdc-chip__icon--trailing:focus { 491 | color: var(--mdc-theme-on-surface-variant); 492 | } 493 | 494 | .mdc-button { 495 | --mdc-shape-small: 20px; 496 | --mdc-outlined-button-container-shape: 20px; 497 | --mdc-text-button-container-shape: 20px; 498 | --mdc-text-button-container-height: 40px; 499 | } 500 | 501 | .mdc-button--outlined:disabled { 502 | opacity: 0.12; 503 | } 504 | 505 | .list-item-chips .setting-item { 506 | flex-direction: column; 507 | align-items: flex-start; 508 | height: max-content; 509 | } 510 | 511 | .mdc-chip-set__chips { 512 | display: flex; 513 | width: 90vw; 514 | flex-direction: row; 515 | flex-wrap: wrap; 516 | } 517 | 518 | .mdc-menu { 519 | max-height: fit-content !important; 520 | top: 16px !important; 521 | width: max-content; 522 | } 523 | 524 | #reset-settings { 525 | margin: 1em; 526 | } 527 | 528 | .remove-ignored-url { 529 | cursor: pointer; 530 | } 531 | 532 | .settings-text-field { 533 | height: 45px; 534 | width: 320px; 535 | } 536 | 537 | .settings-button-add { 538 | height: 40px; 539 | } 540 | 541 | .settings-form { 542 | display: flex; 543 | align-items: center; 544 | justify-content: space-between; 545 | width: 100%; 546 | margin: 24px 0 5px; 547 | } 548 | 549 | .mdc-segmented-buttons { 550 | color: var(--mdc-theme-on-surface); 551 | padding: 0; 552 | display: flex; 553 | } 554 | 555 | .mdc-button--segmented { 556 | border-radius: 0; 557 | border: 1px solid var(--md-sys-color-outline); 558 | border-left: none; 559 | padding-left: 12px; 560 | padding-right: 12px; 561 | } 562 | 563 | .mdc-button--segmented:first-child { 564 | border-left: 1px solid var(--md-sys-color-outline); 565 | border-radius: 24px 0 0 24px; 566 | } 567 | 568 | .mdc-button--segmented:last-child { 569 | border-radius: 0 24px 24px 0; 570 | } 571 | 572 | .mdc-button--segmented.selected { 573 | color: var(--md-sys-color-on-secondary-container); 574 | background-color: var(--md-sys-color-secondary-container); 575 | } 576 | 577 | .mdc-button--segmented .mdc-button__ripple { 578 | border-radius: 0; 579 | } 580 | 581 | .mdc-button--segmented:first-child .mdc-button__ripple { 582 | border-radius: 24px 0 0 24px; 583 | } 584 | 585 | .mdc-button--segmented:last-child .mdc-button__ripple { 586 | border-radius: 0 24px 24px 0; 587 | } 588 | 589 | /*# sourceMappingURL=styles.css.map */ 590 | --------------------------------------------------------------------------------