├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── ---feature-request.md
│ └── ---bug-report.md
├── .gitignore
├── src
├── images
│ ├── sh-at.png
│ ├── logo-large.png
│ ├── cross.svg
│ ├── path.svg
│ ├── link.svg
│ ├── trash.svg
│ ├── calendar.svg
│ └── icon.svg
├── manifest.json
├── html
│ ├── options.html
│ └── ui.html
├── _locales
│ ├── ja
│ │ └── messages.json
│ ├── en
│ │ └── messages.json
│ ├── nl
│ │ └── messages.json
│ ├── hsb
│ │ └── messages.json
│ ├── dsb
│ │ └── messages.json
│ └── de
│ │ └── messages.json
├── js
│ ├── lib
│ │ └── i18n.js
│ └── core
│ │ ├── options.js
│ │ ├── ui.js
│ │ └── background.js
└── css
│ └── ui.css
├── screenshots
├── keep-or-delete-bookmarks-en-1.png
├── keep-or-delete-bookmarks-en-2.png
├── keep-or-delete-bookmarks-en-3.png
├── keep-or-delete-bookmarks-en-4.png
└── keep-or-delete-bookmarks-en-5.png
├── jsdoc.json
├── gulpfile.js
├── .htmllintrc.json
├── package.json
├── CODE_OF_CONDUCT.md
├── README.md
├── CHANGELOG.md
├── .stylelintrc.json
├── .eslintrc.json
└── LICENSE.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: https://www.paypal.com/paypalme/agenedia/3
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | dist
4 | docs
5 | logo.psd
6 | node_modules
7 | _TODO.txt
8 |
--------------------------------------------------------------------------------
/src/images/sh-at.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/src/images/sh-at.png
--------------------------------------------------------------------------------
/src/images/logo-large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/src/images/logo-large.png
--------------------------------------------------------------------------------
/screenshots/keep-or-delete-bookmarks-en-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/screenshots/keep-or-delete-bookmarks-en-1.png
--------------------------------------------------------------------------------
/screenshots/keep-or-delete-bookmarks-en-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/screenshots/keep-or-delete-bookmarks-en-2.png
--------------------------------------------------------------------------------
/screenshots/keep-or-delete-bookmarks-en-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/screenshots/keep-or-delete-bookmarks-en-3.png
--------------------------------------------------------------------------------
/screenshots/keep-or-delete-bookmarks-en-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/screenshots/keep-or-delete-bookmarks-en-4.png
--------------------------------------------------------------------------------
/screenshots/keep-or-delete-bookmarks-en-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cadeyrn/keep-or-delete-bookmarks/HEAD/screenshots/keep-or-delete-bookmarks-en-5.png
--------------------------------------------------------------------------------
/src/images/cross.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/path.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/images/trash.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4A1 Feature request"
3 | about: Suggest an idea!
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/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": "Keep or Delete Bookmarks",
18 | "copyright": "© 2023, 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 |
--------------------------------------------------------------------------------
/src/images/calendar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41E Bug report"
3 | about: Found an error? Please report!
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '…'
16 | 2. Click on '…'
17 | 3. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Version information:**
26 | - Firefox version: [e.g. Firefox 109, Firefox ESR 102]
27 | - Keep or Delete Bookmarks version [e.g. 3.0.0]
28 |
29 | **Additional context**
30 | Add any other context about the problem here.
31 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const gulp = require('gulp');
4 | const gulpEslint = require('gulp-eslint-new');
5 | const gulpHtmllint = require('gulp-htmllint');
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({ config : '.htmllintrc.json' }))
11 | );
12 |
13 | gulp.task('lint-js', () => gulp.src(['gulpfile.js', './src/js/**/*.js'])
14 | .pipe(gulpEslint({ overrideConfigFile : '.eslintrc.json' }))
15 | .pipe(gulpEslint.format())
16 | );
17 |
18 | gulp.task('lint-css', () => gulp.src(['./src/css/*.css'])
19 | .pipe(gulpStylelint({
20 | failAfterError : false,
21 | reporters : [
22 | {
23 | formatter : 'string',
24 | console : true
25 | }
26 | ]
27 | }))
28 | );
29 |
30 | const jsdocsConfig = require('./jsdoc.json');
31 | gulp.task('docs', () => gulp.src(['CHANGELOG.md', 'README.md', './src/js/*/*.js'], { read : false })
32 | .pipe(jsdoc(jsdocsConfig))
33 | );
34 |
--------------------------------------------------------------------------------
/src/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "__MSG_extension_name__",
4 | "version": "3.0.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/firefox-webextensions/keep-or-delete-bookmarks/?utm_campaign=webext&utm_term=keep-or-delete-bookmarks"
12 | },
13 | "background": {
14 | "scripts": ["js/core/background.js"]
15 | },
16 | "action": {
17 | "browser_style": false,
18 | "default_icon": "images/icon.svg",
19 | "default_title": "__MSG_extension_name__"
20 | },
21 | "permissions": [
22 | "bookmarks",
23 | "menus",
24 | "storage",
25 | "tabs"
26 | ],
27 | "host_permissions": [
28 | ""
29 | ],
30 | "content_security_policy": {
31 | "extension_pages": "script-src 'self';"
32 | },
33 | "options_ui": {
34 | "page": "html/options.html",
35 | "open_in_tab": true
36 | },
37 | "default_locale": "en",
38 | "browser_specific_settings": {
39 | "gecko": {
40 | "id": "keep-or-delete-bookmarks@agenedia.com",
41 | "strict_min_version": "109.0"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/html/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 | |
19 | |
20 | |
21 | |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/images/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.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": "double",
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": false,
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": "allownull",
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": false,
48 | "label-req-for": true,
49 | "lang-style": "case",
50 | "line-end-style": "lf",
51 | "line-max-len": 180,
52 | "line-max-len-ignore-regex": false,
53 | "line-no-trailing-whitespace": true,
54 | "link-req-noopener": true,
55 | "spec-char-escape": false,
56 | "table-req-caption": false,
57 | "table-req-header": false,
58 | "tag-bans": [
59 | "b",
60 | "i",
61 | "style"
62 | ],
63 | "tag-close": true,
64 | "tag-name-lowercase": true,
65 | "tag-name-match": true,
66 | "tag-self-close": "always",
67 | "title-max-len": 65,
68 | "title-no-dup": true
69 | }
70 |
--------------------------------------------------------------------------------
/src/_locales/ja/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "これでバッチリです! もうブックマークを検査する必要はありません。"
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "削除する"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "維持する"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "開く"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "ホワイトリストを開く"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "戻る"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "次へ"
22 | },
23 | "check_status_failure": {
24 | "message": "ブックマークが壊れている可能性があります。"
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Click to grant permission to check for broken bookmarks."
28 | },
29 | "check_status_skipped": {
30 | "message": "照合できません。"
31 | },
32 | "check_status_success": {
33 | "message": "ブックマークは大丈夫そうです。"
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "取り消す"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "OK"
40 | },
41 | "confirm_checkbox": {
42 | "message": "確認のダイアログを有効にする"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "ブックマークは、本当に削除して大丈夫でしょうか?"
46 | },
47 | "date_added": {
48 | "message": "追加された日付"
49 | },
50 | "extension_description": {
51 | "message": "ブックマークの整理は面倒でうんざりです。Firefox のアドオン Keep or Delete Bookmarks(ブックマークの削除や維持)を使用すれば、\",Tinder\", や類似のサービスのようにブックマークを並べ替えることができ、この退屈な作業にいくらかの楽しさが得られます。"
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks(ブックマークの削除や維持)"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "ホワイトリストから全てを削除する"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "ホワイトリストから外す"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "ホワイトリストには、まだブックマークはありません。"
64 | },
65 | "whitelist_th_name": {
66 | "message": "名称"
67 | },
68 | "whitelist_th_path": {
69 | "message": "パス"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "Well done! No more bookmarks to check."
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "delete"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "keep"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "open"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "open whitelist"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "back"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "next"
22 | },
23 | "check_status_failure": {
24 | "message": "The bookmark may be broken."
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Click to grant permission to check for broken bookmarks."
28 | },
29 | "check_status_skipped": {
30 | "message": "No check possible."
31 | },
32 | "check_status_success": {
33 | "message": "The bookmark seems to be okay."
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "Cancel"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "OK"
40 | },
41 | "confirm_checkbox": {
42 | "message": "Enable confirmation dialogs"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "Should the bookmark really be deleted?"
46 | },
47 | "date_added": {
48 | "message": "Date added"
49 | },
50 | "extension_description": {
51 | "message": "Cleaning up bookmarks is boring. The Firefox add-on Keep or Delete Bookmarks brings some fun to this task by allowing you to sort out the bookmarks like on \"Tinder\" or similar services."
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "remove all from whitelist"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "remove from whitelist"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "No bookmarks on the whitelist yet."
64 | },
65 | "whitelist_th_name": {
66 | "message": "Name"
67 | },
68 | "whitelist_th_path": {
69 | "message": "Path"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/_locales/nl/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "Goed gedaan! Er zijn geen favorieten meer te checken."
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "verwijder"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "bewaar"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "open"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "open whitelist"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "terug"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "volgende"
22 | },
23 | "check_status_failure": {
24 | "message": "De favoriet kan stuk zijn."
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Klik om toestemming te geven om te controleren op favoriten die stuk zijn."
28 | },
29 | "check_status_skipped": {
30 | "message": "Check is niet mogelijk."
31 | },
32 | "check_status_success": {
33 | "message": "De favoriet lijkt in orde te zijn."
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "Annuleer"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "Oké"
40 | },
41 | "confirm_checkbox": {
42 | "message": "Activeer bevestigingsmeldingen"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "Moet de favoriet werkelijk worden verwijderd?"
46 | },
47 | "date_added": {
48 | "message": "Datum toegevoegd"
49 | },
50 | "extension_description": {
51 | "message": "Opruimen van favorieten kan saai werk zijn. Deze Firefox-add-on Keep or Delete Bookmarks maakt het leuker doordat je favorieten sorteert zoals \"Tinder\" of vergelijkbare diensten werken."
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "verwijder alles van de whitelist"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "verwijder favoriet"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "Nog geen favorieten in de whitelist."
64 | },
65 | "whitelist_th_name": {
66 | "message": "Naam"
67 | },
68 | "whitelist_th_path": {
69 | "message": "Pad"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/_locales/hsb/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "Derje činił! Žane dalše zapołožki přepruwować."
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "zhašeć"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "wobchować"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "wočinić"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "Bełu lisćinu wočinić"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "wróćo"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "dale"
22 | },
23 | "check_status_failure": {
24 | "message": "Zapołožka je snano zmylna."
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Klikńće, zo byšće prawo za pytanje za defektnymi zapołožkami dał."
28 | },
29 | "check_status_skipped": {
30 | "message": "Žane přepruwowanje móžne."
31 | },
32 | "check_status_success": {
33 | "message": "Zapołožka zda so w porjadku być."
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "Přetorhnyć"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "W porjadku"
40 | },
41 | "confirm_checkbox": {
42 | "message": "Wobkrućenske dialogi zmóžnić"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "Ma so zapołožka woprawdźe zhašeć?"
46 | },
47 | "date_added": {
48 | "message": "Přidaty"
49 | },
50 | "extension_description": {
51 | "message": "Rumowanje zapołožkow je wostudłe. Rozšěrjenje Firefox Keep or Delete Bookmarks trochu wjesela do tutoho nadawka přinjese, dokelž dowola, zapołožki kaž na „Tinder“ abo podobnych słužbach wusortěrować."
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "Wšě z běłeje lisćiny wotstronić"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "z běłeje lisćiny wotstronić"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "hišće žane zapołožki w běłej lisćinje."
64 | },
65 | "whitelist_th_name": {
66 | "message": "Mjeno"
67 | },
68 | "whitelist_th_path": {
69 | "message": "Šćežka"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "keep-or-delete-bookmarks",
3 | "version": "3.0.0",
4 | "description": "Cleaning up bookmarks is boring. The Firefox add-on Keep or Delete Bookmarks brings some fun to this task by allowing you to sort out the bookmarks like on \"Tinder\" or similar services.",
5 | "author": {
6 | "name": "Sören Hentzschel",
7 | "email": "kontakt@agenedia.com",
8 | "url": "https://firefox.agenedia.com"
9 | },
10 | "homepage": "https://www.soeren-hentzschel.at/firefox-webextensions/keep-or-delete-bookmarks/?utm_campaign=webext&utm_term=keep-or-delete-bookmarks",
11 | "bugs": {
12 | "email": "kontakt@agenedia.com"
13 | },
14 | "license": "MPL 2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/cadeyrn/keep-or-delete-bookmarks/"
18 | },
19 | "private": true,
20 | "browserslist": [
21 | "Firefox >= 109"
22 | ],
23 | "devDependencies": {
24 | "eslint": "8.33.0",
25 | "eslint-plugin-compat": "4.0.2",
26 | "eslint-plugin-no-unsanitized": "4.0.2",
27 | "eslint-plugin-promise": "6.1.1",
28 | "eslint-plugin-sort-requires": "2.1.0",
29 | "eslint-plugin-xss": "0.1.12",
30 | "gulp": "4.0.2",
31 | "gulp-eslint-new": "1.7.1",
32 | "gulp-htmllint": "0.0.19",
33 | "gulp-jsdoc3": "3.0.0",
34 | "gulp-stylelint": "13.0.0",
35 | "htmllint": "0.8.0",
36 | "jsdoc": "4.0.0",
37 | "jsdoc-strip-async-await": "0.1.0",
38 | "npm-run-all": "4.1.5",
39 | "stylelint": "15.10.1",
40 | "stylelint-csstree-validator": "2.1.0",
41 | "stylelint-order": "6.0.1",
42 | "web-ext": "7.6.2"
43 | },
44 | "scripts": {
45 | "build": "cd src && web-ext build -a ../dist",
46 | "docs": "gulp docs",
47 | "lint": "npm-run-all lint:*",
48 | "lint:html": "gulp lint-html",
49 | "lint:js": "gulp lint-js",
50 | "lint:css": "gulp lint-css",
51 | "lint:webext": "cd src && web-ext lint",
52 | "run:nightly": "cd src && web-ext run --firefox=nightly",
53 | "run:beta": "cd src && web-ext run --firefox=beta",
54 | "run:stable": "cd src && web-ext run --firefox=firefox",
55 | "run:esr": "cd src && web-ext run --firefox=\"/Applications/Firefox ESR.app\""
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/_locales/dsb/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "Derje gótował! Žedne dalšne cytańske znamjenja pśeglědowaś."
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "wulašowaś"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "wobchowaś"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "wócyniś"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "Bełu lisćinu wócyniś"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "slědk"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "dalej"
22 | },
23 | "check_status_failure": {
24 | "message": "Cytańske znamje jo snaź zmólkate."
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Klikniśo, aby dał pšawo za pytanje defektnymi cytańskimi znamjenjami."
28 | },
29 | "check_status_skipped": {
30 | "message": "Žedno pśeglědanje móžne."
31 | },
32 | "check_status_success": {
33 | "message": "Cytańske znamje zda se w pórědku."
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "Pśetergnuś"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "W pórěźe"
40 | },
41 | "confirm_checkbox": {
42 | "message": "Wobkšuśeńske dialogi zmóžniś"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "Ma se cytańske znamje napšawdu lašowaś?"
46 | },
47 | "date_added": {
48 | "message": "Pśidany"
49 | },
50 | "extension_description": {
51 | "message": "Rumowanje cytańskich znamjenjow jo wóstudne. Rozšyrjenje Firefox Keep or Delete Bookmarks pitśku wjasela do toś togo nadawka pśinjaso, dokulaž dowólujo, cytańske znamjenja ako na „Tinder“ abo pódobnych słužbach wusortěrowaś."
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "wšykne z běłeje lisćiny wówónoźeś"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "z běłeje lisćiny wótwónoźeś"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "Hyšći žedne cytańske znamjenja w běłej lisćinje."
64 | },
65 | "whitelist_th_name": {
66 | "message": "Mě"
67 | },
68 | "whitelist_th_path": {
69 | "message": "Sćažka"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/_locales/de/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "bookmarks_empty_state": {
3 | "message": "Gut gemacht! Keine weiteren Lesezeichen zu überprüfen."
4 | },
5 | "btn_delete_bookmark": {
6 | "message": "löschen"
7 | },
8 | "btn_keep_bookmark": {
9 | "message": "behalten"
10 | },
11 | "btn_open_bookmark": {
12 | "message": "öffnen"
13 | },
14 | "btn_open_whitelist": {
15 | "message": "Whitelist öffnen"
16 | },
17 | "btn_previous_bookmark": {
18 | "message": "zurück"
19 | },
20 | "btn_skip_bookmark": {
21 | "message": "weiter"
22 | },
23 | "check_status_failure": {
24 | "message": "Das Lesezeichen ist möglicherweise fehlerhaft."
25 | },
26 | "check_status_permission_needed": {
27 | "message": "Klicken, um Berechtigung zur Prüfung auf defekte Lesezeichen zu erteilen."
28 | },
29 | "check_status_skipped": {
30 | "message": "Keine Überprüfung möglich."
31 | },
32 | "check_status_success": {
33 | "message": "Das Lesezeichen scheint in Ordnung zu sein."
34 | },
35 | "confirm_btn_cancel": {
36 | "message": "Abbrechen"
37 | },
38 | "confirm_btn_ok": {
39 | "message": "OK"
40 | },
41 | "confirm_checkbox": {
42 | "message": "Bestätigungsdialoge aktivieren"
43 | },
44 | "confirm_delete_bookmark": {
45 | "message": "Soll das Lesezeichen wirklich gelöscht werden?"
46 | },
47 | "date_added": {
48 | "message": "Hinzugefügt am"
49 | },
50 | "extension_description": {
51 | "message": "Lesezeichen aufräumen ist langweilig. Die Firefox-Erweiterung Keep or Delete Bookmarks bringt etwas Spaß in diese Aufgabe, indem sie es erlaubt, die Lesezeichen wie auf „Tinder“ oder ähnlichen Diensten auszusortieren."
52 | },
53 | "extension_name": {
54 | "message": "Keep or Delete Bookmarks"
55 | },
56 | "btn_whitelist_remove_all": {
57 | "message": "alle von Whitelist entfernen"
58 | },
59 | "whitelist_remove_bookmark": {
60 | "message": "von Whitelist entfernen"
61 | },
62 | "whitelist_empty_state": {
63 | "message": "Noch keine Lesezeichen auf der Whitelist."
64 | },
65 | "whitelist_th_name": {
66 | "message": "Name"
67 | },
68 | "whitelist_th_path": {
69 | "message": "Pfad"
70 | },
71 | "whitelist_th_url": {
72 | "message": "URL"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/js/lib/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());
25 | },
26 |
27 | /**
28 | * This method is used to get the translation for a given key.
29 | *
30 | * @param {string} key - translation key
31 | *
32 | * @returns {string} - translation
33 | */
34 | getMessage (key) {
35 | return browser.i18n.getMessage(key);
36 | },
37 |
38 | /**
39 | * Translates all strings in text nodes, placeholders and title attributes.
40 | *
41 | * @returns {void}
42 | */
43 | translate () {
44 | document.removeEventListener('DOMContentLoaded', i18n.translate);
45 |
46 | // text node translation
47 | const nodes = document.querySelectorAll('[data-i18n]');
48 |
49 | for (let i = 0, len = nodes.length; i < len; i++) {
50 | const node = nodes[i];
51 | const children = Array.from(node.children);
52 | const text = i18n.getMessage(node.dataset.i18n);
53 | const parts = text.split(/({\d+})/);
54 |
55 | parts.forEach((part) => {
56 | if ((/{\d+}/).test(part)) {
57 | const index = parseInt(part.slice(1));
58 | node.appendChild(children[index]);
59 | }
60 | else {
61 | node.appendChild(document.createTextNode(part));
62 | }
63 | });
64 | }
65 |
66 | // attribute translation
67 | const attributes = ['placeholder', 'title'];
68 |
69 | for (const attribute of attributes) {
70 | const i18nAttribute = `data-i18n-${attribute}`;
71 | const attrNodes = document.querySelectorAll(`[${i18nAttribute}]`);
72 | const { length } = attrNodes;
73 |
74 | for (let i = 0; i < length; i++) {
75 | const node = attrNodes[i];
76 | const msg = node.getAttribute(i18nAttribute);
77 | node.setAttribute(attribute, i18n.getMessage(msg));
78 | }
79 | }
80 | }
81 | };
82 |
83 | window.addEventListener('DOMContentLoaded', i18n.init);
84 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at kontakt@agenedia.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/src/html/ui.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
…
30 |
31 |
32 |
73 |
74 |
75 |
76 |
81 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firefox Add-on: Keep or Delete Bookmarks
2 |
3 |
4 |
5 | ## Support the development
6 |
7 | **Please consider making [a donation](https://www.paypal.com/paypalme/agenedia/) to support the further development of
8 | Keep or Delete Bookmarks. Thank you very much!**
9 |
10 | ## Description
11 |
12 | **Cleaning up bookmarks is boring. The Firefox add-on Keep or Delete Bookmarks brings some fun to this task by
13 | allowing you to sort out the bookmarks the "Tinder way".**
14 |
15 | The add-on randomly displays one bookmark, including the title, the URL and the bookmark folder. The user has
16 | several options:
17 |
18 | 1) Keep the bookmark. The bookmark will be added to a whitelist and Keep or Delete Bookmarks will never ask again about
19 | this bookmark.
20 |
21 | 2) Delete the bookmark. The bookmark will be deleted from your Firefox.
22 |
23 | 3) Skip the bookmark and defer the decision. Keep or Delete Bookmarks will show you the next bookmark without any
24 | action.
25 |
26 | 4) Open the bookmark. Maybe you are not sure about the bookmark yet. This options lets you open the bookmark in a new
27 | tab before you make a decision.
28 |
29 | ### Features
30 |
31 | - Keep or Delete Bookmarks always shows one random bookmark
32 | - The add-on automatically checks whether the bookmark still works or is broken
33 | - An internal skip list is used for domains that are known to be unverifiable
34 | - You can keep or delete the bookmark, you can open the bookmark in a new tab, or you can defer the decision
35 | - After an action Keep or Delete Bookmarks shows you the next bookmark
36 | - Keep or Delete Bookmark makes sure that you never see the same bookmark two times in a row
37 | - There is a confirmation dialog when you press the delete button
38 | - You can disable the confirmation dialogs with one click
39 | - You can remove bookmarks from the whitelist at any time
40 | - You can also use keyboard shortcuts for the primary actions:
41 | - Left Arrow: show previous bookmark
42 | - Right Arrow: show next (random) bookmark
43 | - Enter: open bookmark
44 | - Space: add bookmark to whitelist
45 | - Backspace: delete bookmark (or opens confirmation dialog if enabled)
46 | - in bookmark deletion confirmation dialog:
47 | - ESC: close dialog
48 | - Enter: delete bookmark
49 |
50 | ### Planned features
51 |
52 | You can find the roadmap and request new features in the
53 | [issues tracker](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues).
54 |
55 | ### Languages
56 |
57 | The add-on is currently available in the following languages:
58 |
59 | - English
60 | - German
61 | - Dutch (Thanks, PanderMusubi!)
62 | - Japanese (Thanks, Shitennouji!)
63 | - Upper Sorbian (Thanks, milupo!)
64 | - Lower Sorbian (Thanks, milupo!)
65 |
66 | ### Screenhots
67 |
68 | | | |
69 | :-------------------------:|:-------------------------:
70 |  | 
71 |  | 
72 |  |
73 |
74 | ### Permissions
75 |
76 | Keep or Delete Bookmarks needs several permissions to work properly.
77 |
78 | #### mandatory permissions
79 |
80 | Keep or Delete Bookmarks does not work without the following permissions:
81 |
82 | ##### access browser tabs
83 |
84 | The permission to access the browser tabs is needed so that Keep or Delete Bookmarks can jump to the already opened
85 | user interface if the user interface is already opened in another tab and you click the button in the browser's toolbar.
86 |
87 | ##### read and modify bookmarks
88 |
89 | You installed Keep or Delete Bookmarks to show and remove bookmarks, so it should be clear why the permission is needed
90 | to read and modify your bookmarks.
91 |
92 | #### optional permissions
93 |
94 | ##### access your data for all sites
95 |
96 | The add-on checks the bookmarks by sending a request to the appropriate URLs. This cannot work without the permission
97 | to access these sites. Keep or Delete Bookmarks asks at runtime for this permission if you want to execute this check.
98 |
99 | #### silent permissions
100 |
101 | Keep or Delete Bookmarks needs some more permissions, but Firefox does not prompt for the following permissions:
102 |
103 | ##### menus
104 |
105 | The menus permission is needed for providing a menu entry in the tools menu to access Keep or Delete Bookmarks's user
106 | interface.
107 |
108 | ##### storage
109 |
110 | The storage permission is needed so that Keep or Delete Bookmarks can remember which bookmarks you want to keep.
111 |
112 | ## Compatibility
113 |
114 | Keep or Delete Bookmarks is a WebExtension and compatible with Firefox Browser 68 and higher (Firefox Browser 109 or
115 | higher is required for the latest version of Keep or Delete Bookmarks).
116 |
117 | ## Download
118 |
119 | [Download Keep or Delete Bookmarks](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/)
120 |
121 | ## Release Notes
122 |
123 | [Release Notes](CHANGELOG.md "Release Notes")
124 |
--------------------------------------------------------------------------------
/src/js/core/options.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const elBody = document.querySelector('body');
4 | const elNoBookmarks = document.getElementById('no-bookmarks');
5 | const elRemoveAllWrapper = document.getElementById('whitelist-remove-all-wrapper');
6 | const elWhitelistTable = document.getElementById('whitelist-table');
7 |
8 | /**
9 | * @exports options
10 | */
11 | const options = {
12 | /**
13 | * Fired when the initial HTML document has been completely loaded and parsed. Shows the bookmark whitelist.
14 | *
15 | * @returns {void}
16 | */
17 | init () {
18 | options.listBookmarks();
19 | },
20 |
21 | /**
22 | * Fired when one of the buttons is clicked.
23 | *
24 | * @param {MouseEvent} e - event
25 | *
26 | * @returns {void}
27 | */
28 | handleButtonClicks (e) {
29 | if (e.target.getAttribute('data-action')) {
30 | e.preventDefault();
31 |
32 | switch (e.target.getAttribute('data-action')) {
33 | case 'remove-all-from-whitelist':
34 | options.emptyWhitelist();
35 | break;
36 | default:
37 | // do nothing
38 | }
39 | }
40 | },
41 |
42 | /**
43 | * List all the bookmarks on the whitelist.
44 | *
45 | * @returns {void}
46 | */
47 | async listBookmarks () {
48 | const { whitelist } = await browser.storage.local.get({ whitelist : {} });
49 | const whitelistLength = Object.keys(whitelist).length;
50 |
51 | // show notice if the whitelist is empty, otherwise show the bookmarks on the whitelist
52 | if (whitelistLength === 0) {
53 | elNoBookmarks.removeAttribute('hidden');
54 | elRemoveAllWrapper.setAttribute('hidden', 'true');
55 | elWhitelistTable.setAttribute('hidden', 'true');
56 | }
57 | else {
58 | elNoBookmarks.setAttribute('hidden', 'true');
59 | elRemoveAllWrapper.removeAttribute('hidden');
60 | elWhitelistTable.removeAttribute('hidden');
61 | }
62 |
63 | if (whitelistLength > 0) {
64 | // tbody element, bookmark rows will be added here
65 | const elTableBody = elWhitelistTable.querySelector('tbody');
66 |
67 | // remove old content
68 | while (elTableBody.firstChild) {
69 | elTableBody.removeChild(elTableBody.firstChild);
70 | }
71 |
72 | // add bookmarks to table in reversed order (newest at top)
73 | Object.keys(whitelist).reverse().forEach((id) => {
74 | // row
75 | const elRow = document.createElement('tr');
76 | elTableBody.appendChild(elRow);
77 |
78 | // name column
79 | const elNameColumn = document.createElement('td');
80 | elNameColumn.textContent = whitelist[id].title;
81 | elNameColumn.setAttribute('title', whitelist[id].title);
82 | elRow.appendChild(elNameColumn);
83 |
84 | // URL column
85 | const elUrlColumn = document.createElement('td');
86 | const pattern = new RegExp(/^https?:\/\//, 'gi');
87 |
88 | if (pattern.test(whitelist[id].url)) {
89 | const elUrl = document.createElement('a');
90 | elUrl.setAttribute('href', whitelist[id].url);
91 | elUrl.setAttribute('target', '_blank');
92 | elUrl.setAttribute('rel', 'noopener');
93 | elUrl.textContent = whitelist[id].url;
94 | elUrlColumn.appendChild(elUrl);
95 | }
96 | else {
97 | elUrlColumn.textContent = whitelist[id].url;
98 | }
99 |
100 | elUrlColumn.setAttribute('title', whitelist[id].url);
101 | elRow.appendChild(elUrlColumn);
102 |
103 | // path column
104 | const elPathColumn = document.createElement('td');
105 | elPathColumn.textContent = whitelist[id].path;
106 | elPathColumn.setAttribute('title', whitelist[id].path);
107 | elRow.appendChild(elPathColumn);
108 |
109 | // icon column
110 | const elIconColumn = document.createElement('td');
111 | elIconColumn.classList.add('actions');
112 | elRow.appendChild(elIconColumn);
113 |
114 | // remove icon
115 | const elRemoveLink = document.createElement('a');
116 | elRemoveLink.setAttribute('title', browser.i18n.getMessage('whitelist_remove_bookmark'));
117 | elRemoveLink.setAttribute('data-idx', id);
118 | elRemoveLink.classList.add('icon', 'trash-icon');
119 | elRemoveLink.addEventListener('click', options.removeFromWhitelist);
120 | elIconColumn.appendChild(elRemoveLink);
121 |
122 | const elRemoveIcon = document.createElement('img');
123 | elRemoveIcon.src = '/images/cross.svg';
124 | elRemoveIcon.setAttribute('alt', browser.i18n.getMessage('whitelist_remove_bookmark'));
125 | elRemoveLink.appendChild(elRemoveIcon);
126 | });
127 | }
128 | },
129 |
130 | /**
131 | * Removes a bookmark from the whitelist.
132 | *
133 | * @param {MouseEvent} e - event
134 | *
135 | * @returns {void}
136 | */
137 | async removeFromWhitelist (e) {
138 | e.preventDefault();
139 |
140 | const { whitelist } = await browser.storage.local.get({ whitelist : {} });
141 | delete whitelist[e.target.parentNode.getAttribute('data-idx')];
142 | browser.storage.local.set({ whitelist : whitelist });
143 |
144 | options.listBookmarks();
145 | },
146 |
147 | /**
148 | * Removes all bookmarks from the whitelist.
149 | *
150 | * @returns {void}
151 | */
152 | emptyWhitelist () {
153 | browser.storage.local.set({ whitelist : {} });
154 | options.listBookmarks();
155 | }
156 | };
157 |
158 | document.addEventListener('DOMContentLoaded', options.init);
159 | elBody.addEventListener('click', options.handleButtonClicks);
160 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### Version 3.0.0 (2023-02-08)
2 |
3 | #### Notable Changes
4 |
5 | - **Keep or Delete Bookmarks now uses Manifest v3**, fixes
6 | [#44](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/44)
7 | - **Keep or Delete Bookmarks now asks at runtime for permission** to access all website data if permission is not
8 | granted. The permission is technically needed to check for broken bookmarks. Therefore, the permission is no longer
9 | needed for installation. Keep or Delete Bookmarks also reacts to permission changes via the add-ons manager, fixes
10 | [#56](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/56)
11 | - bumped the minimum required Firefox version to Firefox 109, fixes
12 | [#55](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/55)
13 | - solved a few edge cases around added or removed bookmarks while the user interface of Keep or Delete Bookmarks was
14 | already opened, fixes [#61](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/61)
15 | - removed testpilot.firefox.com from internal skip list because this domain is not part of
16 | extensions.webextensions.restrictedDomains, fixes [#41](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/41)
17 | - changed copyright year from 2022 to 2023, fixes [#54](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/54)
18 |
19 | #### Code Quality
20 |
21 | - optimized image files to save a few bytes, fixes [#57](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/57)
22 | - updated the translation mechanism to the newest version to share more code with other extensions and to improve the
23 | maintainability, fixes [#40](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/40)
24 |
25 | #### Bugfixes
26 |
27 | - the confirmation dialog did not close if you used the Enter key to confirm, fixes
28 | [#59](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/59)
29 | - the 'remove all from whitelist' button was seen briefly on the whitelist screen if the whitelist was empty,
30 | fixes [#38](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/38) (Thanks, alexmajor!)
31 |
32 | #### Dependencies
33 |
34 | - updated eslint from version 8.5.0 to 8.33.0 and updated configuration
35 | - updated eslint-plugin-compat from version 4.0.0 to 4.0.2
36 | - updated eslint-plugin-no-unsanitized from version 4.0.1 to 4.0.2
37 | - updated eslint-plugin-promise from version 6.0.0 to 6.1.1
38 | - updated eslint-plugin-xss from version 0.1.11 to 0.1.12
39 | - updated gulp-eslint-new from version 1.1.0 to 1.7.1
40 | - updated jsdoc from version 3.6.7 to 4.0.0
41 | - updated stylelint from version 14.2.0 to 14.16.1 and updated configuration
42 | - updated stylelint-csstree-validator from version 2.0.0 to 2.1.0
43 | - updated stylelint-order from version 5.0.0 to 6.0.1
44 | - updated webext from version 6.6.0 to 7.5.0
45 |
46 | [All Changes](https://github.com/cadeyrn/keep-or-delete-bookmarks/compare/v2.0.1...v3.0.0)
47 | [Download Signed WebExtension](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/versions/?page=1#version-3.0.0)
48 |
49 | ---
50 |
51 | ### [Version 2.0.1](https://github.com/cadeyrn/keep-or-delete-bookmarks/releases/tag/v2.0.1) (2021-12-23)
52 |
53 | #### Translations
54 |
55 | - added Japanese translation (Thanks, Shitennouji!), fixes
56 | [#18](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/18)
57 |
58 | #### Dependencies
59 |
60 | - updated stylelint from version 14.1.0 to 14.2.0
61 |
62 | [All Changes](https://github.com/cadeyrn/keep-or-delete-bookmarks/compare/v2.0.0...v2.0.1)
63 | [Download Signed WebExtension](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/versions/?page=1#version-2.0.1)
64 |
65 | ---
66 |
67 | ### [Version 2.0.0](https://github.com/cadeyrn/keep-or-delete-bookmarks/releases/tag/v2.0.0) (2021-12-22)
68 |
69 | #### New Features
70 |
71 | - Keep or Delete Bookmarks now checks for broken bookmarks! An internal skip list is used for domains that are known
72 | to be unverifiable. This is the reason why the add-on needs the permission to access your data for all sites starting
73 | with this update, fixes [#9](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/9)
74 | - added ability to show previous bookmark after skipping, fixes
75 | [#13](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/13)
76 | - added keyboard support, fixes [#3](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/3)
77 | - Left Arrow: show previous bookmark
78 | - Right Arrow: show next (random) bookmark
79 | - Enter: open bookmark
80 | - Space: add bookmark to whitelist
81 | - Backspace: delete bookmark (or opens confirmation dialog if enabled)
82 | - in bookmark deletion confirmation dialog:
83 | - ESC: close dialog
84 | - Enter: delete bookmark
85 | - added the date when a bookmark was added to Firefox, fixes
86 | [#10](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/10)
87 |
88 | #### Notable Changes
89 |
90 | - bumped the minimum required Firefox version to Firefox 91, fixes
91 | [#31](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/31)
92 | - changed copyright year from 2019 to 2022, fixes [#30](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/30)
93 |
94 | #### Code Quality
95 |
96 | - replaced deprecated method call, fixes [#29](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/29)
97 | - fixed some code style issues and typos, fixes [#32](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/32)
98 |
99 | #### Bugfixes
100 |
101 | - fixed wrong tooltip for removing a bookmark from the whitelist, fixes
102 | [#33](https://github.com/cadeyrn/keep-or-delete-bookmarks/issues/33)
103 |
104 | #### Dependencies
105 |
106 | - replaced gulp-eslint 6.0.0 with gulp-eslint-new 1.1.0
107 | - updated eslint from version 6.0.1 to 8.5.0 and updated configuration
108 | - updated eslint-plugin-compat from version 3.0.2 to 4.0.0
109 | - updated eslint-plugin-no-unsanitized from version 3.0.2 to 4.0.1
110 | - updated eslint-plugin-promise from version 4.2.1 to 6.0.0
111 | - updated eslint-plugin-xss from version 0.1.9 to 0.1.11
112 | - updated gulp-htmllint from version 0.0.16 to 0.0.19
113 | - updated gulp-jsdoc3 from version 2.0.0 to 3.0.0
114 | - updated gulp-stylelint from version 9.0.0 to 13.0.0
115 | - updated jsdoc from version 3.6.3 to 3.6.7
116 | - updated stylelint from version 10.1.0 to 14.1.0 and updated configuration
117 | - updated stylelint-csstree-validator from version 1.5.2 to 2.0.0
118 | - updated stylelint-order from version 3.0.1 to 5.0.0
119 | - updated webext from version 3.1.0 to 6.6.0
120 |
121 | [All Changes](https://github.com/cadeyrn/keep-or-delete-bookmarks/compare/v1.0.1...v2.0.0)
122 | [Download Signed WebExtension](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/versions/?page=1#version-2.0.0)
123 |
124 | ---
125 |
126 | ### [Version 1.0.1](https://github.com/cadeyrn/keep-or-delete-bookmarks/releases/tag/v1.0.1) (2019-07-15)
127 |
128 | #### Translations
129 |
130 | - added Dutch translation (Thanks, PanderMusubi!)
131 | - added Upper Sorbian translation (Thanks, milupo!)
132 | - added Lower Sorbian translation (Thanks, milupo!)
133 | - fixed typos in English translation
134 |
135 | #### Dependencies
136 |
137 | - updated jsdoc from version 3.6.2 to 3.6.3
138 |
139 | [All Changes](https://github.com/cadeyrn/keep-or-delete-bookmarks/compare/v1.0.0...v1.0.1)
140 | [Download Signed WebExtension](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/versions/?page=1#version-1.0.1)
141 |
142 | ---
143 |
144 | ### [Version 1.0.0](https://github.com/cadeyrn/keep-or-delete-bookmarks/releases/tag/v1.0.0) (2019-07-13)
145 |
146 | - initial release for [addons.mozilla.org](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/)
147 |
148 | #### Features of the first version
149 |
150 | - Keep or Delete Bookmarks always shows one random bookmark
151 | - You can keep or delete the bookmark, you can open the bookmark in a new tab, or you can defer the decision
152 | - After an action Keep or Delete Bookmarks shows you the next bookmark
153 | - Keep or Delete Bookmark makes sure that you never see the same bookmark two times in a row
154 | - There is a confirmation dialog when you press the delete button
155 | - You can disable the confirmation dialogs with one click
156 | - You can also remove bookmarks from the whitelist at any time
157 | - Translations: English, German
158 |
159 | [Download Signed WebExtension](https://addons.mozilla.org/en-US/firefox/addon/keep-or-delete-bookmarks/versions/?page=1#version-1.0.0)
160 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "stylelint-csstree-validator",
4 | "stylelint-order"
5 | ],
6 | "rules": {
7 | "alpha-value-notation": "number",
8 | "at-rule-allowed-list": null,
9 | "at-rule-disallowed-list": null,
10 | "at-rule-empty-line-before": "always",
11 | "at-rule-name-case": "lower",
12 | "at-rule-name-newline-after": null,
13 | "at-rule-name-space-after": "always",
14 | "at-rule-no-unknown": true,
15 | "at-rule-no-vendor-prefix": true,
16 | "at-rule-property-required-list": null,
17 | "at-rule-semicolon-newline-after": "always",
18 | "at-rule-semicolon-space-before": "never",
19 | "block-closing-brace-empty-line-before": "never",
20 | "block-closing-brace-newline-after": "always",
21 | "block-closing-brace-newline-before": "always",
22 | "block-closing-brace-space-after": "never-single-line",
23 | "block-closing-brace-space-before": "always-single-line",
24 | "block-no-empty": true,
25 | "block-opening-brace-newline-after": "always",
26 | "block-opening-brace-newline-before": null,
27 | "block-opening-brace-space-after": "never-single-line",
28 | "block-opening-brace-space-before": "always-single-line",
29 | "color-function-notation": "legacy",
30 | "color-hex-alpha": "never",
31 | "color-hex-case": "lower",
32 | "color-hex-length": "short",
33 | "color-named": "never",
34 | "color-no-hex": true,
35 | "color-no-invalid-hex": true,
36 | "comment-empty-line-before": ["always", { "ignore": ["stylelint-commands"] }],
37 | "comment-no-empty": true,
38 | "comment-whitespace-inside": "always",
39 | "comment-word-disallowed-list": ["todo", "fixme", "xxx"],
40 | "csstree/validator": true,
41 | "custom-media-pattern": null,
42 | "custom-property-empty-line-before": "never",
43 | "custom-property-no-missing-var-function": true,
44 | "custom-property-pattern": null,
45 | "declaration-bang-space-after": "never",
46 | "declaration-bang-space-before": "always",
47 | "declaration-block-no-duplicate-properties": true,
48 | "declaration-block-no-redundant-longhand-properties": true,
49 | "declaration-block-no-shorthand-property-overrides": true,
50 | "declaration-block-semicolon-newline-after": "always",
51 | "declaration-block-semicolon-newline-before": null,
52 | "declaration-block-semicolon-space-after": "always-single-line",
53 | "declaration-block-semicolon-space-before": "never",
54 | "declaration-block-single-line-max-declarations": 1,
55 | "declaration-block-trailing-semicolon": "always",
56 | "declaration-colon-newline-after": "always-multi-line",
57 | "declaration-colon-space-after": "always-single-line",
58 | "declaration-colon-space-before": "never",
59 | "declaration-empty-line-before": "never",
60 | "declaration-no-important": true,
61 | "declaration-property-unit-allowed-list": null,
62 | "declaration-property-unit-disallowed-list": null,
63 | "declaration-property-value-allowed-list": null,
64 | "declaration-property-value-disallowed-list": null,
65 | "font-family-name-quotes": "always-where-recommended",
66 | "font-family-no-duplicate-names": true,
67 | "font-family-no-missing-generic-family-keyword": true,
68 | "font-weight-notation": "numeric",
69 | "function-allowed-list": null,
70 | "function-calc-no-unspaced-operator": true,
71 | "function-comma-newline-after": "never-multi-line",
72 | "function-comma-newline-before": "never-multi-line",
73 | "function-comma-space-after": "always",
74 | "function-comma-space-before": "never",
75 | "function-disallowed-list": null,
76 | "function-linear-gradient-no-nonstandard-direction": true,
77 | "function-max-empty-lines": 0,
78 | "function-name-case": "lower",
79 | "function-no-unknown": true,
80 | "function-parentheses-newline-inside": "never-multi-line",
81 | "function-parentheses-space-inside": "never",
82 | "function-url-no-scheme-relative": null,
83 | "function-url-quotes": "always",
84 | "function-url-scheme-allowed-list": ["https", "data"],
85 | "function-url-scheme-disallowed-list": null,
86 | "function-whitespace-after": "always",
87 | "hue-degree-notation": "angle",
88 | "indentation": 2,
89 | "keyframe-declaration-no-important": true,
90 | "keyframes-name-pattern": null,
91 | "length-zero-no-unit": true,
92 | "linebreaks": "unix",
93 | "max-empty-lines": 1,
94 | "max-line-length": 120,
95 | "max-nesting-depth": 0,
96 | "media-feature-colon-space-after": "always",
97 | "media-feature-colon-space-before": "never",
98 | "media-feature-name-allowed-list": null,
99 | "media-feature-name-case": "lower",
100 | "media-feature-name-disallowed-list": null,
101 | "media-feature-name-no-unknown": true,
102 | "media-feature-name-no-vendor-prefix": true,
103 | "media-feature-name-value-allowed-list": null,
104 | "media-feature-parentheses-space-inside": "never",
105 | "media-feature-range-operator-space-after": "always",
106 | "media-feature-range-operator-space-before": "always",
107 | "media-query-list-comma-newline-after": "always-multi-line",
108 | "media-query-list-comma-newline-before": "never-multi-line",
109 | "media-query-list-comma-space-after": "always-single-line",
110 | "media-query-list-comma-space-before": "never-single-line",
111 | "media-feature-range-notation": "prefix",
112 | "named-grid-areas-no-invalid": true,
113 | "no-descending-specificity": null,
114 | "no-duplicate-at-import-rules": true,
115 | "no-duplicate-selectors": true,
116 | "no-empty-first-line": true,
117 | "no-empty-source": true,
118 | "no-eol-whitespace": true,
119 | "no-extra-semicolons": true,
120 | "no-invalid-double-slash-comments": true,
121 | "no-invalid-position-at-import-rule": true,
122 | "no-irregular-whitespace": true,
123 | "no-missing-end-of-source-newline": true,
124 | "no-unknown-animations": true,
125 | "number-leading-zero": "never",
126 | "number-max-precision": 2,
127 | "number-no-trailing-zeros": true,
128 | "order/properties-order": [
129 | "appearance",
130 | "display",
131 | "position",
132 | "z-index",
133 | "top",
134 | "bottom",
135 | "left",
136 | "right",
137 | "float",
138 | "clear",
139 | "box-sizing",
140 | "margin",
141 | "padding",
142 | "width",
143 | "height",
144 | "line-height",
145 | "transform",
146 | "font",
147 | "border",
148 | "background",
149 | "color",
150 | "list-style",
151 | "vertical-align",
152 | "text-align",
153 | "text-decoration",
154 | "whitespace",
155 | "overflow",
156 | "cursor",
157 | "opacity",
158 | "animation",
159 | "transition"
160 | ],
161 | "property-allowed-list": null,
162 | "property-case": "lower",
163 | "property-disallowed-list": null,
164 | "property-no-unknown": true,
165 | "property-no-vendor-prefix": true,
166 | "rule-empty-line-before": ["always", { "except" : ["first-nested"] }],
167 | "rule-selector-property-disallowed-list": null,
168 | "selector-attribute-brackets-space-inside": "never",
169 | "selector-attribute-operator-allowed-list": null,
170 | "selector-attribute-operator-disallowed-list": null,
171 | "selector-attribute-operator-space-after": "never",
172 | "selector-attribute-operator-space-before": "never",
173 | "selector-attribute-quotes": "always",
174 | "selector-class-pattern": null,
175 | "selector-combinator-allowed-list": null,
176 | "selector-combinator-disallowed-list": null,
177 | "selector-combinator-space-after": "always",
178 | "selector-combinator-space-before": "always",
179 | "selector-descendant-combinator-no-non-space": true,
180 | "selector-disallowed-list": null,
181 | "selector-id-pattern": null,
182 | "selector-list-comma-newline-after": "always-multi-line",
183 | "selector-list-comma-newline-before": "never-multi-line",
184 | "selector-list-comma-space-after": "always-single-line",
185 | "selector-list-comma-space-before": "never-single-line",
186 | "selector-max-attribute": 2,
187 | "selector-max-class": 3,
188 | "selector-max-combinators": null,
189 | "selector-max-compound-selectors": 4,
190 | "selector-max-id": 2,
191 | "selector-max-pseudo-class": 3,
192 | "selector-max-specificity": null,
193 | "selector-max-type": 3,
194 | "selector-max-universal": 0,
195 | "selector-nested-pattern": null,
196 | "selector-no-qualifying-type": null,
197 | "selector-no-vendor-prefix": true,
198 | "selector-pseudo-class-allowed-list": null,
199 | "selector-pseudo-class-case": "lower",
200 | "selector-pseudo-class-disallowed-list": null,
201 | "selector-pseudo-class-no-unknown": true,
202 | "selector-pseudo-class-parentheses-space-inside": "never",
203 | "selector-pseudo-element-case": "lower",
204 | "selector-pseudo-element-colon-notation": "double",
205 | "selector-pseudo-element-no-unknown": true,
206 | "selector-type-case": "lower",
207 | "selector-type-no-unknown": true,
208 | "selector-max-empty-lines": 0,
209 | "shorthand-property-no-redundant-values": true,
210 | "string-no-newline": true,
211 | "string-quotes": "single",
212 | "time-min-milliseconds": 100,
213 | "unicode-bom": "never",
214 | "unit-allowed-list": null,
215 | "unit-case": "lower",
216 | "unit-disallowed-list": null,
217 | "unit-no-unknown": true,
218 | "value-keyword-case": "lower",
219 | "value-list-comma-newline-after": "always-multi-line",
220 | "value-list-comma-newline-before": "never-multi-line",
221 | "value-list-comma-space-after": "always-single-line",
222 | "value-list-comma-space-before": "never-single-line",
223 | "value-list-max-empty-lines": 0,
224 | "value-no-vendor-prefix": true
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "compat",
4 | "no-unsanitized",
5 | "promise",
6 | "sort-requires",
7 | "xss"
8 | ],
9 | "parserOptions": {
10 | "ecmaVersion": "latest"
11 | },
12 | "env": {
13 | "browser": true,
14 | "es6": true,
15 | "webextensions": true
16 | },
17 | "globals": {
18 | "Promise": true,
19 | "require": true
20 | },
21 | "rules": {
22 | "accessor-pairs": "error",
23 | "array-bracket-newline": ["error", { "multiline": true }],
24 | "array-bracket-spacing": "error",
25 | "array-callback-return": "error",
26 | "array-element-newline": "off",
27 | "arrow-body-style": "error",
28 | "arrow-parens": "error",
29 | "arrow-spacing": "error",
30 | "block-scoped-var": "error",
31 | "block-spacing": "error",
32 | "brace-style": ["error", "stroustrup"],
33 | "camelcase": "off",
34 | "capitalized-comments": "off",
35 | "class-methods-use-this": "error",
36 | "comma-dangle": "error",
37 | "comma-spacing": "error",
38 | "comma-style": "error",
39 | "compat/compat": "error",
40 | "complexity": "error",
41 | "computed-property-spacing": "error",
42 | "consistent-return": "error",
43 | "consistent-this": ["error", "self"],
44 | "constructor-super": "error",
45 | "curly": "error",
46 | "default-case": "error",
47 | "default-case-last": "error",
48 | "default-param-last": "error",
49 | "dot-location": ["error", "property"],
50 | "dot-notation": "off",
51 | "eol-last": "error",
52 | "eqeqeq": "error",
53 | "for-direction": "error",
54 | "func-call-spacing": "error",
55 | "function-call-argument-newline": ["error", "consistent"],
56 | "func-name-matching": "error",
57 | "func-names": "off",
58 | "func-style": "error",
59 | "function-paren-newline": "off",
60 | "generator-star-spacing": "error",
61 | "getter-return": "error",
62 | "grouped-accessor-pairs": "error",
63 | "guard-for-in": "error",
64 | "id-denylist": "error",
65 | "id-length": ["error", { "max": 80, "exceptions": ["e", "i", "p"] }],
66 | "id-match": "error",
67 | "implicit-arrow-linebreak": ["error", "beside"],
68 | "indent": ["error", 2, { "SwitchCase" : 1}],
69 | "init-declarations": "error",
70 | "jsx-quotes": "error",
71 | "key-spacing": ["error", { "beforeColon": true }],
72 | "keyword-spacing": "error",
73 | "lines-between-class-members": ["error", "always"],
74 | "line-comment-position": "error",
75 | "linebreak-style": ["error", "unix"],
76 | "lines-around-comment":[ "error", { "beforeBlockComment": false }],
77 | "logical-assignment-operators": ["error", "never"],
78 | "max-classes-per-file": "off",
79 | "max-depth": "error",
80 | "max-len": ["off"],
81 | "max-lines": "off",
82 | "max-lines-per-function": "off",
83 | "max-nested-callbacks": ["error", 4],
84 | "max-params": ["error", 7],
85 | "max-statements-per-line": "error",
86 | "max-statements": "off",
87 | "multiline-comment-style": "off",
88 | "multiline-ternary": ["error", "never"],
89 | "new-cap": "error",
90 | "new-parens": "error",
91 | "newline-per-chained-call": "off",
92 | "no-alert": "error",
93 | "no-array-constructor": "error",
94 | "no-async-promise-executor": "error",
95 | "no-await-in-loop": "error",
96 | "no-bitwise": "error",
97 | "no-buffer-constructor": "error",
98 | "no-caller": "error",
99 | "no-case-declarations": "off",
100 | "no-class-assign": "error",
101 | "no-compare-neg-zero": "error",
102 | "no-cond-assign": "error",
103 | "no-confusing-arrow": "error",
104 | "no-console": "error",
105 | "no-const-assign": "error",
106 | "no-constant-binary-expression": "error",
107 | "no-constant-condition": "error",
108 | "no-constructor-return": "error",
109 | "no-continue": "error",
110 | "no-control-regex": "error",
111 | "no-debugger": "error",
112 | "no-delete-var": "error",
113 | "no-div-regex": "error",
114 | "no-dupe-args": "error",
115 | "no-dupe-class-members": "error",
116 | "no-dupe-else-if": "error",
117 | "no-dupe-keys": "error",
118 | "no-duplicate-case": "error",
119 | "no-duplicate-imports": "error",
120 | "no-else-return": "error",
121 | "no-empty": "error",
122 | "no-empty-character-class": "error",
123 | "no-empty-function": "error",
124 | "no-empty-pattern": "error",
125 | "no-empty-static-block": "error",
126 | "no-eq-null": "error",
127 | "no-eval": "error",
128 | "no-ex-assign": "error",
129 | "no-extend-native": "error",
130 | "no-extra-bind": "error",
131 | "no-extra-boolean-cast": "error",
132 | "no-extra-label": "error",
133 | "no-extra-parens": ["error", "all", { "nestedBinaryExpressions": false }],
134 | "no-extra-semi": "error",
135 | "no-fallthrough": "error",
136 | "no-floating-decimal": "error",
137 | "no-func-assign": "error",
138 | "no-global-assign": "error",
139 | "no-implicit-coercion": "error",
140 | "no-implicit-globals": "error",
141 | "no-implied-eval": "error",
142 | "no-import-assign": "error",
143 | "no-inline-comments": "error",
144 | "no-inner-declarations": ["error", "both"],
145 | "no-invalid-regexp": "error",
146 | "no-invalid-this": "error",
147 | "no-irregular-whitespace": ["error", { "skipStrings": false }],
148 | "no-iterator": "error",
149 | "no-label-var": "error",
150 | "no-labels": "error",
151 | "no-lone-blocks": "error",
152 | "no-lonely-if": "error",
153 | "no-loop-func": "error",
154 | "no-loss-of-precision": "error",
155 | "no-magic-numbers": ["error", { "enforceConst": true, "ignore": [-1, 0, 1, 2, 5, 36], "ignoreArrayIndexes": true }],
156 | "no-misleading-character-class": "error",
157 | "no-mixed-operators": "error",
158 | "no-mixed-spaces-and-tabs": "error",
159 | "no-multi-assign": "error",
160 | "no-multi-spaces": "error",
161 | "no-multi-str": "error",
162 | "no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 0 }],
163 | "no-negated-condition": "error",
164 | "no-nested-ternary": "error",
165 | "no-new": "error",
166 | "no-new-func": "error",
167 | "no-new-native-nonconstructor": "error",
168 | "no-new-symbol": "error",
169 | "no-new-wrappers": "error",
170 | "no-new-object": "error",
171 | "no-nonoctal-decimal-escape": "error",
172 | "no-obj-calls": "error",
173 | "no-octal-escape": "error",
174 | "no-octal": "error",
175 | "no-param-reassign": "error",
176 | "no-plusplus": "off",
177 | "no-promise-executor-return": "error",
178 | "no-proto": "error",
179 | "no-prototype-builtins": "error",
180 | "no-redeclare": ["error", { "builtinGlobals": true }],
181 | "no-regex-spaces": "error",
182 | "no-restricted-exports": "error",
183 | "no-restricted-globals": "error",
184 | "no-restricted-imports": "error",
185 | "no-restricted-properties": "error",
186 | "no-restricted-syntax": "error",
187 | "no-return-assign": ["error", "always"],
188 | "no-return-await": "error",
189 | "no-script-url": "error",
190 | "no-self-assign": ["error", { "props": true }],
191 | "no-self-compare": "error",
192 | "no-sequences": "error",
193 | "no-setter-return": "error",
194 | "no-shadow": "off",
195 | "no-shadow-restricted-names": "error",
196 | "no-sparse-arrays": "error",
197 | "no-tabs": "error",
198 | "no-template-curly-in-string": "error",
199 | "no-ternary": "off",
200 | "no-this-before-super": "error",
201 | "no-throw-literal": "error",
202 | "no-trailing-spaces": "error",
203 | "no-undef-init": "error",
204 | "no-undef": ["error", { "typeof": true }],
205 | "no-undefined": "error",
206 | "no-underscore-dangle": "error",
207 | "no-unexpected-multiline": "error",
208 | "no-unmodified-loop-condition": "error",
209 | "no-unneeded-ternary": "error",
210 | "no-unreachable": "error",
211 | "no-unreachable-loop": "error",
212 | "no-unsafe-finally": "error",
213 | "no-unsafe-negation": "error",
214 | "no-unsafe-optional-chaining": "error",
215 | "no-unsanitized/method": "error",
216 | "no-unsanitized/property": "error",
217 | "no-unused-expressions": ["error", { "allowTernary": true }],
218 | "no-unused-labels": "error",
219 | "no-unused-private-class-members": "error",
220 | "no-unused-vars": ["error", { "vars": "local" }],
221 | "no-use-before-define": "error",
222 | "no-useless-backreference": "error",
223 | "no-useless-call": "error",
224 | "no-useless-catch": "error",
225 | "no-useless-computed-key": "error",
226 | "no-useless-concat": "error",
227 | "no-useless-constructor": "error",
228 | "no-useless-escape": "error",
229 | "no-useless-rename": "error",
230 | "no-useless-return": "error",
231 | "no-var": "error",
232 | "no-void": "off",
233 | "no-warning-comments": "error",
234 | "no-whitespace-before-property": "error",
235 | "no-with": "error",
236 | "nonblock-statement-body-position": ["error", "below"],
237 | "object-curly-newline": "error",
238 | "object-curly-spacing": ["error", "always"],
239 | "object-property-newline": ["error", { "allowAllPropertiesOnSameLine": true }],
240 | "object-shorthand": ["error", "methods"],
241 | "one-var-declaration-per-line": ["error", "always"],
242 | "one-var": ["error", "never"],
243 | "operator-assignment": "error",
244 | "operator-linebreak": "error",
245 | "padded-blocks": ["error", "never"],
246 | "padding-line-between-statements": [
247 | "error",
248 | { "blankLine": "always", "prev": "*", "next": "return" },
249 | { "blankLine": "always","prev": "directive", "next": "*" },
250 | { "blankLine": "any", "prev": "directive", "next": "directive" }
251 | ],
252 | "prefer-arrow-callback": "error",
253 | "prefer-const": "error",
254 | "prefer-destructuring": ["error", { "array": false, "object": true }],
255 | "prefer-exponentiation-operator": "error",
256 | "prefer-named-capture-group": "off",
257 | "prefer-numeric-literals": "error",
258 | "prefer-object-has-own": "error",
259 | "prefer-object-spread": "off",
260 | "prefer-promise-reject-errors": "error",
261 | "prefer-regex-literals": "error",
262 | "prefer-rest-params": "error",
263 | "prefer-spread": "error",
264 | "prefer-template": "off",
265 | "promise/always-return": "error",
266 | "promise/avoid-new": "off",
267 | "promise/catch-or-return": "error",
268 | "promise/no-callback-in-promise": "error",
269 | "promise/no-multiple-resolved": "error",
270 | "promise/no-native": "off",
271 | "promise/no-nesting": "error",
272 | "promise/no-promise-in-callback": "error",
273 | "promise/no-return-wrap": "error",
274 | "promise/param-names": "error",
275 | "promise/prefer-await-to-callbacks": "error",
276 | "promise/prefer-await-to-then": "off",
277 | "quote-props": ["error", "as-needed"],
278 | "quotes": ["error", "single"],
279 | "radix": ["error", "as-needed"],
280 | "require-atomic-updates": "error",
281 | "require-await": "error",
282 | "require-jsdoc": ["error", {
283 | "require": {
284 | "FunctionDeclaration": true,
285 | "MethodDefinition": true,
286 | "ClassDeclaration": true,
287 | "ArrowFunctionExpression": false
288 | }
289 | }],
290 | "require-unicode-regexp": "off",
291 | "require-yield": "error",
292 | "rest-spread-spacing": "error",
293 | "semi": "error",
294 | "semi-style": ["error", "last"],
295 | "semi-spacing": "error",
296 | "sort-imports": "error",
297 | "sort-keys": "off",
298 | "sort-requires/sort-requires": "error",
299 | "sort-vars": "error",
300 | "space-before-blocks": "error",
301 | "space-before-function-paren": "error",
302 | "space-in-parens": "error",
303 | "space-infix-ops": "error",
304 | "space-unary-ops": "error",
305 | "spaced-comment": "error",
306 | "strict": ["error", "global"],
307 | "symbol-description": "error",
308 | "switch-colon-spacing": ["error", { "after": true, "before": false }],
309 | "template-curly-spacing": "error",
310 | "template-tag-spacing": ["error", "always"],
311 | "unicode-bom": "error",
312 | "use-isnan": "error",
313 | "valid-jsdoc": ["error", {
314 | "prefer": {
315 | "arg": "param",
316 | "argument": "param",
317 | "class": "constructor",
318 | "return": "returns",
319 | "virtual": "abstract"
320 | },
321 | "preferType": {
322 | "Boolean": "boolean",
323 | "Number": "number",
324 | "object": "Object",
325 | "String": "string"
326 | }
327 | }],
328 | "valid-typeof": "error",
329 | "vars-on-top": "error",
330 | "wrap-iife": "error",
331 | "wrap-regex": "error",
332 | "xss/no-location-href-assign": "error",
333 | "yield-star-spacing": "error",
334 | "yoda": "error"
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/src/js/core/ui.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const WHITELIST_PAGE = 'options.html';
4 |
5 | const elBody = document.querySelector('body');
6 | const elBookmarkCard = document.getElementById('bookmark-card');
7 | const elBookmarkDateAdded = document.getElementById('bookmark-date-added');
8 | const elBookmarkId = document.getElementById('bookmark-id');
9 | const elBookmarkPath = document.getElementById('bookmark-path');
10 | const elBookmarkUrl = document.getElementById('bookmark-url');
11 | const elBookmarkTitle = document.getElementById('bookmark-title');
12 | const elButtonWrapper = document.getElementById('button-wrapper');
13 | const elConfirmDialog = document.getElementById('confirm-dialog');
14 | const elEmptyState = document.getElementById('empty-state');
15 | const elEnableConfirmations = document.getElementById('enable-confirmations');
16 | const elOpenBookmarkBtn = document.querySelector('button[data-action="open-bookmark"]');
17 | const elPreviousBookmarkBtn = document.querySelector('button[data-action="previous-bookmark"]');
18 | const elStatus = document.getElementById('bookmark-status');
19 | const elStatusIndicator = elStatus.querySelector('.indicator');
20 | const elStatusText = elStatus.querySelector('.text');
21 |
22 | /**
23 | * @exports ui
24 | */
25 | const ui = {
26 | /**
27 | * Whether or not confirmation dialogs are enabled or not. Default: true.
28 | *
29 | * @type {boolean}
30 | */
31 | confirmations : true,
32 |
33 | /**
34 | * Fired when the initial HTML document has been completely loaded and parsed. Starts the collecting process.
35 | *
36 | * @returns {void}
37 | */
38 | init () {
39 | browser.runtime.sendMessage({ message : 'collect' });
40 | ui.setupPermissionGrantHandler();
41 | },
42 |
43 | /**
44 | * Set up the listener for the link to grant the permission for the broken bookmark check.
45 | *
46 | * @returns {void}
47 | */
48 | setupPermissionGrantHandler () {
49 | elStatusText.onclick = async () => {
50 | if (elStatusText.classList.contains('permission-needed')) {
51 | const granted = await browser.permissions.request({
52 | origins : ['']
53 | });
54 |
55 | if (granted) {
56 | browser.runtime.sendMessage({
57 | message : 'recheck-bookmark',
58 | id : elBookmarkId.textContent
59 | });
60 | }
61 | }
62 | };
63 | },
64 |
65 | /**
66 | * Confirmation dialog implementation.
67 | *
68 | * @param {string} title - the title of the bookmark
69 | *
70 | * @returns {Promise} - resolves on success (OK button)
71 | */
72 | confirm (title) {
73 | const elModal = document.getElementById('confirm-dialog');
74 | elModal.classList.add('visible');
75 |
76 | const elTitle = document.getElementById('confirm-title');
77 | elTitle.textContent = title;
78 |
79 | const elSubmitButton = elModal.querySelector('#button-confirm-ok');
80 | const elCloseButton = elModal.querySelector('#button-confirm-cancel');
81 |
82 | const hideModal = () => {
83 | elModal.classList.remove('visible');
84 | };
85 |
86 | return new Promise((resolve) => {
87 | window.onkeydown = function (e) {
88 | e.preventDefault();
89 |
90 | if (e.key === 'Escape') {
91 | hideModal();
92 | }
93 | else if (e.key === 'Enter') {
94 | hideModal();
95 | resolve();
96 | }
97 | };
98 |
99 | window.onclick = function (e) {
100 | if (e.target === elModal) {
101 | hideModal();
102 | }
103 | };
104 |
105 | elCloseButton.onclick = () => {
106 | hideModal();
107 | };
108 |
109 | elSubmitButton.onclick = () => {
110 | hideModal();
111 | resolve();
112 | };
113 | });
114 | },
115 |
116 | /**
117 | * Fired when a message is sent from the background script to the UI script.
118 | *
119 | * @param {Object} response - contains the response from the background script
120 | *
121 | * @returns {void}
122 | */
123 | handleResponse (response) {
124 | if (response.message === 'permission-change') {
125 | browser.runtime.sendMessage({
126 | message : 'recheck-bookmark',
127 | id : elBookmarkId.textContent
128 | });
129 | }
130 | else if (response.message === 'confirmations') {
131 | ui.confirmations = response.confirmations;
132 |
133 | if (response.confirmations) {
134 | elEnableConfirmations.setAttribute('checked', 'true');
135 | }
136 | else {
137 | elEnableConfirmations.removeAttribute('checked');
138 | }
139 | }
140 | else if (response.message === 'show-bookmark') {
141 | if (response.bookmark) {
142 | const pattern = new RegExp(/^https?:\/\//, 'gi');
143 | const dateAdded = new Intl.DateTimeFormat('default', {
144 | day : '2-digit', month : '2-digit', year : 'numeric'
145 | }).format(new Date(response.bookmark.dateAdded));
146 |
147 | elBookmarkCard.removeAttribute('hidden');
148 | elButtonWrapper.removeAttribute('hidden');
149 | elEmptyState.setAttribute('hidden', 'true');
150 | elBookmarkId.textContent = response.bookmark.id;
151 | elBookmarkTitle.textContent = response.bookmark.title;
152 | elBookmarkPath.textContent = response.bookmark.path.join(' / ');
153 | elBookmarkDateAdded.textContent = browser.i18n.getMessage('date_added') + ': ' + dateAdded;
154 |
155 | if (elBookmarkUrl.firstChild) {
156 | elBookmarkUrl.removeChild(elBookmarkUrl.firstChild);
157 | }
158 |
159 | if (pattern.test(response.bookmark.url)) {
160 | const elUrl = document.createElement('a');
161 | elUrl.setAttribute('href', response.bookmark.url);
162 | elUrl.setAttribute('target', '_blank');
163 | elUrl.setAttribute('rel', 'noopener');
164 | elUrl.textContent = response.bookmark.url;
165 | elBookmarkUrl.appendChild(elUrl);
166 | elOpenBookmarkBtn.removeAttribute('disabled');
167 | }
168 | else {
169 | elBookmarkUrl.textContent = response.bookmark.url;
170 | elOpenBookmarkBtn.setAttribute('disabled', 'true');
171 | }
172 |
173 | elPreviousBookmarkBtn.removeAttribute('disabled');
174 | }
175 | else {
176 | elBookmarkCard.setAttribute('hidden', 'true');
177 | elButtonWrapper.setAttribute('hidden', 'true');
178 | elEmptyState.removeAttribute('hidden');
179 | }
180 | }
181 | else if (response.message === 'disable-previous-button') {
182 | elPreviousBookmarkBtn.setAttribute('disabled', 'true');
183 | }
184 | else if (response.message === 'enable-skip-button') {
185 | elButtonWrapper.querySelector('[data-action="skip-bookmark"]').removeAttribute('disabled');
186 | }
187 | else if (response.message === 'disable-skip-button') {
188 | elButtonWrapper.querySelector('[data-action="skip-bookmark"]').setAttribute('disabled', 'true');
189 | }
190 | else if (response.message === 'update-bookmark-status') {
191 | if (response.status === 'permission-needed') {
192 | elStatusIndicator.classList.add('failure');
193 | elStatusIndicator.classList.remove('success');
194 | elStatusText.classList.add('permission-needed');
195 | elStatusText.textContent = browser.i18n.getMessage('check_status_permission_needed');
196 | }
197 | else if (response.status === 'success') {
198 | elStatusIndicator.classList.add('success');
199 | elStatusIndicator.classList.remove('failure');
200 | elStatusText.classList.remove('permission-needed');
201 | elStatusText.textContent = browser.i18n.getMessage('check_status_success');
202 | }
203 | else if (response.status === 'failure') {
204 | elStatusIndicator.classList.add('failure');
205 | elStatusIndicator.classList.remove('success');
206 | elStatusText.classList.remove('permission-needed');
207 | elStatusText.textContent = browser.i18n.getMessage('check_status_failure');
208 | }
209 | else if (response.status === 'skip') {
210 | elStatusIndicator.classList.remove('success', 'failure');
211 | elStatusText.classList.remove('permission-needed');
212 | elStatusText.textContent = browser.i18n.getMessage('check_status_skipped');
213 | }
214 | else {
215 | elStatusIndicator.classList.remove('success', 'failure');
216 | elStatusText.classList.remove('permission-needed');
217 | elStatusText.textContent = '…';
218 | }
219 | }
220 | },
221 |
222 | /**
223 | * Fired when a key is pressed.
224 | *
225 | * @param {KeyboardEvent} e - event
226 | *
227 | * @returns {void}
228 | */
229 | handleKeyPress (e) {
230 | if (elConfirmDialog.classList.contains('visible')) {
231 | return;
232 | }
233 |
234 | if (e.key === 'Backspace') {
235 | ui.deleteBookmark(elBookmarkId.textContent, elBookmarkTitle.textContent);
236 | }
237 | else if (e.key === ' ') {
238 | ui.keepBookmark(
239 | elBookmarkId.textContent, elBookmarkTitle.textContent, elBookmarkUrl.textContent, elBookmarkPath.textContent
240 | );
241 | }
242 | else if (e.key === 'Enter') {
243 | window.open(elBookmarkUrl.textContent, '_blank');
244 | }
245 | else if (e.key === 'ArrowLeft') {
246 | ui.previousBookmark();
247 | }
248 | else if (e.key === 'ArrowRight') {
249 | ui.skipBookmark(elBookmarkId.textContent);
250 | }
251 | },
252 |
253 | /**
254 | * Fired when one of the buttons is clicked.
255 | *
256 | * @param {MouseEvent} e - event
257 | *
258 | * @returns {void}
259 | */
260 | handleButtonClicks (e) {
261 | if (e.target.getAttribute('data-action')) {
262 | e.preventDefault();
263 |
264 | switch (e.target.getAttribute('data-action')) {
265 | case 'delete-bookmark':
266 | ui.deleteBookmark(elBookmarkId.textContent, elBookmarkTitle.textContent);
267 | break;
268 | case 'keep-bookmark':
269 | ui.keepBookmark(
270 | elBookmarkId.textContent, elBookmarkTitle.textContent, elBookmarkUrl.textContent, elBookmarkPath.textContent
271 | );
272 | break;
273 | case 'open-bookmark':
274 | window.open(elBookmarkUrl.textContent, '_blank');
275 | break;
276 | case 'previous-bookmark':
277 | ui.previousBookmark();
278 | break;
279 | case 'skip-bookmark':
280 | ui.skipBookmark(elBookmarkId.textContent);
281 | break;
282 | case 'open-options':
283 | ui.openWhitelist();
284 | break;
285 | default:
286 | // do nothing
287 | }
288 | }
289 | },
290 |
291 | /**
292 | * This method is used to delete a bookmark.
293 | *
294 | * @param {string} id - the id of the bookmark
295 | * @param {string} title - the title of the bookmark
296 | *
297 | * @returns {void}
298 | */
299 | async deleteBookmark (id, title) {
300 | browser.runtime.sendMessage({ message : 'check-confirmations-state' });
301 |
302 | if (ui.confirmations) {
303 | await ui.confirm(title);
304 | }
305 |
306 | browser.runtime.sendMessage({
307 | message : 'delete',
308 | id : id
309 | });
310 | },
311 |
312 | /**
313 | * This method is used to keep a bookmark.
314 | *
315 | * @param {string} id - the id of the bookmark
316 | * @param {string} title - the title of the bookmark
317 | * @param {string} url - the URL of the bookmark
318 | * @param {string} path - the path of the bookmark
319 | *
320 | * @returns {void}
321 | */
322 | keepBookmark (id, title, url, path) {
323 | browser.runtime.sendMessage({
324 | message : 'keep',
325 | id : id,
326 | title : title,
327 | url : url,
328 | path : path
329 | });
330 | },
331 |
332 | /**
333 | * This method is used to show the previous bookmark.
334 | *
335 | * @returns {void}
336 | */
337 | previousBookmark () {
338 | browser.runtime.sendMessage({ message : 'previous' });
339 | },
340 |
341 | /**
342 | * This method is used to skip a bookmark.
343 | *
344 | * @param {string} id - the id of the bookmark
345 | *
346 | * @returns {void}
347 | */
348 | skipBookmark (id) {
349 | browser.runtime.sendMessage({
350 | message : 'skip',
351 | id : id
352 | });
353 | },
354 |
355 | /**
356 | * This method is used to open the options.
357 | *
358 | * @returns {void}
359 | */
360 | openWhitelist () {
361 | browser.tabs.update({ url : WHITELIST_PAGE });
362 | }
363 | };
364 |
365 | document.addEventListener('DOMContentLoaded', ui.init);
366 | window.addEventListener('keydown', ui.handleKeyPress);
367 | elBody.addEventListener('click', ui.handleButtonClicks);
368 |
369 | elEnableConfirmations.addEventListener('change', (e) => {
370 | ui.confirmations = e.target.checked;
371 | browser.storage.local.set({ confirmations : e.target.checked });
372 | });
373 |
374 | browser.runtime.onMessage.addListener(ui.handleResponse);
375 |
--------------------------------------------------------------------------------
/src/css/ui.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --color-body-background: rgb(235, 235, 235);
3 | --color-body-text: rgb(33, 37, 42);
4 | --color-blue: rgb(10, 132, 255);
5 | --color-blue-dark: rgb(0, 102, 225);
6 | --color-dark-border: rgb(54, 57, 89);
7 | --color-disabled: rgb(194, 194, 194);
8 | --color-green: rgb(18, 188, 0);
9 | --color-green-dark: rgb(0, 158, 0);
10 | --color-grey: rgb(200, 200, 200);
11 | --color-grey-dark: rgb(115, 115, 115);
12 | --color-red: rgb(215, 0, 34);
13 | --color-red-dark: rgb(185, 0, 4);
14 | --color-white: rgb(255, 255, 255);
15 | }
16 |
17 | body {
18 | margin: 0;
19 | font-family: Verdana, sans-serif;
20 | font-size: 16px;
21 | background: var(--color-body-background);
22 | color: var(--color-body-text);
23 | overflow-x: hidden;
24 | }
25 |
26 | main {
27 | padding-top: 100px;
28 | padding-bottom: 64px;
29 | }
30 |
31 | .modal-dialog {
32 | display: flex;
33 | align-items: center;
34 | justify-content: center;
35 | position: fixed;
36 | z-index: 200;
37 | top: 0;
38 | bottom: 0;
39 | left: 0;
40 | right: 0;
41 | transform: scale(.7);
42 | opacity: 0;
43 | transition: all 300ms;
44 | pointer-events: none;
45 | }
46 |
47 | .modal-dialog.visible {
48 | transform: scale(1);
49 | opacity: 1;
50 | pointer-events: all;
51 | }
52 |
53 | .modal-dialog ~ .modal-dialog-bg {
54 | position: fixed;
55 | z-index: 100;
56 | top: 0;
57 | left: 0;
58 | width: 100%;
59 | height: 100%;
60 | visibility: hidden;
61 | background: rgba(0, 0, 0, .8);
62 | opacity: 0;
63 | transition: all 300ms;
64 | }
65 |
66 | .modal-dialog.visible ~ .modal-dialog-bg {
67 | opacity: 1;
68 | visibility: visible;
69 | }
70 |
71 | .modal-dialog > div {
72 | width: 550px;
73 | max-width: 98%;
74 | max-height: 90vh;
75 | overflow: auto;
76 | border-width: 0;
77 | border-radius: 5px;
78 | background-color: var(--color-white);
79 | box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .3);
80 | }
81 |
82 | .modal-dialog header {
83 | padding: 10px 15px;
84 | background-color: var(--color-blue);
85 | color: var(--color-white);
86 | }
87 |
88 | .modal-dialog section {
89 | padding: 30px 15px;
90 | }
91 |
92 | .modal-dialog aside {
93 | padding: 15px;
94 | background-color: var(--color-body-background);
95 | text-align: right;
96 | }
97 |
98 | .modal-dialog .primary-action {
99 | background-color: var(--color-green);
100 | }
101 |
102 | .modal-dialog .primary-action::before {
103 | background: var(--color-green-dark);
104 | }
105 |
106 | .modal-dialog .secondary-action {
107 | margin-right: 5px;
108 | background-color: var(--color-red);
109 | }
110 |
111 | .modal-dialog .secondary-action::before {
112 | background: var(--color-red-dark);
113 | }
114 |
115 | .checkbox {
116 | display: inline-block;
117 | position: relative;
118 | margin-bottom: 5px;
119 | margin-right: 15px;
120 | padding-left: 30px;
121 | padding-top: 3px;
122 | }
123 |
124 | .checkbox input {
125 | position: absolute;
126 | left: -9999px;
127 | opacity: 0;
128 | }
129 |
130 | .checkbox label {
131 | cursor: pointer;
132 | font-size: 14px;
133 | }
134 |
135 | .checkbox label::before {
136 | content: '';
137 | position: absolute;
138 | z-index: 0;
139 | top: 1px;
140 | left: 0;
141 | margin-top: 2px;
142 | width: 18px;
143 | height: 18px;
144 | border: 2px solid var(--color-dark-border);
145 | border-radius: 2px;
146 | cursor: pointer;
147 | transition: all 250ms ease;
148 | }
149 |
150 | .checkbox input:checked + label::before {
151 | top: -1px;
152 | left: -3px;
153 | width: 8px;
154 | height: 17px;
155 | transform: rotate(40deg);
156 | border: 2px solid transparent;
157 | border-right-color: var(--color-green);
158 | border-bottom-color: var(--color-green);
159 | transform-origin: 100% 100%;
160 | backface-visibility: hidden;
161 | }
162 |
163 | button.circle-btn {
164 | border-radius: 50%;
165 | border: 5px solid var(--color-white);
166 | cursor: pointer;
167 | transition: background ease 250ms;
168 | }
169 |
170 | button.circle-btn[disabled], button.circle-btn[disabled]:hover {
171 | color: var(--color-disabled);
172 | }
173 |
174 | button.circle-btn:hover {
175 | color: var(--color-white);
176 | }
177 |
178 | button.circle-btn svg {
179 | display: block;
180 | pointer-events: none;
181 | }
182 |
183 | button.circle-btn[data-action='delete-bookmark'],
184 | button.circle-btn[data-action='keep-bookmark'] {
185 | width: 135px;
186 | height: 135px;
187 | }
188 |
189 | button.circle-btn[data-action='delete-bookmark'] svg,
190 | button.circle-btn[data-action='keep-bookmark'] svg {
191 | margin: 0 auto 8px;
192 | width: 32px;
193 | height: 32px;
194 | }
195 |
196 | button.circle-btn[data-action='previous-bookmark'],
197 | button.circle-btn[data-action='skip-bookmark'],
198 | button.circle-btn[data-action='open-bookmark'] {
199 | width: 90px;
200 | height: 90px;
201 | }
202 |
203 | button.circle-btn[data-action='open-bookmark'] {
204 | margin-top: -15px;
205 | }
206 |
207 | button.circle-btn[data-action='previous-bookmark'] svg,
208 | button.circle-btn[data-action='skip-bookmark'] svg {
209 | margin: 0 auto;
210 | width: 24px;
211 | height: 24px;
212 | }
213 |
214 | button.circle-btn[data-action='open-bookmark'] svg {
215 | margin: 0 auto 4px;
216 | width: 18px;
217 | height: 18px;
218 | }
219 |
220 | button.circle-btn[data-action='delete-bookmark']:not([disabled]):hover {
221 | background-color: rgba(215, 0, 34, .5);
222 | }
223 |
224 | button.circle-btn[data-action='delete-bookmark']:not([disabled]) svg path {
225 | fill: var(--color-red);
226 | }
227 |
228 | button.circle-btn[data-action='delete-bookmark']:not([disabled]):hover svg path {
229 | fill: var(--color-white);
230 | }
231 |
232 | button.circle-btn[data-action='keep-bookmark']:not([disabled]):hover {
233 | background-color: rgba(18, 188, 0, .5);
234 | }
235 |
236 | button.circle-btn[data-action='keep-bookmark']:not([disabled]) svg path {
237 | fill: var(--color-green);
238 | }
239 |
240 | button.circle-btn[data-action='keep-bookmark']:not([disabled]):hover svg path {
241 | fill: var(--color-white);
242 | }
243 |
244 | button.circle-btn[data-action='previous-bookmark']:not([disabled]):hover,
245 | button.circle-btn[data-action='skip-bookmark']:not([disabled]):hover,
246 | button.circle-btn[data-action='open-bookmark']:not([disabled]):hover {
247 | background-color: rgba(10, 132, 255, .5);
248 | }
249 |
250 | button.circle-btn[data-action='previous-bookmark']:not([disabled]) svg path:first-child,
251 | button.circle-btn[data-action='skip-bookmark']:not([disabled]) svg path:first-child {
252 | fill: var(--color-blue);
253 | }
254 |
255 | button.circle-btn[data-action='previous-bookmark']:not([disabled]):hover svg path:first-child,
256 | button.circle-btn[data-action='skip-bookmark']:not([disabled]):hover svg path:first-child {
257 | fill: var(--color-white);
258 | }
259 |
260 | button.circle-btn[data-action='previous-bookmark'][disabled] svg path:first-child,
261 | button.circle-btn[data-action='skip-bookmark'][disabled] svg path:first-child {
262 | fill: var(--color-disabled);
263 | }
264 |
265 | button.circle-btn[data-action='open-bookmark']:not([disabled]) svg {
266 | stroke: var(--color-blue);
267 | }
268 |
269 | button.circle-btn[data-action='open-bookmark']:not([disabled]):hover svg {
270 | stroke: var(--color-white);
271 | }
272 |
273 | button.circle-btn[data-action='open-bookmark'][disabled] svg {
274 | stroke: var(--color-disabled);
275 | }
276 |
277 | button[disabled] {
278 | cursor: not-allowed;
279 | }
280 |
281 | #header {
282 | position: fixed;
283 | z-index: 10;
284 | top: 0;
285 | left: 0;
286 | right: 0;
287 | height: 100px;
288 | background: var(--color-white);
289 | border-bottom: 1px solid var(--color-dark-border);
290 | transition: height 250ms ease-in-out;
291 | }
292 |
293 | #logo {
294 | display: block;
295 | width: 467px;
296 | height: 100px;
297 | background: url('../images/logo-large.png') 25px center / 442px 70px no-repeat transparent;
298 | }
299 |
300 | #header-btn-wrapper {
301 | position: absolute;
302 | z-index: 20;
303 | top: 30px;
304 | right: 15px;
305 | }
306 |
307 | button.action-btn {
308 | padding: 8px 15px;
309 | transform: perspective(1px) translateZ(0);
310 | font-size: 16px;
311 | border: none;
312 | border-radius: 2px;
313 | background: var(--color-blue);
314 | color: var(--color-white);
315 | cursor: pointer;
316 | transition: color ease 500ms;
317 | }
318 |
319 | button.action-btn::before {
320 | content: '';
321 | position: absolute;
322 | z-index: -1;
323 | top: 0;
324 | bottom: 0;
325 | left: 0;
326 | right: 0;
327 | transform: scaleX(0);
328 | border-radius: 2px;
329 | background: var(--color-blue-dark);
330 | transform-origin: 0 50%;
331 | transition-property: transform;
332 | transition-duration: 500ms;
333 | transition-timing-function: ease-out;
334 | }
335 |
336 | button.action-btn:hover::before {
337 | transform: scaleX(1);
338 | transition-timing-function: cubic-bezier(.52, 1.64, .37, .66);
339 | }
340 |
341 | #button-wrapper {
342 | margin-top: 50px;
343 | text-align: center;
344 | }
345 |
346 | #bookmark-card-wrapper,
347 | #no-bookmarks:not([hidden]) {
348 | display: flex;
349 | justify-content: center;
350 | align-items: center;
351 | height: calc(100vh - 165px);
352 | }
353 |
354 | #bookmark-card {
355 | text-align: center;
356 | }
357 |
358 | #bookmark-title {
359 | font-weight: 700;
360 | font-size: 30px;
361 | }
362 |
363 | #bookmark-url {
364 | margin-top: 50px;
365 | }
366 |
367 | #bookmark-url::before, #whitelist-table td:nth-child(2)::before {
368 | content: '';
369 | display: inline-block;
370 | margin-right: 10px;
371 | width: 24px;
372 | height: 24px;
373 | background: url('../images/link.svg') center center / 24px 24px no-repeat transparent;
374 | vertical-align: -6px;
375 | }
376 |
377 | #bookmark-url a, #whitelist-table a {
378 | color: var(--color-blue);
379 | text-decoration: none;
380 | transition: color ease 250ms;
381 | }
382 |
383 | #bookmark-url a:hover, #whitelist-table a:hover {
384 | color: var(--color-blue-dark);
385 | }
386 |
387 | #bookmark-path {
388 | margin-top: 10px;
389 | }
390 |
391 | #bookmark-path::before, #whitelist-table td:nth-child(3)::before {
392 | content: '';
393 | display: inline-block;
394 | margin-right: 10px;
395 | width: 24px;
396 | height: 24px;
397 | background: url('../images/path.svg') center center / 24px 24px no-repeat transparent;
398 | vertical-align: -6px;
399 | }
400 |
401 | #bookmark-status {
402 | margin-top: 30px;
403 | font-size: 14px;
404 | }
405 |
406 | #bookmark-status .indicator {
407 | display: inline-block;
408 | margin-right: 10px;
409 | width: 16px;
410 | height: 16px;
411 | border-radius: 50%;
412 | background-color: var(--color-grey-dark);
413 | vertical-align: -3px;
414 | }
415 |
416 | #bookmark-status .indicator.success {
417 | background-color: var(--color-green-dark);
418 | }
419 |
420 | #bookmark-status .indicator.failure {
421 | background-color: var(--color-red-dark);
422 | }
423 |
424 | #bookmark-status .permission-needed {
425 | color: var(--color-red);
426 | cursor: pointer;
427 | transition: color ease 250ms;
428 | }
429 |
430 | #bookmark-status .permission-needed:hover {
431 | color: var(--color-red-dark);
432 | }
433 |
434 | #bookmark-date-added {
435 | margin-top: 10px;
436 | font-size: 14px;
437 | color: var(--color-grey-dark);
438 | }
439 |
440 | #bookmark-date-added::before {
441 | content: '';
442 | display: inline-block;
443 | margin-right: 5px;
444 | width: 18px;
445 | height: 18px;
446 | background: url('../images/calendar.svg') center center / 18px 18px no-repeat transparent;
447 | vertical-align: -3px;
448 | }
449 |
450 | #whitelist-table {
451 | table-layout: fixed;
452 | margin: 30px;
453 | width: calc(100vw - 60px);
454 | border-collapse: collapse;
455 | }
456 |
457 | #whitelist-table th, #whitelist-table td {
458 | padding: 15px 5px;
459 | text-align: left;
460 | }
461 |
462 | #whitelist-table th {
463 | border-bottom: 1px solid var(--color-dark-border);
464 | }
465 |
466 | #whitelist-table th:not(:last-child) {
467 | width: 33%;
468 | }
469 |
470 | #whitelist-table tr:not(:last-child) td {
471 | border-bottom: 1px solid var(--color-grey);
472 | }
473 |
474 | #whitelist-table td {
475 | overflow: hidden;
476 | text-overflow: ellipsis;
477 | white-space: nowrap;
478 | }
479 |
480 | #whitelist-table td:first-child {
481 | padding-right: 10px;
482 | max-width: 230px;
483 | overflow: hidden;
484 | text-overflow: ellipsis;
485 | }
486 |
487 | #whitelist-table .actions {
488 | width: 90px;
489 | text-align: right;
490 | }
491 |
492 | #whitelist-table .icon img {
493 | width: 24px;
494 | height: 24px;
495 | vertical-align: middle;
496 | cursor: pointer;
497 | opacity: .5;
498 | transition: opacity ease 250ms;
499 | }
500 |
501 | #whitelist-table .icon img:hover {
502 | opacity: 1;
503 | }
504 |
505 | #whitelist-remove-all-wrapper {
506 | margin: 30px 0;
507 | text-align: center;
508 | }
509 |
510 | #footer {
511 | position: fixed;
512 | bottom: 0;
513 | left: 0;
514 | right: 0;
515 | padding: 0 20px;
516 | height: 64px;
517 | line-height: 64px;
518 | font-size: 14px;
519 | border-top: 1px solid var(--color-dark-border);
520 | background: var(--color-white);
521 | }
522 |
523 | #footer a {
524 | display: block;
525 | padding-left: 40px;
526 | background-image: url('../images/sh-at.png');
527 | background-repeat: no-repeat;
528 | background-size: 32px 32px;
529 | background-position: center left;
530 | color: var(--color-body-text);
531 | text-decoration: none;
532 | }
533 |
534 | @media screen and (max-width: 899px) {
535 | .checkbox {
536 | display: none;
537 | }
538 | }
539 |
--------------------------------------------------------------------------------
/src/js/core/background.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const UI_PAGE = 'html/ui.html';
4 |
5 | /**
6 | * @exports kodb
7 | */
8 | const kodb = {
9 | /**
10 | * Timeout for reaching a server in milliseconds. It's always 15 seconds, there is no user setting.
11 | *
12 | * @type {int}
13 | */
14 | CHECK_TIMEOUT_IN_MS : 15000,
15 |
16 | /**
17 | * Whether the internal skip list should be used or not. It's always true, there is no user setting.
18 | *
19 | * @type {boolean}
20 | */
21 | CHECK_USE_SKIP_LIST : true,
22 |
23 | /**
24 | * An array of URL patterns which should be ignored while checking for broken bookmarks. Please only add patterns
25 | * if there are known problems and add a comment with the corresponding GitHub issue.
26 | *
27 | * @type {Array.}
28 | *
29 | */
30 | CHECK_SKIP_LIST : [
31 | /* eslint-disable no-useless-escape, line-comment-position, no-inline-comments */
32 | '^https?:\/\/groups.google.com/group/', // issue #9
33 | '^https?:\/\/accounts-static.cdn.mozilla.net', // issue #9
34 | '^https?:\/\/accounts.firefox.com', // issue #9
35 | '^https?:\/\/addons.cdn.mozilla.net', // issue #9
36 | '^https?:\/\/addons.mozilla.org', // issue #9
37 | '^https?:\/\/api.accounts.firefox.com', // issue #9
38 | '^https?:\/\/content.cdn.mozilla.net', // issue #9
39 | '^https?:\/\/discovery.addons.mozilla.org', // issue #9
40 | '^https?:\/\/install.mozilla.org', // issue #9
41 | '^https?:\/\/oauth.accounts.firefox.com', // issue #9
42 | '^https?:\/\/profile.accounts.firefox.com', // issue #9
43 | '^https?:\/\/support.mozilla.org', // issue #9
44 | '^https?:\/\/sync.services.mozilla.com' // issue #9
45 | /* eslint-enable no-useless-escape, line-comment-position, no-inline-comments */
46 | ],
47 |
48 | /**
49 | * Status code for a server response we are waiting for.
50 | *
51 | * @type {string}
52 | */
53 | CHECK_STATUS_AWAIT : 'await',
54 |
55 | /**
56 | * Status code for a broken bookmark.
57 | *
58 | * @type {string}
59 | */
60 | CHECK_STATUS_FAILURE : 'failure',
61 |
62 | /**
63 | * Status code for a broken bookmark.
64 | *
65 | * @type {string}
66 | */
67 | CHECK_STATUS_PERMISSION_NEEDED : 'permission-needed',
68 |
69 | /**
70 | * Status code if a bookmark can not be checked.
71 | *
72 | * @type {string}
73 | */
74 | CHECK_STATUS_SKIP : 'skip',
75 |
76 | /**
77 | * Status code for a working bookmark.
78 | *
79 | * @type {string}
80 | */
81 | CHECK_STATUS_SUCCESS : 'success',
82 |
83 | /**
84 | * An object containing the IDs and paths of all bookmarks on the whitelist.
85 | *
86 | * @type {Object}
87 | */
88 | whitelist : {},
89 |
90 | /**
91 | * An array containing all the user's bookmarks.
92 | *
93 | * @type {Array.}
94 | */
95 | collectedBookmarks : [],
96 |
97 | /**
98 | * Additional data stored for bookmarks. In current version it only contains the full bookmark path.
99 | *
100 | * @type {Array.