├── .gitignore ├── src ├── images │ ├── fox.png │ ├── folder.png │ ├── sh-at.png │ ├── start.png │ ├── bookmark.png │ ├── redirect.png │ ├── arrow-down.png │ ├── logo-large.png │ └── icon.svg ├── js │ ├── options.js │ ├── status.js │ ├── i18n.js │ ├── background.js │ └── ui.js ├── manifest.json ├── html │ ├── options.html │ └── ui.html ├── _locales │ ├── zh-CN │ │ └── messages.json │ ├── cs │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── pl │ │ └── messages.json │ ├── hsb │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── nl │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── de │ │ └── messages.json │ └── dsb │ │ └── messages.json └── css │ └── ui.css ├── dist ├── bookmark_checker-0.1.xpi ├── bookmark_checker-0.10.xpi ├── bookmark_checker-0.2.1.xpi ├── bookmark_checker-0.2.xpi ├── bookmark_checker-0.3.xpi ├── bookmark_checker-0.4.xpi ├── bookmark_checker-0.5.xpi ├── bookmark_checker-0.6.1.xpi ├── bookmark_checker-0.6.xpi ├── bookmark_checker-0.7.1.xpi ├── bookmark_checker-0.7.xpi ├── bookmark_checker-0.8.xpi ├── bookmark_checker-0.9.xpi ├── bookmarks_organizer-0.11.xpi ├── bookmarks_organizer-1.0.xpi ├── bookmarks_organizer-1.0.1.xpi ├── bookmarks_organizer-1.0.2.xpi ├── bookmarks_organizer-1.0.3.xpi ├── bookmarks_organizer-1.0.4.xpi ├── bookmarks_organizer-1.0.5.xpi ├── bookmarks_organizer-1.0.6.xpi ├── bookmarks_organizer-1.1.0.xpi ├── bookmarks_organizer-1.2.0.xpi └── bookmarks_organizer-1.3.0.xpi ├── jsdoc.json ├── gulpfile.js ├── .htmllintrc.json ├── package.json ├── README.md ├── CHANGELOG.md ├── .stylelintrc.json └── .eslintrc.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | docs 4 | node_modules 5 | _TODO.txt 6 | -------------------------------------------------------------------------------- /src/images/fox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/fox.png -------------------------------------------------------------------------------- /src/images/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/folder.png -------------------------------------------------------------------------------- /src/images/sh-at.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/sh-at.png -------------------------------------------------------------------------------- /src/images/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/start.png -------------------------------------------------------------------------------- /src/images/bookmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/bookmark.png -------------------------------------------------------------------------------- /src/images/redirect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/redirect.png -------------------------------------------------------------------------------- /src/images/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/arrow-down.png -------------------------------------------------------------------------------- /src/images/logo-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/src/images/logo-large.png -------------------------------------------------------------------------------- /dist/bookmark_checker-0.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.1.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.10.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.10.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.2.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.2.1.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.2.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.2.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.3.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.3.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.4.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.4.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.5.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.5.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.6.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.6.1.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.6.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.6.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.7.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.7.1.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.7.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.7.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.8.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.8.xpi -------------------------------------------------------------------------------- /dist/bookmark_checker-0.9.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmark_checker-0.9.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-0.11.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-0.11.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.1.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.1.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.2.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.2.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.3.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.3.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.4.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.4.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.5.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.5.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.0.6.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.0.6.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.1.0.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.1.0.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.2.0.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.2.0.xpi -------------------------------------------------------------------------------- /dist/bookmarks_organizer-1.3.0.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TarekJor/bookmarks-organizer/master/dist/bookmarks_organizer-1.3.0.xpi -------------------------------------------------------------------------------- /jsdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "node_modules/jsdoc-strip-async-await" 4 | ], 5 | "tags": { 6 | "allowUnknownTags": true 7 | }, 8 | "opts": { 9 | "destination": "./docs" 10 | }, 11 | "templates": { 12 | "cleverLinks": false, 13 | "monospaceLinks": false, 14 | "default": { 15 | "outputSourceFiles": true 16 | }, 17 | "systemName": "Bookmark Checker", 18 | "copyright": "© 2017, soeren-hentzschel.at", 19 | "path": "ink-docstrap", 20 | "theme": "united", 21 | "linenums": true, 22 | "dateFormat": "DD.MM.YYYY, HH:mm:ss", 23 | "outputSourceFiles": true, 24 | "search": false, 25 | "sort": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | const gulpEslint = require('gulp-eslint'); 5 | const gulpHtmllint = require('gulp-html-lint'); 6 | const gulpStylelint = require('gulp-stylelint'); 7 | const jsdoc = require('gulp-jsdoc3'); 8 | 9 | gulp.task('lint-html', () => gulp.src(['./src/html/*.html']) 10 | .pipe(gulpHtmllint({ htmllintrc : '.htmllintrc.json' })) 11 | .pipe(gulpHtmllint.format()) 12 | ); 13 | 14 | gulp.task('lint-js', () => gulp.src(['gulpfile.js', './src/js/*.js']) 15 | .pipe(gulpEslint({ configFile : '.eslintrc.json' })) 16 | .pipe(gulpEslint.format()) 17 | ); 18 | 19 | gulp.task('lint-css', () => gulp.src(['./src/css/*.css']) 20 | .pipe(gulpStylelint({ 21 | failAfterError : false, 22 | reporters : [ 23 | { 24 | formatter : 'string', 25 | console : true 26 | } 27 | ] 28 | })) 29 | ); 30 | 31 | const jsdocsConfig = require('./jsdoc.json'); 32 | gulp.task('docs', () => gulp.src(['CHANGELOG.md', 'README.md', './src/js/*.js'], { read : false }) 33 | .pipe(jsdoc(jsdocsConfig)) 34 | ); 35 | -------------------------------------------------------------------------------- /src/js/options.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const elDebugMode = document.getElementById('debug-mode'); 4 | const elDisableConfirmations = document.getElementById('disable-confirmations'); 5 | 6 | /** 7 | * @exports options 8 | */ 9 | const options = { 10 | /** 11 | * Fired when the initial HTML document has been completely loaded and parsed. This method is used to load the 12 | * current options. 13 | * 14 | * @returns {void} 15 | */ 16 | async load () { 17 | const option = await browser.storage.local.get({ 18 | debugEnabled : false, 19 | disableConfirmations : false 20 | }); 21 | 22 | elDebugMode.checked = option.debugEnabled; 23 | elDisableConfirmations.checked = option.disableConfirmations; 24 | } 25 | }; 26 | 27 | document.addEventListener('DOMContentLoaded', options.load); 28 | 29 | elDebugMode.addEventListener('change', (e) => { 30 | browser.storage.local.set({ debugEnabled : e.target.checked }); 31 | }); 32 | 33 | elDisableConfirmations.addEventListener('change', (e) => { 34 | browser.storage.local.set({ disableConfirmations : e.target.checked }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_extension_name__", 4 | "version": "1.3.0", 5 | "description": "__MSG_extension_description__", 6 | "icons": { 7 | "48": "images/icon.svg" 8 | }, 9 | "developer": { 10 | "name": "Sören Hentzschel", 11 | "url": "https://www.soeren-hentzschel.at/bookmarks-organizer/" 12 | }, 13 | "background": { 14 | "scripts": ["js/status.js", "js/background.js"] 15 | }, 16 | "browser_action": { 17 | "browser_style": false, 18 | "default_icon": "images/icon.svg", 19 | "default_title": "__MSG_button_check_bookmarks__" 20 | }, 21 | "commands": { 22 | "_execute_browser_action": { 23 | "suggested_key": { 24 | "default": "Ctrl+Shift+L" 25 | } 26 | } 27 | }, 28 | "omnibox": { 29 | "keyword" : "bookmarks" 30 | }, 31 | "options_ui": { 32 | "page": "html/options.html", 33 | "open_in_tab": true 34 | }, 35 | "permissions": [ 36 | "", 37 | "bookmarks", 38 | "storage", 39 | "tabs" 40 | ], 41 | "default_locale": "en", 42 | "applications": { 43 | "gecko": { 44 | "id": "bookmarksorganizer@agenedia.com", 45 | "strict_min_version": "52.0" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/js/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Status codes for bookmarks, used in background script and in ui script. 5 | * 6 | * @exports STATUS 7 | */ 8 | const STATUS = { 9 | /** 10 | * Status Code '200' for 'OK'. There are no issues with this bookmark. 11 | * 12 | * @type {integer} 13 | */ 14 | OK : 200, 15 | 16 | /** 17 | * Status Code '300' for 'REDIRECT'. The bookmark redirects to another url. 18 | * 19 | * @type {integer} 20 | */ 21 | REDIRECT : 300, 22 | 23 | /** 24 | * Status Code '404' for 'NOT_FOUND'. The bookmark no longer works. 25 | * 26 | * @type {integer} 27 | */ 28 | NOT_FOUND : 404, 29 | 30 | /** 31 | * Status Code '902' for 'FETCH_ERROR'. There was an error while fetching. Maybe the website no longer exists. 32 | * 33 | * @type {integer} 34 | */ 35 | FETCH_ERROR : 902, 36 | 37 | /** 38 | * Status Code '903' for 'EMPTY_BODY'. Not every server allows HEAD requests, but some websites send a wrong HTTP 39 | * status code. So we check if the response has a body, otherwise we set the status to EMPTY_BODY and try again with 40 | * the GET method. 41 | * 42 | * @type {integer} 43 | */ 44 | EMPTY_BODY : 903, 45 | 46 | /** 47 | * Status Code '999' for 'UNKNOWN'. The status is not known, for example after editing a bookmark. 48 | * 49 | * @type {integer} 50 | */ 51 | UNKNOWN : 999 52 | }; 53 | -------------------------------------------------------------------------------- /src/html/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | __MSG_extension_name__ 6 | 7 | 8 | 9 | 10 |
11 | 16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.htmllintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "attr-bans": [ 3 | "align", 4 | "background", 5 | "bgcolor", 6 | "border", 7 | "frameborder", 8 | "longdesc", 9 | "marginwidth", 10 | "marginheight", 11 | "scrolling", 12 | "style", 13 | "width" 14 | ], 15 | "attr-name-ignore-regex": false, 16 | "attr-name-style": "dash", 17 | "attr-new-line": false, 18 | "attr-no-dup": true, 19 | "attr-no-unsafe-char": true, 20 | "attr-order": [ 21 | "id", 22 | "class" 23 | ], 24 | "attr-quote-style": "single", 25 | "attr-req-value": true, 26 | "class-no-dup": true, 27 | "class-style": "dash", 28 | "doctype-first": true, 29 | "doctype-html5": true, 30 | "fig-req-figcaption": true, 31 | "focusable-tabindex-style": true, 32 | "head-req-title": true, 33 | "head-valid-content-model": true, 34 | "href-style": false, 35 | "html-req-lang": false, 36 | "html-valid-content-model": true, 37 | "id-class-ignore-regex": false, 38 | "id-class-no-ad": true, 39 | "id-class-style": "dash", 40 | "id-no-dup": true, 41 | "img-req-alt": true, 42 | "img-req-src": true, 43 | "indent-style": "spaces", 44 | "indent-width": 2, 45 | "indent-width-cont": false, 46 | "input-radio-req-name": true, 47 | "input-req-label": true, 48 | "label-req-for": true, 49 | "lang-style": "case", 50 | "line-end-style": "lf", 51 | "line-max-len": 120, 52 | "line-max-len-ignore-regex": false, 53 | "spec-char-escape": false, 54 | "table-req-caption": true, 55 | "table-req-header": true, 56 | "tag-bans": [ 57 | "b", 58 | "i", 59 | "style" 60 | ], 61 | "tag-close": true, 62 | "tag-name-lowercase": true, 63 | "tag-name-match": true, 64 | "tag-self-close": "always", 65 | "title-max-len": 65, 66 | "title-no-dup": true 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookmarkchecker", 3 | "version": "1.3.0", 4 | "description": "With the Bookmarks Organizer it's easy to put order in your bookmarks. The Bookmarks Organizer finds no longer working bookmarks, redirects, duplicates and more!", 5 | "author": { 6 | "name": "Sören Hentzschel", 7 | "email": "kontakt@agenedia.com", 8 | "url": "https://agenedia.com" 9 | }, 10 | "homepage": "https://www.soeren-hentzschel.at/bookmarks-organizer/", 11 | "bugs": { 12 | "email": "kontakt@agenedia.com" 13 | }, 14 | "license": "MPL 2.0", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/cadeyrn/bookmarks-organizer/" 18 | }, 19 | "private": true, 20 | "browserslist": [ 21 | "Firefox >= 52" 22 | ], 23 | "devDependencies": { 24 | "eslint": "4.2.0", 25 | "eslint-plugin-compat": "1.0.4", 26 | "eslint-plugin-no-unsanitized": "2.0.1", 27 | "eslint-plugin-sort-requires": "2.1.0", 28 | "eslint-plugin-xss": "0.1.8", 29 | "gulp": "3.9.1", 30 | "gulp-eslint": "4.0.0", 31 | "gulp-html-lint": "0.0.2", 32 | "gulp-jsdoc3": "1.0.1", 33 | "gulp-stylelint": "3.9.0", 34 | "htmllint": "0.6.0", 35 | "jsdoc": "3.5.3", 36 | "jsdoc-strip-async-await": "0.1.0", 37 | "npm-run-all": "4.0.2", 38 | "stylelint": "7.13.0", 39 | "stylelint-csstree-validator": "1.1.1", 40 | "stylelint-order": "0.5.0", 41 | "web-ext": "1.10.1" 42 | }, 43 | "scripts": { 44 | "build": "cd src && web-ext build -a ../dist", 45 | "docs": "gulp docs", 46 | "lint": "npm-run-all lint:*", 47 | "lint:html": "gulp lint-html", 48 | "lint:js": "gulp lint-js", 49 | "lint:css": "gulp lint-css", 50 | "lint:webext": "cd src && web-ext lint", 51 | "run:nightly": "cd src && web-ext run --firefox=\"/Applications/Firefox Nightly.app\"", 52 | "run:aurora": "cd src && web-ext run --firefox=\"/Applications/Firefox Developer Edition.app\"", 53 | "run:beta": "cd src && web-ext run --firefox=\"/Applications/Firefox Beta.app\"", 54 | "run:stable": "cd src && web-ext run --firefox=\"/Applications/Firefox Stable.app\"" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firefox Add-on: Bookmarks Organizer (WebExtension) 2 | 3 | Logo 4 | 5 | ## Description 6 | 7 | With the Bookmarks Organizer, it's easy to put order in your bookmarks. The Bookmarks Organizer finds no longer working 8 | bookmarks, redirects, duplicates and more! 9 | 10 | **The Bookmarks Organizer is a WebExtension and also compatible with Firefox 57 and later.** 11 | 12 | ### IMPORTANT NOTICE - PLEASE READ 13 | 14 | Users of a script blocker such as uMatrix or NoScript should ensure that it does not block the scripts of the 15 | add-on. When the tracking protection of Firefox is enabled, Firefox bookmarks which are on the tracking list used by 16 | Mozilla will be marked as broken. Please double check if these bookmarks can be removed. 17 | 18 | ### Features 19 | 20 | - Finds no longer working bookmarks 21 | - Finds duplicate bookmarks 22 | - Finds bookmarks without name 23 | - Broken bookmarks can be edited or deleted 24 | - Detects redirects and offers the automatic correction of individual or all redirects 25 | 26 | ### Shortcuts 27 | 28 | The interface can also be accessed via the keyboard. For this purpose the combination **Ctrl + Shift + L 29 | (macOS: Cmd + Shift + L)** is reserved. 30 | 31 | It is also possible to start individual tests directly via the address bar: 32 | 33 | - **bookmarks organizer**: opens the interface 34 | - **bookmarks duplicates**: search for duplicates 35 | - **bookmarks empty-names**: search for bookmarks without name 36 | - **bookmarks errors**: search for broken bookmarks 37 | - **bookmarks redirects**: search for redirects 38 | 39 | ### Planned features 40 | 41 | There are already some features planned for the future. 42 | 43 | - Whitelist: create exceptions for bookmarks, which will no longer be objected to during further checks 44 | - Features for bookmark folders, including empty folders 45 | - & more… 46 | 47 | ### Languages 48 | 49 | The extension is currently available in the following languages: 50 | 51 | - English 52 | - German 53 | - French (Thanks, Primokorn!) 54 | - Dutch (Thanks, Tonnes!) 55 | - Czech (Thanks, MekliCZ!) 56 | - Polish (Thanks, WaldiPL!) 57 | - Chinese (simplified) (Thanks, yfdyh000!) 58 | - Upper Sorbian (Thanks, milupo!) 59 | - Lower Sorbian (Thanks, milupo!) 60 | 61 | ## Compatibility 62 | 63 | The extension requires at least Firefox 52. Because the extension makes use of modern web technologies and the latest 64 | WebExtension APIs, support for older versions of Firefox is not possible for technical reasons. 65 | 66 | ## Download 67 | 68 | [Download Bookmarks Organizer](https://addons.mozilla.org/en-US/firefox/addon/bookmarks-organizer/) 69 | 70 | ## Release Notes 71 | 72 | [Release Notes](CHANGELOG.md "Release Notes") 73 | -------------------------------------------------------------------------------- /src/js/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @exports i18n 5 | */ 6 | const i18n = { 7 | /** 8 | * Fired when the initial HTML document has been completely loaded and parsed. Starts the translation of all the 9 | * strings. 10 | * 11 | * @returns {void} 12 | */ 13 | init () { 14 | i18n.translate(); 15 | i18n.setLangAttribute(); 16 | }, 17 | 18 | /** 19 | * This method is used to set the lang attribute to the element. 20 | * 21 | * @returns {void} 22 | */ 23 | setLangAttribute () { 24 | document.querySelector('html').setAttribute('lang', browser.i18n.getUILanguage().substring(0, 2)); 25 | }, 26 | 27 | /** 28 | * Returns an XPathResult based on a XPath expression. 29 | * 30 | * @param {string} path - a XPath expression 31 | * 32 | * @returns {XPathResult} - XPathResult based on an XPath expression 33 | */ 34 | findWithXPath (path) { 35 | return document.evaluate(path, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 36 | }, 37 | 38 | /** 39 | * This method is used to get the translation for a given key. 40 | * 41 | * @param {string} string - not used parameter 42 | * @param {string} key - translsation key 43 | * 44 | * @returns {string} - translation 45 | */ 46 | getMessage (string, key) { 47 | return browser.i18n.getMessage(key); 48 | }, 49 | 50 | /** 51 | * Replaces a __MSG___ string with the correct translation. 52 | * 53 | * @param {string} string - __MSG___ string 54 | * 55 | * @returns {string} - translated string 56 | */ 57 | replace (string) { 58 | return string.replace(/__MSG_([a-z_.]+)__/gi, i18n.getMessage); 59 | }, 60 | 61 | /** 62 | * Translates all strings in text nodes, placesholders and title attributes. 63 | * 64 | * @returns {void} 65 | */ 66 | translate () { 67 | document.removeEventListener('DOMContentLoaded', i18n.translate); 68 | 69 | const textNodes = i18n.findWithXPath('//text()[contains(self::text(), "__MSG_")]'); 70 | const textSnapshotLength = textNodes.snapshotLength; 71 | 72 | for (let i = 0; i < textSnapshotLength; i++) { 73 | const text = textNodes.snapshotItem(i); 74 | text.nodeValue = i18n.replace(text.nodeValue); 75 | } 76 | 77 | const attributes = ['title', 'placeholder']; 78 | for (const attribute of attributes) { 79 | const nodes = i18n.findWithXPath('//*/attribute::' + attribute + '[contains(., "__MSG_")]'); 80 | const AttributesSnapshotLength = nodes.snapshotLength; 81 | 82 | for (let i = 0; i < AttributesSnapshotLength; i++) { 83 | const node = nodes.snapshotItem(i); 84 | node.value = i18n.replace(node.value); 85 | } 86 | } 87 | } 88 | }; 89 | 90 | window.addEventListener('DOMContentLoaded', i18n.init); 91 | -------------------------------------------------------------------------------- /src/images/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 72 | -------------------------------------------------------------------------------- /src/_locales/zh-CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "删除所有失效书签" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "纠正所有重定向" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "检查所有书签…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "编辑" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "删除" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "纠正重定向" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "确定删除此书签?此操作不能撤销!" 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "确定删除所有失效书签?此操作不能撤销!" 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "确定更改所有已重定向的书签网址?此操作不能撤销!" 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "确定更改此书签的网址?此操作不能撤销!" 31 | }, 32 | "bookmark_name": { 33 | "message": "名称" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "无名称…" 37 | }, 38 | "bookmark_path": { 39 | "message": "路径" 40 | }, 41 | "bookmark_url": { 42 | "message": "网址" 43 | }, 44 | "close_overlay": { 45 | "message": "关闭" 46 | }, 47 | "edit_bookmark": { 48 | "message": "编辑书签" 49 | }, 50 | "extension_description": { 51 | "message": "使用 Bookmarks Organizer 可以轻松整理您的书签。Bookmarks Organizer 可以帮您找出已经失效、被重定向或者存在重复的书签。" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "错误" 58 | }, 59 | "filter_warnings": { 60 | "message": "警告" 61 | }, 62 | "greeting": { 63 | "message": "Hey!" 64 | }, 65 | "intro_check": { 66 | "message": "检查所有书签可能需要几分钟,这取决于您所选择的模式、书签数量,以及您的网络速度。在检查失效书签期间,您的网络加载速度可能变慢。" 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "失效书签" 70 | }, 71 | "mode_duplicates": { 72 | "message": "重复书签" 73 | }, 74 | "mode_empty_names": { 75 | "message": "缺少书签名称" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "所有书签都很好!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "恭喜!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "查找重复书签" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "查找无名称书签" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "查找不能访问的书签" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "查找被重定向的书签" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "打开 Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "检查您的所有书签,找出有错误、重定向或重复的书签" 100 | }, 101 | "option_debug_mode": { 102 | "message": "启用输出调试信息" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "禁用确认消息(不推荐)" 106 | }, 107 | "save_bookmark": { 108 | "message": "保存" 109 | }, 110 | "search_label": { 111 | "message" : "输入名称或网址过滤结果…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "看上去我们能清理一下!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "检查完成。" 118 | }, 119 | "start_message": { 120 | "message" : "点这里开始…" 121 | }, 122 | "status_checked": { 123 | "message" : "已检查" 124 | }, 125 | "status_errors": { 126 | "message" : "有错误" 127 | }, 128 | "status_total": { 129 | "message" : "书签总数" 130 | }, 131 | "status_warnings": { 132 | "message" : "警告" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Firefox Add-on: Bookmarks Organizer (WebExtension) 2 | 3 | ## Release Notes 4 | 5 | ### Version 1.3.0 (2017-06-11) 6 | 7 | - [ENHANCEMENT] new option to disable the confirmation dialogs (you can find it in the add-on's settings), fixes 8 | [#29](https://github.com/cadeyrn/bookmarks-organizer/issues/29) 9 | - [ENHANCEMENT] make HEAD requests instead of GET requests for better performance and fall back to GET, 10 | fixes [#33](https://github.com/cadeyrn/bookmarks-organizer/issues/33) 11 | - [DEPENDENCY] updated stylelint from version 7.10.1 to 7.11.0 and added new stylelint rule 12 | - [DEPENDENCY] updated stylelint-order from version 0.4.4 to 0.5.0 13 | 14 | ### Version 1.2.0 (2017-06-03) 15 | 16 | - [DESIGN] refreshed design, based on user feedback, including new logo, new header style and more compact header, 17 | fixes [#13](https://github.com/cadeyrn/bookmarks-organizer/issues/13), 18 | [#20](https://github.com/cadeyrn/bookmarks-organizer/issues/20), 19 | [#21](https://github.com/cadeyrn/bookmarks-organizer/issues/21) and 20 | [#22](https://github.com/cadeyrn/bookmarks-organizer/issues/22) 21 | - [TRANSLATION] added French translation (Thanks, Primokorn!) 22 | - [TRANSLATION] fixed typo in Polish translation (Thanks, WaldiPL!) 23 | - [DEPENDENCY] updated eslint-plugin-no-unsanitized from version 2.0.0 to 2.0.1 24 | 25 | **Thanks to [Ura Design](https://ura.design/) for the new logo!** 26 | 27 | ### Version 1.1.0 (2017-05-28) 28 | 29 | - [ENHANCEMENT] preserve the hash for redirects, fixes [#24](https://github.com/cadeyrn/bookmarks-organizer/issues/24) 30 | - [ENHANCEMENT] performance optimization for protocol check 31 | - [TRANSLATION] added Chinese (simplified) translation (Thanks, yfdyh000!) 32 | - [BUGFIX] when checking for bookmarks without a name for some users the default bookmark places were listed, 33 | fixes [#3](https://github.com/cadeyrn/bookmarks-organizer/issues/3) 34 | - [BUGFIX] ignore old google groups urls in broken bookmark check because it's not possible to determine the correct 35 | redirect url for technical reasons, fixes [#25](https://github.com/cadeyrn/bookmarks-organizer/issues/25) 36 | - [BUGFIX] don't get into a broken state if there are no bookmarks, fixes 37 | [#17](https://github.com/cadeyrn/bookmarks-organizer/issues/17) 38 | - [BUGFIX] fixed broken debug setting, fixes [#27](https://github.com/cadeyrn/bookmarks-organizer/issues/27) 39 | - [DEPENDENCY] updated eslint-plugin-compat from version 1.0.2 to 1.0.3 40 | 41 | ### Version 1.0.6 (2017-05-20) 42 | 43 | - [BUGFIX] reverted "removed not needed permission from manifest" 44 | 45 | ### Version 1.0.5 (2017-05-13) 46 | 47 | - [ENHANCEMENT] removed not needed permission from manifest 48 | - [DESIGN] use Mozilla's new Zilla font for the logo 49 | 50 | ### Version 1.0.4 (2017-05-10) 51 | 52 | - [TRANSLATION] added Czech translation (Thanks, MekliCZ!) 53 | - [TRANSLATION] added Polish translation (Thanks, WaldiPL!) 54 | - [BUGFIX] Code, which recognizes bookmarks and immediately updates the number of bookmarks, temporarily disabled due 55 | to a bug in Firefox (#1362863) 56 | - [CODE QUALITY] added no-unsanitized plugin for ESLint 57 | 58 | ### Version 1.0.3 (2017-05-04) 59 | 60 | - [BUGFIX] when deleting a bookmark, two bookmarks were subtracted from the total number of bookmarks 61 | - [DEPENDENCY] updated web-ext from version 1.9.0 to 1.9.1 62 | - [DEPENDENCY] updated stylelint-order from version 0.4.3 to 0.4.4 63 | 64 | ### Version 1.0.2 (2017-05-01) 65 | 66 | - [CODE QUALITY] added code documentation 67 | - [DEPENDENCY] updated web-ext from version 1.8.1 to 1.9.0 68 | 69 | ### Version 1.0.1 (2017-04-19) 70 | 71 | - initial release for [addons.mozilla.org](https://addons.mozilla.org/en-US/firefox/addon/bookmarks-organizer/) 72 | 73 | **Features of the first version** 74 | 75 | - finds no longer working bookmarks 76 | - finds duplicate bookmarks 77 | - finds bookmarks without name 78 | - broken bookmarks can be edited or deleted 79 | - detects redirects and offers the automatic correction of individual or all redirects 80 | - open the interface by keyboard (Ctrl + Shift + L, macOS: Cmd + Shift + L) 81 | - direct start of individual tests via address bar (see [README](README.md "README")) 82 | - translations: English, German, Dutch, Upper Sorbian, Lower Sorbian 83 | -------------------------------------------------------------------------------- /src/_locales/cs/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "smazat všechny rozbité záložky" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "opravit všechna přesměrování" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Zkontrolovat záložky…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "upravit" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "smazat" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "opravit přesměrování" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Opravdu chcete smazat tuto záložku? Tato akce nemůže být vrácena." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Opravdu chcete smazat všechny rozbité záložky? Tato akce nemůže být vrácena." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Opravdu chcete změnit URL adresu všech záložek s přesměrováním? Tato akce nemůže být vrácena." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Opravdu chcete změnit URL adresu této záložky? Tato akce nemůže být vrácena." 31 | }, 32 | "bookmark_name": { 33 | "message": "Název" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "žádný název…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Cesta" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL adresa" 43 | }, 44 | "close_overlay": { 45 | "message": "Zavřít" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Upravit záložku" 49 | }, 50 | "extension_description": { 51 | "message": "S doplňkem Bookmarks Organizer si jednoduše zorganizujete záložky. Bookmarks Organizer hledá již nefunkční záložky, přesměrování, duplikáty a další!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Chyby" 58 | }, 59 | "filter_warnings": { 60 | "message": "Varování" 61 | }, 62 | "greeting": { 63 | "message": "Ahoj!" 64 | }, 65 | "intro_check": { 66 | "message": "Kontrola záložek může zabrat několik minut, v závislosti na vybraném režimu, počtu záložek a rychlosti internetu. Berte prosím v potaz, že v průběhu kontroly rozbitých záložek může být znatelně zpomaleno načítání webových stránek." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "rozbité záložky" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplikáty" 73 | }, 74 | "mode_empty_names": { 75 | "message": "chybějící názvy záložek" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Všechny záložky jsou v pořádku!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Jupí!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Najít duplikáty" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Najít záložky bez názvu" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Najít nepřístupné záložky" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Najít záložky s přesměrováním" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Otevřít Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Zkontrolovat všechny vaše záložky na chyby, přesměrování, duplikáty, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Povolit ladící výstup" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Disable confirmation messages (not recommended)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Uložit" 109 | }, 110 | "search_label": { 111 | "message" : "Název nebo URL adresa…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Vypadá to, že bychom to tu mohli vyčistit!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Hej!" 118 | }, 119 | "start_message": { 120 | "message" : "Začněte zde…" 121 | }, 122 | "status_checked": { 123 | "message" : "Zkontrolováno" 124 | }, 125 | "status_errors": { 126 | "message" : "Chyb" 127 | }, 128 | "status_total": { 129 | "message" : "Počet záložek" 130 | }, 131 | "status_warnings": { 132 | "message" : "Varování" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "delete all broken bookmarks" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "correct all redirects" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Check bookmarks…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "edit" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "delete" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "correct redirect" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Are you sure you want to delete this bookmark? This action cannot be undone." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Are you sure you want to delete all broken bookmarks? This action cannot be undone." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Are you sure you want to change the URLs for all redirected bookmarks? This action cannot be undone." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Are you sure you want to change the URL of this bookmark? This action cannot be undone." 31 | }, 32 | "bookmark_name": { 33 | "message": "Name" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "no name…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Path" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Close" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Edit bookmark" 49 | }, 50 | "extension_description": { 51 | "message": "With the Bookmarks Organizer it's easy to put order in your bookmarks. The Bookmarks Organizer finds no longer working bookmarks, redirects, duplicates and more!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Errors" 58 | }, 59 | "filter_warnings": { 60 | "message": "Warnings" 61 | }, 62 | "greeting": { 63 | "message": "Hey!" 64 | }, 65 | "intro_check": { 66 | "message": "Checking the bookmarks may take several minutes, depending on the selected mode, the number of bookmarks and the Internet speed. Please note that during the check for broken bookmarks the loading of websites can be slowed down noticeably." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "broken bookmarks" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplicates" 73 | }, 74 | "mode_empty_names": { 75 | "message": "missing bookmark names" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "All bookmarks are fine!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Yay!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Find duplicates" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Find bookmarks without names" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Find no more accessible bookmarks" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Find bookmarks that redirect" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Open Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Check all your bookmarks for errors, redirects, duplicates, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Enable debug output" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Disable confirmation messages (not recommended)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Save" 109 | }, 110 | "search_label": { 111 | "message" : "Type name or URL to filter results…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Looks like we could clean up a little here!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Hey!" 118 | }, 119 | "start_message": { 120 | "message" : "Start here…" 121 | }, 122 | "status_checked": { 123 | "message" : "Checked" 124 | }, 125 | "status_errors": { 126 | "message" : "Errors" 127 | }, 128 | "status_total": { 129 | "message" : "Number of bookmarks" 130 | }, 131 | "status_warnings": { 132 | "message" : "Warnings" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/pl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "usuń wszystkie uszkodzone zakładki" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "popraw wszystkie przekierowania" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Sprawdź zakładki…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "edytuj" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "usuń" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "popraw przekierowanie" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Czy na pewno chcesz usunąć tę zakładkę? Nie można cofnąć tej czynności." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Czy na pewno chcesz usunąć wszystkie uszkodzone zakładki? Nie można cofnąć tej czynności." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Czy na pewno chcesz zmienić adresy URL wszystkich przekierowanych zakładek? Nie można cofnąć tej czynności." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Czy na pewno chcesz zmienić adres URL tej zakładki? Nie można cofnąć tej czynności." 31 | }, 32 | "bookmark_name": { 33 | "message": "Nazwa" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "brak nazwy…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Ścieżka" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Zamknij" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Edytuj zakładkę" 49 | }, 50 | "extension_description": { 51 | "message": "Z Bookmarks Organizer łatwo zrobisz porządek w swoich zakładkach. Bookmarks Organizer znajduje niedziałające zakładki, przekierowania, duplikaty i wiele więcej!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Błędy" 58 | }, 59 | "filter_warnings": { 60 | "message": "Ostrzeżenia" 61 | }, 62 | "greeting": { 63 | "message": "Hej!" 64 | }, 65 | "intro_check": { 66 | "message": "Sprawdzanie zakładek może potrwać kilka minut, w zależności od wybranego trybu, liczby zakładek i szybkości internetu. Należy pamiętać, że podczas sprawdzania uszkodzonych zakładek ładowanie stron internetowych może być zauważalnie spowolnione." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "Uszkodzone zakładki" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplikaty" 73 | }, 74 | "mode_empty_names": { 75 | "message": "Brakujące nazwy zakładek" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Wszystkie zakładki są w porządku!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Hurra!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Znajdź duplikaty" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Znajdź zakładki bez nazw" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Znajdź już niedostępne zakładki" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Znajdź zakładki, które przekierowują" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Otwórz Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Sprawdź wszystkie zakładki szukając błędów, przekierowań, duplikatów, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Włącz wyjście debugowania" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Wyłącz komunikaty potwierdzeń (nie zalecane)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Zapisz" 109 | }, 110 | "search_label": { 111 | "message" : "Wpisz nazwę lub adres URL, aby filtrować wyniki…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Wygląda na to, że moglibyśmy tutaj trochę posprzątać!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Hej!" 118 | }, 119 | "start_message": { 120 | "message" : "Zacznij tutaj…" 121 | }, 122 | "status_checked": { 123 | "message" : "Sprawdzonych" 124 | }, 125 | "status_errors": { 126 | "message" : "Błędów" 127 | }, 128 | "status_total": { 129 | "message" : "Liczba zakładek" 130 | }, 131 | "status_warnings": { 132 | "message" : "Ostrzeżeń" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/hsb/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "Wšě zapolołožki ze zmylkami zhašeć" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "Wšě dalesposrědkowanja porjedźić" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Zapołožki přepruwować…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "wobdźěłać" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "wotstronić" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "dalesposrědkowanje porjedźić" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Ma so tuta zapołožka woprawdźe zhašeć? Akcija njeda so cofnyć." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Maja so wšě zapołožki ze zmylkami woprawdźe zhašeć? Akcija njeda cofnyć." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Maja so URL wšěch dale sposrědkowanych zapołožkow woprawdźe změnić? Akcija njeda so cofnyć." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Ma so URL tuteje zapołožki woprawdźe změnić? Akcija njeda so cofnyć." 31 | }, 32 | "bookmark_name": { 33 | "message": "Mjeno" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "žane mjeno zapołožki…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Šćežka zapołožki" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Začinić" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Zapołožki wobdźěłać" 49 | }, 50 | "extension_description": { 51 | "message": "Z Bookmarks Organizer je lochko, porjad do twojich zapołožkow přinjesć. Bookmarks Organizer za tebje hižo njefungowace zapołožki, dalesposrědkowanja, duplikaty a wjace namaka!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Zmylki" 58 | }, 59 | "filter_warnings": { 60 | "message": "Warnowanja" 61 | }, 62 | "greeting": { 63 | "message": "Hej!" 64 | }, 65 | "intro_check": { 66 | "message": "Přepruwowanje zapołožkow móže we wotwisnosći wot wubraneho modusa, ličby zapołožkow kaž tež internetneje spěšnosće wjacore mjeńšiny trać. Prošu dźiwajće na to, zo móže so čitanje webstronow w běhu přepruwowanja za defektnymi zapołožkami jasnje spomalić." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "Defektne zapołožki" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplikaty" 73 | }, 74 | "mode_empty_names": { 75 | "message": "Falowace mjena zapołožkow" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Wšě zapołožki su w porjadku!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Juhuu!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Duplikaty zapołožkow namakać" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Zapołožki bjez mjena namakać" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Hižo njedocpějomne zapołožki namakać" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Zapołožki namakać, kotrež dale posrědkuja" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Bookmarks Organizer wočinić" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Wšě twoje zapołožki za zmylkami, dalesposrědkowanjemi, duplikatami, … přepruwować" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Wudaće zmylkoweho pytanja zmóžnić" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Wobkrućenske pokazki znjemóžnić (njeporuča so)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Składować" 109 | }, 110 | "search_label": { 111 | "message" : "Mjeno abo URL za filtrowanje zapodać…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Zda so, kaž móhli my tu trochu zrumować!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Hlej raz!" 118 | }, 119 | "start_message": { 120 | "message" : "Tu startować…" 121 | }, 122 | "status_checked": { 123 | "message" : "Přepruwowane" 124 | }, 125 | "status_errors": { 126 | "message" : "Zmylki" 127 | }, 128 | "status_total": { 129 | "message" : "Ličba zapołožkow" 130 | }, 131 | "status_warnings": { 132 | "message" : "Warnowanja" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "eliminar todos los marcadores rotos" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "corregir todas las redirecciones" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Comprobar marcadores…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "editar" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "eliminar" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "corregir redirección" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Estás seguro de eliminar este marcador? Esta acción no se puede deshacer." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Estás seguro de querer eliminar todos los marcadores rotos? Esta acción no se puede deshacer." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Estás seguro de querer cambiar las URLs para todos los marcadores redireccionados? Esta acción no se puede deshacer." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Estás seguro de querer cambiar la URL de este marcador? Esta acción no se puede deshacer." 31 | }, 32 | "bookmark_name": { 33 | "message": "Nombre" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "sin nombre…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Ruta" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Cerrar" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Editar marcador" 49 | }, 50 | "extension_description": { 51 | "message": "Con Bookmarks Organizer es fácil poner orden en tus marcadores. Bookmarks Organizer encuentra marcadores que ya no funcionan, redirecciones, duplicados y más!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Errores" 58 | }, 59 | "filter_warnings": { 60 | "message": "Avisos" 61 | }, 62 | "greeting": { 63 | "message": "Hey!" 64 | }, 65 | "intro_check": { 66 | "message": "Comprobar los marcadores puede llevar unos minutos, dependiendo del modo seleccionado, el número de marcadores y la velocidad de Internet. Recuerda que durante la comprobación de marcadores rotos la carga de sitios web puede retrasarse considerablemente." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "marcadores rotos" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplicados" 73 | }, 74 | "mode_empty_names": { 75 | "message": "nombres que faltan de marcadores" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Todos los marcadores están bien!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Yay!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Encontrar duplicados" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Encontrar marcadores sin nombres" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Encontrar marcadores ya no accesibles" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Encontrar marcadores que redireccionan" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Abrir Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Comprobar todos tus marcadores sobre errores, redirecciones, duplicados, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Activar 'debug output'" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Desactivar mensajes de confirmación (no recomendado)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Guardar" 109 | }, 110 | "search_label": { 111 | "message" : "Escribe nombre o URL para filtrar resultados…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Parece que podemos limpiar un poco aquí!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Hey!" 118 | }, 119 | "start_message": { 120 | "message" : "Empezar aquí…" 121 | }, 122 | "status_checked": { 123 | "message" : "Comprobado" 124 | }, 125 | "status_errors": { 126 | "message" : "Errores" 127 | }, 128 | "status_total": { 129 | "message" : "Número de marcadores" 130 | }, 131 | "status_warnings": { 132 | "message" : "Avisos" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "Alle defecte bladwijzers verwijderen" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "Alle omleidingen corrigeren" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Bladwijzers controleren…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "bewerken" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "verwijderen" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "omleiding corrigeren" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Weet u zeker dat u deze bladwijzer wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Weet u zeker dat u alle defecte bladwijzers wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Weet u zeker dat u de URL’s voor alle omgeleide bladwijzers wilt wijzigen? Deze actie kan niet ongedaan worden gemaakt." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Weet u zeker dat u de URL van deze bladwijzer wilt wijzigen? Deze actie kan niet ongedaan worden gemaakt." 31 | }, 32 | "bookmark_name": { 33 | "message": "Naam" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "geen naam…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Pad" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Sluiten" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Bladwijzer bewerken" 49 | }, 50 | "extension_description": { 51 | "message": "Met de Bookmarks Organizer is orde scheppen in uw bladwijzers eenvoudig. De Bookmarks Organizer vindt bladwijzers die niet meer werken, omleidingen, duplicaten en meer!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Fouten" 58 | }, 59 | "filter_warnings": { 60 | "message": "Waarschuwingen" 61 | }, 62 | "greeting": { 63 | "message": "Hallo!" 64 | }, 65 | "intro_check": { 66 | "message": "Het controleren van de bladwijzers kan enkele minuten duren, afhankelijk van de geselecteerde modus, het aantal bladwijzers, en de internetsnelheid. Houd er rekening mee dat het laden van websites tijdens de controle op defecte bladwijzers merkbaar kan worden vertraagd." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "defecte bladwijzers" 70 | }, 71 | "mode_duplicates": { 72 | "message": "duplicaten" 73 | }, 74 | "mode_empty_names": { 75 | "message": "ontbrekende bladwijzernamen" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Alle bladwijzers zijn in orde!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Jippie!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Duplicaten zoeken" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Bladwijzers zonder namen zoeken" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Niet meer toegankelijke bladwijzers zoeken" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Bladwijzers die worden omgeleid zoeken" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Bookmarks Organizer openen" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Controleer al uw bladwijzers op fouten, omleidingen, duplicaten, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Debug-uitvoer inschakelen" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Bevestigingsberichten uitschakelen (niet aanbevolen)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Opslaan" 109 | }, 110 | "search_label": { 111 | "message" : "Typ een naam of URL om resultaten te filteren…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Zo te zien kan hier wel wat worden opgeruimd!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Kijk eens aan!" 118 | }, 119 | "start_message": { 120 | "message" : "Begin hier…" 121 | }, 122 | "status_checked": { 123 | "message" : "Gecontroleerd" 124 | }, 125 | "status_errors": { 126 | "message" : "Fouten" 127 | }, 128 | "status_total": { 129 | "message" : "Aantal bladwijzers" 130 | }, 131 | "status_warnings": { 132 | "message" : "Waarschuwingen" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "Supprimer tous les marque-pages cassés" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "Corriger toutes les redirections" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Vérifier les marque-pages…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "Modifier" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "Supprimer" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "Corriger redirection" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Êtes-vous sûr(e) de vouloir supprimer ce marque-page ? Cette action ne peut être annulée." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Êtes-vous sûr(e) de vouloir supprimer tous les marque-pages cassés ? Cette action ne peut être annulée." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Êtes-vous sûr(e) de vouloir changer les URLs pour tous les marque-pages redirigés ? Cette action ne peut être annulée." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Êtes-vous sûr(e) de vouloir changer l'URL de ce marque-page ? Cette action ne peut être annulée." 31 | }, 32 | "bookmark_name": { 33 | "message": "Nom" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "Aucun nom…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Emplacement" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Fermer" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Modifier marque-page" 49 | }, 50 | "extension_description": { 51 | "message": "Avec Bookmarks Organizer il est facile de ranger vos marque-pages. Bookmarks Organizer trouve les marque-pages qui ne fonctionnent plus, les redirections, les doublons et plus encore !" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Erreurs" 58 | }, 59 | "filter_warnings": { 60 | "message": "Avertissements" 61 | }, 62 | "greeting": { 63 | "message": "Salut !" 64 | }, 65 | "intro_check": { 66 | "message": "La vérification des marque-pages peut prendre plusieurs minutes, selon le mode sélectionné, le nombre de marque-pages et la vitesse Internet. Veuillez noter que pendant la vérification des marque-pages cassés, le chargement des sites web peut être significativement ralenti." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "Marque-pages cassés" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Doublons" 73 | }, 74 | "mode_empty_names": { 75 | "message": "Noms de marque-pages manquants" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Tous les marque-pages sont opérationnels !" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Oui !" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Rechercher doublons" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Rechercher les marque-pages sans nom" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Rechercher les marque-pages inaccessibles" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Rechercher les marque-pages qui ont une redirection" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Ouvrir Bookmarks Organizer" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Vérifier tous les marque-pages ayant des erreurs, redirections, doublons, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Activer le débogage" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Désactiver les messages de confirmation (déconseillé)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Enregistrer" 109 | }, 110 | "search_label": { 111 | "message" : "Taper un nom ou une URL pour filter les résulats…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Il semble que vous pouvez faire un peu de ménage ici !" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Salut !" 118 | }, 119 | "start_message": { 120 | "message" : "Commencez ici…" 121 | }, 122 | "status_checked": { 123 | "message" : "Vérifié" 124 | }, 125 | "status_errors": { 126 | "message" : "Erreurs" 127 | }, 128 | "status_total": { 129 | "message" : "Nombre de marque-pages" 130 | }, 131 | "status_warnings": { 132 | "message" : "Avertissements" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "alle fehlerhaften Lesezeichen löschen" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "alle Weiterleitungen korrigieren" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Lesezeichen prüfen…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "bearbeiten" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "entfernen" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "Weiterleitung korrigieren" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Soll dieses Lesezeichen wirklich gelöscht werden? Die Aktion kann nicht rückgängig gemacht werden." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Sollen wirklich alle fehlerhaften Lesezeichen gelöscht werden? Die Aktion kann nicht rückgängig gemacht werden." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Sollen wirklich die URLs aller weitergeleiteten Lesezeichen geändert werden? Die Aktion kann nicht rückgängig gemacht werden." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Soll die URL dieses Lesezeichen wirklich geändert werden? Die Aktion kann nicht rückgängig gemacht werden." 31 | }, 32 | "bookmark_name": { 33 | "message": "Name" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "kein Lesezeichen-Name…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Lesezeichen-Pfad" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Schließen" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Lesezeichen bearbeiten" 49 | }, 50 | "extension_description": { 51 | "message": "Mit dem Bookmarks Organizer ist es einfach, Ordnung in deine Lesezeichen zu bringen. Der Bookmarks Organizer findet für dich nicht mehr funktionierende Lesezeichen, Weiterleitungen, Duplikate und mehr!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Fehler" 58 | }, 59 | "filter_warnings": { 60 | "message": "Warnungen" 61 | }, 62 | "greeting": { 63 | "message": "Hey!" 64 | }, 65 | "intro_check": { 66 | "message": "Die Überprüfung der Lesezeichen kann, abhängig vom ausgewählten Modus, der Anzahl der Lesezeichen sowie der Internetgeschwindigkeit, mehrere Minuten dauern. Bitte beachte, dass während der Überprüfung auf defekte Lesezeichen das Laden von Webseiten spürbar verlangsamt sein kann." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "defekte Lesezeichen" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplikate" 73 | }, 74 | "mode_empty_names": { 75 | "message": "fehlende Lesezeichen-Namen" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Alle Lesezeichen sind in Ordnung!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Juhuu!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Finde Lesezeichen-Duplikate" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Finde Lesezeichen ohne Namen" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Finde nicht mehr erreichbare Lesezeichen" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Finde Lesezeichen, welche weiterleiten" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Bookmarks Organizer öffnen" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Prüfe alle deine Lesezeichen auf Fehler, Weiterleitungen, Duplikate, …" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Debug-Ausgabe aktivieren" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Bestätigungs-Hinweise deaktivieren (nicht empfohlen)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Speichern" 109 | }, 110 | "search_label": { 111 | "message" : "Name oder URL zum Filtern eingeben…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Sieht aus, als könnten wir hier ein wenig aufräumen!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Schau mal!" 118 | }, 119 | "start_message": { 120 | "message" : "Starte hier…" 121 | }, 122 | "status_checked": { 123 | "message" : "Überprüft" 124 | }, 125 | "status_errors": { 126 | "message" : "Fehler" 127 | }, 128 | "status_total": { 129 | "message" : "Anzahl Lesezeichen" 130 | }, 131 | "status_warnings": { 132 | "message" : "Warnungen" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/_locales/dsb/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "action_delete_all_bookmarks_with_errors": { 3 | "message": "Wše cytańske znamjenja ze zmólkami lašowaś" 4 | }, 5 | "action_repair_all_redirects": { 6 | "message": "Wše dalejpósrědnjenja pórěźiś" 7 | }, 8 | "button_check_bookmarks": { 9 | "message": "Cytańske znamjenja pśespytaś…" 10 | }, 11 | "bookmark_action_edit": { 12 | "message": "wobźěłaś" 13 | }, 14 | "bookmark_action_delete": { 15 | "message": "wótwónoźeś" 16 | }, 17 | "bookmark_action_repair_redirect": { 18 | "message": "dalejpósrědnjenje pórěźiś" 19 | }, 20 | "bookmark_confirmation_delete": { 21 | "message": "Ma se toś to cytańske znamje napšawdu lašowaś? Akcija njedajo se anulěrowaś." 22 | }, 23 | "bookmark_confirmation_delete_all_broken": { 24 | "message": "Maju se wše cytańske znamjenja ze zmólkami napšawdu lašowaś? Akcija njedajo se anulěrowaś." 25 | }, 26 | "bookmark_confirmation_repair_all_redirects": { 27 | "message": "Maju se URL wšych dalej pósrědnjonych cytańskich znamjenjow napšawdu změniś? Akcija njedajo se anulěrowaś." 28 | }, 29 | "bookmark_confirmation_repair_redirect": { 30 | "message": "Ma se URL toś togo cytańskego znamjenja napšawdu změniś? Akcija njedajo se anulěrowaś." 31 | }, 32 | "bookmark_name": { 33 | "message": "Mě" 34 | }, 35 | "bookmark_no_name": { 36 | "message": "žedno mě cytańskego znamjenja…" 37 | }, 38 | "bookmark_path": { 39 | "message": "Sćažka cytańskego znamjenja" 40 | }, 41 | "bookmark_url": { 42 | "message": "URL" 43 | }, 44 | "close_overlay": { 45 | "message": "Zacyniś" 46 | }, 47 | "edit_bookmark": { 48 | "message": "Cytańske znamjenja wobźěłaś" 49 | }, 50 | "extension_description": { 51 | "message": "Z Bookmarks Organizer jo lažko, rěch do twójich cytańskich znamjenjow spóraś. Bookmarks Organizer za tebje wěcej njefunkcioněrujuce cytańske znamjenja, dalejpósrědnjenja, duplikaty a wěcej namakajo!" 52 | }, 53 | "extension_name": { 54 | "message": "Bookmarks Organizer" 55 | }, 56 | "filter_errors": { 57 | "message": "Zmólki" 58 | }, 59 | "filter_warnings": { 60 | "message": "Warnowanja" 61 | }, 62 | "greeting": { 63 | "message": "Hej!" 64 | }, 65 | "intro_check": { 66 | "message": "Pśespytanje cytańskich znamjenjow móžo we wótwisnosći wót wubranego modusa, licby cytańskich znamjenjow ako teke internetneje malsnosći někotare minuty traś. Pšosym źiwajśo na to, až móžo se cytanje webbokow w běgu pśespytanja za defektnymi cytańskimi znamjenjami radnje spómałšyś." 67 | }, 68 | "mode_broken_bookmarks": { 69 | "message": "Defektne cytańske znamjenja" 70 | }, 71 | "mode_duplicates": { 72 | "message": "Duplikaty" 73 | }, 74 | "mode_empty_names": { 75 | "message": "Felujuce mjenja cytańskich znamjenjow" 76 | }, 77 | "no_marked_bookmarks": { 78 | "message": "Wše cytańske znamjenja su w pórědku!" 79 | }, 80 | "no_marked_bookmarks_title": { 81 | "message": "Juhuu!" 82 | }, 83 | "omnibox_command_check_duplicates": { 84 | "message": "Duplikaty cytańskich znamjenjow namakaś" 85 | }, 86 | "omnibox_command_check_empty_names": { 87 | "message": "Cytańske znamjenja bźez mjenja namakaś" 88 | }, 89 | "omnibox_command_check_errors": { 90 | "message": "Wěcej njedosegne cytańske znamjenja namakaś" 91 | }, 92 | "omnibox_command_check_redirects": { 93 | "message": "Cytańske znamjenja namakaś, kótarež dalej pósrědnjaju" 94 | }, 95 | "omnibox_command_open": { 96 | "message": "Bookmarks Organizer wócyniś" 97 | }, 98 | "omnibox_default_description": { 99 | "message": "Wše twóje cytańske znamjenja za zmólkami, dalejpósrědnjenjami, duplikatami, … pśespytaś" 100 | }, 101 | "option_debug_mode": { 102 | "message": "Wudaśe zmólkowego pytanja zmóžniś" 103 | }, 104 | "option_disable_confirmations": { 105 | "message": "Wobkšuśeńske pokazki znjemóžniś (njepśirázijo se)" 106 | }, 107 | "save_bookmark": { 108 | "message": "Składowaś" 109 | }, 110 | "search_label": { 111 | "message" : "Mě abo URL za filtrowanje zapódaś…" 112 | }, 113 | "some_marked_bookmarks": { 114 | "message": "Zda se, ako mógli my how pitśku zrumowaś!" 115 | }, 116 | "some_marked_bookmarks_title": { 117 | "message": "Glej raz!" 118 | }, 119 | "start_message": { 120 | "message" : "How startowaś…" 121 | }, 122 | "status_checked": { 123 | "message" : "Pśespytane" 124 | }, 125 | "status_errors": { 126 | "message" : "Zmólki" 127 | }, 128 | "status_total": { 129 | "message" : "Licba cytańskich znamjenjow" 130 | }, 131 | "status_warnings": { 132 | "message" : "Warnowanja" 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/html/ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | __MSG_extension_name__ 6 | 7 | 8 | 9 | 10 |
11 | 16 |
17 | 18 |
19 |
20 |
21 |
__MSG_status_total__
22 |
0
23 |
24 |
25 |
__MSG_status_checked__
26 |
0
27 |
28 |
29 |
__MSG_status_warnings__
30 |
0
31 |
32 |
33 |
__MSG_status_errors__
34 |
0
35 |
36 |
37 | 38 |
39 |
40 |
41 | 42 | Firefox 43 | 44 | 49 | 72 |
__MSG_start_message__
73 |
74 |
75 | 80 | 81 |
82 |
83 |
84 |
85 | 90 |
91 | Logo 92 |
93 |
94 |
95 |
96 |
97 |
98 | 124 | 132 | 137 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "stylelint-csstree-validator", 4 | "stylelint-order" 5 | ], 6 | "rules": { 7 | "at-rule-blacklist": null, 8 | "at-rule-empty-line-before": "always", 9 | "at-rule-name-case": "lower", 10 | "at-rule-name-newline-after": null, 11 | "at-rule-name-space-after": "always", 12 | "at-rule-no-unknown": true, 13 | "at-rule-no-vendor-prefix": true, 14 | "at-rule-semicolon-newline-after": "always", 15 | "at-rule-semicolon-space-before": "never", 16 | "at-rule-whitelist": null, 17 | "block-closing-brace-empty-line-before": "never", 18 | "block-closing-brace-newline-after": "always", 19 | "block-closing-brace-newline-before": "always", 20 | "block-closing-brace-space-after": "never-single-line", 21 | "block-closing-brace-space-before": "always-single-line", 22 | "block-no-empty": true, 23 | "block-opening-brace-newline-after": "always", 24 | "block-opening-brace-newline-before": null, 25 | "block-opening-brace-space-after": "never-single-line", 26 | "block-opening-brace-space-before": "always-single-line", 27 | "color-hex-case": "lower", 28 | "color-hex-length": "short", 29 | "color-named": "never", 30 | "color-no-hex": true, 31 | "color-no-invalid-hex": true, 32 | "comment-empty-line-before": ["always", { "ignore": ["stylelint-commands"] }], 33 | "comment-no-empty": true, 34 | "comment-whitespace-inside": "always", 35 | "comment-word-blacklist": ["todo", "fixme", "xxx"], 36 | "csstree/validator": true, 37 | "custom-media-pattern": null, 38 | "custom-property-empty-line-before": "never", 39 | "custom-property-pattern": null, 40 | "declaration-bang-space-after": "never", 41 | "declaration-bang-space-before": "always", 42 | "declaration-block-no-duplicate-properties": true, 43 | "declaration-block-no-redundant-longhand-properties": true, 44 | "declaration-block-no-shorthand-property-overrides": true, 45 | "declaration-block-semicolon-newline-after": "always", 46 | "declaration-block-semicolon-newline-before": null, 47 | "declaration-block-semicolon-space-after": "always-single-line", 48 | "declaration-block-semicolon-space-before": "never", 49 | "declaration-block-single-line-max-declarations": 1, 50 | "declaration-block-trailing-semicolon": "always", 51 | "declaration-colon-newline-after": "always-multi-line", 52 | "declaration-colon-space-after": "always-single-line", 53 | "declaration-colon-space-before": "never", 54 | "declaration-empty-line-before": "never", 55 | "declaration-no-important": true, 56 | "declaration-property-unit-blacklist": null, 57 | "declaration-property-unit-whitelist": null, 58 | "declaration-property-value-blacklist": null, 59 | "declaration-property-value-whitelist": null, 60 | "font-family-name-quotes": "always-where-recommended", 61 | "font-family-no-duplicate-names": true, 62 | "font-weight-notation": "numeric", 63 | "function-blacklist": null, 64 | "function-calc-no-unspaced-operator": true, 65 | "function-comma-newline-after": "never-multi-line", 66 | "function-comma-newline-before": "never-multi-line", 67 | "function-comma-space-after": "always", 68 | "function-comma-space-before": "never", 69 | "function-linear-gradient-no-nonstandard-direction": true, 70 | "function-max-empty-lines": 0, 71 | "function-name-case": "lower", 72 | "function-parentheses-newline-inside": "never-multi-line", 73 | "function-parentheses-space-inside": "never", 74 | "function-url-no-scheme-relative": null, 75 | "function-url-quotes": "always", 76 | "function-url-scheme-blacklist": null, 77 | "function-url-scheme-whitelist": ["https", "data"], 78 | "function-whitelist": null, 79 | "function-whitespace-after": "always", 80 | "indentation": 2, 81 | "keyframe-declaration-no-important": true, 82 | "length-zero-no-unit": true, 83 | "max-empty-lines": 1, 84 | "max-line-length": 120, 85 | "max-nesting-depth": 0, 86 | "media-feature-colon-space-after": "always", 87 | "media-feature-colon-space-before": "never", 88 | "media-feature-name-blacklist": null, 89 | "media-feature-name-case": "lower", 90 | "media-feature-name-no-unknown": true, 91 | "media-feature-name-no-vendor-prefix": true, 92 | "media-feature-name-whitelist": null, 93 | "media-feature-parentheses-space-inside": "never", 94 | "media-feature-range-operator-space-after": "always", 95 | "media-feature-range-operator-space-before": "always", 96 | "media-query-list-comma-newline-after": "always-multi-line", 97 | "media-query-list-comma-newline-before": "never-multi-line", 98 | "media-query-list-comma-space-after": "always-single-line", 99 | "media-query-list-comma-space-before": "never-single-line", 100 | "no-descending-specificity": null, 101 | "no-duplicate-selectors": true, 102 | "no-empty-source": true, 103 | "no-eol-whitespace": true, 104 | "no-extra-semicolons": true, 105 | "no-invalid-double-slash-comments": true, 106 | "no-missing-end-of-source-newline": true, 107 | "no-unknown-animations": true, 108 | "number-leading-zero": "never", 109 | "number-max-precision": 2, 110 | "number-no-trailing-zeros": true, 111 | "order/properties-order": [ 112 | "appearance", 113 | "display", 114 | "position", 115 | "z-index", 116 | "top", 117 | "bottom", 118 | "left", 119 | "right", 120 | "float", 121 | "clear", 122 | "box-sizing", 123 | "margin", 124 | "padding", 125 | "width", 126 | "height", 127 | "line-height", 128 | "transform", 129 | "font", 130 | "border", 131 | "background", 132 | "color", 133 | "list-style", 134 | "vertical-align", 135 | "text-align", 136 | "text-decoration", 137 | "whitespace", 138 | "overflow", 139 | "cursor", 140 | "opacity", 141 | "animation", 142 | "transition" 143 | ], 144 | "property-blacklist": null, 145 | "property-case": "lower", 146 | "property-no-unknown": true, 147 | "property-no-vendor-prefix": true, 148 | "property-whitelist": null, 149 | "rule-empty-line-before": ["always", { "except" : ["first-nested"] }], 150 | "selector-attribute-brackets-space-inside": "never", 151 | "selector-attribute-operator-blacklist": null, 152 | "selector-attribute-operator-space-after": "never", 153 | "selector-attribute-operator-space-before": "never", 154 | "selector-attribute-operator-whitelist": null, 155 | "selector-attribute-quotes": "always", 156 | "selector-class-pattern": null, 157 | "selector-combinator-space-after": "always", 158 | "selector-combinator-space-before": "always", 159 | "selector-descendant-combinator-no-non-space": true, 160 | "selector-id-pattern": null, 161 | "selector-list-comma-newline-after": "always-multi-line", 162 | "selector-list-comma-newline-before": "never-multi-line", 163 | "selector-list-comma-space-after": "always-single-line", 164 | "selector-list-comma-space-before": "never-single-line", 165 | "selector-max-attribute": 1, 166 | "selector-max-class": 3, 167 | "selector-max-combinators": null, 168 | "selector-max-compound-selectors": 4, 169 | "selector-max-id": 2, 170 | "selector-max-specificity": null, 171 | "selector-max-type": 2, 172 | "selector-max-universal": 0, 173 | "selector-nested-pattern": null, 174 | "selector-no-qualifying-type": null, 175 | "selector-no-vendor-prefix": true, 176 | "selector-pseudo-class-blacklist": null, 177 | "selector-pseudo-class-case": "lower", 178 | "selector-pseudo-class-no-unknown": true, 179 | "selector-pseudo-class-parentheses-space-inside": "never", 180 | "selector-pseudo-class-whitelist": null, 181 | "selector-pseudo-element-case": "lower", 182 | "selector-pseudo-element-colon-notation": "double", 183 | "selector-pseudo-element-no-unknown": true, 184 | "selector-type-case": "lower", 185 | "selector-type-no-unknown": true, 186 | "selector-max-empty-lines": 0, 187 | "shorthand-property-no-redundant-values": true, 188 | "string-no-newline": true, 189 | "string-quotes": "single", 190 | "time-min-milliseconds": 100, 191 | "unit-blacklist": null, 192 | "unit-case": "lower", 193 | "unit-no-unknown": true, 194 | "unit-whitelist": null, 195 | "value-keyword-case": "lower", 196 | "value-list-comma-newline-after": "always-multi-line", 197 | "value-list-comma-newline-before": "never-multi-line", 198 | "value-list-comma-space-after": "always-single-line", 199 | "value-list-comma-space-before": "never-single-line", 200 | "value-list-max-empty-lines": 0, 201 | "value-no-vendor-prefix": true 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "compat", 4 | "no-unsanitized", 5 | "promise", 6 | "sort-requires", 7 | "xss" 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": 2017 11 | }, 12 | "env": { 13 | "browser": true, 14 | "webextensions": true 15 | }, 16 | "globals": { 17 | "Promise": true, 18 | "require": true 19 | }, 20 | "rules": { 21 | "accessor-pairs": "error", 22 | "array-bracket-newline": ["error", { "multiline": true }], 23 | "array-bracket-spacing": "error", 24 | "array-callback-return": "error", 25 | "array-element-newline": ["error", { "multiline": true }], 26 | "arrow-body-style": "error", 27 | "arrow-parens": "error", 28 | "arrow-spacing": "error", 29 | "block-scoped-var": "error", 30 | "block-spacing": "error", 31 | "brace-style": ["error", "stroustrup"], 32 | "camelcase": ["error", { "properties": "never" }], 33 | "capitalized-comments": "off", 34 | "class-methods-use-this": "error", 35 | "comma-dangle": "error", 36 | "comma-spacing": "error", 37 | "comma-style": "error", 38 | "compat/compat": "error", 39 | "complexity": "error", 40 | "computed-property-spacing": "error", 41 | "consistent-return": "error", 42 | "consistent-this": ["error", "self"], 43 | "constructor-super": "error", 44 | "curly": "error", 45 | "default-case": "error", 46 | "dot-location": ["error", "property"], 47 | "dot-notation": "error", 48 | "eol-last": "error", 49 | "eqeqeq": "error", 50 | "for-direction": "error", 51 | "func-call-spacing": "error", 52 | "func-name-matching": "error", 53 | "func-names": "off", 54 | "func-style": "error", 55 | "generator-star-spacing": "error", 56 | "getter-return": "error", 57 | "guard-for-in": "error", 58 | "id-blacklist": "error", 59 | "id-length": ["error", { "max": 30, "exceptions": ["e", "i", "p"] }], 60 | "id-match": "error", 61 | "indent": ["error", 2, { "SwitchCase" : 1}], 62 | "init-declarations": "error", 63 | "jsx-quotes": "error", 64 | "key-spacing": ["error", { "beforeColon": true }], 65 | "keyword-spacing": "error", 66 | "line-comment-position": "error", 67 | "linebreak-style": ["error", "unix"], 68 | "lines-around-comment":[ "error", { "beforeBlockComment": false }], 69 | "max-depth": "error", 70 | "max-len": ["error", 120, 2], 71 | "max-lines": "off", 72 | "max-nested-callbacks": ["error", 2], 73 | "max-params": ["error", 4], 74 | "max-statements-per-line": "error", 75 | "max-statements": "off", 76 | "multiline-ternary": ["error", "never"], 77 | "new-cap": "error", 78 | "new-parens": "error", 79 | "newline-per-chained-call": "error", 80 | "no-alert": "error", 81 | "no-array-constructor": "error", 82 | "no-await-in-loop": "error", 83 | "no-bitwise": "error", 84 | "no-buffer-constructor": "error", 85 | "no-caller": "error", 86 | "no-case-declarations": "error", 87 | "no-catch-shadow": "error", 88 | "no-class-assign": "error", 89 | "no-compare-neg-zero": "error", 90 | "no-cond-assign": "error", 91 | "no-confusing-arrow": "error", 92 | "no-console": "error", 93 | "no-const-assign": "error", 94 | "no-constant-condition": "error", 95 | "no-continue": "error", 96 | "no-control-regex": "error", 97 | "no-debugger": "error", 98 | "no-delete-var": "error", 99 | "no-div-regex": "error", 100 | "no-dupe-args": "error", 101 | "no-dupe-class-members": "error", 102 | "no-dupe-keys": "error", 103 | "no-duplicate-case": "error", 104 | "no-duplicate-imports": "error", 105 | "no-else-return": "error", 106 | "no-empty": "error", 107 | "no-empty-character-class": "error", 108 | "no-empty-function": "error", 109 | "no-empty-pattern": "error", 110 | "no-eq-null": "error", 111 | "no-eval": "error", 112 | "no-ex-assign": "error", 113 | "no-extend-native": "error", 114 | "no-extra-bind": "error", 115 | "no-extra-boolean-cast": "error", 116 | "no-extra-label": "error", 117 | "no-extra-parens": ["error", "all", { "nestedBinaryExpressions": false }], 118 | "no-extra-semi": "error", 119 | "no-fallthrough": "error", 120 | "no-floating-decimal": "error", 121 | "no-func-assign": "error", 122 | "no-global-assign": "error", 123 | "no-implicit-coercion": "error", 124 | "no-implicit-globals": "error", 125 | "no-implied-eval": "error", 126 | "no-inline-comments": "error", 127 | "no-inner-declarations": ["error", "both"], 128 | "no-invalid-regexp": "error", 129 | "no-invalid-this": "error", 130 | "no-irregular-whitespace": ["error", { "skipStrings": false }], 131 | "no-iterator": "error", 132 | "no-label-var": "error", 133 | "no-labels": "error", 134 | "no-lone-blocks": "error", 135 | "no-lonely-if": "error", 136 | "no-loop-func": "error", 137 | "no-magic-numbers": ["error", { "enforceConst": true, "ignore": [-1, 0, 2], "ignoreArrayIndexes": true }], 138 | "no-mixed-operators": "error", 139 | "no-mixed-spaces-and-tabs": "error", 140 | "no-multi-assign": "error", 141 | "no-multi-spaces": "error", 142 | "no-multi-str": "error", 143 | "no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 0 }], 144 | "no-negated-condition": "error", 145 | "no-nested-ternary": "error", 146 | "no-new": "error", 147 | "no-new-func": "error", 148 | "no-new-symbol": "error", 149 | "no-new-wrappers": "error", 150 | "no-new-object": "error", 151 | "no-obj-calls": "error", 152 | "no-octal-escape": "error", 153 | "no-octal": "error", 154 | "no-param-reassign": "error", 155 | "no-plusplus": "off", 156 | "no-proto": "error", 157 | "no-prototype-builtins": "error", 158 | "no-redeclare": ["error", { "builtinGlobals": true }], 159 | "no-regex-spaces": "error", 160 | "no-restricted-globals": "error", 161 | "no-restricted-imports": "error", 162 | "no-restricted-properties": "error", 163 | "no-restricted-syntax": "error", 164 | "no-return-assign": ["error", "always"], 165 | "no-return-await": "error", 166 | "no-script-url": "error", 167 | "no-self-assign": ["error", { "props": true }], 168 | "no-self-compare": "error", 169 | "no-sequences": "error", 170 | "no-shadow": ["error", { "builtinGlobals": true }], 171 | "no-shadow-restricted-names": "error", 172 | "no-sparse-arrays": "error", 173 | "no-tabs": "error", 174 | "no-template-curly-in-string": "error", 175 | "no-ternary": "off", 176 | "no-this-before-super": "error", 177 | "no-throw-literal": "error", 178 | "no-trailing-spaces": "error", 179 | "no-undef-init": "error", 180 | "no-undef": ["error", { "typeof": true }], 181 | "no-undefined": "error", 182 | "no-underscore-dangle": "error", 183 | "no-unexpected-multiline": "error", 184 | "no-unmodified-loop-condition": "error", 185 | "no-unneeded-ternary": "error", 186 | "no-unreachable": "error", 187 | "no-unsafe-finally": "error", 188 | "no-unsafe-negation": "error", 189 | "no-unsanitized/method": "error", 190 | "no-unsanitized/property": "error", 191 | "no-unused-expressions": "error", 192 | "no-unused-labels": "error", 193 | "no-unused-vars": ["error", { "vars": "local" }], 194 | "no-use-before-define": "error", 195 | "no-useless-call": "error", 196 | "no-useless-computed-key": "error", 197 | "no-useless-concat": "error", 198 | "no-useless-constructor": "error", 199 | "no-useless-escape": "error", 200 | "no-useless-rename": "error", 201 | "no-useless-return": "error", 202 | "no-var": "error", 203 | "no-void": "error", 204 | "no-warning-comments": "error", 205 | "no-whitespace-before-property": "error", 206 | "no-with": "error", 207 | "nonblock-statement-body-position": ["error", "below"], 208 | "object-curly-newline": "error", 209 | "object-curly-spacing": ["error", "always"], 210 | "object-property-newline": "error", 211 | "object-shorthand": ["error", "methods"], 212 | "one-var-declaration-per-line": ["error", "always"], 213 | "one-var": ["error", "never"], 214 | "operator-assignment": "error", 215 | "operator-linebreak": "error", 216 | "padded-blocks": ["error", "never"], 217 | "padding-line-between-statements": [ 218 | "error", 219 | { "blankLine": "always", "prev": "*", "next": "return" }, 220 | { "blankLine": "always","prev": "directive", "next": "*" }, 221 | { "blankLine": "any", "prev": "directive", "next": "directive" } 222 | ], 223 | "prefer-arrow-callback": "error", 224 | "prefer-const": "error", 225 | "prefer-destructuring": "error", 226 | "prefer-numeric-literals": "error", 227 | "prefer-promise-reject-errors": "error", 228 | "prefer-rest-params": "error", 229 | "prefer-spread": "error", 230 | "prefer-template": "off", 231 | "promise/always-return": "error", 232 | "promise/avoid-new": "error", 233 | "promise/catch-or-return": "error", 234 | "promise/no-callback-in-promise": "error", 235 | "promise/no-native": "error", 236 | "promise/no-nesting": "error", 237 | "promise/no-promise-in-callback": "error", 238 | "promise/no-return-wrap": "error", 239 | "promise/param-names": "error", 240 | "promise/prefer-await-to-callbacks": "error", 241 | "promise/prefer-await-to-then": "error", 242 | "quote-props": ["error", "as-needed"], 243 | "quotes": ["error", "single"], 244 | "radix": ["error", "as-needed"], 245 | "require-await": "error", 246 | "require-jsdoc": ["error", { 247 | "require": { 248 | "FunctionDeclaration": true, 249 | "MethodDefinition": true, 250 | "ClassDeclaration": true, 251 | "ArrowFunctionExpression": true 252 | } 253 | }], 254 | "require-yield": "error", 255 | "rest-spread-spacing": "error", 256 | "semi": "error", 257 | "semi-style": ["error", "last"], 258 | "semi-spacing": "error", 259 | "sort-imports": "error", 260 | "sort-keys": "off", 261 | "sort-requires/sort-requires": "error", 262 | "sort-vars": "error", 263 | "space-before-blocks": "error", 264 | "space-before-function-paren": "error", 265 | "space-in-parens": "error", 266 | "space-infix-ops": "error", 267 | "space-unary-ops": "error", 268 | "spaced-comment": "error", 269 | "strict": ["error", "global"], 270 | "symbol-description": "error", 271 | "switch-colon-spacing": ["error", { "after": true, "before": false }], 272 | "template-curly-spacing": "error", 273 | "template-tag-spacing": ["error", "always"], 274 | "unicode-bom": "error", 275 | "use-isnan": "error", 276 | "valid-jsdoc": ["error", { 277 | "prefer": { 278 | "arg": "param", 279 | "argument": "param", 280 | "class": "constructor", 281 | "return": "returns", 282 | "virtual": "abstract" 283 | }, 284 | "preferType": { 285 | "Boolean": "boolean", 286 | "Number": "number", 287 | "object": "Object", 288 | "String": "string" 289 | } 290 | }], 291 | "valid-typeof": "error", 292 | "vars-on-top": "error", 293 | "wrap-iife": "error", 294 | "wrap-regex": "error", 295 | "xss/no-location-href-assign": "error", 296 | "yield-star-spacing": "error", 297 | "yoda": "error" 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/css/ui.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: Verdana, sans-serif; 4 | font-size: 16px; 5 | background: rgb(235, 235, 235); 6 | color: rgb(33, 37, 42); 7 | overflow-x: hidden; 8 | } 9 | 10 | a { 11 | color: rgb(90, 90, 90); 12 | text-decoration: none; 13 | transition: color ease 250ms; 14 | } 15 | 16 | a:not(.button):hover { 17 | color: rgb(120, 120, 120); 18 | } 19 | 20 | input[type='search'], input[type='text'] { 21 | box-sizing: border-box; 22 | padding: 10px; 23 | width: 100%; 24 | border: none; 25 | border-bottom: solid 2px rgb(210, 210, 210); 26 | transition: border ease 500ms; 27 | } 28 | 29 | input[type='search']:hover, input[type='text']:hover { 30 | border-color: rgb(150, 150, 150); 31 | } 32 | 33 | input[type='search']:focus, input[type='text']:focus { 34 | border-color: rgb(98, 181, 71); 35 | } 36 | 37 | button { 38 | position: relative; 39 | margin: 12px 0; 40 | padding-left: 30px; 41 | padding-right: 30px; 42 | height: 40px; 43 | transform: perspective(1px) translateZ(0); 44 | font-size: 18px; 45 | border: 2px solid rgb(98, 181, 71); 46 | background: rgb(255, 255, 255); 47 | color: rgb(33, 37, 42); 48 | cursor: pointer; 49 | box-shadow: 0 0 1px transparent; 50 | transition-property: color; 51 | transition-duration: 250ms; 52 | } 53 | 54 | button:disabled { 55 | color: rgba(33, 37, 42, .5); 56 | cursor: default; 57 | } 58 | 59 | button:not(:disabled)::before { 60 | content: ''; 61 | position: absolute; 62 | z-index: -1; 63 | top: 0; 64 | bottom: 0; 65 | left: 0; 66 | right: 0; 67 | transform: scale(0); 68 | background: rgb(235, 235, 235); 69 | transition-property: transform; 70 | transition-duration: 250ms; 71 | transition-timing-function: ease-out; 72 | } 73 | 74 | button:not(:disabled):hover::before, button:not(:disabled):focus::before, button:not(:disabled):active::before { 75 | transform: scale(1); 76 | } 77 | 78 | .clearfix::after { 79 | content: ''; 80 | display: table-cell; 81 | clear: both; 82 | } 83 | 84 | .hidden { 85 | display: none; 86 | } 87 | 88 | .center { 89 | text-align: center; 90 | } 91 | 92 | .animated-visibility.is-hidden { 93 | visibility: hidden; 94 | opacity: 0; 95 | } 96 | 97 | .animated-visibility { 98 | visibility: visible; 99 | opacity: 1; 100 | transition: visibility 0ms 1000ms, opacity 1000ms linear; 101 | } 102 | 103 | .animated-visibility.active-check { 104 | transition: visibility 0ms 0ms, opacity 1000ms linear; 105 | } 106 | 107 | .checkbox { 108 | position: relative; 109 | width: 90px; 110 | } 111 | 112 | .checkbox.inline { 113 | display: inline-block; 114 | } 115 | 116 | .checkbox:not(.inline) { 117 | margin-bottom: 10px; 118 | } 119 | 120 | .checkbox label { 121 | position: absolute; 122 | top: 0; 123 | left: 0; 124 | width: 25px; 125 | height: 25px; 126 | border: 2px solid rgb(210, 210, 210); 127 | background: rgb(255, 255, 255); 128 | text-indent: 35px; 129 | white-space: nowrap; 130 | cursor: pointer; 131 | transition: border ease 500ms; 132 | } 133 | 134 | .checkbox label:hover { 135 | border-color: rgb(150, 150, 150); 136 | } 137 | 138 | .checkbox label::after { 139 | content: ''; 140 | position: absolute; 141 | top: 6px; 142 | left: 7px; 143 | width: 9px; 144 | height: 5px; 145 | transform: rotate(-45deg); 146 | border: 3px solid rgb(150, 150, 150); 147 | border-top: none; 148 | border-right: none; 149 | opacity: .2; 150 | } 151 | 152 | .checkbox label span { 153 | vertical-align: middle; 154 | } 155 | 156 | .checkbox input[type='checkbox']:checked + label::after { 157 | opacity: 1; 158 | } 159 | 160 | #header-wrapper { 161 | position: fixed; 162 | z-index: 30; 163 | top: 0; 164 | left: 0; 165 | right: 0; 166 | } 167 | 168 | #header { 169 | position: relative; 170 | background: rgb(255, 255, 255); 171 | transition: height 250ms ease-in-out; 172 | } 173 | 174 | #header-wrapper.default #header { 175 | height: 100px; 176 | } 177 | 178 | #header-wrapper.compact #header { 179 | height: 60px; 180 | } 181 | 182 | #logo-wrapper { 183 | position: relative; 184 | left: -10px; 185 | width: 435px; 186 | height: 120px; 187 | transform: skew(-5deg) translateZ(0); 188 | background: rgb(255, 255, 255); 189 | box-shadow: 0 0 10px 0 rgba(0, 0, 0, .25); 190 | transition: top 500ms ease-in-out; 191 | } 192 | 193 | #header-wrapper.default #logo-wrapper { 194 | top: 0; 195 | } 196 | 197 | #header-wrapper.compact #logo-wrapper { 198 | top: -200px; 199 | } 200 | 201 | #logo-wrapper a { 202 | display: block; 203 | width: inherit; 204 | height: inherit; 205 | transform: skew(5deg) translateZ(0); 206 | background: url('../images/logo-large.png') right 25px center / 375px 85px no-repeat transparent; 207 | } 208 | 209 | #logo-small { 210 | position: absolute; 211 | top: 0; 212 | padding: 18px 15px; 213 | width: 29px; 214 | height: 25px; 215 | background: url('../images/icon.svg') center center / 29px 25px no-repeat transparent; 216 | cursor: pointer; 217 | transition: left 500ms ease-in-out; 218 | } 219 | 220 | #header-wrapper.default #logo-small { 221 | left: -200px; 222 | } 223 | 224 | #header-wrapper.compact #logo-small { 225 | left: 0; 226 | } 227 | 228 | #status-board { 229 | position: absolute; 230 | font-size: 14px; 231 | transition: all 500ms ease-in-out; 232 | } 233 | 234 | #header-wrapper.default #status-board { 235 | top: 25px; 236 | left: 450px; 237 | } 238 | 239 | #header-wrapper.compact #status-board { 240 | top: 6px; 241 | left: 65px; 242 | } 243 | 244 | #status-board .item { 245 | display: inline-block; 246 | padding: 0 15px; 247 | text-align: center; 248 | } 249 | 250 | #status-board .label { 251 | font-weight: 700; 252 | } 253 | 254 | #status-board .label::after { 255 | content: ':'; 256 | } 257 | 258 | #status-board .value { 259 | margin-top: 5px; 260 | padding: 2px 5px; 261 | border-radius: 3px; 262 | background: rgb(98, 181, 71); 263 | color: rgb(255, 255, 255); 264 | } 265 | 266 | #status-board .value.errors { 267 | background: rgb(172, 0, 0); 268 | } 269 | 270 | #status-board .value.warnings { 271 | background: rgb(255, 165, 0); 272 | } 273 | 274 | #progress { 275 | /* stylelint-disable-next-line property-no-vendor-prefix */ 276 | -moz-appearance: none; 277 | box-sizing: border-box; 278 | width: 100%; 279 | height: 20px; 280 | border: none; 281 | background: rgb(235, 235, 235); 282 | transition: padding 250ms ease-in-out; 283 | } 284 | 285 | #header-wrapper.default #progress { 286 | padding-left: 355px; 287 | } 288 | 289 | #header-wrapper.compact #progress { 290 | padding-left: 0; 291 | } 292 | 293 | #progress::-moz-progress-bar { 294 | background-image: 295 | linear-gradient(135deg, transparent 33%, rgba(0, 0, 0, .1) 33%, rgba(0, 0, 0, .1) 66%, transparent 66%), 296 | linear-gradient(to bottom, rgba(255, 255, 255, .25), rgba(0, 0, 0, .25)), 297 | linear-gradient(to right, rgb(0, 139, 185), rgb(72, 185, 89)); 298 | background-size: 35px 20px, 100% 100%, 100% 100%; 299 | } 300 | 301 | #main { 302 | display: flex; 303 | box-sizing: border-box; 304 | padding: 130px 0 140px; 305 | width: 100%; 306 | } 307 | 308 | #main-section { 309 | width: 100%; 310 | } 311 | 312 | #fox { 313 | position: absolute; 314 | top: 135px; 315 | right: 30px; 316 | width: 64px; 317 | height: 64px; 318 | border-radius: 50%; 319 | background: rgba(98, 181, 71, .5); 320 | animation: waggle 1000ms 3000ms forwards ease-out; 321 | transition: background ease 500ms; 322 | } 323 | 324 | #fox:hover { 325 | background: rgba(98, 181, 71, 1); 326 | } 327 | 328 | #fox img { 329 | float: right; 330 | width: inherit; 331 | height: inherit; 332 | animation: waggle 1000ms 3000ms forwards ease-out; 333 | } 334 | 335 | #hint { 336 | position: absolute; 337 | z-index: 10; 338 | left: 20px; 339 | right: 130px; 340 | padding: 20px 15px; 341 | border-radius: 5px; 342 | background: rgb(98, 181, 71); 343 | color: rgb(255, 255, 255); 344 | box-shadow: 0 0 10px 0 rgba(0, 0, 0, .25); 345 | } 346 | 347 | #hint.success { 348 | background: rgb(60, 156, 59); 349 | } 350 | 351 | #hint::before { 352 | content: ''; 353 | position: absolute; 354 | top: 35px; 355 | bottom: auto; 356 | left: auto; 357 | right: -20px; 358 | width: 0; 359 | height: 0; 360 | border: 12px solid; 361 | border-color: rgb(98, 181, 71) transparent transparent rgb(98, 181, 71); 362 | } 363 | 364 | #hint.success::before { 365 | border-color: rgb(60, 156, 59) transparent transparent rgb(60, 156, 59); 366 | } 367 | 368 | #hint .notice { 369 | margin-bottom: 10px; 370 | font-weight: 700; 371 | } 372 | 373 | #results { 374 | margin: 35px 0; 375 | } 376 | 377 | #results ul { 378 | margin: 0; 379 | list-style: none; 380 | } 381 | 382 | #results li .wrapper { 383 | padding: 0 10px 0 35px; 384 | } 385 | 386 | #results > ul > li .wrapper { 387 | padding: 10px 10px 10px 35px; 388 | } 389 | 390 | #results > ul { 391 | padding: 0; 392 | } 393 | 394 | #results > ul > li > .wrapper { 395 | margin-top: -1px; 396 | border-top: 1px solid rgb(210, 210, 210); 397 | border-bottom: 1px solid rgb(210, 210, 210); 398 | } 399 | 400 | #results li li.has-children > .wrapper { 401 | margin-top: -1px; 402 | border-left: 1px solid rgb(210, 210, 210); 403 | border-top: 1px solid rgb(210, 210, 210); 404 | border-bottom: 1px solid rgb(210, 210, 210); 405 | } 406 | 407 | #results li li:not(.has-children) > .wrapper { 408 | margin-top: -1px; 409 | border-top: 1px solid rgb(210, 210, 210); 410 | border-bottom: 1px solid rgb(210, 210, 210); 411 | border-left: 1px dotted rgb(210, 210, 210); 412 | } 413 | 414 | #results li.has-children > .wrapper { 415 | background: url('../images/folder.png') 10px center / 16px 16px no-repeat transparent; 416 | } 417 | 418 | #results li:not(.has-children) > .wrapper { 419 | background: url('../images/bookmark.png') 10px 12px / 16px 16px no-repeat transparent; 420 | } 421 | 422 | #results li:not(.has-children) .name { 423 | font-weight: 700; 424 | } 425 | 426 | #results li:not(.has-children) .name.no-name { 427 | font-weight: 400; 428 | font-style: italic; 429 | color: rgba(33, 37, 42, .5); 430 | } 431 | 432 | #results .name { 433 | position: relative; 434 | } 435 | 436 | #results .default-wrapper .name::before, #results .duplicates-item > li > a::before { 437 | content: ''; 438 | position: absolute; 439 | width: 14px; 440 | height: 14px; 441 | border-radius: 50%; 442 | } 443 | 444 | #results .default-wrapper .name::before { 445 | top: 24px; 446 | left: -24px; 447 | } 448 | 449 | #results .duplicates-item > li > a::before { 450 | top: 14px; 451 | left: 14px; 452 | } 453 | 454 | #results .error .default-wrapper .name::before, #results .duplicates-item > li > a::before { 455 | background: rgb(172, 0, 0); 456 | } 457 | 458 | #results .warning .default-wrapper .name::before { 459 | background: rgb(255, 165, 0); 460 | } 461 | 462 | #results .unknown .default-wrapper .name::before { 463 | background: rgb(150, 150, 150); 464 | } 465 | 466 | #results .redirect .new-url { 467 | position: relative; 468 | } 469 | 470 | #results .redirect .new-url::before { 471 | content: ''; 472 | position: absolute; 473 | top: 3px; 474 | left: -25px; 475 | width: 16px; 476 | height: 16px; 477 | background: url('../images/redirect.png') center center / 16px 16px no-repeat transparent; 478 | } 479 | 480 | #results .action-buttons { 481 | margin-top: 5px; 482 | visibility: hidden; 483 | opacity: 0; 484 | transition: visibility 0ms, opacity 500ms linear; 485 | } 486 | 487 | #results li:hover > .wrapper > .action-buttons, #results .duplicates > li:hover .action-buttons { 488 | visibility: visible; 489 | opacity: 1; 490 | } 491 | 492 | #results .action-buttons a { 493 | font-size: 12px; 494 | } 495 | 496 | #results .action-buttons a:hover { 497 | font-size: 12px; 498 | } 499 | 500 | #results .action-buttons a:not(:last-child) { 501 | margin-right: 30px; 502 | } 503 | 504 | #results .duplicates-item { 505 | margin-top: 100px; 506 | } 507 | 508 | #results .duplicates-item > li > a { 509 | display: block; 510 | position: relative; 511 | margin-top: -1px; 512 | padding: 10px 10px 10px 35px; 513 | border-top: 1px solid rgb(210, 210, 210); 514 | border-bottom: 1px solid rgb(210, 210, 210); 515 | } 516 | 517 | #results .duplicates li { 518 | padding: 10px; 519 | border-bottom: 1px solid rgb(210, 210, 210); 520 | border-left: 1px dotted rgb(210, 210, 210); 521 | } 522 | 523 | #results .duplicates .url { 524 | padding-left: 24px; 525 | background: url('../images/folder.png') 0 2px / 16px 16px no-repeat transparent; 526 | } 527 | 528 | #search { 529 | margin-top: 10px; 530 | margin-left: 30px; 531 | width: calc(100% - 165px); 532 | } 533 | 534 | #filterbar { 535 | margin-top: 10px; 536 | margin-left: 30px; 537 | } 538 | 539 | #filterbar:not(.hidden) { 540 | display: inline-block; 541 | } 542 | 543 | #mass-actions { 544 | float: right; 545 | margin-top: 5px; 546 | margin-right: 135px; 547 | text-align: right; 548 | } 549 | 550 | #start { 551 | position: fixed; 552 | bottom: 160px; 553 | left: 30px; 554 | padding-left: 120px; 555 | height: 120px; 556 | font-size: 30px; 557 | background: url('../images/start.png') left center / 100px 100px no-repeat transparent; 558 | } 559 | 560 | #control-bar { 561 | position: fixed; 562 | z-index: 10; 563 | bottom: 64px; 564 | left: 0; 565 | right: 0; 566 | height: 64px; 567 | background: rgb(255, 255, 255); 568 | } 569 | 570 | #control-bar select { 571 | /* stylelint-disable-next-line property-no-vendor-prefix */ 572 | -moz-appearance: none; 573 | margin: 12px 5px 12px 10px; 574 | padding-left: 10px; 575 | padding-right: 40px; 576 | width: 310px; 577 | height: 40px; 578 | font-size: 18px; 579 | border: 2px solid rgb(33, 37, 42); 580 | background: url('../images/arrow-down.png') right 10px center / 20px 20px no-repeat transparent; 581 | cursor: pointer; 582 | transition: background ease 500ms; 583 | } 584 | 585 | #control-bar select:hover { 586 | background-color: rgb(235, 235, 235); 587 | } 588 | 589 | #control-bar select:-moz-focusring { 590 | color: transparent; 591 | text-shadow: 0 0 0 rgb(0, 0, 0); 592 | } 593 | 594 | #control-bar option { 595 | padding: 0 12px; 596 | font-size: 14px; 597 | } 598 | 599 | #options-form { 600 | padding: 10px 30px; 601 | } 602 | 603 | #footer { 604 | position: fixed; 605 | bottom: 0; 606 | left: 0; 607 | right: 0; 608 | padding: 0 20px; 609 | height: 64px; 610 | line-height: 64px; 611 | font-size: 14px; 612 | background: rgb(235, 235, 235); 613 | } 614 | 615 | #footer a { 616 | display: block; 617 | padding-left: 40px; 618 | background: url('../images/sh-at.png') no-repeat; 619 | background-size: 32px 32px; 620 | background-position: center left; 621 | color: rgb(33, 37, 42); 622 | } 623 | 624 | #debug-output-wrapper { 625 | padding: 30px 10px; 626 | max-height: 500px; 627 | border-top: 1px dotted rgb(210, 210, 210); 628 | background: rgb(237, 237, 237); 629 | overflow: scroll; 630 | } 631 | 632 | #mask { 633 | position: absolute; 634 | top: 0; 635 | bottom: 0; 636 | left: 0; 637 | right: 0; 638 | } 639 | 640 | #mask img { 641 | position: absolute; 642 | top: 50%; 643 | left: 50%; 644 | margin-top: -120px; 645 | margin-left: -32px; 646 | width: 64px; 647 | height: 64px; 648 | } 649 | 650 | #spinner { 651 | position: absolute; 652 | top: 50%; 653 | left: 50%; 654 | margin-top: -11px; 655 | margin-left: -35px; 656 | width: 70px; 657 | height: 22px; 658 | text-align: center; 659 | } 660 | 661 | #spinner > div { 662 | display: inline-block; 663 | width: 18px; 664 | height: 18px; 665 | border-radius: 50%; 666 | background-color: rgb(98, 181, 71); 667 | animation: spinner 1400ms infinite ease-in-out both; 668 | } 669 | 670 | #spinner .bounce1 { 671 | animation-delay: -320ms; 672 | } 673 | 674 | #spinner .bounce2 { 675 | animation-delay: -160ms; 676 | } 677 | 678 | .modal-dialog { 679 | position: fixed; 680 | z-index: 10; 681 | top: 0; 682 | left: 0; 683 | width: 100%; 684 | height: 100%; 685 | background-color: rgba(0, 0, 0, .75); 686 | overflow: auto; 687 | } 688 | 689 | .modal-content { 690 | position: relative; 691 | top: 50%; 692 | margin: -150px auto auto auto; 693 | width: 80%; 694 | background-color: rgb(254, 254, 254); 695 | animation-name: animateFromTop; 696 | animation-duration: 250ms; 697 | } 698 | 699 | .close { 700 | float: right; 701 | margin-top: -10px; 702 | font-size: 28px; 703 | font-weight: 700; 704 | color: rgb(255, 255, 255); 705 | text-decoration: none; 706 | cursor: pointer; 707 | transition: color ease 250ms; 708 | } 709 | 710 | .close:hover, .close:focus { 711 | color: rgb(215, 215, 215); 712 | } 713 | 714 | .modal-header { 715 | padding: 15px; 716 | background-color: rgb(98, 181, 71); 717 | color: rgb(255, 255, 255); 718 | } 719 | 720 | .modal-body { 721 | padding: 5px 15px; 722 | background: rgb(235, 235, 235); 723 | } 724 | 725 | .modal-body label { 726 | display: block; 727 | margin: 10px 0; 728 | font-size: 14px; 729 | font-weight: 700; 730 | } 731 | 732 | .modal-footer { 733 | padding: 5px; 734 | background-color: rgb(98, 181, 71); 735 | color: rgb(255, 255, 255); 736 | } 737 | 738 | @keyframes animateFromTop { 739 | from { 740 | top: -300px; 741 | opacity: 0; 742 | } 743 | 744 | to { 745 | top: 50%; 746 | opacity: 1; 747 | } 748 | } 749 | 750 | @keyframes spinner { 751 | 0%, 80%, 100% { 752 | transform: scale(0); 753 | } 754 | 755 | 40% { 756 | transform: scale(1); 757 | } 758 | } 759 | 760 | @keyframes waggle { 761 | 0% { 762 | transform: none; 763 | } 764 | 765 | 50% { 766 | transform: rotateZ(-20deg) scale(1.2); 767 | } 768 | 769 | 60% { 770 | transform: rotateZ(25deg) scale(1.2); 771 | } 772 | 773 | 67.5% { 774 | transform: rotateZ(-15deg) scale(1.2); 775 | } 776 | 777 | 75% { 778 | transform: rotateZ(15deg) scale(1.2); 779 | } 780 | 781 | 82.5% { 782 | transform: rotateZ(-12deg) scale(1.2); 783 | } 784 | 785 | 85% { 786 | transform: rotateZ(0) scale(1.2); 787 | } 788 | 789 | 100% { 790 | transform: rotateZ(0) scale(1); 791 | } 792 | } 793 | -------------------------------------------------------------------------------- /src/js/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global STATUS */ 4 | 5 | const MIN_PROGRESS = 0.01; 6 | const UI_PAGE = 'html/ui.html'; 7 | 8 | /** 9 | * @exports bookmarksorganizer 10 | */ 11 | const bookmarksorganizer = { 12 | /** 13 | * Limits the number of queried bookmarks. A value of 0 disables the limit. It's only there for debugging purposes. 14 | * In production it's always 0, there is no user setting. 15 | * 16 | * @type {integer} 17 | */ 18 | LIMIT : 0, 19 | 20 | /** 21 | * Max attempts to connect to a url. It's always 2, there is no user setting (yet). 22 | * 23 | * @type {integer} 24 | */ 25 | MAX_ATTEMPTS : 2, 26 | 27 | /** 28 | * Enables or disables the debug mode. It defaults to false and can be enabled in the add-on's settings. 29 | * 30 | * @type {boolean} 31 | */ 32 | debugEnabled : false, 33 | 34 | /** 35 | * Disables confirmation messages. It defaults to false and can be changed in the add-on's settings. 36 | * 37 | * @type {boolean} 38 | */ 39 | disableConfirmations : false, 40 | 41 | /** 42 | * Internal variable. It's only true while a check is running. 43 | * 44 | * @type {boolean} 45 | */ 46 | inProgress : false, 47 | 48 | /** 49 | * Internal variable. The index of the bookmark being checked. 50 | * 51 | * @type {integer} 52 | */ 53 | internalCounter : 0, 54 | 55 | /** 56 | * The number of total bookmarks. 57 | * 58 | * @type {integer} 59 | */ 60 | totalBookmarks : 0, 61 | 62 | /** 63 | * The number of already checked bookmarks. 64 | * 65 | * @type {integer} 66 | */ 67 | checkedBookmarks : 0, 68 | 69 | /** 70 | * The number of found errors. 71 | * 72 | * @type {integer} 73 | */ 74 | bookmarkErrors : 0, 75 | 76 | /** 77 | * The number of found warnings (e.g. redirects). 78 | * 79 | * @type {integer} 80 | */ 81 | bookmarkWarnings : 0, 82 | 83 | /** 84 | * An array of bookmarks with errors or warnings. 85 | * 86 | * @type {Array.} 87 | */ 88 | bookmarksResult : [], 89 | 90 | /** 91 | * Additional data stored for bookmarks. In current version it only contains the full bookmark path. 92 | * 93 | * @type {Array.} 94 | */ 95 | additionalData : [], 96 | 97 | /** 98 | * An array with debugging data about bookmark requests. Only used if debug mode is enabled. 99 | * 100 | * @type {Array.} 101 | */ 102 | debug : [], 103 | 104 | /** 105 | * An array of url patterns which should be ignored while checking for broken bookmarks. Please only add patterns 106 | * if there are known problems and add a comment with the GitHub issue. 107 | * 108 | */ 109 | ignoreForBrokenBookmarks : [ 110 | /* eslint-disable no-useless-escape, line-comment-position, no-inline-comments */ 111 | '^https?:\/\/groups.google.com/group/' // issue #25 112 | /* eslint-enable no-useless-escape, line-comment-position, no-inline-comments */ 113 | ], 114 | 115 | /** 116 | * Fired when a bookmark or a bookmark folder is created. 117 | * Not used because of https://bugzilla.mozilla.org/show_bug.cgi?id=1362863 118 | * 119 | * @returns {void} 120 | */ 121 | onBookmarkCreated () { 122 | browser.runtime.sendMessage({ 123 | message : 'total-bookmarks-changed', 124 | total_bookmarks : ++bookmarksorganizer.totalBookmarks 125 | }); 126 | }, 127 | 128 | /** 129 | * Fired when a bookmark or a bookmark folder is removed. 130 | * 131 | * @returns {void} 132 | */ 133 | onBookmarkRemoved () { 134 | browser.runtime.sendMessage({ 135 | message : 'total-bookmarks-changed', 136 | total_bookmarks : --bookmarksorganizer.totalBookmarks 137 | }); 138 | }, 139 | 140 | /** 141 | * Fired whenever the user changes the input, after the user has started interacting with the add-on by entering 142 | * its keyword in the address bar and then pressing the space key. 143 | * 144 | * @param {string} input - user input in the address bar, not including the add-on's keyword itself or the space 145 | * after the keyword

146 | * Supported values: duplicates, empty-names, errors, organizer, redirects 147 | * @param {function} suggest - a callback function that the event listener can call to supply suggestions for the 148 | * address bar's drop-down list 149 | * 150 | * @returns {void} 151 | */ 152 | showOmniboxSuggestions (input, suggest) { 153 | const availableCommands = ['duplicates', 'empty-names', 'errors', 'organizer', 'redirects']; 154 | const suggestions = []; 155 | 156 | for (const command of availableCommands) { 157 | if (command.indexOf(input) !== -1) { 158 | suggestions.push({ 159 | content : command, 160 | description : browser.i18n.getMessage('omnibox_command_check_' + command.replace('-', '_')) 161 | }); 162 | } 163 | 164 | if (suggestions.length === 0) { 165 | suggestions.push({ 166 | content : 'organizer', 167 | description : browser.i18n.getMessage('omnibox_command_open') 168 | }); 169 | } 170 | } 171 | 172 | suggest(suggestions); 173 | }, 174 | 175 | /** 176 | * Fired when the user has selected one of the suggestions the add-on has added to the address bar's drop-down list. 177 | * 178 | * @param {string} input - this is the value that the user selected 179 | * 180 | * @returns {void} 181 | */ 182 | callOmniboxAction (input) { 183 | bookmarksorganizer.openUserInterfaceInCurrentTab(); 184 | switch (input) { 185 | case 'errors': 186 | bookmarksorganizer.execute('broken-bookmarks', 'errors'); 187 | break; 188 | case 'redirects': 189 | bookmarksorganizer.execute('broken-bookmarks', 'warnings'); 190 | break; 191 | case 'duplicates': 192 | bookmarksorganizer.execute('duplicates', 'all'); 193 | break; 194 | case 'empty-names': 195 | bookmarksorganizer.execute('empty-names', 'all'); 196 | break; 197 | case 'organizer': 198 | default: 199 | bookmarksorganizer.openUserInterfaceInCurrentTab(); 200 | } 201 | }, 202 | 203 | /** 204 | * Fired when the toolbar icon is clicked. This method is used to open the user interface in a new tab or to switch 205 | * to the tab with the user interface if the user interface is alreary opened. 206 | * 207 | * @returns {void} 208 | */ 209 | openUserInterface () { 210 | const url = browser.extension.getURL(UI_PAGE); 211 | 212 | browser.tabs.query({}, (tabs) => { 213 | let tabId = null; 214 | 215 | for (const tab of tabs) { 216 | if (tab.url === url) { 217 | tabId = tab.id; 218 | break; 219 | } 220 | } 221 | 222 | if (tabId) { 223 | browser.tabs.update(tabId, { active : true }); 224 | } 225 | else { 226 | browser.tabs.create({ url }); 227 | } 228 | }); 229 | }, 230 | 231 | /** 232 | * This method is used to open the user interface in the current tab. It's used for the omnibox suggestions. 233 | * 234 | * @returns {void} 235 | */ 236 | openUserInterfaceInCurrentTab () { 237 | browser.tabs.update(null, { url : browser.extension.getURL(UI_PAGE) }); 238 | }, 239 | 240 | /** 241 | * Fired when a message is sent from the UI script to the background script. 242 | * 243 | * @param {Object} response - contains the response from the UI script 244 | * 245 | * @returns {void} 246 | */ 247 | async handleResponse (response) { 248 | if (response.message === 'count') { 249 | bookmarksorganizer.initBookmarkCount(); 250 | } 251 | else if (response.message === 'execute') { 252 | if (!bookmarksorganizer.inProgress) { 253 | bookmarksorganizer.execute(response.mode, 'all'); 254 | } 255 | } 256 | else if (response.message === 'edit') { 257 | await browser.bookmarks.update(response.bookmarkId, { 258 | title : response.title, 259 | url : response.url 260 | }); 261 | 262 | if (response.mode === 'duplicate') { 263 | browser.runtime.sendMessage({ 264 | message : 'update-listitem', 265 | bookmarkId : response.bookmarkId, 266 | title : response.title, 267 | path : bookmarksorganizer.additionalData[response.bookmarkId].path, 268 | mode : response.mode 269 | }); 270 | } 271 | else { 272 | const bookmarks = await browser.bookmarks.get(response.bookmarkId); 273 | browser.runtime.sendMessage({ 274 | message : 'update-listitem', 275 | bookmarkId : response.bookmarkId, 276 | bookmark : bookmarks[0], 277 | mode : response.mode 278 | }); 279 | } 280 | } 281 | else if (response.message === 'remove') { 282 | browser.bookmarks.remove(response.bookmarkId); 283 | } 284 | else if (response.message === 'repair-redirect') { 285 | browser.bookmarks.update(response.bookmarkId, { url : response.newUrl }); 286 | } 287 | }, 288 | 289 | /** 290 | * This method is used to start counting the number of bookmarks and to send the number of total bookmarks to the 291 | * UI script when finished. 292 | * 293 | * @returns {void} 294 | */ 295 | async initBookmarkCount () { 296 | bookmarksorganizer.totalBookmarks = 0; 297 | 298 | const bookmarks = await browser.bookmarks.getTree(); 299 | bookmarksorganizer.countBookmarks(bookmarks[0]); 300 | 301 | browser.runtime.sendMessage({ 302 | message : 'total-bookmarks', 303 | total_bookmarks : bookmarksorganizer.totalBookmarks 304 | }); 305 | }, 306 | 307 | /** 308 | * This method is used by the initBookmarkCount() method to count the bookmarks recursively. 309 | * 310 | * @param {Array.} bookmark - a tree of bookmarks 311 | * 312 | * @returns {void} 313 | */ 314 | countBookmarks (bookmark) { 315 | if (bookmark.url) { 316 | if (bookmarksorganizer.LIMIT > 0 && bookmarksorganizer.totalBookmarks === bookmarksorganizer.LIMIT) { 317 | return; 318 | } 319 | 320 | bookmarksorganizer.totalBookmarks++; 321 | } 322 | 323 | if (bookmark.children) { 324 | for (const child of bookmark.children) { 325 | bookmarksorganizer.countBookmarks(child); 326 | } 327 | } 328 | }, 329 | 330 | /** 331 | * This method is the starting point for checking the bookmarks. 332 | * 333 | * @param {string} mode - The checking mode

334 | * Supported values: broken-bookmarks, duplicates, empty-names 335 | * @param {string} type - The requested type of results

336 | * Supported values: errors, warnings, all 337 | * 338 | * @returns {void} 339 | */ 340 | async execute (mode, type) { 341 | bookmarksorganizer.inProgress = true; 342 | bookmarksorganizer.internalCounter = 0; 343 | bookmarksorganizer.checkedBookmarks = 0; 344 | bookmarksorganizer.bookmarkErrors = 0; 345 | bookmarksorganizer.bookmarkWarnings = 0; 346 | bookmarksorganizer.bookmarksResult = []; 347 | bookmarksorganizer.additionalData = []; 348 | bookmarksorganizer.debug = []; 349 | 350 | browser.runtime.sendMessage({ message : 'started' }); 351 | 352 | browser.storage.local.get('debugEnabled', (options) => { 353 | bookmarksorganizer.debugEnabled = options.debugEnabled; 354 | }); 355 | 356 | browser.storage.local.get('disableConfirmations', (options) => { 357 | bookmarksorganizer.disableConfirmations = options.disableConfirmations; 358 | }); 359 | 360 | const bookmarks = await browser.bookmarks.getTree(); 361 | 362 | if (mode === 'duplicates') { 363 | bookmarksorganizer.getBookmarkPath(bookmarks[0], []); 364 | bookmarksorganizer.checkAllBookmarks(bookmarks[0], mode, type); 365 | bookmarksorganizer.checkForDuplicates(); 366 | } 367 | else { 368 | bookmarksorganizer.checkAllBookmarks(bookmarks[0], mode, type); 369 | } 370 | }, 371 | 372 | /** 373 | * Get the full path of all bookmarks. It's only used for the duplicates mode. 374 | * 375 | * @param {Array.} bookmark - a tree of bookmarks 376 | * @param {string} path - the path or a part of the path of the bookmark 377 | * 378 | * @returns {Array.} - An array with the full path of all bookmarks 379 | */ 380 | getBookmarkPath (bookmark, path) { 381 | if (bookmark.title) { 382 | path.push(bookmark.title); 383 | } 384 | 385 | if (bookmark.children) { 386 | for (const childNode of bookmark.children) { 387 | bookmarksorganizer.getBookmarkPath(childNode, path); 388 | } 389 | } 390 | else { 391 | if (!bookmarksorganizer.additionalData[bookmark.id]) { 392 | bookmarksorganizer.additionalData[bookmark.id] = {}; 393 | } 394 | 395 | bookmarksorganizer.additionalData[bookmark.id].path = path.slice(0, -1); 396 | } 397 | 398 | path.pop(); 399 | 400 | return bookmarksorganizer.additionalData; 401 | }, 402 | 403 | /** 404 | * This method is the starting point for checking the bookmarks, called by execute(). 405 | * 406 | * @param {Array.} bookmark - a tree of bookmarks 407 | * @param {string} mode - The checking mode

408 | * Supported values: broken-bookmarks, duplicates, empty-names 409 | * @param {string} type - The requested type of results

410 | * Supported values: errors, warnings, all 411 | * 412 | * @returns {void} 413 | */ 414 | checkAllBookmarks (bookmark, mode, type) { 415 | switch (mode) { 416 | case 'broken-bookmarks': 417 | bookmarksorganizer.checkForBrokenBookmark(bookmark, mode, type); 418 | break; 419 | case 'duplicates': 420 | bookmarksorganizer.checkBookmarkAndAssignPath(bookmark, mode); 421 | break; 422 | case 'empty-names': 423 | bookmarksorganizer.checkForEmptyName(bookmark, mode); 424 | break; 425 | default: 426 | // do nothing 427 | } 428 | 429 | if (bookmark.children) { 430 | for (const child of bookmark.children) { 431 | bookmarksorganizer.checkAllBookmarks(child, mode, type); 432 | } 433 | } 434 | }, 435 | 436 | /** 437 | * This method is used to check for broken bookmarks, called by checkAllBookmarks(). 438 | * 439 | * @param {bookmarks.BookmarkTreeNode} bookmark - a single bookmark 440 | * @param {string} mode - The checking mode

441 | * Supported values: broken-bookmarks, duplicates, empty-names 442 | * @param {string} type - The requested type of results

443 | * Supported values: errors, warnings, all 444 | * 445 | * @returns {void} 446 | */ 447 | async checkForBrokenBookmark (bookmark, mode, type) { 448 | if (bookmark.url) { 449 | if (bookmarksorganizer.LIMIT > 0 && bookmarksorganizer.internalCounter === bookmarksorganizer.LIMIT) { 450 | return; 451 | } 452 | 453 | bookmarksorganizer.internalCounter++; 454 | 455 | if (bookmarksorganizer.ignoreForBrokenBookmarks.some((i) => (new RegExp('\\b' + i + '\\b')).test(bookmark.url))) { 456 | bookmarksorganizer.checkedBookmarks++; 457 | 458 | return; 459 | } 460 | 461 | if (/^https?:\/\//.test(bookmark.url)) { 462 | bookmark.attempts = 0; 463 | 464 | const checkedBookmark = await bookmarksorganizer.checkHttpResponse(bookmark, 'HEAD'); 465 | bookmarksorganizer.checkedBookmarks++; 466 | 467 | switch (checkedBookmark.status) { 468 | case STATUS.REDIRECT: 469 | if (type === 'all' || type === 'warnings') { 470 | bookmarksorganizer.bookmarkWarnings++; 471 | bookmarksorganizer.bookmarksResult.push(checkedBookmark); 472 | } 473 | break; 474 | case STATUS.NOT_FOUND: 475 | case STATUS.FETCH_ERROR: 476 | if (type === 'all' || type === 'errors') { 477 | bookmarksorganizer.bookmarkErrors++; 478 | bookmarksorganizer.bookmarksResult.push(checkedBookmark); 479 | } 480 | break; 481 | default: 482 | // do nothing 483 | } 484 | 485 | bookmarksorganizer.updateProgressUi(mode, true); 486 | } 487 | else { 488 | bookmarksorganizer.checkedBookmarks++; 489 | bookmarksorganizer.updateProgressUi(mode, true); 490 | } 491 | } 492 | else { 493 | bookmarksorganizer.bookmarksResult.push(bookmark); 494 | } 495 | }, 496 | 497 | /** 498 | * This method sends a fetch request to check if a bookmark is broken or not, called by checkForBrokenBookmark(). 499 | * 500 | * @param {bookmarks.BookmarkTreeNode} bookmark - a single bookmark 501 | * @param {string} method - the HTTP method to use (HEAD for first attempt, GET for second attempt) 502 | * 503 | * @returns {bookmarks.BookmarkTreeNode} - the bookmark object 504 | */ 505 | async checkHttpResponse (bookmark, method) { 506 | bookmark.attempts++; 507 | 508 | try { 509 | const response = await fetch(bookmark.url, { 510 | cache : 'no-store', 511 | credentials : 'include', 512 | method : method 513 | }); 514 | 515 | if (response.redirected) { 516 | // redirect to identical url. That's weird but there are cases in the real world… 517 | if (bookmark.url === response.url) { 518 | bookmark.status = STATUS.OK; 519 | } 520 | // redirect to another url 521 | else { 522 | bookmark.status = STATUS.REDIRECT; 523 | } 524 | 525 | bookmark.newUrl = response.url; 526 | 527 | // preserve the hash for redirects (issue #24) 528 | if (bookmark.url.indexOf('#') !== -1) { 529 | bookmark.newUrl += bookmark.url.substring(bookmark.url.indexOf('#')); 530 | } 531 | } 532 | else { 533 | const { headers } = response; 534 | if (headers.has('Content-Length') && headers.get('Content-Length') === '0') { 535 | bookmark.status = STATUS.EMPTY_BODY; 536 | } 537 | else { 538 | bookmark.status = response.status; 539 | } 540 | } 541 | 542 | if (bookmarksorganizer.debugEnabled) { 543 | bookmarksorganizer.debug.push({ 544 | bookmark : { 545 | id : bookmark.id, 546 | parentId : bookmark.parentId, 547 | title : bookmark.title, 548 | url : bookmark.url, 549 | status : bookmark.status 550 | }, 551 | method : method, 552 | cause : 'server-response', 553 | response : { 554 | url : response.url, 555 | redirected : response.redirected, 556 | status : response.status 557 | } 558 | }); 559 | } 560 | 561 | if (bookmark.status > STATUS.REDIRECT) { 562 | if (bookmark.attempts < bookmarksorganizer.MAX_ATTEMPTS) { 563 | await bookmarksorganizer.checkHttpResponse(bookmark, 'GET'); 564 | } 565 | } 566 | } 567 | catch (error) { 568 | bookmark.status = STATUS.FETCH_ERROR; 569 | 570 | if (bookmarksorganizer.debugEnabled) { 571 | bookmarksorganizer.debug.push({ 572 | bookmark : { 573 | id : bookmark.id, 574 | parentId : bookmark.parentId, 575 | title : bookmark.title, 576 | url : bookmark.url, 577 | status : bookmark.status 578 | }, 579 | method : method, 580 | cause : 'fetch-error', 581 | response : error.message 582 | }); 583 | } 584 | 585 | if (bookmark.attempts < bookmarksorganizer.MAX_ATTEMPTS) { 586 | await bookmarksorganizer.checkHttpResponse(bookmark, 'GET'); 587 | } 588 | } 589 | 590 | return bookmark; 591 | }, 592 | 593 | /** 594 | * This method assigns the full bookmark path to a bookmark object. It's only used for the duplicates mode. 595 | * 596 | * @param {bookmarks.BookmarkTreeNode} bookmark - a single bookmark 597 | * @param {string} mode - The checking mode

598 | * Supported values: broken-bookmarks, duplicates, empty-names 599 | * 600 | * @returns {void} 601 | */ 602 | checkBookmarkAndAssignPath (bookmark, mode) { 603 | if (bookmark.url) { 604 | if (bookmarksorganizer.LIMIT > 0 && bookmarksorganizer.internalCounter === bookmarksorganizer.LIMIT) { 605 | return; 606 | } 607 | 608 | bookmark.path = bookmarksorganizer.additionalData[bookmark.id].path; 609 | 610 | bookmarksorganizer.internalCounter++; 611 | bookmarksorganizer.checkedBookmarks++; 612 | bookmarksorganizer.updateProgressUi(mode, false); 613 | } 614 | 615 | bookmarksorganizer.bookmarksResult.push(bookmark); 616 | }, 617 | 618 | /** 619 | * This method is used to check for bookmarks with empty name. 620 | * 621 | * @param {bookmarks.BookmarkTreeNode} bookmark - a single bookmark 622 | * @param {string} mode - The checking mode

623 | * Supported values: broken-bookmarks, duplicates, empty-names 624 | * 625 | * @returns {void} 626 | */ 627 | checkForEmptyName (bookmark, mode) { 628 | if (bookmark.url) { 629 | if (bookmarksorganizer.LIMIT > 0 && bookmarksorganizer.internalCounter === bookmarksorganizer.LIMIT) { 630 | return; 631 | } 632 | 633 | bookmarksorganizer.internalCounter++; 634 | 635 | // skip place:-URIs (issue #3) 636 | if (!bookmark.url.startsWith('place:')) { 637 | if (!bookmark.title) { 638 | bookmarksorganizer.bookmarkErrors++; 639 | bookmarksorganizer.bookmarksResult.push(bookmark); 640 | } 641 | } 642 | 643 | bookmarksorganizer.checkedBookmarks++; 644 | bookmarksorganizer.updateProgressUi(mode, true); 645 | } 646 | else { 647 | bookmarksorganizer.bookmarksResult.push(bookmark); 648 | } 649 | }, 650 | 651 | /** 652 | * This method is used to check for duplicates. 653 | * 654 | * @returns {void} 655 | */ 656 | checkForDuplicates () { 657 | const duplicates = { }; 658 | 659 | bookmarksorganizer.bookmarksResult.forEach((bookmark) => { 660 | if (bookmark.url) { 661 | if (duplicates[bookmark.url]) { 662 | duplicates[bookmark.url].push(bookmark); 663 | } 664 | else { 665 | duplicates[bookmark.url] = [bookmark]; 666 | } 667 | } 668 | }); 669 | 670 | Object.keys(duplicates).forEach((key) => { 671 | if (duplicates[key].length < 2) { 672 | delete duplicates[key]; 673 | } 674 | else { 675 | bookmarksorganizer.bookmarkErrors++; 676 | } 677 | }); 678 | 679 | browser.runtime.sendMessage({ 680 | message : 'show-duplicates-ui', 681 | bookmarks : duplicates, 682 | errors : bookmarksorganizer.bookmarkErrors 683 | }); 684 | 685 | bookmarksorganizer.inProgress = false; 686 | }, 687 | 688 | /** 689 | * This method is used to send the progress to the UI script. 690 | * 691 | * @param {string} mode - The checking mode

692 | * Supported values: broken-bookmarks, duplicates, empty-names 693 | * @param {boolean} checkForFinish - boolean, indicates whether the finished message should be sent to the UI script 694 | * or not 695 | * 696 | * @returns {void} 697 | */ 698 | updateProgressUi (mode, checkForFinish) { 699 | let progress = bookmarksorganizer.checkedBookmarks / bookmarksorganizer.totalBookmarks; 700 | if (progress < MIN_PROGRESS) { 701 | progress = MIN_PROGRESS; 702 | } 703 | 704 | browser.runtime.sendMessage({ 705 | message : 'update-counters', 706 | total_bookmarks : bookmarksorganizer.totalBookmarks, 707 | checked_bookmarks : bookmarksorganizer.checkedBookmarks, 708 | bookmarks_errors : bookmarksorganizer.bookmarkErrors, 709 | bookmarks_warnings : bookmarksorganizer.bookmarkWarnings, 710 | progress : progress 711 | }); 712 | 713 | if (checkForFinish && bookmarksorganizer.checkedBookmarks === bookmarksorganizer.totalBookmarks) { 714 | const bookmarks = bookmarksorganizer.buildResultArray(bookmarksorganizer.bookmarksResult)[0].children; 715 | 716 | browser.runtime.sendMessage({ 717 | message : 'finished', 718 | mode : mode, 719 | bookmarks : bookmarks, 720 | disableConfirmations : bookmarksorganizer.disableConfirmations, 721 | debug : bookmarksorganizer.debug 722 | }); 723 | 724 | bookmarksorganizer.inProgress = false; 725 | } 726 | }, 727 | 728 | /** 729 | * Builds an sorted array (by path) with the results (bookmarks with errors and warnings). Only used for broken 730 | * bookmarks and bookmarks with empty names, not for duplicates. 731 | * 732 | * @param {Array.} bookmarks - a tree of bookmarks 733 | * 734 | * @returns {Array.} - an array with the result of bookmarks with errors and warnings 735 | */ 736 | buildResultArray (bookmarks) { 737 | const result = []; 738 | const mappedArray = {}; 739 | let mappedElement = null; 740 | 741 | for (const bookmark of bookmarks) { 742 | mappedArray[bookmark.id] = bookmark; 743 | mappedArray[bookmark.id].children = []; 744 | } 745 | 746 | for (const id in mappedArray) { 747 | if (Object.prototype.hasOwnProperty.call(mappedArray, id)) { 748 | mappedElement = mappedArray[id]; 749 | if (mappedElement.parentId) { 750 | mappedArray[mappedElement.parentId].children.push(mappedElement); 751 | } 752 | else { 753 | result.push(mappedElement); 754 | } 755 | } 756 | } 757 | 758 | return result; 759 | } 760 | }; 761 | 762 | // disabled because of https://bugzilla.mozilla.org/show_bug.cgi?id=1362863 763 | // browser.bookmarks.onCreated.addListener(bookmarksorganizer.onBookmarkCreated); 764 | 765 | browser.bookmarks.onRemoved.addListener(bookmarksorganizer.onBookmarkRemoved); 766 | browser.browserAction.onClicked.addListener(bookmarksorganizer.openUserInterface); 767 | browser.omnibox.onInputChanged.addListener(bookmarksorganizer.showOmniboxSuggestions); 768 | browser.omnibox.onInputEntered.addListener(bookmarksorganizer.callOmniboxAction); 769 | browser.omnibox.setDefaultSuggestion({ description : browser.i18n.getMessage('omnibox_default_description') }); 770 | browser.runtime.onMessage.addListener(bookmarksorganizer.handleResponse); 771 | -------------------------------------------------------------------------------- /src/js/ui.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* global STATUS */ 4 | 5 | const ESC_KEY = 27; 6 | const HEADER_SWITCH_POSITION = 100; 7 | const HEADER_TIMEOUT_IN_MS = 250; 8 | const MIN_PROGRESS = 0.01; 9 | 10 | const elBody = document.querySelector('body'); 11 | const elButton = document.getElementById('submit-button'); 12 | const elFox = document.getElementById('fox'); 13 | const elHeaderWrapper = document.getElementById('header-wrapper'); 14 | const elHint = document.getElementById('hint'); 15 | const elMode = document.getElementById('mode'); 16 | const elResultWrapper = document.getElementById('result-wrapper'); 17 | const elResults = document.getElementById('results'); 18 | const elTotalBookmarks = document.getElementById('total-bookmarks'); 19 | const elCheckedBookmarks = document.getElementById('checked-bookmarks'); 20 | const elBookmarksErrors = document.getElementById('bookmarks-errors'); 21 | const elBookmarksWarnings = document.getElementById('bookmarks-warnings'); 22 | const elProgress = document.getElementById('progress'); 23 | const elMassActions = document.getElementById('mass-actions'); 24 | const elRepairAllRedirects = document.getElementById('repair-all-redirects'); 25 | const elDeleteAllBookmarksWithErrors = document.getElementById('delete-all-bookmarks-with-errors'); 26 | const elFilterBar = document.getElementById('filterbar'); 27 | const elSearch = document.getElementById('search'); 28 | const elStart = document.getElementById('start'); 29 | const elDebugWrapper = document.getElementById('debug-output-wrapper'); 30 | const elDebugOutput = document.getElementById('debug-output'); 31 | const elMask = document.getElementById('mask'); 32 | const elSpinner = document.getElementById('spinner'); 33 | 34 | /** 35 | * @exports ui 36 | */ 37 | const ui = { 38 | /** 39 | * Disables confirmation messages. It defaults to false and can be changed in the add-on's settings. 40 | * 41 | * @type {boolean} 42 | */ 43 | disableConfirmations : false, 44 | 45 | /** 46 | * Number of bookmarks with errors or warnings. 47 | * 48 | * @type {integer} 49 | */ 50 | markedBookmarks : 0, 51 | 52 | /** 53 | * Number of bookmarks with warnings. 54 | * 55 | * @type {integer} 56 | */ 57 | warnings : 0, 58 | 59 | /** 60 | * boolean, indicates wheter the "no results" message should be shown or not. 61 | * 62 | * @type {boolean} 63 | */ 64 | showNoResultsMessage : false, 65 | 66 | /** 67 | * boolean, indicates wheter the search field should be shown or not. 68 | * 69 | * @type {boolean} 70 | */ 71 | showSearchField : false, 72 | 73 | /** 74 | * boolean, indicates wheter the filter checkboxes should be shown or not. 75 | * 76 | * @type {boolean} 77 | */ 78 | showFilterCheckboxes : false, 79 | 80 | /** 81 | * boolean, indicates wheter the mass action buttons (remove all broken bookmarks, repair all redirects) should be 82 | * shown or not. 83 | * 84 | * @type {boolean} 85 | */ 86 | showMassActionButtons : false, 87 | 88 | /** 89 | * boolean, indicates wheter the debug output should be shown or not. 90 | * 91 | * @type {boolean} 92 | */ 93 | showDebugOutput : false, 94 | 95 | /** 96 | * Fired when the initial HTML document has been completely loaded and parsed. Counts the bookmarks and initializes 97 | * the UI script. 98 | * 99 | * @returns {void} 100 | */ 101 | init () { 102 | browser.runtime.sendMessage({ message : 'count' }); 103 | ui.uiscript(); 104 | }, 105 | 106 | /** 107 | * Setup method for the user interface, called by init(). 108 | * 109 | * @returns {void} 110 | */ 111 | uiscript () { 112 | const delta = 5; 113 | let didScroll = false; 114 | let lastScrollTop = 0; 115 | 116 | const hasScrolled = function () { 117 | const { scrollTop } = document.documentElement; 118 | 119 | if (Math.abs(lastScrollTop - scrollTop) <= delta) { 120 | return; 121 | } 122 | 123 | if (scrollTop > HEADER_SWITCH_POSITION) { 124 | elHeaderWrapper.classList.remove('default'); 125 | elHeaderWrapper.classList.add('compact'); 126 | } 127 | else { 128 | elHeaderWrapper.classList.remove('compact'); 129 | elHeaderWrapper.classList.add('default'); 130 | } 131 | 132 | lastScrollTop = scrollTop; 133 | }; 134 | 135 | window.addEventListener('scroll', () => { 136 | didScroll = true; 137 | }); 138 | 139 | setInterval(() => { 140 | if (didScroll) { 141 | hasScrolled(); 142 | didScroll = false; 143 | } 144 | }, HEADER_TIMEOUT_IN_MS); 145 | 146 | const closeButton = document.getElementById('hint-close-button'); 147 | closeButton.onclick = function () { 148 | elHint.classList.add('hidden'); 149 | }; 150 | 151 | window.onkeydown = function (e) { 152 | if (e.keyCode === ESC_KEY) { 153 | elHint.classList.add('hidden'); 154 | } 155 | }; 156 | }, 157 | 158 | /** 159 | * Fired when the Fox icon is clicked. 160 | * 161 | * @param {MouseEvent} e - event 162 | * 163 | * @returns {void} 164 | */ 165 | handleFoxMessageClick (e) { 166 | e.preventDefault(); 167 | elHint.classList.toggle('hidden'); 168 | }, 169 | 170 | /** 171 | * Fired when the button for starting a bookmark check is clicked. 172 | * 173 | * @param {MouseEvent} e - event 174 | * 175 | * @returns {void} 176 | */ 177 | execute (e) { 178 | e.preventDefault(); 179 | browser.runtime.sendMessage({ 180 | message : 'execute', 181 | mode : elMode.value 182 | }); 183 | }, 184 | 185 | /** 186 | * Fired when a message is sent from the background script to the UI script. 187 | * 188 | * @param {Object} response - contains the response from the background script 189 | * 190 | * @returns {void} 191 | */ 192 | handleResponse (response) { 193 | if (response.message === 'started') { 194 | elMask.classList.remove('is-hidden'); 195 | elMask.classList.add('active-check'); 196 | elSpinner.classList.add('active-check'); 197 | elSpinner.classList.remove('is-hidden'); 198 | elResultWrapper.classList.add('hidden'); 199 | elStart.classList.add('hidden'); 200 | elHint.getElementsByClassName('notice')[0].textContent = browser.i18n.getMessage('greeting'); 201 | elHint.getElementsByClassName('content')[0].textContent = browser.i18n.getMessage('intro_check'); 202 | elHint.classList.add('hidden'); 203 | elHint.classList.remove('success'); 204 | elResults.textContent = ''; 205 | elDebugOutput.textContent = ''; 206 | elProgress.setAttribute('value', MIN_PROGRESS); 207 | elCheckedBookmarks.textContent = 0; 208 | elBookmarksErrors.textContent = 0; 209 | elBookmarksWarnings.textContent = 0; 210 | elButton.disabled = true; 211 | } 212 | else if (response.message === 'total-bookmarks') { 213 | elTotalBookmarks.textContent = response.total_bookmarks; 214 | elMask.classList.add('is-hidden'); 215 | elSpinner.classList.add('is-hidden'); 216 | 217 | if (response.total_bookmarks === 0) { 218 | elStart.classList.add('hidden'); 219 | elHint.getElementsByClassName('notice')[0].textContent = browser.i18n.getMessage('no_marked_bookmarks_title'); 220 | elHint.getElementsByClassName('content')[0].textContent = browser.i18n.getMessage('no_marked_bookmarks'); 221 | elHint.classList.add('success'); 222 | elHint.classList.remove('hidden'); 223 | } 224 | else { 225 | elButton.disabled = false; 226 | } 227 | } 228 | else if (response.message === 'total-bookmarks-changed') { 229 | elTotalBookmarks.textContent = response.total_bookmarks; 230 | } 231 | else if (response.message === 'update-counters') { 232 | elTotalBookmarks.textContent = response.total_bookmarks; 233 | elCheckedBookmarks.textContent = response.checked_bookmarks; 234 | elBookmarksErrors.textContent = response.bookmarks_errors; 235 | elBookmarksWarnings.textContent = response.bookmarks_warnings; 236 | elProgress.setAttribute('value', response.progress); 237 | ui.markedBookmarks = response.bookmarks_errors + response.bookmarks_warnings; 238 | ui.warnings = response.bookmarks_warnings; 239 | } 240 | else if (response.message === 'finished') { 241 | ui.disableConfirmations = response.disableConfirmations; 242 | 243 | elMask.classList.add('is-hidden'); 244 | elMask.classList.remove('active-check'); 245 | elSpinner.classList.add('is-hidden'); 246 | elSpinner.classList.remove('active-check'); 247 | 248 | ui.buildBookmarksTree(response.bookmarks); 249 | ui.hideEmptyCategories(); 250 | 251 | if (ui.markedBookmarks === 0) { 252 | ui.showNoResultsMessage = true; 253 | ui.showSearchField = false; 254 | } 255 | else { 256 | ui.showNoResultsMessage = false; 257 | ui.showSearchField = true; 258 | elSearch.focus(); 259 | } 260 | 261 | if (response.mode !== 'broken-bookmarks' || ui.markedBookmarks === 0) { 262 | ui.showFilterCheckboxes = false; 263 | } 264 | else { 265 | ui.showFilterCheckboxes = true; 266 | } 267 | 268 | if (ui.warnings === 0) { 269 | ui.showMassActionButtons = false; 270 | } 271 | else { 272 | ui.showMassActionButtons = true; 273 | } 274 | 275 | if (response.debug.length === 0) { 276 | ui.showDebugOutput = false; 277 | } 278 | else { 279 | elDebugOutput.textContent = JSON.stringify(response.debug, null, 2); 280 | ui.showDebugOutput = true; 281 | } 282 | 283 | ui.doUiCleanup(); 284 | } 285 | else if (response.message === 'show-duplicates-ui') { 286 | elBookmarksErrors.textContent = response.errors; 287 | 288 | if (response.errors === 0) { 289 | ui.showNoResultsMessage = true; 290 | } 291 | else { 292 | ui.showNoResultsMessage = false; 293 | } 294 | 295 | ui.showSearchField = false; 296 | ui.showFilterCheckboxes = false; 297 | ui.showMassActionButtons = false; 298 | ui.showDebugOutput = false; 299 | 300 | ui.buildDuplicatesUi(response.bookmarks); 301 | ui.doUiCleanup(); 302 | } 303 | else if (response.message === 'update-listitem') { 304 | const listItem = document.getElementById(response.bookmarkId); 305 | 306 | if (response.mode === 'duplicate') { 307 | const title = browser.i18n.getMessage('bookmark_name') + ': ' + response.title; 308 | const path = browser.i18n.getMessage('bookmark_path') + ': ' + response.path.join(' / '); 309 | 310 | listItem.getElementsByClassName('name')[0].textContent = title; 311 | listItem.getElementsByClassName('url')[0].textContent = path; 312 | } 313 | else { 314 | response.bookmark.status = STATUS.UNKNOWN; 315 | listItem.replaceWith(ui.getSingleNode(response.bookmark)); 316 | } 317 | } 318 | }, 319 | 320 | /** 321 | * Shows and hides elements based on the check mode and the result. 322 | * 323 | * @returns {void} 324 | */ 325 | doUiCleanup () { 326 | elButton.disabled = false; 327 | elResultWrapper.classList.remove('hidden'); 328 | elStart.classList.add('hidden'); 329 | elSearch.focus(); 330 | 331 | if (ui.showNoResultsMessage) { 332 | elHint.getElementsByClassName('notice')[0].textContent = browser.i18n.getMessage('no_marked_bookmarks_title'); 333 | elHint.getElementsByClassName('content')[0].textContent = browser.i18n.getMessage('no_marked_bookmarks'); 334 | elHint.classList.add('success'); 335 | elHint.classList.remove('hidden'); 336 | } 337 | else { 338 | elHint.getElementsByClassName('notice')[0].textContent = browser.i18n.getMessage('some_marked_bookmarks_title'); 339 | elHint.getElementsByClassName('content')[0].textContent = browser.i18n.getMessage('some_marked_bookmarks'); 340 | elHint.classList.remove('success'); 341 | } 342 | 343 | if (ui.showMassActionButtons) { 344 | elMassActions.classList.remove('hidden'); 345 | } 346 | else { 347 | elMassActions.classList.add('hidden'); 348 | } 349 | 350 | if (ui.showSearchField) { 351 | elSearch.classList.remove('hidden'); 352 | } 353 | else { 354 | elSearch.classList.add('hidden'); 355 | } 356 | 357 | if (ui.showFilterCheckboxes) { 358 | elFilterBar.classList.remove('hidden'); 359 | } 360 | else { 361 | elFilterBar.classList.add('hidden'); 362 | } 363 | 364 | if (ui.showDebugOutput) { 365 | elDebugWrapper.classList.remove('hidden'); 366 | } 367 | else { 368 | elDebugWrapper.classList.add('hidden'); 369 | } 370 | }, 371 | 372 | /** 373 | * Builds the user interface for the duplicates mode. 374 | * 375 | * @param {Array.} bookmarks - a tree of bookmarks 376 | * 377 | * @returns {void} 378 | */ 379 | buildDuplicatesUi (bookmarks) { 380 | const list = document.createElement('ul'); 381 | list.classList.add('duplicates-item'); 382 | 383 | for (const url in bookmarks) { 384 | if (Object.prototype.hasOwnProperty.call(bookmarks, url)) { 385 | list.appendChild(ui.getSingleDuplicateNode(bookmarks, url)); 386 | } 387 | } 388 | 389 | elResults.appendChild(list); 390 | }, 391 | 392 | /** 393 | * Gets the HTML for a single bookmark item for the duplicates mode. 394 | * 395 | * @param {Array.} bookmarks - a array of bookmarks 396 | * @param {string} url - the url of the duplicate bookmarks 397 | * 398 | * @returns {HTMLElement} - the HTML for a single bookmark list item 399 | */ 400 | getSingleDuplicateNode (bookmarks, url) { 401 | const template = document.getElementById('duplicates-template').content.cloneNode(true); 402 | const elListItem = document.createElement('li'); 403 | elListItem.classList.add('error'); 404 | 405 | const elUrlText = document.createTextNode(url); 406 | const elUrl = template.querySelector('.url'); 407 | elUrl.appendChild(elUrlText); 408 | elUrl.setAttribute('href', url); 409 | elUrl.setAttribute('target', '_blank'); 410 | elUrl.setAttribute('rel', 'noopener'); 411 | elListItem.appendChild(elUrl); 412 | 413 | const elDuplicatesList = document.createElement('ul'); 414 | elDuplicatesList.classList.add('duplicates'); 415 | 416 | const duplicates = bookmarks[url]; 417 | 418 | for (const duplicate of duplicates) { 419 | const elDuplicate = document.createElement('li'); 420 | elDuplicate.id = duplicate.id; 421 | 422 | const elDuplicateName = document.createElement('div'); 423 | elDuplicateName.classList.add('name'); 424 | elDuplicateName.textContent = browser.i18n.getMessage('bookmark_name') + ': ' + duplicate.title; 425 | elDuplicate.appendChild(elDuplicateName); 426 | 427 | const elDuplicatePath = document.createElement('div'); 428 | elDuplicatePath.classList.add('url'); 429 | elDuplicatePath.textContent = browser.i18n.getMessage('bookmark_path') + ': ' + duplicate.path.join(' / '); 430 | elDuplicate.appendChild(elDuplicatePath); 431 | 432 | const elActionButtons = document.createElement('div'); 433 | elActionButtons.classList.add('action-buttons'); 434 | 435 | const elEditButtonText = document.createTextNode(browser.i18n.getMessage('bookmark_action_edit')); 436 | const elEditButton = document.createElement('a'); 437 | elEditButton.appendChild(elEditButtonText); 438 | elEditButton.setAttribute('data-id', duplicate.id); 439 | elEditButton.setAttribute('data-action', 'edit'); 440 | elEditButton.setAttribute('data-name', duplicate.title); 441 | elEditButton.setAttribute('data-url', duplicate.url); 442 | elEditButton.setAttribute('data-mode', 'duplicate'); 443 | elEditButton.setAttribute('href', '#'); 444 | elActionButtons.appendChild(elEditButton); 445 | 446 | const elDeleteButtonText = document.createTextNode(browser.i18n.getMessage('bookmark_action_delete')); 447 | const elDeleteButton = document.createElement('a'); 448 | elDeleteButton.appendChild(elDeleteButtonText); 449 | elDeleteButton.setAttribute('data-id', duplicate.id); 450 | elDeleteButton.setAttribute('data-action', 'delete'); 451 | elDeleteButton.setAttribute('data-confirmation', 'true'); 452 | elDeleteButton.setAttribute('data-confirmation-msg', browser.i18n.getMessage('bookmark_confirmation_delete')); 453 | elDeleteButton.setAttribute('href', '#'); 454 | elActionButtons.appendChild(elDeleteButton); 455 | 456 | elDuplicate.appendChild(elActionButtons); 457 | elDuplicatesList.appendChild(elDuplicate); 458 | } 459 | 460 | elListItem.appendChild(elDuplicatesList); 461 | 462 | return elListItem; 463 | }, 464 | 465 | /** 466 | * Builds the user interface for other modes than the duplicates mode. 467 | * 468 | * @param {Array.} bookmarks - a tree of bookmarks 469 | * 470 | * @returns {void} 471 | */ 472 | buildBookmarksTree (bookmarks) { 473 | elResults.appendChild(ui.getNodes(bookmarks)); 474 | }, 475 | 476 | /** 477 | * Gets the HTML for all nodes for other modes than the duplicates mode. 478 | * 479 | * @param {Array.} bookmarks - a tree of bookmarks 480 | * 481 | * @returns {HTMLElement} - the HTML for a unordered list of bookmarks 482 | */ 483 | getNodes (bookmarks) { 484 | const list = document.createElement('ul'); 485 | 486 | for (const bookmark of bookmarks) { 487 | if (bookmark.url || (!bookmark.url && bookmark.children.length > 0)) { 488 | list.appendChild(ui.getSingleNode(bookmark)); 489 | } 490 | } 491 | 492 | return list; 493 | }, 494 | 495 | /** 496 | * Gets the HTML for a single bookmark for other modes than the duplicates mode. 497 | * 498 | * @param {bookmarks.BookmarkTreeNode} bookmark - a single bookmark 499 | * 500 | * @returns {HTMLElement} - the HTML for a single bookmark list item 501 | */ 502 | getSingleNode (bookmark) { 503 | let template = null; 504 | const li = document.createElement('li'); 505 | li.id = bookmark.id; 506 | li.setAttribute('data-filter-searchfield', 'true'); 507 | li.setAttribute('data-filter-checkbox', 'true'); 508 | 509 | if (bookmark.url) { 510 | li.classList.add('is-bookmark'); 511 | template = document.getElementById('result-template-url').content.cloneNode(true); 512 | 513 | const elName = template.querySelector('.name'); 514 | let { title } = bookmark; 515 | 516 | if (!title) { 517 | elName.classList.add('no-name'); 518 | title = browser.i18n.getMessage('bookmark_no_name'); 519 | } 520 | 521 | const elNameText = document.createTextNode(title); 522 | elName.appendChild(elNameText); 523 | 524 | const elUrlText = document.createTextNode(bookmark.url); 525 | const elUrl = template.querySelector('.url'); 526 | elUrl.appendChild(elUrlText); 527 | elUrl.setAttribute('href', bookmark.url); 528 | elUrl.setAttribute('target', '_blank'); 529 | elUrl.setAttribute('rel', 'noopener'); 530 | 531 | if (bookmark.status) { 532 | switch (bookmark.status) { 533 | case STATUS.REDIRECT: 534 | li.classList.add('warning', 'redirect'); 535 | break; 536 | case STATUS.NOT_FOUND: 537 | case STATUS.FETCH_ERROR: 538 | li.classList.add('error'); 539 | break; 540 | case STATUS.UNKNOWN: 541 | li.classList.add('unknown'); 542 | break; 543 | default: 544 | // do nothing 545 | } 546 | } 547 | else { 548 | li.classList.add('error'); 549 | } 550 | 551 | const elActionButtons = template.querySelector('.action-buttons'); 552 | 553 | const elEditButtonText = document.createTextNode(browser.i18n.getMessage('bookmark_action_edit')); 554 | const elEditButton = document.createElement('a'); 555 | elEditButton.appendChild(elEditButtonText); 556 | elEditButton.setAttribute('data-id', bookmark.id); 557 | elEditButton.setAttribute('data-action', 'edit'); 558 | elEditButton.setAttribute('data-name', bookmark.title); 559 | elEditButton.setAttribute('data-url', bookmark.url); 560 | elEditButton.setAttribute('data-mode', 'default'); 561 | elEditButton.setAttribute('href', '#'); 562 | elActionButtons.appendChild(elEditButton); 563 | 564 | if (bookmark.status && bookmark.status === STATUS.REDIRECT) { 565 | const elNewUrlText = document.createTextNode(bookmark.newUrl); 566 | const elNewUrl = template.querySelector('.new-url'); 567 | elNewUrl.appendChild(elNewUrlText); 568 | elNewUrl.setAttribute('href', bookmark.newUrl); 569 | elNewUrl.setAttribute('target', '_blank'); 570 | elNewUrl.setAttribute('rel', 'noopener'); 571 | 572 | const elRepairRedirectButtonText = document.createTextNode( 573 | browser.i18n.getMessage('bookmark_action_repair_redirect') 574 | ); 575 | const elRepairRedirectButton = document.createElement('a'); 576 | elRepairRedirectButton.appendChild(elRepairRedirectButtonText); 577 | elRepairRedirectButton.setAttribute('data-id', bookmark.id); 578 | elRepairRedirectButton.setAttribute('data-action', 'repair-redirect'); 579 | elRepairRedirectButton.setAttribute('data-confirmation', 'true'); 580 | elRepairRedirectButton.setAttribute( 581 | 'data-confirmation-msg', browser.i18n.getMessage('bookmark_confirmation_repair_redirect') 582 | ); 583 | elRepairRedirectButton.setAttribute('data-new-url', bookmark.newUrl); 584 | elRepairRedirectButton.setAttribute('href', '#'); 585 | elActionButtons.appendChild(elRepairRedirectButton); 586 | } 587 | 588 | const elDeleteButtonText = document.createTextNode(browser.i18n.getMessage('bookmark_action_delete')); 589 | const elDeleteButton = document.createElement('a'); 590 | elDeleteButton.appendChild(elDeleteButtonText); 591 | elDeleteButton.setAttribute('data-id', bookmark.id); 592 | elDeleteButton.setAttribute('data-action', 'delete'); 593 | elDeleteButton.setAttribute('data-confirmation', 'true'); 594 | elDeleteButton.setAttribute('data-confirmation-msg', browser.i18n.getMessage('bookmark_confirmation_delete')); 595 | elDeleteButton.setAttribute('href', '#'); 596 | elActionButtons.appendChild(elDeleteButton); 597 | } 598 | else { 599 | template = document.getElementById('result-template-name').content.cloneNode(true); 600 | 601 | const title = bookmark.title ? bookmark.title : browser.i18n.getMessage('bookmark_no_name'); 602 | const elNameText = document.createTextNode(title); 603 | const elName = template.querySelector('.name'); 604 | elName.appendChild(elNameText); 605 | } 606 | 607 | li.appendChild(template); 608 | 609 | if (bookmark.children && bookmark.children.length > 0) { 610 | li.classList.add('has-children'); 611 | li.appendChild(ui.getNodes(bookmark.children)); 612 | } 613 | 614 | return li; 615 | }, 616 | 617 | /** 618 | * Shows the edit overlay for a broken bookmark. 619 | * 620 | * @param {integer} bookmarkId - the id of the bookmark 621 | * @param {string} title - the current title of the bookmark 622 | * @param {string} url - the current url of the bookmark 623 | * @param {string} mode - the mode of the bookmark check 624 | * 625 | * @returns {void} 626 | */ 627 | showEditBookmarkOverlay (bookmarkId, title, url, mode) { 628 | const modal = document.getElementById('modal-dialog'); 629 | modal.classList.remove('hidden'); 630 | 631 | const closeButton = document.getElementById('close-button'); 632 | closeButton.onclick = function () { 633 | modal.classList.add('hidden'); 634 | }; 635 | 636 | window.onclick = function (e) { 637 | if (e.target === modal) { 638 | modal.classList.add('hidden'); 639 | } 640 | }; 641 | 642 | window.onkeydown = function (e) { 643 | if (e.keyCode === ESC_KEY) { 644 | modal.classList.add('hidden'); 645 | } 646 | }; 647 | 648 | const elName = document.getElementById('name'); 649 | elName.value = title; 650 | elName.focus(); 651 | 652 | const elUrl = document.getElementById('url'); 653 | elUrl.value = url; 654 | 655 | const submitButton = document.getElementById('submit-changes'); 656 | submitButton.onclick = function (e) { 657 | e.preventDefault(); 658 | 659 | modal.classList.add('hidden'); 660 | ui.editBookmark(bookmarkId, elName.value, elUrl.value, mode); 661 | }; 662 | }, 663 | 664 | /** 665 | * This method is used to edit a bookmark. 666 | * 667 | * @param {integer} bookmarkId - the id of the bookmark 668 | * @param {string} title - the new title of the bookmark 669 | * @param {string} url - the new url of the bookmark 670 | * @param {string} mode - the mode of the bookmark check 671 | * 672 | * @returns {void} 673 | */ 674 | editBookmark (bookmarkId, title, url, mode) { 675 | browser.runtime.sendMessage({ 676 | message : 'edit', 677 | bookmarkId : bookmarkId, 678 | title : title, 679 | url : url, 680 | mode : mode 681 | }); 682 | }, 683 | 684 | /** 685 | * This method is used to delete a bookmark. 686 | * 687 | * @param {integer} bookmarkId - the id of the bookmark 688 | * 689 | * @returns {void} 690 | */ 691 | deleteBookmark (bookmarkId) { 692 | browser.runtime.sendMessage({ 693 | message : 'remove', 694 | bookmarkId : bookmarkId 695 | }); 696 | }, 697 | 698 | /** 699 | * This method is used to change the url of a bookmark which is marked as redirect. 700 | * 701 | * @param {integer} bookmarkId - the id of the bookmark 702 | * @param {string} newUrl - the new url of the bookmark 703 | * 704 | * @returns {void} 705 | */ 706 | repairRedirect (bookmarkId, newUrl) { 707 | browser.runtime.sendMessage({ 708 | message : 'repair-redirect', 709 | bookmarkId : bookmarkId, 710 | newUrl : newUrl 711 | }); 712 | }, 713 | 714 | /** 715 | * Fired when of the action buttons is clicked. 716 | * 717 | * @param {MouseEvent} e - event 718 | * 719 | * @returns {void} 720 | */ 721 | handleActionButtonClicks (e) { 722 | if (e.target.getAttribute('data-action')) { 723 | e.preventDefault(); 724 | 725 | if (e.target.getAttribute('data-confirmation')) { 726 | // eslint-disable-next-line no-alert 727 | if (!ui.disableConfirmations && !confirm(e.target.getAttribute('data-confirmation-msg'))) { 728 | return; 729 | } 730 | } 731 | 732 | const bookmarkId = e.target.getAttribute('data-id'); 733 | const elBookmark = document.getElementById(bookmarkId); 734 | const title = e.target.getAttribute('data-name'); 735 | const url = e.target.getAttribute('data-url'); 736 | const mode = e.target.getAttribute('data-mode'); 737 | 738 | switch (e.target.getAttribute('data-action')) { 739 | case 'edit': 740 | ui.showEditBookmarkOverlay(bookmarkId, title, url, mode); 741 | break; 742 | case 'delete': 743 | elBookmark.remove(); 744 | ui.hideEmptyCategories(); 745 | ui.deleteBookmark(bookmarkId); 746 | break; 747 | case 'repair-redirect': 748 | elBookmark.remove(); 749 | ui.hideEmptyCategories(); 750 | ui.repairRedirect(bookmarkId, e.target.getAttribute('data-new-url')); 751 | break; 752 | default: 753 | // do nothing 754 | } 755 | } 756 | else if (e.target.getAttribute('data-filter')) { 757 | ui.applyCheckboxFilter(e); 758 | } 759 | }, 760 | 761 | /** 762 | * This method is used to change the url of all bookmarks which are marked as redirect. 763 | * 764 | * @param {MouseEvent} e - event 765 | * 766 | * @returns {void} 767 | */ 768 | repairAllRedirects (e) { 769 | e.preventDefault(); 770 | 771 | // eslint-disable-next-line no-alert 772 | if (!ui.disableConfirmations && !confirm(browser.i18n.getMessage('bookmark_confirmation_repair_all_redirects'))) { 773 | return; 774 | } 775 | 776 | const bookmarks = document.querySelectorAll('.redirect'); 777 | 778 | for (const bookmark of bookmarks) { 779 | if (!bookmark.classList.contains('hidden')) { 780 | bookmark.remove(); 781 | ui.repairRedirect(bookmark.id, bookmark.getElementsByClassName('new-url')[0].getAttribute('href')); 782 | } 783 | } 784 | 785 | ui.hideEmptyCategories(); 786 | }, 787 | 788 | /** 789 | * This method is used to delete all broken bookmarks. 790 | * 791 | * @param {MouseEvent} e - event 792 | * 793 | * @returns {void} 794 | */ 795 | deleteAllBookmarksWithErrors (e) { 796 | e.preventDefault(); 797 | 798 | // eslint-disable-next-line no-alert 799 | if (!ui.disableConfirmations && !confirm(browser.i18n.getMessage('bookmark_confirmation_delete_all_broken'))) { 800 | return; 801 | } 802 | 803 | const bookmarks = document.querySelectorAll('.error'); 804 | 805 | for (const bookmark of bookmarks) { 806 | if (!bookmark.classList.contains('hidden')) { 807 | bookmark.remove(); 808 | ui.deleteBookmark(bookmark.id); 809 | } 810 | } 811 | 812 | ui.hideEmptyCategories(); 813 | }, 814 | 815 | /** 816 | * This method is used to filter the result based on the search field. 817 | * 818 | * @param {MouseEvent} e - event 819 | * 820 | * @returns {void} 821 | */ 822 | applySearchFieldFilter (e) { 823 | const matcher = new RegExp(e.target.value, 'i'); 824 | const urls = elResults.querySelectorAll('.url'); 825 | 826 | for (const url of urls) { 827 | const parentElement = url.parentNode.parentNode; 828 | const title = parentElement.querySelector('.name'); 829 | 830 | if (matcher.test(title.textContent) || matcher.test(url.textContent)) { 831 | parentElement.setAttribute('data-filter-searchfield', 'true'); 832 | } 833 | else { 834 | parentElement.removeAttribute('data-filter-searchfield'); 835 | } 836 | } 837 | 838 | ui.hideFilteredElements(); 839 | }, 840 | 841 | /** 842 | * This method is used to filter the result based on the checkboxes for errors and warnings. 843 | * 844 | * @param {MouseEvent} e - event 845 | * 846 | * @returns {void} 847 | */ 848 | applyCheckboxFilter (e) { 849 | const urls = elResults.querySelectorAll('.url'); 850 | 851 | for (const url of urls) { 852 | const parentElement = url.parentNode.parentNode; 853 | 854 | if (parentElement.classList.contains(e.target.getAttribute('data-filter'))) { 855 | if (e.target.checked) { 856 | parentElement.setAttribute('data-filter-checkbox', 'true'); 857 | } 858 | else { 859 | parentElement.removeAttribute('data-filter-checkbox'); 860 | } 861 | } 862 | } 863 | 864 | ui.hideFilteredElements(); 865 | }, 866 | 867 | /** 868 | * This method is used to hide all result items which are filtered by the search field or the checkboxes. 869 | * 870 | * @returns {void} 871 | */ 872 | hideFilteredElements () { 873 | const elements = elResults.querySelectorAll('li'); 874 | 875 | for (const element of elements) { 876 | if (element.getElementsByClassName('url').length !== 0) { 877 | if (element.hasAttribute('data-filter-searchfield') && element.hasAttribute('data-filter-checkbox')) { 878 | element.classList.remove('hidden'); 879 | } 880 | else { 881 | element.classList.add('hidden'); 882 | } 883 | } 884 | } 885 | 886 | ui.hideEmptyCategories(); 887 | }, 888 | 889 | /** 890 | * This method is used to hide all categories without bookmarks. 891 | * 892 | * @returns {void} 893 | */ 894 | hideEmptyCategories () { 895 | const elements = elResults.querySelectorAll('li.has-children'); 896 | for (const element of elements) { 897 | const subelements = element.querySelectorAll('li.is-bookmark'); 898 | let count = 0; 899 | 900 | for (const subelement of subelements) { 901 | if (!subelement.classList.contains('hidden')) { 902 | count++; 903 | break; 904 | } 905 | } 906 | 907 | if (count > 0) { 908 | element.classList.remove('hidden'); 909 | } 910 | else { 911 | element.classList.add('hidden'); 912 | } 913 | } 914 | } 915 | }; 916 | 917 | document.addEventListener('DOMContentLoaded', ui.init); 918 | 919 | elButton.addEventListener('click', ui.execute); 920 | elBody.addEventListener('click', ui.handleActionButtonClicks); 921 | elFox.addEventListener('click', ui.handleFoxMessageClick); 922 | elRepairAllRedirects.addEventListener('click', ui.repairAllRedirects); 923 | elDeleteAllBookmarksWithErrors.addEventListener('click', ui.deleteAllBookmarksWithErrors); 924 | elSearch.addEventListener('input', ui.applySearchFieldFilter); 925 | 926 | browser.runtime.onMessage.addListener(ui.handleResponse); 927 | --------------------------------------------------------------------------------