├── .eslintrc ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── README_AMO.md ├── extension ├── _locales │ ├── de │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── fr │ │ └── messages.json │ └── it │ │ └── messages.json ├── background │ └── background.wp.js ├── content │ ├── addon-page │ │ ├── addon-page.css │ │ ├── addon-page.html │ │ └── addon-page.wp.js │ ├── detail-view │ │ ├── detail-view.css │ │ ├── detail-view.html │ │ └── detail-view.wp.js │ ├── options-ui │ │ ├── options.css │ │ ├── options.html │ │ └── options.wp.js │ ├── page-injections │ │ ├── injection.css │ │ └── injection.wp.js │ ├── sidebar │ │ ├── sidebar.css │ │ ├── sidebar.html │ │ └── sidebar.wp.js │ └── tbb-menu │ │ ├── tbb-menu.css │ │ ├── tbb-menu.html │ │ └── tbb-menu.wp.js ├── icons │ ├── arrow-down.png │ ├── bin.png │ ├── bm.png │ ├── bm2.png │ ├── bm3.png │ ├── book.png │ ├── clock.png │ ├── copy.png │ ├── data.png │ ├── debug.png │ ├── del-bm.png │ ├── dna.png │ ├── donate.png │ ├── double-arrow.png │ ├── down.png │ ├── download.png │ ├── edit.png │ ├── email.png │ ├── export.png │ ├── folder.png │ ├── forklift.png │ ├── gear.png │ ├── ghost.png │ ├── github.png │ ├── highlight.png │ ├── m.png │ ├── menu-blue.png │ ├── note.png │ ├── note2.png │ ├── note4.png │ ├── off16.png │ ├── off18.png │ ├── on16.png │ ├── on18.png │ ├── on32.png │ ├── on36.png │ ├── on64.png │ ├── pageaction16.png │ ├── pageaction32.png │ ├── pause.png │ ├── redo.png │ ├── retry.png │ ├── save.png │ ├── save2.png │ ├── search.png │ ├── sel.jpg │ ├── start.png │ ├── submit.png │ ├── sync.png │ ├── sync2.png │ ├── tm48.png │ ├── to-bm.png │ ├── toggle-notes.png │ ├── tools.png │ ├── undo.png │ ├── up.png │ ├── view.png │ └── view2.png ├── manifest.json └── web-ext-artifacts │ └── textmarker-5.3.3.zip ├── package-lock.json ├── package.json ├── src ├── background │ ├── index.js │ ├── modules │ │ ├── context-menu.js │ │ ├── error-logging.js │ │ ├── injection-manager.js │ │ ├── namer.js │ │ ├── notifications.js │ │ ├── page-action.js │ │ ├── sidebars.js │ │ ├── store-manager.js │ │ ├── tabs.js │ │ ├── version-manager.js │ │ ├── web-navigation.js │ │ └── windows.js │ ├── port.js │ ├── storage.js │ └── utils.js ├── content │ ├── _shared │ │ ├── sass │ │ │ ├── _buttons.scss │ │ │ ├── _forms.scss │ │ │ ├── _general.scss │ │ │ ├── _helpers.scss │ │ │ ├── _icons.scss │ │ │ ├── _infos.scss │ │ │ ├── _links.scss │ │ │ ├── _notes.scss │ │ │ ├── _reset.scss │ │ │ ├── _shadows.scss │ │ │ ├── _switches.scss │ │ │ └── _vars.scss │ │ └── utils.js │ ├── addon-page │ │ ├── _store.js │ │ ├── bootstrap.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── contact.js │ │ │ ├── history-pagination.js │ │ │ ├── history-sort.js │ │ │ ├── history.js │ │ │ ├── import.js │ │ │ ├── logs.js │ │ │ ├── marker.js │ │ │ ├── nav.js │ │ │ ├── permissions.js │ │ │ ├── settings.js │ │ │ ├── syncing.js │ │ │ ├── toggler.js │ │ │ └── troubleshooting.js │ │ ├── port.js │ │ └── sass │ │ │ ├── _buttons.scss │ │ │ ├── _general.scss │ │ │ ├── _helpers.scss │ │ │ ├── _navs.scss │ │ │ ├── components │ │ │ ├── _alerts.scss │ │ │ ├── _contact.scss │ │ │ ├── _data-management.scss │ │ │ ├── _history.scss │ │ │ ├── _logs.scss │ │ │ ├── _news.scss │ │ │ ├── _settings.scss │ │ │ └── _troubleshooting.scss │ │ │ └── index.scss │ ├── detail-view │ │ ├── index.js │ │ ├── modules │ │ │ ├── header.js │ │ │ ├── marks.js │ │ │ └── meta.js │ │ └── sass │ │ │ ├── _general.scss │ │ │ ├── _tables.scss │ │ │ └── index.scss │ ├── options-ui │ │ ├── index.js │ │ └── sass │ │ │ └── index.scss │ ├── page-injections │ │ ├── _store.js │ │ ├── index.js │ │ ├── main.js │ │ ├── modules │ │ │ ├── auto-marker.js │ │ │ ├── bookmark.js │ │ │ ├── contextmenu.js │ │ │ ├── mark-item.js │ │ │ ├── marker-popup.js │ │ │ ├── marker.js │ │ │ ├── note.js │ │ │ ├── notes.js │ │ │ ├── page.js │ │ │ ├── restorer.js │ │ │ ├── selection.js │ │ │ └── tmui.js │ │ ├── port.js │ │ └── sass │ │ │ ├── components │ │ │ ├── _copyshop.scss │ │ │ ├── _marker-popup.scss │ │ │ ├── _marks.scss │ │ │ ├── _notes.scss │ │ │ └── _tmui.scss │ │ │ └── index.scss │ ├── sidebar │ │ ├── _store.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── header.js │ │ │ ├── history-actions.js │ │ │ ├── links.js │ │ │ ├── mark-actions.js │ │ │ ├── markers.js │ │ │ ├── marks.js │ │ │ ├── meta-infos.js │ │ │ ├── page-actions.js │ │ │ ├── page-notes.js │ │ │ ├── tabs.js │ │ │ ├── tags.js │ │ │ └── themes.js │ │ ├── port.js │ │ └── sass │ │ │ ├── _buttons.scss │ │ │ ├── _form-elements.scss │ │ │ ├── _general.scss │ │ │ ├── components │ │ │ ├── _actions.scss │ │ │ ├── _links.scss │ │ │ ├── _marker.scss │ │ │ ├── _marks.scss │ │ │ ├── _page-notes.scss │ │ │ ├── _tabs.scss │ │ │ └── _tags.scss │ │ │ ├── index.scss │ │ │ └── themes │ │ │ └── _dark.scss │ └── tbb-menu │ │ ├── index.js │ │ ├── port.js │ │ └── sass │ │ └── index.scss ├── data │ ├── default-storage.js │ ├── global-settings.js │ └── log-keys.js ├── icons │ ├── arrow-down.png │ ├── bin.png │ ├── bm.png │ ├── bm2.png │ ├── bm3.png │ ├── book.png │ ├── clipboard.png │ ├── clock.png │ ├── copy.png │ ├── debug.png │ ├── del-bm.png │ ├── dna.png │ ├── donate.png │ ├── double-arrow.png │ ├── down.png │ ├── download.png │ ├── edit.png │ ├── email.png │ ├── export.png │ ├── forklift.png │ ├── ghost.png │ ├── github.png │ ├── highlight.png │ ├── m.png │ ├── menu-blue.png │ ├── note.png │ ├── note2.png │ ├── note4.png │ ├── off16.png │ ├── off18.png │ ├── on16.png │ ├── on18.png │ ├── on32.png │ ├── on36.png │ ├── on64.png │ ├── pageaction16.png │ ├── pageaction32.png │ ├── pause.png │ ├── redo.png │ ├── retry.png │ ├── save.png │ ├── save2.png │ ├── search.png │ ├── sel.jpg │ ├── start.png │ ├── submit.png │ ├── sync.png │ ├── sync2.png │ ├── tm48.png │ ├── tm64.png │ ├── to-bm.png │ ├── toggle-notes.png │ ├── tools.png │ ├── undo.png │ ├── up.png │ └── view.png └── utils │ ├── copy.js │ ├── dommodule.js │ ├── error-tracker.js │ ├── extend.js │ ├── getActiveTab.js │ ├── hashless.js │ ├── l10n.js │ ├── mediator.js │ ├── module.js │ ├── port.js │ └── store.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "commonjs": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: unflybi 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[bug] add title here" 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 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen (leave blank if obvious from bug description) 17 | 18 | **Screenshots** 19 | If applicable, add screenshots to help explain your problem. 20 | 21 | **Which OS, Firefox edition, Textmarker version** 22 | These infos could be helpful in order to reproduce the buggy behavior 23 | OS (i.e. Linux, MacOS, Win7, Win10, Android ...): 24 | FF edition (i.e. Regular, Nightly, ESR, ...): 25 | Textmarker version (i.e. 5.1.2): 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[feature] add title here" 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 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Textmaker 2 | ======== 3 | 4 | [![Firefox Add-on](https://img.shields.io/amo/v/textmarkerpro.svg)](https://addons.mozilla.org/firefox/addon/textmarkerpro/) 5 | 6 | Highly customizable text highlighter with different colors and save option for later visits. 7 | -------------------------------------------------------------------------------- /README_AMO.md: -------------------------------------------------------------------------------- 1 | # Textmarker - build instructions for AMO review team 2 | 3 | ## Summarized 4 | 5 | __Source code:__ 6 | The _src/_ folder contains the JavaScript and SASS source files. 7 | 8 | _webpack_ is used for precompiling and bundling. 9 | 10 | __Production code:__ 11 | The _extension/_ folder contains the addon code. 12 | 13 | ## Prerequisites 14 | 15 | * __node.js__ v16.15.1 16 | * __npm__ 8.11.0 17 | 18 | ## Installing modules 19 | 20 | __Run the following from console/terminal (root folder):__ 21 | for installing all required node modules (see: _package.json_) 22 | ``` 23 | npm install 24 | ``` 25 | 26 | ## Webpack 27 | __Run the following from console/terminal (root folder):__ 28 | for bundling the source code 29 | ``` 30 | npm run prod 31 | ``` 32 | -------------------------------------------------------------------------------- /extension/content/detail-view/detail-view.css: -------------------------------------------------------------------------------- 1 | html, body, object, iframe, 2 | div, h1, h2, h3, h4, h5, h6, p, 3 | span, a, abbr, img, 4 | em, small, sub, sup, 5 | dl, dt, dd, ol, ul, li, 6 | fieldset, form, label, 7 | table, caption, tbody, thead, tr, th, td { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | outline: 0; 12 | font-weight: inherit; 13 | font-style: inherit; 14 | font-family: inherit; 15 | line-height: normal; 16 | vertical-align: baseline; 17 | color: inherit; } 18 | 19 | :focus { 20 | outline: none; } 21 | 22 | ::-moz-focus-inner { 23 | border: 0; } 24 | 25 | ::-moz-color-swatch { 26 | border: 0; } 27 | 28 | * { 29 | box-sizing: border-box; } 30 | 31 | body { 32 | font-family: Verdana, Arial, sans-serif; 33 | color: #37414b; 34 | cursor: default; 35 | font-size: 15px; } 36 | 37 | body { 38 | background: #fbfbfb; } 39 | 40 | p { 41 | margin-bottom: 5px; } 42 | 43 | header { 44 | background: #37414b; 45 | padding: 10px 20px; } 46 | 47 | header p { 48 | color: #2ba5b7; } 49 | 50 | section { 51 | margin-bottom: 30px; } 52 | 53 | section:last-child { 54 | margin: 0; } 55 | 56 | h1 { 57 | color: #fbfbfb; 58 | font-size: 20px; } 59 | 60 | h2 { 61 | font-size: 17px; 62 | margin-bottom: 15px; } 63 | 64 | table { 65 | border-collapse: collapse; } 66 | .disabled table { 67 | display: none; } 68 | .folded table { 69 | display: none; } 70 | table td { 71 | padding: 2px 15px 2px 0; } 72 | table.table--bordered { 73 | width: 100%; } 74 | table.table--bordered col { 75 | width: 50%; } 76 | table.table--bordered td, table.table--bordered th { 77 | border: 1px solid #c8c8c8; } 78 | table.table--bordered th { 79 | padding: 5px 10px; 80 | text-align: left; } 81 | table.table--bordered td { 82 | padding: 10px; } 83 | .no-notes table col:first-child { 84 | width: 100%; } 85 | .no-notes table col:last-child { 86 | width: 0; } 87 | .no-notes table thead { 88 | display: none; } 89 | .no-notes table td:last-child { 90 | display: none; } 91 | .hide-notes table col:first-child { 92 | width: 100%; } 93 | .hide-notes table col:last-child { 94 | width: 0; } 95 | .hide-notes table th:last-child { 96 | text-indent: -1000px; 97 | overflow: hidden; } 98 | .hide-notes table td:last-child { 99 | border: 0; } 100 | .hide-notes table .mark-note { 101 | display: none; } 102 | 103 | .col-toggle { 104 | background-image: url(../../icons/double-arrow.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 center, 0 0; 107 | background-size: 18px, 100%; 108 | width: 18px; 109 | height: 20px; 110 | float: right; 111 | cursor: pointer; } 112 | .hide-notes .col-toggle { 113 | transform: rotate(180deg); } 114 | 115 | a, .link { 116 | color: #2ba5b7; 117 | text-decoration: none; 118 | cursor: pointer; } 119 | a:hover, .link:hover { 120 | text-decoration: underline; } 121 | 122 | #main { 123 | padding: 20px; } 124 | 125 | .table-toggle { 126 | background-image: url(../../icons/down.png); 127 | background-repeat: no-repeat; 128 | background-position: 0 center, 0 0; 129 | background-size: 14px, 100%; 130 | width: 14px; 131 | height: 12px; 132 | display: inline-block; 133 | cursor: pointer; 134 | opacity: 0.25; 135 | margin-right: 8px; } 136 | .folded .table-toggle { 137 | transform: rotate(-90deg); } 138 | .disabled .table-toggle { 139 | display: none; } 140 | 141 | .count { 142 | color: #c8c8c8; } 143 | -------------------------------------------------------------------------------- /extension/content/detail-view/detail-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Details 7 | 8 | 9 | 10 |
11 |

12 |

:

13 |
14 |
15 |
16 |

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
URL
26 |
27 |
28 |

29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /extension/content/options-ui/options.css: -------------------------------------------------------------------------------- 1 | button { 2 | border: 1px solid #c8c8c8; 3 | background-image: linear-gradient(transparent 50%, rgba(43, 165, 183, 0.1) 50%); 4 | background-repeat: no-repeat; 5 | background-color: transparent; 6 | color: #777; 7 | cursor: pointer; 8 | font-size: 0.8rem; 9 | font-family: inherit; 10 | padding: 3px 7px 5px; 11 | min-height: 22px; 12 | transition: background 0.5s; } 13 | button:hover { 14 | background-image: linear-gradient(rgba(43, 165, 183, 0.1) 50%, transparent 50%); 15 | color: #37414b; } 16 | 17 | @keyframes btn { 18 | from { 19 | color: #2ba5b7; } 20 | to { 21 | color: #fbfbfb; } } 22 | 23 | button { 24 | width: 120px; 25 | font-size: inherit; 26 | margin-bottom: 12px; 27 | background: #fff; } 28 | button:hover { 29 | background: #2ba5b7; 30 | animation: 1s forwards btn; } 31 | -------------------------------------------------------------------------------- /extension/content/options-ui/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textmarker 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /extension/content/tbb-menu/tbb-menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Textmarker Toolbar Menu 7 | 8 | 9 | 10 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /extension/icons/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/arrow-down.png -------------------------------------------------------------------------------- /extension/icons/bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/bin.png -------------------------------------------------------------------------------- /extension/icons/bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/bm.png -------------------------------------------------------------------------------- /extension/icons/bm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/bm2.png -------------------------------------------------------------------------------- /extension/icons/bm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/bm3.png -------------------------------------------------------------------------------- /extension/icons/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/book.png -------------------------------------------------------------------------------- /extension/icons/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/clock.png -------------------------------------------------------------------------------- /extension/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/copy.png -------------------------------------------------------------------------------- /extension/icons/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/data.png -------------------------------------------------------------------------------- /extension/icons/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/debug.png -------------------------------------------------------------------------------- /extension/icons/del-bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/del-bm.png -------------------------------------------------------------------------------- /extension/icons/dna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/dna.png -------------------------------------------------------------------------------- /extension/icons/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/donate.png -------------------------------------------------------------------------------- /extension/icons/double-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/double-arrow.png -------------------------------------------------------------------------------- /extension/icons/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/down.png -------------------------------------------------------------------------------- /extension/icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/download.png -------------------------------------------------------------------------------- /extension/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/edit.png -------------------------------------------------------------------------------- /extension/icons/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/email.png -------------------------------------------------------------------------------- /extension/icons/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/export.png -------------------------------------------------------------------------------- /extension/icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/folder.png -------------------------------------------------------------------------------- /extension/icons/forklift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/forklift.png -------------------------------------------------------------------------------- /extension/icons/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/gear.png -------------------------------------------------------------------------------- /extension/icons/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/ghost.png -------------------------------------------------------------------------------- /extension/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/github.png -------------------------------------------------------------------------------- /extension/icons/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/highlight.png -------------------------------------------------------------------------------- /extension/icons/m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/m.png -------------------------------------------------------------------------------- /extension/icons/menu-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/menu-blue.png -------------------------------------------------------------------------------- /extension/icons/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/note.png -------------------------------------------------------------------------------- /extension/icons/note2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/note2.png -------------------------------------------------------------------------------- /extension/icons/note4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/note4.png -------------------------------------------------------------------------------- /extension/icons/off16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/off16.png -------------------------------------------------------------------------------- /extension/icons/off18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/off18.png -------------------------------------------------------------------------------- /extension/icons/on16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/on16.png -------------------------------------------------------------------------------- /extension/icons/on18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/on18.png -------------------------------------------------------------------------------- /extension/icons/on32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/on32.png -------------------------------------------------------------------------------- /extension/icons/on36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/on36.png -------------------------------------------------------------------------------- /extension/icons/on64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/on64.png -------------------------------------------------------------------------------- /extension/icons/pageaction16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/pageaction16.png -------------------------------------------------------------------------------- /extension/icons/pageaction32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/pageaction32.png -------------------------------------------------------------------------------- /extension/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/pause.png -------------------------------------------------------------------------------- /extension/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/redo.png -------------------------------------------------------------------------------- /extension/icons/retry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/retry.png -------------------------------------------------------------------------------- /extension/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/save.png -------------------------------------------------------------------------------- /extension/icons/save2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/save2.png -------------------------------------------------------------------------------- /extension/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/search.png -------------------------------------------------------------------------------- /extension/icons/sel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/sel.jpg -------------------------------------------------------------------------------- /extension/icons/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/start.png -------------------------------------------------------------------------------- /extension/icons/submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/submit.png -------------------------------------------------------------------------------- /extension/icons/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/sync.png -------------------------------------------------------------------------------- /extension/icons/sync2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/sync2.png -------------------------------------------------------------------------------- /extension/icons/tm48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/tm48.png -------------------------------------------------------------------------------- /extension/icons/to-bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/to-bm.png -------------------------------------------------------------------------------- /extension/icons/toggle-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/toggle-notes.png -------------------------------------------------------------------------------- /extension/icons/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/tools.png -------------------------------------------------------------------------------- /extension/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/undo.png -------------------------------------------------------------------------------- /extension/icons/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/up.png -------------------------------------------------------------------------------- /extension/icons/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/view.png -------------------------------------------------------------------------------- /extension/icons/view2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/icons/view2.png -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Textmarker", 4 | "version": "5.3.3", 5 | 6 | "browser_specific_settings": { 7 | "gecko": { 8 | "id": "textMarker@underFlyingBirches.org", 9 | "strict_min_version": "57.0" 10 | } 11 | }, 12 | 13 | "default_locale": "en", 14 | 15 | "background": { 16 | "scripts": ["background/background.wp.js"] 17 | }, 18 | 19 | "permissions": [ 20 | "", 21 | "storage", 22 | "activeTab", 23 | "tabs", 24 | "notifications", 25 | "menus", 26 | "clipboardWrite", 27 | "webNavigation" 28 | ], 29 | 30 | "optional_permissions": [ 31 | "webNavigation" 32 | ], 33 | 34 | "icons": { 35 | "16": "icons/on16.png", 36 | "32": "icons/on32.png" 37 | }, 38 | 39 | "browser_action": { 40 | "default_title": "Textmarker", 41 | "default_icon": { 42 | "16": "icons/on16.png", 43 | "18": "icons/on18.png", 44 | "32": "icons/on32.png" 45 | }, 46 | "default_popup": "content/tbb-menu/tbb-menu.html", 47 | "browser_style": true 48 | }, 49 | 50 | "page_action": { 51 | "browser_style": true, 52 | "default_icon": { 53 | "16": "icons/pageaction16.png", 54 | "32": "icons/pageaction32.png" 55 | } 56 | }, 57 | 58 | "sidebar_action": { 59 | "default_title": "Textmarker", 60 | "default_panel": "content/sidebar/sidebar.html", 61 | "default_icon": { 62 | "16": "icons/on16.png", 63 | "18": "icons/on18.png", 64 | "32": "icons/on32.png" 65 | } 66 | }, 67 | 68 | "options_ui": { 69 | "page": "content/options-ui/options.html", 70 | "browser_style": true 71 | }, 72 | 73 | "commands": { 74 | "_execute_sidebar_action": { 75 | "suggested_key": { 76 | "default": "Ctrl+Alt+T", 77 | "mac": "MacCtrl+Shift+T" 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /extension/web-ext-artifacts/textmarker-5.3.3.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/extension/web-ext-artifacts/textmarker-5.3.3.zip -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textmarker", 3 | "author": "underflyingbirches", 4 | "description": "webextensions-addon-textmarker", 5 | "main": "src/main.js", 6 | "devDependencies": { 7 | "@babel/core": "^7.20.5", 8 | "@babel/preset-env": "^7.20.2", 9 | "babel-loader": "^9.1.0", 10 | "cross-env": "^5.2.1", 11 | "css-loader": "^6.7.3", 12 | "file-loader": "^6.2.0", 13 | "mini-css-extract-plugin": "^2.7.2", 14 | "sass": "^1.56.2", 15 | "sass-loader": "^13.2.0", 16 | "style-loader": "^3.3.1", 17 | "web-ext": "^7.4.0", 18 | "webpack": "^5.75.0", 19 | "webpack-cli": "^5.0.0" 20 | }, 21 | "scripts": { 22 | "__webpack": "webpack --progress --config=webpack.config.js --hide-modules --display=minimal", 23 | "dev": "cross-env NODE_ENV=DEV npm run __webpack", 24 | "prod": "cross-env NODE_ENV=PROD npm run __webpack" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/background/modules/context-menu.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | 4 | export default function() { 5 | return new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'toggled:addon': 'toggle', 9 | 'updated:ctm-settings': 'update' 10 | } 11 | }, 12 | 13 | items: { 14 | m: { contexts: ['selection'] }, 15 | w: { contexts: ['selection'] }, 16 | d: { contexts: ['frame', 'link', 'page'] }, 17 | b: { contexts: ['frame', 'link', 'page'] }, 18 | '-b': { contexts: ['frame', 'link', 'page'] }, 19 | n: { contexts: ['frame', 'link', 'page'] }, 20 | sb: { contexts: ['all', 'tab'] }, 21 | c: { contexts: ['frame', 'link', 'page'] } 22 | }, 23 | created: [], 24 | 25 | autoinit() { 26 | let item; 27 | for (let i in this.items) { 28 | item = this.items[i]; 29 | item.id = i; 30 | item.title = browser.i18n.getMessage('ctx_' + (i === '-b' ? 'db' : i)); 31 | item.onclick = (infos, tab) => this.onClick(infos, tab); 32 | } 33 | this.update(); 34 | }, 35 | 36 | create(id) { 37 | if (!this.created.includes(id)) { 38 | browser.menus.create(this.items[id]); 39 | this.created.push(id); 40 | } 41 | }, 42 | remove(id) { 43 | if (this.created.includes(id)) { 44 | browser.menus.remove(id); 45 | this.created.splice(this.created.indexOf(id), 1); 46 | } 47 | }, 48 | removeAll() { 49 | if (this.created.length) { 50 | browser.menus.removeAll(); 51 | this.created = []; 52 | } 53 | }, 54 | toggle(on) { 55 | if (on) this.update(); 56 | else this.removeAll(); 57 | }, 58 | update() { 59 | const created = this.created; 60 | _STORAGE.get('shortcuts').then(shortcuts => { 61 | for (let i in this.items) { 62 | if (shortcuts[i][2]) this.create(i); 63 | else this.remove(i); 64 | } 65 | }); 66 | }, 67 | onClick(infos, tab) { 68 | const id = infos.menuItemId; 69 | 70 | if (id === 'w') this.emit('lookup:word', infos.selectionText); 71 | else if (id === 'sb') browser.sidebarAction.open(); 72 | else if (id === 'c') { 73 | browser.permissions.contains({ permissions: ['clipboardWrite'] }).then(granted => { 74 | this.emit('ctx:' + id, granted, null, { tab: tab.id }); 75 | }); 76 | } 77 | else this.emit('ctx:' + id, null, null, { tab: tab.id }); 78 | } 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /src/background/modules/error-logging.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | import _LOG_KEYS from './../../data/log-keys' 4 | 5 | new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'error': 'log', 9 | 'warning': 'log', 10 | 'failed:save-entry': 'log', 11 | 'failed:update-entry': 'log', 12 | 'failed:delete-entry': 'log', 13 | 'failed:restoration': 'onFailedRestoration', 14 | 'warn:mixed-entry-types': 'onMixedEntryTypes', 15 | 'warn:multiple-unlocked-entries': 'onMultipleUnlockedEntries', 16 | 'failed:pbm': 'onFailedPBM', 17 | 'failed:open-tab': 'onOpenTabFailure', 18 | 'error:import': 'log', 19 | 'error:browser-console': 'log', 20 | 'clear:logs': 'clear', 21 | 'failed:restore-range': 'log', 22 | 'failed:inject-content-script': 'onScriptInjectionFailure', 23 | 'failed:inject-stylesheet': 'onCSSInjectionFailure', 24 | 'missing-permission:webNavigation': 'onMissingWebNavigationPermission' 25 | } 26 | }, 27 | 28 | log(error, info) { 29 | let log, msg; 30 | if (error.time) { 31 | log = [error.time, error.message + ' [' + error.location + ']']; 32 | } else { 33 | log = [(new Date().getTime()), _LOG_KEYS[error] || error]; 34 | if (info) { 35 | if (info.report && typeof info.report === 'string') { 36 | log.push(info.report); 37 | if (info.attempt) log.push(info.attempt); 38 | if (info.url) log.push(info.url); 39 | } 40 | else if (typeof info === 'string') { 41 | log.push(info); 42 | } 43 | } 44 | } 45 | _STORAGE.set('log', log).then(() => this.emit('updated:logs logged:error', log)); 46 | }, 47 | clear() { 48 | _STORAGE.set('log', { clear: true }).then(() => this.emit('updated:logs')); 49 | }, 50 | onMixedEntryTypes() { 51 | this.log('note_restoration_warning_1'); 52 | }, 53 | onMultipleUnlockedEntries() { 54 | this.log('note_restoration_warning_2'); 55 | }, 56 | onFailedRestoration(info) { 57 | this.log('note_restoration_failure', info); 58 | }, 59 | onOpenTabFailure() { 60 | this.log('note_url'); 61 | }, 62 | onFailedPBM() { 63 | this.log('note_pbm'); 64 | }, 65 | onScriptInjectionFailure(err) { 66 | this.log('js_injection_failure', err); 67 | }, 68 | onCSSInjectionFailure() { 69 | this.log('css_injection_failure'); 70 | }, 71 | onMissingWebNavigationPermission() { 72 | this.log('missing_permission_wn'); 73 | }, 74 | }); 75 | -------------------------------------------------------------------------------- /src/background/modules/namer.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | import _GLOBAL_SETTINGS from './../../data/global-settings' 4 | 5 | export default function() { 6 | return new _MODULE({ 7 | events: { 8 | ENV: { 9 | 'granted:save-entry': 'name', 10 | 'rename:entry': 'rename' 11 | } 12 | }, 13 | 14 | name(entry) { 15 | if (entry.name) return this.adjustName(entry.name, entry); 16 | 17 | _STORAGE.get('naming').then(naming => this.adjustName(null, entry, naming)) 18 | .catch(() => this.emit('error', 'error_naming')); 19 | }, 20 | rename(oldName, newName, area) { 21 | newName = newName.substring(0, _GLOBAL_SETTINGS.MAX_ENTRY_NAME_CHARS - 1); 22 | 23 | _STORAGE.get('history').then(history => { 24 | let counter = this.getDoubleNameCount(history, newName); 25 | if (counter) newName += ' (' + (counter + 1) + ')'; 26 | this.emit('renamed:entry', oldName, newName, area); 27 | }) 28 | .catch(() => this.emit('error', 'error_naming')); 29 | }, 30 | adjustName(name, entry, method) { 31 | name = name ? name : 32 | method === 'title' ? entry.title.trim() ? entry.title.trim() : entry.url : 33 | method === 'date' ? (new Date(entry.first).toLocaleString()) : ''; 34 | 35 | name = name.substring(0, _GLOBAL_SETTINGS.MAX_ENTRY_NAME_CHARS - 1); 36 | 37 | _STORAGE.get('history').then(history => { 38 | let counter = this.getDoubleNameCount(history, name); 39 | 40 | if (counter && entry.locked) { 41 | this.emit('failed:save-entry-double-locked', 'error_double_locked_name', name); 42 | } else { 43 | if (counter) name += ' (' + (counter + 1) + ')'; 44 | entry.name = name; 45 | this.emit('named:entry', entry); 46 | } 47 | }) 48 | .catch(() => this.emit('error', 'error_naming')); 49 | }, 50 | getDoubleNameCount(history, name) { 51 | let existingNames = Object.keys(history.entries), 52 | l = existingNames.length, 53 | counter = 0, 54 | checkpoint; 55 | 56 | while (l--) { 57 | checkpoint = existingNames[l]; 58 | if (this.isDoubleName(name, checkpoint)) counter++; 59 | } 60 | return counter; 61 | }, 62 | isDoubleName(name, checkpoint) { 63 | if (name === checkpoint) return true; 64 | 65 | let l = name.length, 66 | checkpoint_start = checkpoint.substring(0, l), 67 | checkpoint_end; 68 | 69 | if (name !== checkpoint_start) return false; 70 | 71 | checkpoint_end = checkpoint.substring(l, checkpoint.length); 72 | 73 | if (/^\s*\(\d+\)$/.test(checkpoint_end)) return true; 74 | 75 | return false; 76 | } 77 | }); 78 | } 79 | -------------------------------------------------------------------------------- /src/background/modules/page-action.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | 4 | export default function() { 5 | return new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'dom:loaded': 'show', 9 | 'updated:autocs-settings': 'update' 10 | } 11 | }, 12 | active: false, 13 | 14 | autoinit() { 15 | this.update(); 16 | browser.pageAction.onClicked.addListener(tab => { 17 | this.emit('clicked:page-action', tab.id, tab.url); 18 | browser.pageAction.hide(tab.id); 19 | }); 20 | }, 21 | 22 | show(infos) { 23 | if (this.active) { 24 | browser.pageAction.show(infos.tabId); 25 | } 26 | }, 27 | 28 | update() { 29 | _STORAGE.get('settings').then(settings => { 30 | this.active = !settings || settings.addon.autocs ? false : true; 31 | }); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/background/modules/sidebars.js: -------------------------------------------------------------------------------- 1 | import { _MODULE, _GET_ACTIVE_TAB, _HASHLESS } from './../utils' 2 | 3 | export default function() { 4 | return new _MODULE({ 5 | events: { 6 | ENV: { 7 | 'activated:tab': 'setPanel', 8 | 'changed:url': 'setPanel', 9 | 'entry:found': 'storeEntry', 10 | 'saved:entry': 'storeEntry', 11 | 'updated:entry': 'updateEntry', 12 | 'deleted:entry': 'removeEntry', 13 | 'opened:sidebar':'sendEntry', 14 | 'visually-ordered:marks': 'sendOrderedMarks' 15 | } 16 | }, 17 | 18 | entries: {}, 19 | 20 | setPanel(tabId, tabInfos) { 21 | this.isOpen().then(open => { 22 | if (open) { 23 | const url = tabInfos.url || 'blank'; 24 | browser.sidebarAction.setPanel({ 25 | panel: browser.runtime.getURL(`content/sidebar/sidebar.html#${tabId}_${url}`), 26 | tabId 27 | }); 28 | } 29 | }); 30 | }, 31 | isOpen() { 32 | return browser.sidebarAction.isOpen({}); 33 | }, 34 | storeEntry(entry) { 35 | const ignoreHash = Array.isArray(entry) ? !entry[0].hashSensitive : !entry.hashSensitive; 36 | const entries = this.entries; 37 | 38 | _GET_ACTIVE_TAB().then(tab => { 39 | const id = tab.id; 40 | const url = ignoreHash ? _HASHLESS(tab.url) : tab.url; 41 | 42 | entries[id] = entries[id] || []; 43 | entries[id][url] = entry; 44 | }); 45 | }, 46 | updateEntry(entry) { 47 | const ignoreHash = !entry.hashSensitive; 48 | const entries = this.entries; 49 | const entryUrl = ignoreHash ? _HASHLESS(entry.url) : entry.url; 50 | 51 | for (let id in entries) { 52 | for (let url in entries[id]) { 53 | if (url === entryUrl) { 54 | entries[id][url] = entry; 55 | } 56 | } 57 | } 58 | _GET_ACTIVE_TAB().then(tab => { 59 | const tabUrl = ignoreHash ? _HASHLESS(tab.url) : tab.url; 60 | 61 | if (tabUrl === entryUrl) { 62 | this.emit('entry:found-for-tab', entry); 63 | } 64 | }); 65 | }, 66 | removeEntry(name, url, hashSensitive) { 67 | const entries = this.entries; 68 | const entryUrl = hashSensitive ? url : _HASHLESS(url); 69 | 70 | for (let id in entries) { 71 | for (let savedUrl in entries[id]) { 72 | if (savedUrl === entryUrl) { 73 | delete entries[id][savedUrl]; 74 | } 75 | } 76 | } 77 | _GET_ACTIVE_TAB().then(tab => { 78 | const tabUrl = hashSensitive ? tab.url : _HASHLESS(tab.url); 79 | if (tabUrl === entryUrl) { 80 | this.emit('entry:deleted-for-tab'); 81 | } 82 | }); 83 | }, 84 | sendEntry() { 85 | _GET_ACTIVE_TAB().then(tab => { 86 | const hashlessUrl = _HASHLESS(tab.url); 87 | const entriesForThisTab = this.entries[tab.id]; 88 | let entry = null; 89 | if (entriesForThisTab) { 90 | entry = entriesForThisTab[tab.url] || entriesForThisTab[hashlessUrl]; 91 | } 92 | this.emit('entry:found-for-tab', entry); 93 | }); 94 | }, 95 | sendOrderedMarks(marks) { 96 | this.emit('entry:ordered-marks', marks); 97 | } 98 | }); 99 | } 100 | -------------------------------------------------------------------------------- /src/background/modules/tabs.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | 4 | export default function() { 5 | return new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'toggled:addon': 'toggleTabEventHandlers', 9 | 'started:app': 'openInitPage', 10 | 'open:addon-page(sb)': 'openAddonPage', 11 | 'open:addon-page(tbb)': 'openAddonPage', 12 | 'open:addon-page(am)': 'openAddonPage', 13 | 'lookup:word': 'openSearch', 14 | 'open:entries': 'open' 15 | } 16 | }, 17 | urls: { 18 | news: 'content/addon-page/addon-page.html#page=news', 19 | settings: 'content/addon-page/addon-page.html#page=settings', 20 | history: 'content/addon-page/addon-page.html#page=history', 21 | info: 'content/addon-page/addon-page.html#page=new', 22 | help: 'content/addon-page/addon-page.html#page=manual', 23 | contact: 'content/addon-page/addon-page.html#page=contact', 24 | troubleshooting: 'content/addon-page/addon-page.html#page=troubleshooting', 25 | logs: 'content/addon-page/addon-page.html#page=logs', 26 | export: 'content/addon-page/addon-page.html#page=export', 27 | sync: 'content/addon-page/addon-page.html#page=sync' 28 | }, 29 | tabEventHandlers: { 30 | onActivated: null, 31 | onUpdated: null 32 | }, 33 | 34 | autoinit() { 35 | _STORAGE.get('mode').then(mode => this.toggleTabEventHandlers(mode)); 36 | }, 37 | 38 | addTabEventHandlers() { 39 | if (!this.tabEventHandlers.onActivated) { 40 | const onActivated = this.tabEventHandlers.onActivated = this.onActivated.bind(this); 41 | browser.tabs.onActivated.addListener(onActivated); 42 | } 43 | 44 | if (!this.tabEventHandlers.onUpdated) { 45 | const onUpdated = this.tabEventHandlers.onUpdated = this.onUpdated.bind(this); 46 | browser.tabs.onUpdated.addListener(onUpdated/*, { properties: ['status'] }*/); // ESR throws wrong argument type error when using filters 47 | } 48 | }, 49 | removeTabEventHandlers() { 50 | ['onActivated', 'onUpdated'].forEach(ev => { 51 | if (this.tabEventHandlers[ev]) { 52 | browser.tabs[ev].removeListener(this.tabEventHandlers[ev]); 53 | } 54 | }); 55 | this.tabEventHandlers = {}; 56 | }, 57 | toggleTabEventHandlers(on) { 58 | if (on) this.addTabEventHandlers(); 59 | else this.removeTabEventHandlers(); 60 | }, 61 | onActivated(tab) { 62 | this.emit('activated:tab', tab.tabId, (tab.url || '')); 63 | }, 64 | onUpdated(tabId, changed, tab) { 65 | if (changed.url) { 66 | this.emit('changed:url', tabId, changed, tab); 67 | } 68 | }, 69 | 70 | open(urls, names) { 71 | urls = typeof urls === 'string' ? [urls] : urls; 72 | names = typeof names === 'string' ? [names] : names; 73 | let l = urls.length, 74 | securityWarning = false, 75 | url; 76 | while (l--) { 77 | (function(self, l) { 78 | url = urls[l]; 79 | browser.tabs.create({ url: urls[l] }) 80 | .catch(() => { 81 | if (!securityWarning) self.emit('failed:open-tab'); 82 | securityWarning = true; 83 | }) 84 | .then(() => { 85 | if (names) self.emit('opened:entry', { url: url, name: names[l] }); 86 | }); 87 | })(this, l); 88 | } 89 | }, 90 | openAddonPage(id) { 91 | this.open(this.urls[id]); 92 | }, 93 | openInitPage(version, loadReason) { 94 | if (loadReason) { 95 | if (loadReason === 'install' || loadReason === 'update') this.openAddonPage('help'); 96 | //else if (loadReason === 'update') this.openAddonPage('news'); 97 | } 98 | }, 99 | openSearch(word) { 100 | _STORAGE.get('settings').then(settings => { 101 | let custom = settings.misc.customSearch, url; 102 | if (custom) url = 'https://' + custom[0] + word + custom[1]; 103 | else url = 'https://' + browser.i18n.getMessage('lng') + '.wikipedia.org/wiki/' + word; 104 | this.open(url); 105 | }); 106 | } 107 | }); 108 | } 109 | -------------------------------------------------------------------------------- /src/background/modules/web-navigation.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './../utils' 2 | 3 | export default function() { 4 | return new _MODULE({ 5 | events: { 6 | ENV: { 7 | 'granted-permission:webNavigation': 'addListener' 8 | } 9 | }, 10 | autoinit() { 11 | const permission = { permissions: ['webNavigation'] }; 12 | 13 | browser.permissions.contains(permission).then(granted => { 14 | if (granted) { 15 | this.addListener(); 16 | } else { 17 | this.emit('missing-permission:webNavigation'); 18 | } 19 | }); 20 | }, 21 | addListener() { 22 | browser.webNavigation.onDOMContentLoaded.addListener(infos => this.emit('dom:loaded', infos)); 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/background/modules/windows.js: -------------------------------------------------------------------------------- 1 | import _STORAGE from './../storage' 2 | import { _MODULE } from './../utils' 3 | 4 | export default function() { 5 | return new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'view:entry': 'openEntryDetailPage' 9 | } 10 | }, 11 | 12 | openEntryDetailPage(name) { 13 | const popupURL = browser.runtime.getURL('content/detail-view/detail-view.html'); 14 | 15 | browser.windows.getCurrent().then(currentWindow => { 16 | 17 | browser.windows.create({ 18 | url: popupURL + '#' + encodeURIComponent(name), 19 | type: 'panel', 20 | height: currentWindow.height - 22, 21 | width: Math.min(currentWindow.width, 980), 22 | incognito: currentWindow.incognito 23 | }); 24 | }); 25 | } 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/background/port.js: -------------------------------------------------------------------------------- 1 | import { _BGPORT } from './utils' 2 | 3 | new _BGPORT({ 4 | name: 'background', 5 | type: 'background', 6 | postponeConnection: true, 7 | events: { 8 | ONEOFF: [ 9 | 'started:app', 10 | 'toggled:addon', 11 | 'toggled:sync', 12 | 'updated:settings', 13 | 'updated:history', 14 | 'updated:history-on-restoration', 15 | 'updated:entry-sync', 16 | 'updated:entry-name', 17 | 'updated:logs', 18 | 'updated:ctm-settings', 19 | 'updated:misc-settings', 20 | 'updated:naming-settings', 21 | 'updated:bg-color-settings', 22 | 'updated:custom-search-settings', 23 | 'updated:saveopt-settings', 24 | 'updated:mark-method-settings', 25 | 'updated:marker-settings', 26 | 'entries:found', 27 | 'saved:entry', 28 | 'deleted:entry', 29 | 'deleted:entries', 30 | 'imported:settings', 31 | 'imported:history', 32 | 'ctx:m', 33 | 'ctx:d', 34 | 'ctx:b', 35 | 'ctx:-b', 36 | 'ctx:n', 37 | 'ctx:c', 38 | 'sidebar:highlight', 39 | 'sidebar:delete-highlight', 40 | 'sidebar:bookmark', 41 | 'sidebar:delete-bookmark', 42 | 'sidebar:note', 43 | 'sidebar:immut', 44 | 'sidebar:save-changes', 45 | 'sidebar:undo', 46 | 'sidebar:redo', 47 | 'sidebar:copy', 48 | 'sidebar:scroll-to-bookmark', 49 | 'sidebar:toggle-notes', 50 | 'sidebar:next-mark', 51 | 'sidebar:retry-restoration', 52 | 'sidebar:selected-marker', 53 | 'opened:sidebar', 54 | 'changed:url', 55 | 'injected?' 56 | ], 57 | CONNECTION: [ 58 | 'started:app', 59 | 'toggled:addon', 60 | 'toggled:sync', 61 | 'updated:settings', 62 | 'updated:entry-on-save', 63 | 'saved:entry', 64 | 'updated:pagenotes', 65 | 'changed:selection', 66 | 'unsaved-changes', 67 | 'clicked:mark', 68 | 'added:bookmark', 69 | 'removed:bookmark', 70 | 'added:note', 71 | 'removed:last-note', 72 | 'page-state', 73 | 'notes-state', 74 | 'entry:found', 75 | 'entry:found-for-tab', 76 | 'entry:deleted-for-tab', 77 | 'entry:ordered-marks' 78 | ] 79 | } 80 | }); 81 | -------------------------------------------------------------------------------- /src/background/utils.js: -------------------------------------------------------------------------------- 1 | import { _COPY } from './../utils/copy' 2 | import _EXTEND from './../utils/extend' 3 | import { _GET_ACTIVE_TAB } from './../utils/getActiveTab' 4 | import { _MODULE } from './../utils/module' 5 | import { _STORE } from './../utils/store' 6 | import { _BGPORT } from './../utils/port' 7 | import { _HASHLESS } from './../utils/hashless' 8 | import _ERRORTRACKER from './../utils/error-tracker' 9 | 10 | export { _COPY, _EXTEND, _GET_ACTIVE_TAB, _MODULE, _STORE, _BGPORT, _ERRORTRACKER, _HASHLESS } 11 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_buttons.scss: -------------------------------------------------------------------------------- 1 | %button { 2 | border: 1px solid $color-border; 3 | background-image: $gradient--button; 4 | background-repeat: no-repeat; 5 | background-color: transparent; 6 | color: $color-text--grey; 7 | cursor: pointer; 8 | font-size: $font-size--s3; 9 | font-family: inherit; 10 | padding: 3px 7px 5px; 11 | min-height: 22px; 12 | transition: background 0.5s; 13 | 14 | &:hover { 15 | background-image: $gradient--button-hover; 16 | color: $color-text--dark; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_forms.scss: -------------------------------------------------------------------------------- 1 | ::-moz-placeholder, 2 | :placeholder-shown { 3 | color: $color-text--disabled; 4 | } 5 | select, input { 6 | font-family: inherit !important; 7 | } 8 | label { 9 | width: 224px; 10 | display: inline-block; 11 | } 12 | label span { 13 | display: inline-block; 14 | line-height: 1.2; 15 | height: 22px; 16 | width: 22px; 17 | text-align: center; 18 | color: transparent; 19 | background-color: transparent; 20 | text-indent: -1000px; 21 | overflow: hidden; 22 | border: 1px solid $color-border; 23 | border-radius: 2px; 24 | vertical-align: -5px; 25 | margin-right: 8px; 26 | 27 | &:active { 28 | background-color: $color-bg--turquoise; 29 | color: $color-text--light !important; 30 | } 31 | } 32 | select { 33 | @include icon('arrow-down', right 7px); 34 | background-color: transparent; 35 | -moz-appearance: none; 36 | border: 1px solid $color-border; 37 | height: 22px; 38 | min-width: 34px; 39 | padding-right: 20px; 40 | } 41 | select.small { 42 | width: 68px; 43 | } 44 | select.big { 45 | width: 140px; 46 | } 47 | .cb-sub-select { 48 | margin: 10px 0 0 35px; 49 | } 50 | input:disabled { 51 | opacity: 0.25; 52 | } 53 | input[type="search"] { 54 | float: right; 55 | border: 1px solid $color-border--light; 56 | color: inherit; 57 | text-align: left; 58 | padding: 0 0 0 5px; 59 | height: 27px; 60 | } 61 | input[type="color"] { 62 | padding: 0; 63 | } 64 | input[type="color"] { 65 | vertical-align: middle; 66 | width: 36px; 67 | text-align: center; 68 | } 69 | input[type="text"] { 70 | border: 1px solid $color-border; 71 | color: inherit; 72 | padding: 0; 73 | height: 22px; 74 | width: 190px; 75 | padding: 0 4px; 76 | 77 | @include mq('mobile') { 78 | width: 126px; 79 | } 80 | } 81 | input[type="file"] { 82 | display: none; 83 | } 84 | /* custom inputs */ 85 | input[type="checkbox"], 86 | input[type="radio"] { 87 | display: none; 88 | } 89 | input[type="radio"] + label span { 90 | border-radius: 50%; 91 | } 92 | label.fake-cb, 93 | label.fake-rb { 94 | width: auto; 95 | } 96 | label sup { 97 | font-size: inherit; 98 | vertical-align: 0; 99 | width: calc(100% - 40px); 100 | } 101 | label.fake-cb:not(.pull-right) sup, 102 | label.fake-rb:not(.pull-right) sup { 103 | display: inline-block; 104 | vertical-align: middle; 105 | } 106 | label.pull-right span { 107 | margin: 0 0 0 8px; 108 | } 109 | label.fake-cb + label { 110 | width: 188px; 111 | } 112 | input[type="checkbox"]:checked + label span, 113 | input[type="radio"]:checked + label span { 114 | text-indent: 0; 115 | color: $color-text--turquoise; 116 | } 117 | input[type="number"] { 118 | width: 45px; 119 | border: 1px solid $color-border; 120 | -moz-appearance: none; 121 | height: 22px; 122 | padding-left: 5px; 123 | -moz-user-select: none; 124 | } 125 | 126 | label span, 127 | input[type="number"], 128 | select { 129 | transition: color .3s ease-in; 130 | 131 | &:hover { 132 | border-color: $color-grey--medium; 133 | background-color: #fff; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_general.scss: -------------------------------------------------------------------------------- 1 | :focus { 2 | outline: none; 3 | } 4 | ::-moz-focus-inner { 5 | border: 0; 6 | } 7 | ::-moz-color-swatch { 8 | border: 0; 9 | } 10 | * { 11 | box-sizing: border-box; 12 | } 13 | body { 14 | font-family: Verdana, Arial, sans-serif; 15 | color: $color-text--dark; 16 | cursor: default; 17 | font-size: $font-size--base; 18 | } 19 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_icons.scss: -------------------------------------------------------------------------------- 1 | @mixin button_icon($name, $gradient: true, $important: '') { 2 | @if ($gradient) { 3 | background-image: url(../../../icons/#{$name}.png), $gradient--button; 4 | &:hover { 5 | background-image: url(../../../icons/#{$name}.png), $gradient--button-hover; 6 | } 7 | } @else { 8 | background-image: url(../../../icons/#{$name}.png) #{$important}; 9 | } 10 | } 11 | 12 | @mixin icon($name: '', $position: 0 0, $size: unset, $repeat: no-repeat, $gradient: false, $important: '') { 13 | @if ($name) { 14 | @include button_icon($name, $gradient, $important); 15 | } 16 | background-repeat: $repeat; 17 | background-position: $position, 0 0; 18 | background-size: $size, 100%; 19 | } 20 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_infos.scss: -------------------------------------------------------------------------------- 1 | .i { 2 | cursor: pointer; 3 | color: $color-text--turquoise; 4 | text-decoration: underline; 5 | border-radius: 50%; 6 | display: inline-block; 7 | line-height: 1; 8 | width: 15px; 9 | height: 15px; 10 | text-align: center; 11 | font-size: 11px; 12 | padding: 2px; 13 | font-family: Georgia, serif; 14 | margin-top: 3px; 15 | 16 | &:hover { 17 | text-decoration: none; 18 | color: #fff; 19 | background: $color-bg--turquoise; 20 | border: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_links.scss: -------------------------------------------------------------------------------- 1 | @mixin links($color: $color-text--turquoise) { 2 | a, .link { 3 | color: $color; 4 | text-decoration: none; 5 | cursor: pointer; 6 | 7 | &:hover { 8 | text-decoration: underline; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_notes.scss: -------------------------------------------------------------------------------- 1 | $colors: 2 | (green, #dfd, #efe, #cfc), 3 | (white, #eee, #fff, #eee), 4 | (yellow, #ffd, #ffe, #ffc), 5 | (orange, #fec, #fed, #feb), 6 | (red, #fdd, #fee, #fcc), 7 | (purple, #edf, #eef, #ecf), 8 | (blue, #cef, #def, #bef), 9 | (turquoise, #c1e9f2, #d5f0f7, #b9e4ec); 10 | 11 | @each $name, $val, $val2, $val3 in $colors { 12 | .tmnote--#{$name} { 13 | tmnotepalette, 14 | tmnoteheader { 15 | background: $val3; 16 | } 17 | tmnotecustomize, 18 | tmnoteminimize, 19 | tmnotedelete { 20 | background: $val2; 21 | &:hover { 22 | background: $val; 23 | } 24 | } 25 | textarea { 26 | background-image: linear-gradient($val2, $val) !important; 27 | } 28 | } 29 | .tmnotecolor--#{$name} { 30 | border-color: $val; 31 | background-image: linear-gradient($val2, $val); 32 | } 33 | } 34 | 35 | tmnote { 36 | font-family: Verdana, sans-serif; 37 | font-size: 12px; 38 | line-height: 1.4; 39 | } 40 | tmnoteheader { 41 | height: 22px; 42 | position: relative; 43 | z-index: 1; 44 | display: block; 45 | } 46 | tmnotepalette { 47 | z-index: 1; 48 | width: 100%; 49 | text-align: center; 50 | border-bottom: 1px solid $color-border--light; 51 | } 52 | tmnotecolor { 53 | position: relative; 54 | display: inline-block; 55 | border-top: 2px solid; 56 | cursor: pointer; 57 | box-shadow: 0 1px 3px $color-shadow--lighter; 58 | &:hover { 59 | top: -1px; 60 | box-shadow: 0 1px 3px $color-shadow--light; 61 | filter: brightness(1.02); 62 | } 63 | } 64 | tmnotecustomize, 65 | tmnoteminimize, 66 | tmnotedelete { 67 | font-size: 14px; 68 | cursor: pointer; 69 | color: $color-text--disabled; 70 | line-height: 1; 71 | height: 20px; 72 | 73 | &:hover { 74 | color: $color-text--dark; 75 | } 76 | } 77 | tmnotedelete { 78 | font-size: 10px; 79 | font-weight: bold; 80 | } 81 | tmnoteminimize { 82 | font-size: 18px; 83 | } 84 | tmnotecustomize { 85 | font-size: 16px; 86 | } 87 | .__note_textarea { 88 | display: block !important; 89 | position: relative !important; 90 | font-family: Verdana, sans-serif !important; 91 | font-size: 12px !important; 92 | min-height: 100px !important; 93 | border: 0 !important; 94 | } 95 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_reset.scss: -------------------------------------------------------------------------------- 1 | html, body, object, iframe, 2 | div, h1, h2, h3, h4, h5, h6, p, 3 | span, a, abbr, img, 4 | em, small, sub, sup, 5 | dl, dt, dd, ol, ul, li, 6 | fieldset, form, label, 7 | table, caption, tbody, thead, tr, th, td { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | outline: 0; 12 | font-weight: inherit; 13 | font-style: inherit; 14 | font-family: inherit; 15 | line-height: normal; 16 | vertical-align: baseline; 17 | color: inherit; 18 | } 19 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_shadows.scss: -------------------------------------------------------------------------------- 1 | @mixin shadow-switch($color) { 2 | box-shadow: 0 0 2px $color inset; 3 | } 4 | 5 | %shadow-switch { 6 | @include shadow-switch($color-shadow--light); 7 | } 8 | %shadow-switch--hover { 9 | @include shadow-switch($color-shadow--dark); 10 | } 11 | %shadow-switch--active { 12 | @include shadow-switch($color-shadow--medium); 13 | } 14 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_switches.scss: -------------------------------------------------------------------------------- 1 | .switch { 2 | @extend %shadow-switch; 3 | position: relative; 4 | width: 44px; 5 | height: 22px; 6 | padding: 2px; 7 | cursor: pointer; 8 | 9 | &:not(.switch__indicator):not(.switch--immut) { 10 | &:before { 11 | content: 'O'; 12 | position: absolute; 13 | font-weight: bold; 14 | top: 3px; 15 | font-size: 14px; 16 | left: 27px; 17 | color: $color-grey--lighter; 18 | font-family: Arial; 19 | } 20 | 21 | &.active { 22 | &:before { 23 | content: 'I'; 24 | left: 8px; 25 | } 26 | } 27 | } 28 | 29 | &--sync { 30 | margin-right: 10px; 31 | } 32 | &--save { 33 | .autosave { 34 | display: none; 35 | } 36 | } 37 | &:hover { 38 | @extend %shadow-switch--active; 39 | 40 | &.active { 41 | @extend %shadow-switch--hover; 42 | } 43 | } 44 | &.active { 45 | @extend %shadow-switch--active; 46 | } 47 | span { 48 | padding: 0 4px; 49 | } 50 | } 51 | 52 | .switch__indicator { 53 | width: 18px; 54 | height: 18px; 55 | background-color: $color-grey--light; 56 | display: inline-block; 57 | margin-left: 0; 58 | text-align: center; 59 | transition: margin 0.1s; 60 | 61 | .switch--sync & { 62 | @include icon('sync', center, 67%); 63 | } 64 | .switch--save & { 65 | @include icon('save2', center, 67%); 66 | } 67 | .switch--immut & { 68 | &:before { 69 | content: '\221E'; 70 | display: block; 71 | font-size: 16px; 72 | position: relative; 73 | top: -5px; 74 | color: #fff; 75 | } 76 | } 77 | .switch.active & { 78 | background-color: transparentize($color-bg--turquoise, .25); 79 | margin-left: 22px; 80 | } 81 | .switch.active:hover & { 82 | background-color: $color-bg--turquoise; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/content/_shared/sass/_vars.scss: -------------------------------------------------------------------------------- 1 | // colors 2 | $color-turquoise: #2ba5b7; 3 | $color-grey--darker: #37414b; 4 | $color-grey--dark-ff: #38383d; 5 | $color-grey--dark: #555; 6 | $color-grey--medium: #777; 7 | $color-grey--medium-light: #aaa; 8 | $color-grey--light: #c8c8c8; 9 | $color-grey--lighter: #e1e2e3; 10 | $color-red: #ad3a3a; 11 | $color-green: #3aad74; 12 | $color-black: #000; 13 | $color-white: #fbfbfb; 14 | 15 | $color-bg--dark: $color-grey--darker; 16 | $color-bg--dark-ff: $color-grey--dark-ff; 17 | $color-bg--light: $color-white; 18 | $color-bg--turquoise: $color-turquoise; 19 | 20 | $color-border: $color-grey--light; 21 | $color-border--light: $color-grey--lighter; 22 | $color-border--dark: $color-grey--darker; 23 | 24 | $color-shadow--darker: $color-grey--darker; 25 | $color-shadow--dark: $color-grey--dark; 26 | $color-shadow--medium: $color-grey--medium; 27 | $color-shadow--light: $color-grey--medium-light; 28 | $color-shadow--lighter: $color-grey--light; 29 | 30 | $color-text--dark: $color-grey--darker; 31 | $color-text--light: $color-white; 32 | $color-text--light-grey: $color-grey--lighter; 33 | $color-text--grey: $color-grey--medium; 34 | $color-text--turquoise: $color-turquoise; 35 | $color-text--red: $color-red; 36 | $color-text--green: $color-green; 37 | $color-text--disabled: $color-grey--light; 38 | 39 | // media queries 40 | $mq-desk--small: 940px; 41 | $mq-mobile: 768px; 42 | 43 | // font-sizes 44 | $font-size--base: 15px; 45 | $font-size--base-small: 13px; 46 | 47 | $font-size--s5: 0.6667rem; // 10/15px 48 | $font-size--s4: 0.733rem; // 11/15px 49 | $font-size--s3: 0.8rem; // 12/15px 50 | $font-size--s2: 0.8667rem; // 13/15px 51 | $font-size--s1: 0.9333rem; // 14/15px 52 | $font-size--default: 1rem; 53 | $font-size--l1: 1.133rem; // 17/15px 54 | $font-size--l2: 1.333rem; // 20/15px 55 | $font-size--l3: 1.467rem; // 22/15px 56 | 57 | // spacing 58 | $spacing--tiny: 5px; 59 | $spacing--small: 10px; 60 | $spacing--medium: 15px; 61 | $spacing--large: 20px; 62 | $spacing--huge: 30px; 63 | 64 | // layers 65 | $layer--sub: -1; 66 | $layer--ground: 0; 67 | $layer--up: 1; 68 | $layer--upper: 2; 69 | $layer--sky: 1000; 70 | $layer--sun: 1001; 71 | $layer--top: 1002; 72 | $layer--max: 2147483646; 73 | 74 | // gradients 75 | $gradient--button-hover: linear-gradient(transparentize($color-bg--turquoise, .9) 50%, transparent 50%); 76 | $gradient--button: linear-gradient(transparent 50%, transparentize($color-bg--turquoise, .9) 50%); 77 | -------------------------------------------------------------------------------- /src/content/_shared/utils.js: -------------------------------------------------------------------------------- 1 | import { _COPY } from './../../utils/copy' 2 | import { _GET_ACTIVE_TAB } from './../../utils/getActiveTab' 3 | import _EXTEND from './../../utils/extend' 4 | import { _MODULE } from './../../utils/module' 5 | import { _DOMMODULE } from './../../utils/dommodule' 6 | import { _STORE } from './../../utils/store' 7 | import { _PORT, _PRIVPORT } from './../../utils/port' 8 | import { _HASHLESS } from './../../utils/hashless' 9 | import _L10N from './../../utils/l10n' 10 | import _ERRORTRACKER from './../../utils/error-tracker' 11 | 12 | export { _COPY, _GET_ACTIVE_TAB, _EXTEND, _MODULE, _DOMMODULE, _STORE, _PORT, _PRIVPORT, _L10N, _ERRORTRACKER, _HASHLESS } 13 | -------------------------------------------------------------------------------- /src/content/addon-page/_store.js: -------------------------------------------------------------------------------- 1 | import { _STORE } from './../_shared/utils' 2 | 3 | export default new _STORE({ 4 | events: { 5 | ENV: { 6 | 'toggled:sync': 'onToggledSync' 7 | } 8 | }, 9 | env: 'addon-page', 10 | 11 | _get_download_option() { 12 | return browser.storage[this.area_settings].get().then(storage => { 13 | if (!storage || !storage.settings) return 'text'; 14 | return storage.settings.history.download; 15 | }); 16 | }, 17 | _get_markers() { 18 | return browser.storage[this.area_settings].get().then(storage => storage.settings.markers); 19 | }, 20 | _get_shortcuts() { 21 | return browser.storage[this.area_settings].get().then(storage => storage.settings.shortcuts); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/content/addon-page/bootstrap.js: -------------------------------------------------------------------------------- 1 | import _STORE from './_store' 2 | import { _MODULE } from './../_shared/utils' 3 | 4 | import _HISTORY from './modules/history' 5 | import _SETTINGS from './modules/settings' 6 | import _LOGGING from './modules/logs' 7 | import _SYNCING from './modules/syncing' 8 | import _IMPORT from './modules/import' 9 | import _CONTACT from './modules/contact' 10 | import _TROUBLESHOOTING from './modules/troubleshooting' 11 | import _PAGINATOR from './modules/history-pagination' 12 | import _PERMISSIONS from './modules/permissions' 13 | 14 | import _NAV from './modules/nav' 15 | 16 | new _MODULE({ 17 | events: { 18 | ENV: { 19 | 'started:app': 'start', 20 | 'updated:settings': 'start', 21 | 'updated:history': 'start' 22 | } 23 | }, 24 | allowedHashes: [/*'news', */'manual', 'settings', 'history', 'contact', 'sync', 'export', 'troubleshooting', 'logs'], 25 | bootstrapped: false, 26 | autoinit() { 27 | _STORE.get().then(storage => { 28 | if (storage.settings && storage.history) this.start(); 29 | }); 30 | }, 31 | start() { 32 | if (!this.bootstrapped) { 33 | this.bootstrapped = true; 34 | _HISTORY(); 35 | _SETTINGS(); 36 | _LOGGING(); 37 | _SYNCING(); 38 | _IMPORT(); 39 | _CONTACT(); 40 | _TROUBLESHOOTING(); 41 | _PAGINATOR(); 42 | _PERMISSIONS(); 43 | this.initMainNav(); 44 | } 45 | }, 46 | initMainNav() { 47 | const tab = window.location.hash.split('=')[1]; 48 | 49 | if (this.allowedHashes.includes(tab)) { 50 | window.document.getElementById('mainnav-' + tab).classList.add('active'); 51 | } 52 | new _NAV(window.document.getElementById('mainnav')); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /src/content/addon-page/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import _ERRORTRACKER from './../_shared/utils' 4 | import { _L10N } from './../_shared/utils' 5 | import _NAV from './modules/nav' 6 | import _TOGGLER from './modules/toggler' 7 | import './port' 8 | import './_store' 9 | import './bootstrap' 10 | 11 | _L10N(); 12 | 13 | /* auto-insert current version number */ 14 | document.getElementById('version-number').innerText = browser.runtime.getManifest().version; 15 | /* end: auto-insert current version number */ 16 | 17 | /* configure subnavs */ 18 | const subnavs = document.getElementsByClassName('subnav'); 19 | let n = subnavs.length; 20 | 21 | while(n--) new _NAV(subnavs[n]); 22 | /* end: configure navs */ 23 | 24 | /* configure toggle elements */ 25 | let toggleButtons = document.getElementsByClassName('toggle-button'), 26 | t = toggleButtons.length; 27 | 28 | while(t--) new _TOGGLER(toggleButtons[t]); 29 | /* end: configure toggle elements */ 30 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/contact.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | export default function() { 5 | return new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'updated:logs': 'setLogLink' 9 | } 10 | }, 11 | autoinit() { 12 | this.setLogLink(); 13 | }, 14 | setLogLink() { 15 | const logLink = document.getElementById('log-mail'); 16 | 17 | _STORE.get('logs').then(logs => { 18 | let l = logs ? logs.length : 0, 19 | href = 20 | 'mailto:undflybir@gmx.de?subject=Textmarker' + 21 | encodeURIComponent(' : Error Logs') + 22 | '&body='+ 23 | encodeURIComponent('- ' + browser.i18n.getMessage('your_msg') + ' -\n\n\nLOGS:\n\n'), 24 | log; 25 | 26 | while(l--) { 27 | log = logs[l]; 28 | href += log[1] + (log[2] ? ' (' + log[2] + ')' : '') + ' - ' + encodeURIComponent((new Date(log[0]).toUTCString()) + '\n'); 29 | } 30 | logLink.href = href; 31 | }); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/history-pagination.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | import _TOGGLER from './toggler' 4 | 5 | export default function() { 6 | return new _DOMMODULE({ 7 | el: document.getElementById('paginator'), 8 | events: { 9 | ENV: { 10 | 'saved:entry': 'add', 11 | 'deleted:entries': 'updateFromStorage', 12 | 'filtered:history': 'reset', 13 | 'imported:history': 'updateFromStorage', 14 | 'changed:per-page-count': 'changeCountPerPage' 15 | }, 16 | DOM: { 17 | click: { 18 | '.page': 'goto', 19 | '.prev': 'prev', 20 | '.next': 'next' 21 | } 22 | } 23 | }, 24 | numberPages: 1, 25 | numberEntries: 0, 26 | currentPage: 1, 27 | perPage: 10, 28 | 29 | autoinit() { 30 | this.updateFromStorage(); 31 | }, 32 | goto(e, el) { 33 | const newPage = el.getAttribute('data-page') * 1; 34 | if (this.currentPage !== newPage) { 35 | this.emit('paginate:history', newPage); 36 | this.currentPage = newPage; 37 | this.render(); 38 | } 39 | }, 40 | prev() { 41 | if (this.currentPage === 1) return; 42 | this.emit('paginate:history', --this.currentPage); 43 | this.render(); 44 | }, 45 | next() { 46 | if (this.currentPage === this.numberPages) return; 47 | this.emit('paginate:history', ++this.currentPage); 48 | this.render(); 49 | }, 50 | add() { 51 | this.update(++this.numberEntries); 52 | }, 53 | remove() { 54 | this.update(--this.numberEntries); 55 | }, 56 | updateFromStorage() { 57 | _STORE.get('settings').then(settings => this.perPage = settings.history.pp || 10) 58 | .then(() => _STORE.get('history').then(history => this.update(Object.keys(history.entries).length))); 59 | }, 60 | update(l, silent) { 61 | this.numberEntries = l; 62 | this.numberPages = l ? Math.ceil(l / this.perPage) : 1; 63 | if (this.currentPage > this.numberPages) { 64 | this.currentPage = this.numberPages; 65 | if (!silent) this.emit('paginate:history', this.currentPage); 66 | } 67 | this.render(); 68 | }, 69 | reset(l) { 70 | this.currentPage = 1; 71 | this.update(l); 72 | }, 73 | render() { 74 | const ul = document.getElementById('paginator-list'); 75 | const l = this.numberEntries; 76 | Array.from(ul.getElementsByClassName('page')).forEach(li => li.remove()); 77 | 78 | if (l < this.perPage + 1) { 79 | this.el.classList.add('u-display--none'); 80 | return; 81 | } else { 82 | this.el.classList.remove('u-display--none'); 83 | } 84 | const p = this.currentPage; 85 | 86 | const pages = this.numberPages; 87 | const r = 7 - (pages - 1 - p); 88 | const frag = document.createDocumentFragment(); 89 | const next = ul.getElementsByClassName('next')[0]; 90 | let i = Math.max(2, p - 7); 91 | if (r > 0) i = Math.max(2, Math.min(i, i - r)); 92 | const j = Math.min(pages, i + 14) + 1; 93 | 94 | this.appendButton(frag, 1); 95 | for (; i < j; i++) this.appendButton(frag, i); 96 | if (pages > j - 1) this.appendButton(frag, pages); 97 | 98 | ul.insertBefore(frag, next); 99 | }, 100 | appendButton(frag, b) { 101 | const btn = document.createElement('li'); 102 | btn.setAttribute('data-page', b); 103 | btn.appendChild(document.createTextNode(b)); 104 | btn.classList.add('page'); 105 | if (b === this.currentPage) btn.classList.add('active'); 106 | frag.appendChild(btn); 107 | }, 108 | changeCountPerPage(p) { 109 | this.perPage = p; 110 | this.update(this.numberEntries, true); 111 | } 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/history-sort.js: -------------------------------------------------------------------------------- 1 | export default { 2 | by: { 3 | date: { 4 | created(object) { 5 | return this._sort(object, 'first'); 6 | }, 7 | last(object) { 8 | return this._sort(object, 'last'); 9 | }, 10 | _sort(object, field) { 11 | return Object.keys(object).sort((a, b) => (new Date(object[b][field])) - (new Date(object[a][field]))); 12 | } 13 | }, 14 | name: { 15 | az(object) { 16 | return this._sort(object); 17 | }, 18 | za(object) { 19 | return this._sort(object).reverse(); 20 | }, 21 | _sort(object) { 22 | return Object.keys(object).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/import.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | export default function() { 5 | return new _DOMMODULE({ 6 | el: document.getElementById('export'), 7 | events: { 8 | ENV: { 9 | 'updated:settings': 'updateExportLinks', 10 | 'updated:history': 'updateExportLinks', 11 | 'toggled:sync': 'updateExportLinks', 12 | 'synced:entry': 'updateExportLinks' 13 | }, 14 | DOM: { 15 | click: { 16 | '.import-button': 'triggerFileInput' 17 | }, 18 | change: { 19 | '.import-file': 'handleFile' 20 | }, 21 | mousedown: { 22 | '.export-button': 'updateExportName' 23 | } 24 | } 25 | }, 26 | 27 | autoinit() { 28 | this.updateExportLinks(); 29 | }, 30 | 31 | import: function(storeString, type) { 32 | var parsedString; 33 | 34 | try { 35 | parsedString = JSON.parse(storeString); 36 | } catch(e) { 37 | return this.displayFailure(browser.i18n.getMessage('error_file_parse')); 38 | } 39 | 40 | if (parsedString) { 41 | this.emit('import:storage', parsedString, type); 42 | this.displayFailure(''); 43 | } 44 | }, 45 | 46 | triggerFileInput: function(e, el) { 47 | document.getElementById('import--' + el.getAttribute('data-type')).click(); 48 | }, 49 | handleFile(e, el) { 50 | let reader = new FileReader(), 51 | file = el.files[0], 52 | size = file.size / 1000000, 53 | type = el.getAttribute('data-type'), 54 | mod = this; 55 | 56 | if (size > 50) 57 | return this.displayFailure(browser.i18n.getMessage('error_file_size')); 58 | 59 | if (type === 'sync' && size > 0.099) 60 | return this.displayFailure(browser.i18n.getMessage('error_file_size_sync')); 61 | 62 | if (file.name.split('.').pop() !== 'json') 63 | return this.displayFailure(browser.i18n.getMessage('error_file_format')); 64 | 65 | reader.onload = (function(file) { 66 | return function(e) { 67 | mod.import(e.target.result, type); 68 | }; 69 | })(file); 70 | 71 | reader.readAsText(file); 72 | }, 73 | displayFailure(reason) { 74 | document.getElementById('import-error').innerText = reason; 75 | }, 76 | 77 | updateExportLinks() { 78 | const localDataLink = document.getElementById('export-local'); 79 | const syncedDataLink = document.getElementById('export-synced'); 80 | 81 | _STORE.get('local_storage').then(storage => { 82 | localDataLink.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(storage)); 83 | }) 84 | .then(() => _STORE.get('synced_storage').then(storage => { 85 | syncedDataLink.href = 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(storage)); 86 | })); 87 | }, 88 | updateExportName(e, el) { 89 | el.parentNode.setAttribute('download', 90 | 'Textmarker-data-' + 91 | el.getAttribute('data-type') + '-' + 92 | (new Date().toLocaleString().replace(/\D/g, '_')) + 93 | '.json' 94 | ); 95 | } 96 | }); 97 | } 98 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/logs.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | import _LOG_KEYS from '../../../data/log-keys' 4 | 5 | export default function() { 6 | return new _DOMMODULE({ 7 | el: document.getElementById('logs'), 8 | events: { 9 | ENV: { 10 | 'updated:logs': 'log' 11 | }, 12 | DOM: { 13 | click: { 14 | '#clear-logs': 'clear', 15 | '.permission': 'askPermission' 16 | } 17 | } 18 | }, 19 | autoinit() { 20 | this.logMissingPermissions().then(() => this.log()); 21 | }, 22 | logMissingPermissions() { 23 | return browser.permissions.contains({ permissions: ['webNavigation'] }) 24 | .then(granted => { 25 | if (!granted) { 26 | document.getElementById('no-permission--webNavigation').classList.remove('u-display--none'); 27 | } 28 | }); 29 | }, 30 | log() { 31 | _STORE.get('logs').then(logs => { 32 | logs = logs || []; 33 | let tableBody = this.el.getElementsByTagName('tbody')[0], 34 | l = logs.length, 35 | frag = document.createDocumentFragment(), 36 | tr, td_date, td_msg, node_date, node_msg, node_attempt, url, log, time, msg, reason; 37 | 38 | if (l) { 39 | this.el.classList.remove('nologs'); 40 | while(l--) { 41 | log = logs[l]; 42 | msg = log[1]; 43 | if (typeof msg === 'number') msg = browser.i18n.getMessage(_LOG_KEYS.getKeyByValue(log[1])); 44 | //'nu',{year:'numeric',month:'2-digit',day:'2-digit',hour:'numeric',second:'numeric',minute:'numeric'} 45 | time = this.optimizeDateString((new Date(log[0])).toLocaleString()); 46 | tr = document.createElement('tr'); 47 | td_date = document.createElement('td'); 48 | td_msg = document.createElement('td'); 49 | node_date = document.createTextNode(time); 50 | node_msg = document.createTextNode(msg); 51 | 52 | td_date.appendChild(node_date); 53 | td_msg.appendChild(node_msg); 54 | 55 | if (log[2]) { 56 | reason = document.createElement('div'); 57 | //reason.appendChild(document.createTextNode(log[2])); 58 | reason.innerText = log[2]; 59 | td_msg.appendChild(reason); 60 | } 61 | if (log[3]) { 62 | node_attempt = document.createElement('span'); 63 | node_attempt.appendChild(document.createTextNode(`[#${log[3]}]`)); 64 | td_msg.insertBefore(node_attempt, node_msg); 65 | } 66 | if (log[4]) { 67 | url = document.createElement('div'); 68 | url.appendChild(document.createTextNode(`URL: ${log[4]}`)); 69 | td_msg.appendChild(url); 70 | } 71 | tr.appendChild(td_date); 72 | tr.appendChild(td_msg); 73 | frag.appendChild(tr); 74 | } 75 | tableBody.innerText = ''; 76 | tableBody.appendChild(frag); 77 | } else { 78 | this.el.classList.add('nologs'); 79 | } 80 | }); 81 | }, 82 | clear() { 83 | this.emit('clear:logs'); 84 | }, 85 | optimizeDateString(date) { 86 | return (date 87 | .replace(/^(\d{1})(\D{1})/, (m, p, q)=> '0' + p + q) 88 | .replace(/(\D{1})(\d{1}\D{1})/g, (m, p, q) => p + '0' + q)); 89 | }, 90 | askPermission() { 91 | browser.permissions.request({ permissions: ['webNavigation'] }).then(granted => { 92 | if (granted) { 93 | this.emit('granted-permission:webNavigation'); 94 | Array.from(document.getElementsByClassName('permission-alert')) 95 | .forEach(alert => alert.classList.add('u-display--none')); 96 | } 97 | }); 98 | } 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/nav.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | 3 | export default function(el) { 4 | 5 | return new _DOMMODULE({ 6 | el, 7 | events: { 8 | DOM: { 9 | click: { 10 | 'li': 'toggle', 11 | '.navitem': 'toggle' 12 | } 13 | } 14 | }, 15 | pageNav: null, 16 | current: null, 17 | 18 | autoinit() { 19 | this.pageNav = el.hasAttribute('data-page-nav'); 20 | let current = this.current = el.getElementsByClassName('active')[0]; 21 | if (current) { 22 | this.open(current); 23 | } 24 | }, 25 | 26 | toggle(e, el) { 27 | if (el.classList.contains('disabled') || this.current == el) return false; 28 | 29 | if (this.current) this.close(this.current); 30 | 31 | this.open(el); 32 | }, 33 | open(el) { 34 | const targetId = el.getAttribute('data-target'); 35 | el.classList.add('active'); 36 | document.getElementById(targetId).classList.remove('u-display--none'); 37 | this.emit('opened:tab', targetId); 38 | this.current = el; 39 | if (this.pageNav) window.document.title = 'Textmarker - ' + browser.i18n.getMessage(targetId); 40 | }, 41 | close(el) { 42 | el.classList.remove('active'); 43 | document.getElementById(el.getAttribute('data-target')).classList.add('u-display--none'); 44 | } 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/permissions.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | 3 | export default function() { 4 | return new _DOMMODULE({ 5 | el: document.getElementById('permission-alert'), 6 | events: { 7 | DOM: { 8 | click: { 9 | '.permission': 'askPermission' 10 | } 11 | } 12 | }, 13 | 14 | autoinit() { 15 | browser.permissions.contains({ permissions: ['webNavigation'] }) 16 | .then(granted => { 17 | if (!granted) { 18 | this.el.classList.remove('u-display--none'); 19 | } 20 | }); 21 | }, 22 | 23 | askPermission() { 24 | browser.permissions.request({ permissions: ['webNavigation'] }).then(granted => { 25 | if (granted) { 26 | this.emit('granted-permission:webNavigation'); 27 | Array.from(document.getElementsByClassName('permission-alert')) 28 | .forEach(alert => alert.classList.add('u-display--none')); 29 | } 30 | }); 31 | } 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/syncing.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | export default function() { 5 | return new _DOMMODULE({ 6 | el: document.getElementById('sync'), 7 | events: { 8 | ENV: { 9 | 'failed:toggle-sync': 'undo' 10 | }, 11 | DOM: { 12 | click: { 13 | '.switch': 'toggleSwitch' 14 | } 15 | } 16 | }, 17 | 18 | autoinit() { 19 | document.getElementById('sync-switch--settings').classList.toggle('active', _STORE.area_settings === 'sync'); 20 | document.getElementById('sync-switch--history').classList.toggle('active', _STORE.area_history === 'sync'); 21 | document.getElementById('sync-switch--pagenotes').classList.toggle('active', _STORE.area_pagenotes === 'sync'); 22 | }, 23 | 24 | toggleSwitch(e, el) { 25 | el = el.classList.contains('switch--sync') ? el : el.parentNode; 26 | el.classList.toggle('active'); 27 | this.emit('toggle:sync', el.getAttribute('data-type'), el.classList.contains('active')); 28 | }, 29 | 30 | undo(field) { 31 | document.getElementById('sync-switch--' + field).classList.toggle('active'); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/toggler.js: -------------------------------------------------------------------------------- 1 | export default class _TOGGLER { 2 | 3 | constructor(el) { 4 | this.el = el; 5 | this.init(); 6 | } 7 | 8 | init() { 9 | this.el.addEventListener('click', this.toggle, false); 10 | } 11 | toggle(e) { 12 | e.stopPropagation(); 13 | 14 | let dataTarget = this.getAttribute('data-target'), 15 | targets = dataTarget ? dataTarget.split(' ') : null, 16 | dataToggle = this.getAttribute('data-toggle'), 17 | roles = dataToggle ? dataToggle.split(' ') : null; 18 | 19 | if (roles) { 20 | roles.forEach((role, i) => document.getElementById(targets[i]).classList[role]('open')); 21 | } 22 | else document.getElementById(targets[0]).disabled = !this.checked; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/content/addon-page/modules/troubleshooting.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | export default function() { 5 | return new _DOMMODULE({ 6 | el: document.getElementById('troubleshooting'), 7 | events: { 8 | DOM: { 9 | click: { 10 | 'option': 'toggleTopic', 11 | '.trouble__answer--btn': 'showArticle', 12 | '.trouble__answer--sub': 'showArticle', 13 | '.trouble__cancel': 'cancel', 14 | '.trouble__answer--no-entries': 'scanHistory' 15 | } 16 | } 17 | }, 18 | 19 | currentTrouble: null, 20 | 21 | autoinit() { 22 | 23 | }, 24 | 25 | toggleTopic(e, el) { 26 | if (this.currentTrouble) this.currentTrouble.classList.remove('open'); 27 | const newTrouble = this.currentTrouble = document.getElementById(el.getAttribute('data-target')); 28 | newTrouble.classList.add('open'); 29 | }, 30 | showArticle(e, el) { 31 | el = el.nodeName === 'BUTTON' ? el : el.parentNode; 32 | const boxClassList = el.parentNode.parentNode.classList; 33 | boxClassList.add('answered'); 34 | boxClassList.add(`answered--${el.getAttribute('data-value')}`); 35 | }, 36 | cancel() { 37 | Array.from(document.getElementsByClassName('answered')).forEach(el => { 38 | el.classList.remove('answered'); 39 | el.classList.remove('answered--no'); 40 | el.classList.remove('answered--yes'); 41 | }); 42 | }, 43 | scanHistory() { 44 | 45 | const articles = [ 46 | document.getElementById('history-scan__error'), 47 | document.getElementById('history-scan__empty'), 48 | document.getElementById('history-scan__nonempty'), 49 | document.getElementById('history-scan__nomarks') 50 | ]; 51 | const [errEl, emptyEl, nonemptyEl, nomarksEl] = articles; 52 | 53 | articles.forEach(a => a.classList.add('u-display--none')); 54 | 55 | _STORE.get('history').then(history => { 56 | if (!history || !history.entries) { 57 | errEl.classList.remove('u-display--none'); 58 | } else { 59 | const len = history.entries ? Object.keys(history.entries).length : 0; 60 | 61 | if (len) { 62 | nonemptyEl.classList.remove('u-display--none'); 63 | let nomarks = true, entry; 64 | for (let e in history.entries) { 65 | entry = history.entries[e]; 66 | if (entry.marks && entry.marks.length) { 67 | document.getElementById('history-scan__count').innerText = len; 68 | nomarks = false; 69 | break; 70 | } 71 | } 72 | if (nomarks) { 73 | nomarksEl.classList.remove('u-display--none'); 74 | } 75 | } else { 76 | emptyEl.classList.remove('u-display--none'); 77 | } 78 | } 79 | }) 80 | .catch(e => { 81 | errEl.classList.remove('u-display--none'); 82 | document.getElementById('history-scan__errormsg1').innerText = e.toString(); 83 | }) 84 | .then(() => { 85 | const articleClasses = document.getElementById('trouble--3').classList; 86 | 87 | articleClasses.add('answered'); 88 | articleClasses.add('answered--yes'); 89 | }); 90 | } 91 | }); 92 | } 93 | -------------------------------------------------------------------------------- /src/content/addon-page/port.js: -------------------------------------------------------------------------------- 1 | import { _PORT } from './../_shared/utils' 2 | 3 | export default new _PORT({ 4 | name: 'addon-page', 5 | type: 'content', 6 | events: { 7 | ONEOFF: [ 8 | 'change:style-setting', 9 | 'change:autonote-setting', 10 | 'change:mark-method-setting', 11 | 'toggle:shortcut-setting', 12 | 'change:shortcut-setting', 13 | 'toggle:ctm-setting', 14 | 'change:saveopt-setting', 15 | 'toggle:priv-setting', 16 | 'change:immut-setting', 17 | 'change:dropLosses-setting', 18 | 'change:autoRetry-setting', 19 | 'change:namingopt-setting', 20 | 'change:sort-setting', 21 | 'change:view-setting', 22 | 'change:hash-setting', 23 | 'toggle:noteopt-setting', 24 | 'toggle:quickbuttonopt-setting', 25 | 'switch:quickbuttonopt-setting', 26 | 'toggle:notification-setting', 27 | 'toggle:misc-setting', 28 | 'change:misc-setting', 29 | 'toggle:tbbpower-setting', 30 | 'change:autocs-setting', 31 | 'change:iframe-setting', 32 | 'add:custom-marker', 33 | 'remove:custom-marker', 34 | 'delete:entries', 35 | 'clean:entries', 36 | 'open:entries', 37 | 'rename:entry', 38 | 'correct-name:entry', 39 | 'view:entry', 40 | 'sync:entry', 41 | 'sync:history', 42 | 'sync:settings', 43 | 'import:storage', 44 | 'toggle:sync', 45 | 'change:custom-search-setting', 46 | 'changed:per-page-count', 47 | 'error:browser-console', 48 | 'clear:logs', 49 | 'tag:entries', 50 | 'granted-permission:webNavigation' 51 | ] 52 | } 53 | }) 54 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/_buttons.scss: -------------------------------------------------------------------------------- 1 | @keyframes btn { 2 | from { color: $color-bg--turquoise; } 3 | to { color: $color-text--light; } 4 | } 5 | 6 | button, .btn { 7 | @extend %button; 8 | &--small { 9 | height: 22px; 10 | width: 22px; 11 | padding: 2px 5px; 12 | background-position: 5px 5px, 0 0; 13 | background-size: 13px, 100%; 14 | margin-left: 5px; 15 | } 16 | &:not(.btn--small) { 17 | background: #fff; 18 | 19 | &:hover { 20 | background: $color-bg--turquoise; 21 | //color: $color-text--light; 22 | animation: 1s forwards btn; 23 | } 24 | } 25 | } 26 | 27 | aside { 28 | position: absolute; 29 | top: 50px; 30 | right: -37px; 31 | 32 | @include mq('desk') { 33 | right: 0; 34 | top: 0; 35 | position: relative; 36 | } 37 | 38 | a { 39 | width: 37px; 40 | height: 24px; 41 | border: 1px solid $color-shadow--light; 42 | border-left: 0; 43 | border-radius: 0 3px 3px 0; 44 | display: block; 45 | margin-bottom: 7px; 46 | opacity: .75; 47 | transition: opacity .3s ease-in; 48 | 49 | &:hover { 50 | opacity: 1; 51 | border-color: $color-shadow--lighter; 52 | } 53 | 54 | @include mq('desk') { 55 | border: 0; 56 | border-radius: 2px; 57 | display: inline-block; 58 | background: $color-bg--turquoise; 59 | float: right; 60 | margin-left: 7px; 61 | opacity: 1; 62 | 63 | &:hover { 64 | border: 0; 65 | opacity: .75; 66 | } 67 | 68 | &.github, 69 | &.email { 70 | background-position: center !important; 71 | } 72 | 73 | img { 74 | margin-top: 6px !important; 75 | } 76 | } 77 | 78 | &.github { 79 | @include icon('github', 9px center); 80 | } 81 | 82 | &.email { 83 | @include icon('email', center center); 84 | } 85 | 86 | img { 87 | width: 20px; 88 | height: 13px; 89 | margin: 4px 0 0 9px; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/_general.scss: -------------------------------------------------------------------------------- 1 | body, html, body > div { 2 | height: 100%; 3 | } 4 | body { 5 | background: $color-bg--dark; 6 | } 7 | p { 8 | margin-bottom: 1em; 9 | } 10 | h1 { 11 | font-size: 1em; 12 | font-weight: normal; 13 | text-align: right; 14 | margin: 0 -1px 15px 0; 15 | height: 38px; 16 | 17 | a { 18 | color: inherit; 19 | text-decoration: none; 20 | 21 | &:hover { 22 | color: $color-text--turquoise; 23 | text-decoration: none; 24 | } 25 | } 26 | > span { 27 | display: block; 28 | font-size: 0.65em; 29 | color: $color-text--disabled; 30 | } 31 | } 32 | h2 { 33 | font-weight: normal; 34 | padding: 0 0 27px; 35 | color: $color-text--turquoise; 36 | 37 | span { 38 | color: $color-text--disabled; 39 | font-size: 0.7em; 40 | display: block; 41 | margin-top: 10px; 42 | } 43 | } 44 | h3 { 45 | font-size: 1.15em; 46 | font-weight: normal; 47 | padding: 0 0 20px; 48 | color: $color-text--turquoise; 49 | } 50 | h4 { 51 | font-size: 1em; 52 | font-weight: normal; 53 | color: $color-text--turquoise; 54 | padding: 25px 0 15px; 55 | } 56 | ul, 57 | li { 58 | list-style-type: none; 59 | } 60 | li { 61 | -moz-user-select: none; 62 | } 63 | ol { 64 | margin: -5px 0 1em 0; 65 | 66 | li { 67 | margin-top: 5px; 68 | 69 | strong.u-color--turquoise { 70 | font-family: Helvetica, Verdana, Arial, sans-serif; 71 | } 72 | } 73 | } 74 | .unset li { 75 | list-style-type: unset; 76 | margin-left: 20px; 77 | } 78 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/_helpers.scss: -------------------------------------------------------------------------------- 1 | .width--full { 2 | width: 100% !important; 3 | } 4 | .border-top { 5 | border-bottom: 0 !important; 6 | border-left: 0 !important; 7 | border-right: 0 !important; 8 | } 9 | .open { 10 | display: block !important; 11 | } 12 | .toggle-button { 13 | @include mq('mobile') { 14 | position: relative; 15 | } 16 | } 17 | .nowrap { 18 | white-space: nowrap; 19 | } 20 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/_navs.scss: -------------------------------------------------------------------------------- 1 | @keyframes nav { 2 | 0% { padding-left: 37px; } 3 | 20% { padding-left: 12px; } 4 | 100% { padding-left: 37px; } 5 | } 6 | 7 | @keyframes navicon { 8 | from { left: 12px; } 9 | to { left: -25px; } 10 | } 11 | 12 | @keyframes ghost { 13 | 0% { left: 12px; top: 16px; } 14 | 20%, 40% { left: -25px; filter: grayscale(100%); transform: scale(1); } 15 | 90% { left: 12px; top: 12px; transform: scale(1.7); opacity: 1; } 16 | 100% { left: -25px; transform: scale(0); opacity: 0; } 17 | } 18 | 19 | .nav li { 20 | cursor: default; 21 | } 22 | /* main nav */ 23 | #mainnav { 24 | float: left; 25 | width: 220px; 26 | color: $color-text--light; 27 | height: 100%; 28 | padding-top: 127px; 29 | 30 | @include mq('mobile') { 31 | position: absolute; 32 | width: 100%; 33 | height: auto; 34 | box-shadow: none; 35 | padding-top: 0; 36 | z-index: 2; 37 | } 38 | 39 | li { 40 | height: 53px; 41 | padding: 14px 20px 20px 37px; 42 | position: relative; 43 | 44 | &:hover { 45 | background-color: rgba(255, 255, 255, 0.1); 46 | } 47 | 48 | &:before { 49 | content: ''; 50 | display: block; 51 | width: 16px; 52 | height: 16px; 53 | position: absolute; 54 | top: 16px; 55 | left: 12px; 56 | } 57 | 58 | &.active { 59 | background-color: rgba(43, 165, 183, 0.5); 60 | background-position: 0 6px; 61 | background-image: none; 62 | animation-name: nav; 63 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 64 | animation-duration: .5s; 65 | animation-fill-mode: forwards; 66 | transition: background-color .5s ease-out; 67 | 68 | &:before { 69 | animation-name: navicon; 70 | animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); 71 | animation-duration: .5s; 72 | animation-fill-mode: forwards; 73 | } 74 | 75 | &#mainnav-contact { 76 | &:before { 77 | animation-name: ghost; 78 | animation-duration: 1.5s; 79 | } 80 | } 81 | } 82 | 83 | &.deactivated { 84 | color: rgba(255, 255, 255, 0.25); 85 | } 86 | 87 | span.icon { 88 | color: $color-text--turquoise; 89 | margin-right: 10px; 90 | } 91 | } 92 | } 93 | .menu-toggler { 94 | @include mq('mobile') { 95 | width: 48px; 96 | height: 48px; 97 | @include icon('menu-blue'); 98 | filter: hue-rotate(331deg) brightness(1.3); 99 | position: absolute; 100 | top: 25px; 101 | left: 21px; 102 | } 103 | } 104 | #mainnav-list { 105 | @include mq('mobile') { 106 | display: none; 107 | background: $color-bg--dark; 108 | padding-top: 125px; 109 | width: 100%; 110 | } 111 | } 112 | $icons: ( 113 | 'news': 'forklift', 114 | 'manual': 'book', 115 | 'settings': 'tools', 116 | 'history': 'clock', 117 | 'sync': 'sync2', 118 | 'export': 'export', 119 | 'contact': 'ghost', 120 | 'troubleshooting': 'debug', 121 | 'logs': 'dna' 122 | ); 123 | 124 | @each $navitem, $icon in $icons { 125 | #mainnav-#{$navitem} { 126 | &:before { 127 | @include icon(#{$icon}); 128 | } 129 | } 130 | } 131 | 132 | /* subnavs */ 133 | .subnav { 134 | ul { 135 | border-bottom: 1px solid $color-border--dark; 136 | border-top: 1px solid $color-border--dark; 137 | } 138 | 139 | li { 140 | display: inline-block; 141 | padding: 10px 10px 17px; 142 | 143 | @include mq('desk') { 144 | margin: -1px 0 0 0; 145 | padding: 10px 5px 17px; 146 | float: none; 147 | } 148 | 149 | &.active { 150 | border-bottom: 4px solid $color-turquoise; 151 | 152 | @include mq('desk') { 153 | color: $color-text--turquoise; 154 | border: 0; 155 | text-decoration: underline; 156 | } 157 | } 158 | 159 | &.disabled { 160 | color: $color-text--disabled; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_alerts.scss: -------------------------------------------------------------------------------- 1 | .tm-alert { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | padding: 10px 240px; 7 | color: #fbfbfb; 8 | z-index: 2; 9 | 10 | @include mq('mobile') { 11 | position: relative; 12 | padding: 10px; 13 | } 14 | 15 | &--error { 16 | background: transparentize($color-red, .1); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_contact.scss: -------------------------------------------------------------------------------- 1 | a .at { 2 | font-size: 22px; 3 | vertical-align: -2px; 4 | margin-left: 0px; 5 | display: inline-block; 6 | font-family: times; 7 | } 8 | #contact a:hover { 9 | font-style: italic; 10 | } 11 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_data-management.scss: -------------------------------------------------------------------------------- 1 | a.export { 2 | text-decoration: none !important; 3 | } 4 | #export button { 5 | float: none; 6 | margin: 0 0 10px; 7 | } 8 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_logs.scss: -------------------------------------------------------------------------------- 1 | .nologs { 2 | #no-logs { 3 | display: block !important; 4 | } 5 | #clear-logs { 6 | display: none; 7 | } 8 | #logs-table { 9 | display: none; 10 | } 11 | } 12 | 13 | #logs-table { 14 | border-collapse: collapse; 15 | 16 | th { 17 | padding: 3px 5px 15px; 18 | text-align: left; 19 | text-transform: uppercase; 20 | 21 | &:first-child { 22 | width: 190px; 23 | } 24 | } 25 | td { 26 | padding: 4px 5px 5px; 27 | border-top: 1px solid $color-border; 28 | border-bottom: 1px solid $color-border; 29 | font-size: 13px; 30 | 31 | tr:last-child & { 32 | border: 0; 33 | } 34 | 35 | div { 36 | margin-top: 5px; 37 | color: $color-text--grey; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_news.scss: -------------------------------------------------------------------------------- 1 | .news-box { 2 | border: 1px solid $color-border--light; 3 | padding: $spacing--small; 4 | margin-bottom: $spacing--small; 5 | overflow: hidden; 6 | max-height: 43px; 7 | transition: none; 8 | 9 | &.open { 10 | max-height: 1000px; 11 | transition: max-height .5s ease-in; 12 | } 13 | 14 | &--simple { 15 | h3 { 16 | &:after { 17 | display: none !important; 18 | } 19 | } 20 | } 21 | 22 | h3 { 23 | padding-right: $spacing--large; 24 | padding-bottom: $spacing--small; 25 | margin-bottom: $spacing--medium; 26 | border-bottom: 1px solid $color-border--light; 27 | position: relative; 28 | text-overflow: ellipsis; 29 | overflow: hidden; 30 | white-space: nowrap; 31 | 32 | &:after { 33 | content: ''; 34 | display: block; 35 | width: 16px; 36 | height: 16px; 37 | position: absolute; 38 | right: 3px; 39 | top: 3px; 40 | @include icon('down'); 41 | transform: rotate(90deg); 42 | opacity: .25; 43 | } 44 | &:hover { 45 | &:after { 46 | opacity: .5; 47 | } 48 | } 49 | } 50 | 51 | p { 52 | display: none; 53 | } 54 | &.open { 55 | h3 { 56 | white-space: unset; 57 | 58 | &:after { 59 | transform: none; 60 | } 61 | } 62 | 63 | p { 64 | display: block; 65 | } 66 | } 67 | 68 | small { 69 | display: block; 70 | margin-top: $spacing--tiny; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/components/_settings.scss: -------------------------------------------------------------------------------- 1 | .option-row { 2 | margin-top: 13px; 3 | 4 | button { 5 | float: right; 6 | padding: 0 7px; 7 | } 8 | > div { 9 | display: inline-block; 10 | } 11 | } 12 | .key { 13 | background: $color-bg--dark; 14 | color: $color-text--light; 15 | font-size: inherit; 16 | padding: 3px 7px 5px; 17 | width: 40px; 18 | height: 40px; 19 | float: left; 20 | border-radius: 5px; 21 | margin: 8px 8px 0 0; 22 | } 23 | .accordion { 24 | max-height: 0; 25 | overflow: hidden; 26 | transition: max-height 1s; 27 | clear: both; 28 | 29 | &.open { 30 | max-height: 200px; 31 | } 32 | } 33 | /* markers */ 34 | #customized-key { 35 | margin-left: 4px; 36 | } 37 | #shadow-settings > div { 38 | margin-bottom: 5px; 39 | } 40 | #preview { 41 | position: relative; 42 | margin-top: 26px; 43 | 44 | button { 45 | position: absolute; 46 | right: 0; 47 | top: -26px; 48 | } 49 | } 50 | #example { 51 | padding: 0 5px; 52 | overflow: hidden; 53 | float: left; 54 | } 55 | #custom-keys div { 56 | vertical-align: bottom; 57 | } 58 | 59 | /* shortcuts */ 60 | #customize-shortcuts { 61 | table, tr { 62 | display: block; 63 | border-collapse: collapse; 64 | td { 65 | display: inline-block; 66 | width: 140px; 67 | padding: 12px 10px 0 0; 68 | 69 | &:first-child { 70 | width: calc(100% - 160px); 71 | } 72 | } 73 | } 74 | label { 75 | width: 100%; 76 | 77 | sup sup { 78 | width: auto; 79 | } 80 | } 81 | } 82 | #overwrite-hint { 83 | text-indent: -9px; 84 | display: inline-block; 85 | margin-left: 9px; 86 | } 87 | 88 | /* history */ 89 | #notification { 90 | margin: 0 3px 0 5px; 91 | } 92 | #custom-name-row { 93 | display: none; 94 | } 95 | #quickbutton-download-select { 96 | display: none; 97 | } 98 | #download-text:checked + label + #quickbutton-download-select { 99 | display: inline-block; 100 | } 101 | #drop-losses-box, 102 | #name-mark-box, 103 | #ignore-hash-box { 104 | &:hover small { 105 | display: block !important; 106 | } 107 | } 108 | /* misc */ 109 | .tmuipos { 110 | display: none; 111 | border-color: $color-border--light; 112 | } 113 | .misc-cb:checked + label + .tmuipos { 114 | display: inline; 115 | } 116 | #custom-search { 117 | @include mq('desk') { 118 | float: none; 119 | margin-top: 10px; 120 | vertical-align: -2px; 121 | } 122 | } 123 | #addon-autocs:checked + label + small { 124 | display: none !important; 125 | } 126 | -------------------------------------------------------------------------------- /src/content/addon-page/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/reset'; 3 | @import '../../_shared/sass/general'; 4 | @import '../../_shared/sass/helpers'; 5 | @import '../../_shared/sass/buttons'; 6 | @import '../../_shared/sass/icons'; 7 | @import '../../_shared/sass/forms'; 8 | @import '../../_shared/sass/links'; 9 | @import '../../_shared/sass/shadows'; 10 | @import '../../_shared/sass/switches'; 11 | @import '../../_shared/sass/infos'; 12 | 13 | @import './general'; 14 | @import './helpers'; 15 | @import './navs'; 16 | @import './buttons'; 17 | @import './components/settings'; 18 | @import './components/history'; 19 | @import './components/data-management'; 20 | @import './components/contact'; 21 | @import './components/logs'; 22 | @import './components/news'; 23 | @import './components/troubleshooting'; 24 | @import './components/alerts'; 25 | 26 | @include links; 27 | 28 | button { 29 | @extend %button; 30 | } 31 | 32 | /* MAIN CONTENT */ 33 | #main { 34 | position: relative; 35 | padding: 20px; 36 | max-width: 760px; 37 | width: calc(100% - 220px); 38 | float: left; 39 | min-height: 100%; 40 | box-shadow: 0 0 8px 0px $color-shadow--darker; 41 | background: $color-bg--light; 42 | 43 | @include mq('mobile') { 44 | width: 100%; 45 | } 46 | small { 47 | color: $color-text--grey; 48 | } 49 | } 50 | .content { 51 | padding-bottom: 20px; 52 | 53 | a { 54 | color: $color-text--turquoise; 55 | text-decoration: underline; 56 | } 57 | @include mq('mobile') { 58 | padding-top: 35px; 59 | } 60 | } 61 | .tab { 62 | padding: 40px 0; 63 | } 64 | .block-spacing { 65 | padding: 12px 0 21px; 66 | } 67 | .seperator { 68 | border-top: 1px solid $color-border; 69 | margin: 20px 0; 70 | } 71 | 72 | #deprication-note { 73 | position: fixed; 74 | bottom: 0; 75 | left: 0; 76 | width: 100%; 77 | padding: 10px; 78 | z-index: 1; 79 | background: #3aad74; 80 | color: #fbfbfb; 81 | font-size: 30px; 82 | } -------------------------------------------------------------------------------- /src/content/detail-view/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import { _MODULE } from './../_shared/utils' 4 | import { _L10N } from './../_shared/utils' 5 | import './modules/header' 6 | import './modules/meta' 7 | //import './modules/notes' 8 | import './modules/marks' 9 | 10 | _L10N(); 11 | 12 | new _MODULE({ 13 | autoinit() { 14 | const name = decodeURIComponent(window.location.hash).slice(1); 15 | 16 | browser.storage.sync.get().then(storage => { 17 | if (Object.keys(storage.history.entries).includes(name)) return storage.history.entries[name]; 18 | return browser.storage.local.get().then(storage => { 19 | if (Object.keys(storage.history.entries).includes(name)) return storage.history.entries[name]; 20 | }); 21 | }) 22 | .then(entry => { 23 | this.emit('entry', entry); 24 | document.title = entry.name; 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /src/content/detail-view/modules/header.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | 3 | new _DOMMODULE({ 4 | el: document.getElementById('header'), 5 | events: { 6 | ENV: { 7 | 'entry': 'render' 8 | } 9 | }, 10 | entry: null, 11 | 12 | setTitle(entry) { 13 | this.el.innerText = entry.name; 14 | }, 15 | setTag(entry) { 16 | const tag = entry.tag || browser.i18n.getMessage('detail_notag'); 17 | document.getElementById('tag').innerText = tag; 18 | }, 19 | render(entry) { 20 | this.entry = entry; 21 | this.setTitle(entry); 22 | this.setTag(entry); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /src/content/detail-view/modules/marks.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _SETTINGS from './../../../data/global-settings' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('marks'), 6 | events: { 7 | ENV: { 8 | 'entry': 'render' 9 | }, 10 | DOM: { 11 | click: { 12 | '.col-toggle': 'toggleNotes', 13 | '.table-toggle': 'toggleTable' 14 | } 15 | } 16 | }, 17 | entry: null, 18 | tmpl: null, 19 | tbody: null, 20 | marks: [], 21 | notes: false, 22 | notesShown: true, 23 | 24 | render(entry) { 25 | this.entry = entry; 26 | const tmpl = this.tmpl = document.getElementById('mark-template'); 27 | const tbody = this.tbody = document.getElementById('marks-content'); 28 | const marks = this.marks = this.sortById(entry.marks); 29 | 30 | if (!marks.length) this.el.classList.add('disabled'); 31 | 32 | this.renderCount(entry); 33 | marks.forEach(mark => this.renderMark(mark)); 34 | tbody.removeChild(tmpl); 35 | if (!this.notes) this.el.classList.add('no-notes'); 36 | }, 37 | renderCount(entry) { 38 | document.getElementById('marks-count').innerText = '(' + entry.marks.length + ')'; 39 | }, 40 | renderMark(mark) { 41 | const markEl = this.tmpl.cloneNode(true); 42 | const td_text = markEl.getElementsByClassName('mark-text')[0]; 43 | const td_note = markEl.getElementsByClassName('mark-note')[0]; 44 | const text = document.createElement('p'); 45 | let noteText, noteColor; 46 | td_text.innerText = mark.text; 47 | td_text.setAttribute('style', mark.style); 48 | if (mark.note) { 49 | if (typeof mark.note === 'string') { 50 | noteText = mark.note; 51 | noteColor = _SETTINGS.NOTE_COLORS.YELLOW; 52 | } else { 53 | noteText = mark.note.text || ''; 54 | noteColor = mark.note.color || 'yellow'; 55 | noteColor = _SETTINGS.NOTE_COLORS[noteColor.toUpperCase()]; 56 | } 57 | if (noteText) { 58 | td_note.innerText = noteText; 59 | td_note.parentNode.style.backgroundColor = noteColor; 60 | this.notes = true; 61 | } 62 | } 63 | markEl.id = 'mark-' + mark.id; 64 | this.tbody.appendChild(markEl); 65 | }, 66 | sortById(marks) { 67 | return marks.sort((mark1, mark2) => { 68 | const id1 = mark1.id; 69 | const id2 = mark2.id; 70 | if (id1 === id2) return 0; 71 | return id1 < id2 ? -1 : 1; 72 | }); 73 | }, 74 | toggleNotes() { 75 | if (this.notesShown) { 76 | this.el.classList.add('hide-notes'); 77 | } else { 78 | this.el.classList.remove('hide-notes'); 79 | } 80 | this.notesShown = !this.notesShown; 81 | }, 82 | toggleTable() { 83 | this.el.classList.toggle('folded'); 84 | } 85 | }); 86 | -------------------------------------------------------------------------------- /src/content/detail-view/modules/meta.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | 3 | new _DOMMODULE({ 4 | el: document.getElementById('meta'), 5 | events: { 6 | ENV: { 7 | 'entry': 'render' 8 | } 9 | }, 10 | entry: null, 11 | 12 | setDates(entry) { 13 | document.getElementById('created').innerText = this.optimizeDateString(new Date(entry.first).toLocaleString()); 14 | document.getElementById('last_modified').innerText = this.optimizeDateString(new Date(entry.last).toLocaleString()); 15 | }, 16 | setTitle(entry) { 17 | document.getElementById('title').innerText = entry.title; 18 | }, 19 | setLink(entry) { 20 | const link = document.getElementById('url'); 21 | const url = entry.url; 22 | link.href = url; 23 | link.innerText = url; 24 | }, 25 | setSyncMode(entry) { 26 | const val = entry.synced ? browser.i18n.getMessage('yes') : browser.i18n.getMessage('no'); 27 | document.getElementById('synced').innerText = val; 28 | }, 29 | render(entry) { 30 | this.entry = entry; 31 | this.setDates(entry); 32 | this.setTitle(entry); 33 | this.setLink(entry); 34 | this.setSyncMode(entry); 35 | }, 36 | optimizeDateString(date) { 37 | return (date 38 | .replace(/^(\d{1})(\D{1})/, (m, p, q)=> '0' + p + q) 39 | .replace(/(\D{1})(\d{1}\D{1})/g, (m, p, q) => p + '0' + q)); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /src/content/detail-view/sass/_general.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: $color-bg--light; 3 | } 4 | p { 5 | margin-bottom: 5px; 6 | } 7 | header { 8 | background: $color-bg--dark; 9 | padding: 10px 20px; 10 | } 11 | header p { 12 | color: $color-text--turquoise; 13 | } 14 | section { 15 | margin-bottom: 30px; 16 | } 17 | section:last-child { 18 | margin: 0; 19 | } 20 | h1 { 21 | color: $color-text--light; 22 | font-size: 20px; 23 | } 24 | h2 { 25 | font-size: 17px; 26 | margin-bottom: 15px; 27 | } 28 | -------------------------------------------------------------------------------- /src/content/detail-view/sass/_tables.scss: -------------------------------------------------------------------------------- 1 | table { 2 | .disabled & { 3 | display: none; 4 | } 5 | 6 | .folded & { 7 | display: none; 8 | } 9 | 10 | border-collapse: collapse; 11 | 12 | td { 13 | padding: 2px 15px 2px 0; 14 | } 15 | 16 | &.table--bordered { 17 | width: 100%; 18 | 19 | col { 20 | width: 50%; 21 | } 22 | td, th { 23 | border: 1px solid $color-border; 24 | } 25 | th { 26 | padding: 5px 10px; 27 | text-align: left; 28 | } 29 | td { 30 | padding: 10px; 31 | } 32 | } 33 | .no-notes & { 34 | col { 35 | &:first-child { 36 | width: 100%; 37 | } 38 | &:last-child { 39 | width: 0; 40 | } 41 | } 42 | thead { 43 | display: none; 44 | } 45 | td { 46 | &:last-child { 47 | display: none; 48 | } 49 | } 50 | } 51 | .hide-notes & { 52 | col { 53 | &:first-child { 54 | width: 100%; 55 | } 56 | &:last-child { 57 | width: 0; 58 | } 59 | } 60 | th { 61 | &:last-child { 62 | text-indent: -1000px; 63 | overflow: hidden; 64 | } 65 | } 66 | td { 67 | &:last-child { 68 | border: 0; 69 | } 70 | } 71 | .mark-note { 72 | display: none; 73 | } 74 | } 75 | } 76 | 77 | .col-toggle { 78 | @include icon('double-arrow', 0 center, 18px); 79 | width: 18px; 80 | height: 20px; 81 | float: right; 82 | cursor: pointer; 83 | 84 | .hide-notes & { 85 | transform: rotate(180deg); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/content/detail-view/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/reset'; 3 | @import '../../_shared/sass/general'; 4 | @import '../../_shared/sass/links'; 5 | @import '../../_shared/sass/icons'; 6 | 7 | @import './general'; 8 | @import './tables'; 9 | 10 | @include links($color-text--turquoise); 11 | 12 | #main { 13 | padding: 20px; 14 | } 15 | .table-toggle { 16 | @include icon('down', 0 center, 14px); 17 | width: 14px; 18 | height: 12px; 19 | display: inline-block; 20 | cursor: pointer; 21 | opacity: 0.25; 22 | margin-right: 8px; 23 | 24 | .folded & { 25 | transform: rotate(-90deg); 26 | } 27 | 28 | .disabled & { 29 | display: none; 30 | } 31 | } 32 | .count { 33 | color: $color-text--disabled; 34 | } 35 | -------------------------------------------------------------------------------- /src/content/options-ui/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import { _L10N } from './../_shared/utils' 4 | 5 | _L10N(); 6 | 7 | Array.from(document.getElementsByTagName('button')).forEach(button => { 8 | button.addEventListener('click', e => { 9 | browser.runtime.sendMessage({ 10 | ev: 'open:addon-page(am)', 11 | args: [e.target.getAttribute('data-id')] 12 | }); 13 | }, false); 14 | }); 15 | -------------------------------------------------------------------------------- /src/content/options-ui/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/buttons'; 3 | 4 | @keyframes btn { 5 | from { color: $color-bg--turquoise; } 6 | to { color: $color-text--light; } 7 | } 8 | 9 | button { 10 | @extend %button; 11 | 12 | width: 120px; 13 | font-size: inherit; 14 | margin-bottom: 12px; 15 | background: #fff; 16 | 17 | &:hover { 18 | background: $color-bg--turquoise; 19 | animation: 1s forwards btn; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/content/page-injections/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import _ERRORTRACKER from '../_shared/utils' 4 | import './port' 5 | 6 | import './modules/page' 7 | import './modules/contextmenu' 8 | import './modules/marker' 9 | import './modules/notes' 10 | import './modules/tmui' 11 | import './modules/marker-popup' 12 | import './modules/auto-marker' 13 | 14 | import './main' 15 | -------------------------------------------------------------------------------- /src/content/page-injections/main.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from '../_shared/utils' 2 | import _STORE from './_store' 3 | import _RESTORER from './modules/restorer' 4 | 5 | new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'opened:sidebar': 'sendPageState', 9 | 10 | 'failed:restoration': 'activateRetry', 11 | 'succeeded:restoration': 'deactivateRetry', 12 | 'update:entry?': 'deactivateRetry', 13 | 14 | 'updated:naming-settings': 'updateStatus', 15 | 'updated:hashopt-settings': 'updateStatus', 16 | 'updated:entry-sync': 'setSyncForEntry', 17 | 'updated:entry-name': 'renameEntry', 18 | 'saved:entry': 'onSavedEntry', 19 | 'deleted:entry': 'removeEntry', 20 | 'resumed-on-hashchange': 'onResumed', 21 | 22 | // @RESTORER 23 | 'entries:found': 'onEntriesFound', 24 | 25 | // register at BG 26 | 'injected?': 'notifyBG' 27 | } 28 | }, 29 | 30 | retryActive: false, 31 | restorer: null, 32 | 33 | autoinit() { 34 | _STORE.updateLocation(); 35 | _STORE.updateStatus(); 36 | if (!this.iframe) { 37 | window.addEventListener('hashchange', this.proxy(this, this.onHashChange), false); 38 | } 39 | }, 40 | 41 | onHashChange() { 42 | if (_STORE.hashSensitive) { 43 | this.emit('hashchange'); 44 | } 45 | }, 46 | onResumed() { 47 | _STORE.updateLocation(); 48 | _STORE.isNew || _STORE.resume(); 49 | 50 | this.emit('fetch:entries', _STORE.url); 51 | }, 52 | onSavedEntry(entry) { 53 | const ignoreHash = !entry.hashSensitive; 54 | const url = ignoreHash ? _STORE.hashlessURL : _STORE.url; 55 | 56 | if (entry.url === url) { 57 | _STORE.addEntries([entry]); 58 | } 59 | }, 60 | activateRetry() { 61 | this.retryActive = true; 62 | }, 63 | deactivateRetry() { 64 | this.retryActive = false; 65 | }, 66 | sendPageState(info) { 67 | this.emit('page-state', { 68 | selection: !window.getSelection().isCollapsed, 69 | bookmark: !!document.getElementById('textmarker-bookmark-anchor'), 70 | retryActive: this.retryActive 71 | }, info); 72 | }, 73 | 74 | updateStatus() { 75 | _STORE.updateStatus(); 76 | }, 77 | setSyncForEntry(...args) { 78 | _STORE.setSyncForEntry(...args); 79 | }, 80 | renameEntry(...args) { 81 | _STORE.renameEntry(...args); 82 | }, 83 | removeEntry(name) { 84 | if (_STORE.name === name) { 85 | _STORE.removeEntry(name); 86 | this.emit('removed:entry'); 87 | } 88 | }, 89 | 90 | // @RESTORER 91 | onEntriesFound(info) { 92 | if (!_STORE.updated) { 93 | this.on('updated:store-status', this.proxy(this, this.startRestoration, info)); 94 | } else { 95 | this.startRestoration(info); 96 | } 97 | }, 98 | startRestoration(info) { 99 | let entries = info.entries; 100 | entries = Array.isArray(entries) ? entries : [entries]; 101 | _STORE.locked = info.locked; 102 | if (!this.restorer) this.restorer = _RESTORER(); 103 | _STORE.addEntries(entries); 104 | this.emit('set:entries', entries); 105 | if (info.recentlyOpenedEntry) this.updateName(entries, info.recentlyOpenedEntry); 106 | this.emit('restore:marks', info.entries); 107 | }, 108 | updateName(entries, recentlyOpenedEntry) { 109 | const firstEntry = entries[0]; 110 | const ignoreHash = !firstEntry.hashSensitive; 111 | const url = ignoreHash ? _STORE.hashlessURL : _STORE.url; 112 | 113 | if (recentlyOpenedEntry.url === url) { 114 | _STORE.name = recentlyOpenedEntry.name; 115 | } 116 | }, 117 | 118 | notifyBG(sender, sendResponse) { 119 | sendResponse(true); 120 | } 121 | }); 122 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/auto-marker.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | events: { 6 | ENV: { 7 | 'updated:mark-method-settings': 'update', 8 | 'changed:selection': 'onSelectionChange', 9 | 'sidebar:selected-marker': 'setMarker' 10 | } 11 | }, 12 | 13 | handler: null, 14 | marker: 'm', 15 | 16 | autoinit() { 17 | this.update(); 18 | }, 19 | 20 | update() { 21 | _STORE.get('settings').then(settings => { 22 | this.active = settings.misc.markmethod === 'auto'; 23 | }); 24 | }, 25 | setMarker(key) { 26 | this.marker = key; 27 | }, 28 | onSelectionChange(selected) { 29 | if (this.active) { 30 | if (selected && !this.listening) { 31 | this.startListening(); 32 | } 33 | else if (!selected && this.listening) { 34 | this.stopListening(); 35 | } 36 | } 37 | else if (this.listening) { 38 | this.stopListening(); 39 | } 40 | }, 41 | startListening() { 42 | if (!this.listening) { 43 | const handler = this.handler = this.onMouseup.bind(this); 44 | window.document.body.addEventListener('mouseup', handler, false); 45 | this.listening = true; 46 | } 47 | }, 48 | stopListening() { 49 | if (this.listening) { 50 | window.document.body.removeEventListener('mouseup', this.handler, false); 51 | this.listening = false; 52 | } 53 | }, 54 | onMouseup() { 55 | this.stopListening(); 56 | const selection = window.getSelection().toString(); 57 | if (selection) this.emit('selection-end', this.marker); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/bookmark.js: -------------------------------------------------------------------------------- 1 | import _STORE from './../_store' 2 | 3 | export default class _BOOKMARK { 4 | 5 | constructor() { 6 | this.mark = null; 7 | this.anchor = null; 8 | } 9 | 10 | set(mark) { 11 | mark = mark || this.mark; 12 | 13 | let wrappers = mark.wrappers, 14 | w = wrappers.length, 15 | anchor = wrappers[0]; 16 | 17 | mark.keyData.bookmark = true; 18 | anchor.id = 'textmarker-bookmark-anchor'; 19 | 20 | while (w--) 21 | wrappers[w].classList.add('textmarker-bookmark'); 22 | 23 | this.mark = mark; 24 | this.anchor = anchor; 25 | 26 | return this; 27 | } 28 | remove() { 29 | let mark = this.mark, 30 | anchor = this.anchor, 31 | wrappers = mark.wrappers, 32 | w = wrappers.length; 33 | 34 | anchor.id = ''; 35 | mark.keyData.bookmark = false; 36 | 37 | while (w--) 38 | wrappers[w].classList.remove('textmarker-bookmark'); 39 | } 40 | scrollIntoView(bm) { 41 | if (bm || (bm = window.document.getElementById('textmarker-bookmark-anchor'))) 42 | bm.scrollIntoView({ behavior: 'smooth', block: 'center' }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/contextmenu.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | events: { 6 | ENV: { 7 | 'toggled:addon': 'activate' 8 | } 9 | }, 10 | active: false, 11 | 12 | autoinit() { 13 | this.activate(true); 14 | }, 15 | 16 | activate(on) { 17 | if (on && !this.active) { 18 | this.registerHandler(); 19 | this.active = true; 20 | } 21 | else if (!on && this.active) { 22 | this.removeListeners(); 23 | this.active = false; 24 | } 25 | }, 26 | registerHandler() { 27 | this.addListener('mousedown', e => { 28 | if (e.button === 2) { 29 | if (e.target.classList.contains('textmarker-highlight')) { 30 | _STORE.tmid = e.target.getAttribute('data-tm-id'); 31 | } 32 | else _STORE.tmid = ''; 33 | } 34 | }, window.document); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/marker-popup.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | events: { 6 | ENV: { 7 | 'updated:marker-settings': 'recreate', 8 | 'updated:mark-method-settings': 'update', 9 | 'changed:selection': 'onSelectionChange' 10 | } 11 | }, 12 | 13 | handler: null, 14 | appended: false, 15 | height: 0, 16 | 17 | autoinit() { 18 | this.update().then(() => this.create()); 19 | }, 20 | 21 | recreate() { 22 | this.remove().update().then(() => this.create()); 23 | }, 24 | update() { 25 | return _STORE.get('settings').then(settings => { 26 | this.active = settings.misc.markmethod === 'popup'; 27 | this.markers = settings.markers; 28 | }); 29 | }, 30 | create() { 31 | const popup = this.el = window.document.createElement('tmpopup'); 32 | const bgColorRegExp = /background-color:(#[a-f0-9]{6})/; 33 | let bgColor, colorBtn; 34 | for (let m in this.markers) { 35 | bgColor = this.markers[m].style.match(bgColorRegExp); 36 | if (bgColor) { 37 | colorBtn = window.document.createElement('tmpopupcolor'); 38 | popup.appendChild(colorBtn); 39 | colorBtn.style.background = bgColor.pop(); 40 | colorBtn.id = 'tmpopupcolor--' + m; 41 | } 42 | } 43 | }, 44 | remove() { 45 | if (this.appended) { 46 | window.document.body.removeChild(this.el); 47 | this.el.removeEventListener('mousedown', this.handler, false); 48 | this.appended = false; 49 | } 50 | return this; 51 | }, 52 | show() { 53 | const popup = this.el; 54 | const style = popup.style; 55 | let popupHeight; 56 | 57 | if (!this.appended) { 58 | window.document.body.appendChild(popup); 59 | const handler = this.handler = this.onMousedown.bind(this); 60 | popup.addEventListener('mousedown', handler, false); 61 | this.appended = true; 62 | 63 | const selectionPosition = window.getSelection().getRangeAt(0).getBoundingClientRect(); 64 | 65 | style.top = selectionPosition.top - selectionPosition.height + window.scrollY + 'px'; 66 | style.left = selectionPosition.left + window.scrollX + 'px'; 67 | 68 | popupHeight = popup.offsetHeight; 69 | 70 | style.top = Math.max(0, parseInt(style.top) - popupHeight) + 'px'; 71 | } 72 | else if ((popupHeight = popup.offsetHeight) !== this.height) { 73 | style.top = parseInt(style.top) + popupHeight - this.height + 'px'; 74 | } 75 | this.height = popupHeight; 76 | }, 77 | onSelectionChange(selected) { 78 | if (this.active) { 79 | if (!selected) this.remove(); 80 | else this.show(); 81 | } 82 | else if (this.appended) { 83 | this.remove(); 84 | } 85 | }, 86 | onMousedown(e) { 87 | e.preventDefault(); 88 | e.stopPropagation(); 89 | const el = e.target; 90 | if (el.nodeName === 'TMPOPUPCOLOR') { 91 | this.emit('clicked:popup-marker', el.id.split('--').pop()); 92 | } 93 | } 94 | }); 95 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/notes.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | import _NOTE from './note' 4 | 5 | new _MODULE({ 6 | events: { 7 | ENV: { 8 | 'add:note': 'addAndShow', 9 | 'removed:note': 'removeNoteStorage', 10 | 'restore:notes': 'restore', 11 | 'removed:mark': 'removeNote', 12 | 'toggle:notes': 'toggleAll', 13 | 'sidebar:toggle-notes': 'toggleAll', 14 | 'updated:misc-settings': 'updateStyle', 15 | 'start:drag': 'startDraggingNote', 16 | 'opened:sidebar': 'sendNotesState', 17 | 'finished:all-restorations': 'report' 18 | } 19 | }, 20 | 21 | notes: {}, 22 | toggle: null, 23 | dragHandler: null, 24 | dragStopHandler: null, 25 | 26 | autoinit() { 27 | this.updateStyle(); 28 | }, 29 | add(mark, color) { 30 | const note = this.notes[mark.id]; 31 | if (note) return note; 32 | if (!_STORE.restoring) this.emit('added:note'); 33 | return this.notes[mark.id] = new _NOTE(mark, color); 34 | }, 35 | restore(marks) { 36 | for (let mark of marks) { 37 | if (mark.keyData.note) { 38 | this.add(mark); 39 | } 40 | } 41 | }, 42 | addAndShow(mark, color) { 43 | this.add(mark, color).show(); 44 | }, 45 | removeNoteStorage(id) { 46 | delete this.notes[id]; 47 | if (this.isEmpty(this.notes)) this.emit('removed:last-note'); 48 | }, 49 | removeNote(id) { 50 | if (this.notes[id]) this.notes[id].remove(); 51 | }, 52 | toggleAll() { 53 | if (!this.notes) return; 54 | const notes = this.notes; 55 | let meth = window.document.getElementsByTagName('tmnote').length ? 'hide' : 'show', 56 | condition = meth === 'hide' ? true : false, 57 | note; 58 | for (let n in notes) { 59 | note = notes[n]; 60 | if (note.visible === condition) { 61 | note[meth](); 62 | } 63 | } 64 | }, 65 | updateStyle() { 66 | const bodyClasses = window.document.body.classList; 67 | _STORE.get('settings').then(settings => { 68 | if (settings && settings.misc) { 69 | if (settings.misc.notetransp) bodyClasses.add('tmnotes--0_8'); 70 | else bodyClasses.remove('tmnotes--0_8'); 71 | if (settings.misc.noteplainview) bodyClasses.add('tmnotes--plain-view'); 72 | else bodyClasses.remove('tmnotes--plain-view'); 73 | _STORE.noteFontSize = settings.misc.notefontsize || 12; 74 | } 75 | }); 76 | }, 77 | isEmpty(obj) { 78 | return !Object.keys(obj).length; 79 | }, 80 | startDraggingNote(note) { 81 | const dragHandler = this.dragHandler = (e) => this.emitDragEvent(note, e); 82 | const dragStopHandler = this.dragStopHandler = (e) => this.stopDraggingNote(note, e); 83 | const doc = window.document; 84 | doc.addEventListener('mousemove', dragHandler, false); 85 | doc.addEventListener('mouseup', dragStopHandler, false); 86 | doc.addEventListener('touchmove', dragHandler, false); 87 | doc.addEventListener('touchend', dragStopHandler, false); 88 | }, 89 | stopDraggingNote(note, e) { 90 | const doc = window.document; 91 | doc.removeEventListener('mousemove', this.dragHandler, false); 92 | doc.removeEventListener('mouseup', this.dragStopHandler, false); 93 | doc.removeEventListener('touchmove', this.dragHandler, false); 94 | doc.removeEventListener('touchend', this.dragStopHandler, false); 95 | 96 | this.emit('dragstop:note', note, e); 97 | }, 98 | emitDragEvent(note, e) { 99 | e.preventDefault(); 100 | this.emit('drag:note', note, e); 101 | }, 102 | sendNotesState(info) { 103 | this.emit('notes-state', !this.isEmpty(this.notes), info); 104 | }, 105 | report() { 106 | this.isEmpty(this.notes) || this.emit('added:note'); 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /src/content/page-injections/modules/page.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: window.document, 6 | autoPause: true, 7 | events: { 8 | ENV: { 9 | 'started:restorations': 'removeListeners', 10 | 'completed:restoration-process': 'addListeners' 11 | }, 12 | DOM: { 13 | keydown: { 14 | '*': 'delegate' 15 | }, 16 | selectionchange: { 17 | '*': 'onSelectionChange' 18 | } 19 | } 20 | }, 21 | 22 | selectionCollapsed: true, 23 | shiftSensitiveKeys: [13, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 171, 173], 24 | keyCodeMap: { 25 | '13': 'Enter', 26 | '48': '0', '49': '1', '50': '2', '51': '3', '52': '4', 27 | '53': '5', '54': '6', '55': '7', '56': '8', '57': '9', 28 | '171': '+', '173': '-' 29 | }, 30 | 31 | isEditable(el) { 32 | const name = el.tagName; 33 | 34 | return (name === 'TEXTAREA' || name === 'INPUT' || el.contentEditable === 'true'); 35 | }, 36 | delegate(e) { 37 | let key = e.key.toLowerCase(); 38 | const keyCode = e.keyCode, 39 | modKey = (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey), 40 | arrowKeys = ['arrowdown', 'arrowup'], 41 | lockedActions = ['b', 's', 'y', 'z', 'd'], 42 | functionKeys = lockedActions.concat(arrowKeys).concat(['c', 'n']), 43 | defaultMarkers = ['m', '2', '3']; 44 | 45 | if (_STORE.locked && lockedActions.includes(key)) return true; 46 | 47 | if (this.shiftSensitiveKeys.includes(keyCode)) key = this.keyCodeMap[keyCode]; 48 | 49 | if (!functionKeys.includes(key) && window.getSelection().isCollapsed) return true; 50 | 51 | if (this.isEditable(e.target)) return true; 52 | 53 | _STORE.get('settings').then(settings => { 54 | 55 | if (!settings) return true; 56 | 57 | const origKey = key; 58 | const markers = settings.markers; 59 | const shortcuts = settings.shortcuts; 60 | const isMarkerKey = markers[key]; 61 | const isCustomMarkerKey = isMarkerKey && !defaultMarkers.includes(key); 62 | if (isCustomMarkerKey) key = 'cm'; 63 | const setting = shortcuts[key]; 64 | 65 | if (!setting) return true; 66 | 67 | if (!modKey) { 68 | if (key === 'w' && !setting[0] && setting[1]) { 69 | this.emit('lookup:word', window.getSelection().toString()); 70 | } 71 | else if (isMarkerKey && !setting[0] && setting[1]) { 72 | this.emit('pressed:marker-key', e, origKey); 73 | } 74 | } else { 75 | if (!setting[1]) return true; 76 | 77 | const shortcut = setting[0].split('-'); 78 | const s1 = shortcut[0]; 79 | const s2 = shortcut[1]; 80 | 81 | if (!e[s1] || (s2 && !e[s2])) return true; 82 | 83 | if (key === 'w') this.emit('lookup:word', window.getSelection().toString()); 84 | 85 | else if (isMarkerKey) { 86 | this.emit('pressed:marker-key', e, origKey); 87 | } 88 | else this.emit('pressed:hotkey', key) 89 | } 90 | }); 91 | }, 92 | onSelectionChange(e) { 93 | const selectionCollapsed = window.getSelection().isCollapsed; 94 | if (this.selectionCollapsed !== selectionCollapsed) { 95 | this.emit('changed:selection', !selectionCollapsed); 96 | this.selectionCollapsed = selectionCollapsed; 97 | } 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /src/content/page-injections/port.js: -------------------------------------------------------------------------------- 1 | import { _PORT } from '../_shared/utils' 2 | 3 | export default new _PORT({ 4 | name: 'injection', 5 | type: 'content', 6 | events: { 7 | ONEOFF: [ 8 | 'copy:marks', 9 | 'save:entry?', 10 | 'update:entry?', 11 | 'lookup:word', 12 | 'error:browser-console', 13 | 'changed:selection', 14 | 'unsaved-changes', 15 | 'clicked:mark', 16 | 'activated:mark', 17 | 'added:bookmark', 18 | 'removed:bookmark', 19 | 'added:note', 20 | 'removed:last-note', 21 | 'page-state', 22 | 'notes-state', 23 | 'visually-ordered:marks', 24 | 'fetch:entries', 25 | 26 | // @RESTORER 27 | 'finished:restoration', 28 | 'failed:restoration', 29 | 'succeeded:restoration', 30 | 'failed:restore-range', 31 | 'canceled:save-after-canceled-restoration' 32 | ] 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/components/_copyshop.scss: -------------------------------------------------------------------------------- 1 | copyshop { 2 | all: unset !important; 3 | display: block !important; 4 | position: absolute !important; 5 | bottom: -100% !important; 6 | left: -100% !important; 7 | } 8 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/components/_marker-popup.scss: -------------------------------------------------------------------------------- 1 | tmpopup { 2 | position: absolute; 3 | display: block; 4 | z-index: $layer--max; 5 | background: $color-bg--dark; 6 | border-radius: 3px; 7 | box-sizing: border-box !important; 8 | padding: 2px 4px; 9 | } 10 | tmpopupcolor { 11 | display: inline-block; 12 | width: 16px; 13 | height: 16px; 14 | border-radius: 50%; 15 | margin: 4px 2px 0; 16 | cursor: pointer; 17 | 18 | &:hover { 19 | box-shadow: 0 0 1px $color-bg--light; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/components/_marks.scss: -------------------------------------------------------------------------------- 1 | tm {display: inline !important;} 2 | span.textmarker-highlight { 3 | all: inherit; 4 | background-image: none !important; 5 | display: inline !important; 6 | position: static !important; 7 | padding: 0 !important; 8 | margin: 0 !important; 9 | border: 0; 10 | outline: 0 !important; 11 | float: none !important; 12 | cursor: pointer; 13 | 14 | &--active { 15 | border-top: 4px groove #2ba5b7 !important; 16 | border-bottom: 4px groove #2ba5b7 !important; 17 | } 18 | } 19 | 20 | #textmarker-bookmark-anchor { 21 | padding-left: 15px !important; 22 | @include icon('bm2', -2px 0, $important: !important); 23 | } 24 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/components/_notes.scss: -------------------------------------------------------------------------------- 1 | $colors: 2 | (green, #dfd, #efe, #cfc), 3 | (white, #eee, #fff, #eee), 4 | (yellow, #ffd, #ffe, #ffc), 5 | (orange, #fec, #fed, #feb), 6 | (red, #fdd, #fee, #fcc), 7 | (purple, #edf, #eef, #ecf), 8 | (blue, #cef, #def, #bef), 9 | (turquoise, #c1e9f2, #d5f0f7, #b9e4ec); 10 | 11 | @each $name, $val, $val2, $val3 in $colors { 12 | .tmnote--#{$name} { 13 | tmnotepalette, 14 | tmnoteheader { 15 | background: $val3; 16 | } 17 | tmnoteactions { 18 | background: $val2; 19 | &:hover { 20 | background: $val; 21 | } 22 | } 23 | textarea { 24 | background-image: linear-gradient($val2, $val) !important; 25 | } 26 | } 27 | .tmnotecolor--#{$name} { 28 | border-color: $val; 29 | background-image: linear-gradient($val2, $val); 30 | } 31 | } 32 | 33 | tmnote { 34 | display: none; 35 | position: absolute; 36 | font-family: Verdana, sans-serif; 37 | font-size: 12px; 38 | color: $color-grey--dark; 39 | line-height: 1.4; 40 | margin: 0; 41 | z-index: $layer--max; 42 | box-sizing: border-box !important; 43 | 44 | .tmnotes--0_8 & { 45 | opacity: 0.8; 46 | } 47 | &:hover { 48 | opacity: 1 !important; 49 | } 50 | } 51 | tmnoteheader { 52 | height: 22px; 53 | position: relative; 54 | display: block; 55 | z-index: 1; 56 | cursor: move; 57 | 58 | .tmnotes--plain-view & { 59 | display: none; 60 | } 61 | tmnote:hover & { 62 | display: block !important; 63 | } 64 | } 65 | tmnotepalette { 66 | position: absolute; 67 | top: 0; 68 | left: 0; 69 | z-index: 1; 70 | width: 100%; 71 | padding: 4px 0 6px; 72 | text-align: center; 73 | box-sizing: border-box; 74 | border-bottom: 1px solid $color-border--light; 75 | } 76 | tmnotecolor { 77 | width: 22px; 78 | height: 14px; 79 | position: relative; 80 | display: inline-block; 81 | margin: 0 7px; 82 | border-top: 2px solid; 83 | cursor: pointer; 84 | box-shadow: 0 1px 3px $color-shadow--lighter; 85 | &:hover { 86 | top: -1px; 87 | box-shadow: 0 1px 3px $color-shadow--light; 88 | filter: brightness(1.02); 89 | } 90 | } 91 | tmnoteactions { 92 | position: absolute; 93 | display: block; 94 | right: -20px; 95 | background: #ffe; 96 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); 97 | border-radius: 0 2px 2px 2px; 98 | box-sizing: border-box !important; 99 | 100 | .tmnotes--plain-view & { 101 | display: none; 102 | } 103 | tmnote:hover & { 104 | display: block !important; 105 | } 106 | } 107 | tmnotedelete, 108 | tmnoteminimize, 109 | tmnotecustomize { 110 | display: block; 111 | font-size: 14px; 112 | border-radius: 2px; 113 | cursor: pointer; 114 | color: $color-text--disabled; 115 | line-height: 1; 116 | height: 20px; 117 | box-sizing: border-box !important; 118 | 119 | &:hover { 120 | color: $color-text--dark; 121 | } 122 | } 123 | tmnotedelete { 124 | top: 40px; 125 | font-size: 10px; 126 | font-weight: bold; 127 | padding: 5px 6px 0 6px; 128 | } 129 | tmnoteminimize { 130 | top: 60px; 131 | font-size: 18px; 132 | padding: 0px 4px 0 5px; 133 | } 134 | tmnotecustomize { 135 | top: 20px; 136 | font-size: 16px; 137 | padding: 1px 3px 2px 5px; 138 | } 139 | body > tmnote { 140 | > textarea[data-tm-note] { 141 | display: block !important; 142 | padding: 10px !important; 143 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5) !important; 144 | position: relative !important; 145 | width: 300px; 146 | min-height: 3em !important; 147 | font-family: Verdana, sans-serif !important; 148 | font-size: 12px !important; 149 | border: 0 !important; 150 | margin: 0 !important; 151 | box-sizing: border-box !important; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/components/_tmui.scss: -------------------------------------------------------------------------------- 1 | tmui { 2 | display: block; 3 | z-index: $layer--max; 4 | position: fixed; 5 | background: $color-bg--light; 6 | border: 1px solid $color-border--light; 7 | padding-top: 3px; 8 | box-sizing: border-box !important; 9 | } 10 | tmbm, tmnotestoggle { 11 | display: inline-block; 12 | cursor: pointer; 13 | margin-left: 3px; 14 | margin-right: 3px; 15 | vertical-align: top; 16 | width: 12px; 17 | filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.25)); 18 | } 19 | tmbm { 20 | @include icon('bm', center); 21 | height: 18px; 22 | } 23 | tmbm:hover { 24 | filter: brightness(1.15); 25 | } 26 | tmnotestoggle { 27 | @include icon('note', center); 28 | height: 16px; 29 | } 30 | tmnotestoggle:hover { 31 | filter: brightness(1.15); 32 | } 33 | -------------------------------------------------------------------------------- /src/content/page-injections/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/icons'; 3 | 4 | @import './components/marks'; 5 | @import './components/notes'; 6 | @import './components/tmui'; 7 | @import './components/marker-popup'; 8 | @import './components/copyshop'; 9 | -------------------------------------------------------------------------------- /src/content/sidebar/_store.js: -------------------------------------------------------------------------------- 1 | import { _STORE } from './../_shared/utils' 2 | 3 | export default new _STORE({ 4 | events: { 5 | ENV: { 6 | 'toggled:sync': 'onToggledSync', 7 | 'saved:entry': 'updateEntry', 8 | 'entry:found': 'updateEntryOnFound', 9 | 'entry:found-for-tab': 'updateEntry', 10 | 'entry:deleted-for-tab': 'resume' 11 | } 12 | }, 13 | 14 | env: 'sidebar', 15 | entry: null, 16 | locked: false, 17 | 18 | updateEntry(entry) { 19 | if (entry) { 20 | const isArr = Array.isArray(entry); 21 | const currentEntry = !!this.entry; 22 | 23 | this.locked = this.locked || isArr || entry.locked; 24 | 25 | if (!this.locked || isArr) { 26 | this.entry = entry; 27 | } 28 | else if (this.locked && !isArr) { 29 | if (this.entry && Array.isArray(this.entry)) this.entry.push(entry); 30 | else this.entry = [entry]; 31 | } 32 | 33 | if (currentEntry) this.emit('updated:stored-entry', this.entry); 34 | else this.emit('stored:entry', this.entry); 35 | } 36 | }, 37 | updateEntryOnFound(entry) { 38 | if (entry) { 39 | this.updateEntry(entry); 40 | if (!Array.isArray(entry)) { 41 | this.emit('initially-stored:entry', entry); 42 | } 43 | } 44 | }, 45 | 46 | resume() { 47 | this.entry = null; 48 | this.locked = false; 49 | this.emit('removed:entry'); 50 | }, 51 | 52 | 53 | _get_mode() { 54 | return browser.storage[this.area_settings].get().then(storage => { 55 | if (!storage || !storage.settings || storage.settings.addon.active) return true; 56 | return false; 57 | }); 58 | }, 59 | _get_autosave() { 60 | return browser.storage[this.area_settings].get().then(storage => { 61 | if (!storage || !storage.settings) return false; 62 | return storage.settings.history.autosave; 63 | }); 64 | }, 65 | _get_settings() { 66 | return browser.storage[this.area_settings].get().then(storage => storage.settings); 67 | }, 68 | _get_markers() { 69 | return browser.storage[this.area_settings].get().then(storage => storage.settings.markers); 70 | }, 71 | _get_pagenotes() { 72 | return browser.storage[this.area_settings].get().then(storage => storage.pagenotes || null); 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /src/content/sidebar/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import _ERRORTRACKER from './../_shared/utils' 4 | import { _MODULE } from './../_shared/utils' 5 | import { _L10N } from './../_shared/utils' 6 | import _PORT from './port' 7 | import _STORE from './_store' 8 | 9 | import './modules/themes' 10 | import './modules/tabs' 11 | import './modules/header' 12 | import './modules/meta-infos' 13 | import './modules/tags' 14 | import './modules/page-notes' 15 | import './modules/markers' 16 | import './modules/history-actions' 17 | import './modules/mark-actions' 18 | import './modules/page-actions' 19 | import './modules/marks' 20 | import './modules/links' 21 | 22 | _L10N(); 23 | 24 | new _MODULE({ 25 | events: { 26 | ENV: { 27 | 'started:app': 'onStart', 28 | 'toggled:addon': 'power', 29 | 'stored:entry': 'toggle', 30 | 'updated:stored-entry': 'toggle', 31 | 'initially-stored:entry': 'toggle' 32 | } 33 | }, 34 | 35 | autoinit() { 36 | this.emit('opened:sidebar', { tab: 'active' }); 37 | }, 38 | 39 | power(on) { 40 | const placeholder = document.getElementById('textmarker-sidebar--paused'); 41 | const content = document.getElementById('textmarker-sidebar'); 42 | 43 | if (on) { 44 | placeholder.classList.add('u-display--none'); 45 | content.classList.remove('u-display--none'); 46 | } else { 47 | placeholder.classList.remove('u-display--none'); 48 | content.classList.add('u-display--none'); 49 | } 50 | }, 51 | onStart() { 52 | _STORE.get('mode').then(mode => this.power(mode)); 53 | }, 54 | toggle(entry) { 55 | const sidebar = document.getElementById('textmarker-sidebar'); 56 | if (entry && _STORE.locked) { 57 | sidebar.classList.add('textmarker-sidebar--locked'); 58 | } else { 59 | sidebar.classList.remove('textmarker-sidebar--locked'); 60 | } 61 | } 62 | }) 63 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/header.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('header'), 6 | events: { 7 | ENV: { 8 | 'stored:entry': 'render', 9 | 'updated:stored-entry': 'render' 10 | } 11 | }, 12 | 13 | render(entry) { 14 | const header = this.el; 15 | 16 | if (!entry) header.classList.add('u-display--none'); 17 | else if (Array.isArray(entry)) return; 18 | 19 | header.classList.remove('u-display--none'); 20 | this.updateName(entry.name); 21 | }, 22 | 23 | updateName(name) { 24 | const el = this.el.getElementsByClassName('header__name')[0]; 25 | el.innerText = name; 26 | el.title = name; 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/history-actions.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('history-actions'), 6 | events: { 7 | ENV: { 8 | 'started:app': 'toggleSave', 9 | 'updated:settings': 'toggleSave', 10 | 'set-areas-after-sync-change': 'toggleSave', 11 | 'updated:entry-on-save': 'deactivateSave', 12 | 'saved:entry': 'deactivateSave', 13 | 'unsaved-changes': 'activateSave', 14 | 'finished:restoration': 'activateRetry', 15 | 'update:entry?': 'deactivateRetry', 16 | 'stored:entry': 'updateImmut', 17 | 'page-state': 'onPageState', 18 | 'initially-stored:entry': 'updateImmut' 19 | }, 20 | DOM: { 21 | click: { 22 | '#page-action--retry': 'retryRestoration', 23 | '#page-action--save': 'save', 24 | '#page-action--delete': 'onDeleteRequest', 25 | '.switch': 'toggleImmut' 26 | } 27 | } 28 | }, 29 | 30 | retryBtnShown: false, 31 | saveBtn: document.getElementById('page-action--save'), 32 | retryBtn: document.getElementById('page-action--retry'), 33 | 34 | autoinit() { 35 | this.toggleSave(); 36 | }, 37 | 38 | save() { 39 | this.emit('sidebar:save-changes', { tab: 'active' }); 40 | }, 41 | retryRestoration() { 42 | this.emit('sidebar:retry-restoration', { tab: 'active' }); 43 | this.deactivateRetry(); 44 | }, 45 | onDeleteRequest() { 46 | const confirmed = window.confirm(browser.i18n.getMessage('sb514')); 47 | if (confirmed) { 48 | if (_STORE.entry) { 49 | this.emit('sidebar:delete-entry', [_STORE.entry.name], { tab: 'active' }); 50 | } else { 51 | window.alert(browser.i18n.getMessage('sb515')); 52 | } 53 | } 54 | }, 55 | toggleSave() { 56 | _STORE.get('autosave').then(autosave => { 57 | const meth = autosave ? 'add' : 'remove'; 58 | this.saveBtn.classList[meth]('u-display--none'); 59 | }); 60 | }, 61 | activateSave(on = true) { 62 | if (on) this.saveBtn.removeAttribute('disabled'); 63 | else this.saveBtn.setAttribute('disabled', true); 64 | }, 65 | deactivateSave() { 66 | this.activateSave(false); 67 | }, 68 | activateRetry() { 69 | if (!this.retryBtnShown) { 70 | this.retryBtn.classList.remove('u-display--none'); 71 | this.retryBtnShown = true; 72 | } 73 | }, 74 | deactivateRetry() { 75 | if (this.retryBtnShown) { 76 | this.retryBtn.classList.add('u-display--none'); 77 | this.retryBtnShown = false; 78 | } 79 | }, 80 | toggleImmut(e, el) { 81 | el = el.classList.contains('switch--immut') ? el : el.parentNode; 82 | el.classList.toggle('active'); 83 | this.emit('sidebar:immut', el.classList.contains('active'), { tab: 'active' }); 84 | }, 85 | updateImmut(entry) { 86 | if (entry) { 87 | const meth = entry.immut ? 'add' : 'remove'; 88 | document.getElementById('page-action--immut').classList[meth]('active'); 89 | document.getElementById('switch-box').classList.remove('u-display--none'); 90 | } else { 91 | document.getElementById('switch-box').classList.add('u-display--none'); 92 | } 93 | }, 94 | onPageState(state) { 95 | if (state.retryActive) this.activateRetry(); 96 | } 97 | }); 98 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/links.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | 3 | new _DOMMODULE({ 4 | el: document.getElementById('tab--links'), 5 | events: { 6 | DOM: { 7 | click: { 8 | '.link': 'link', 9 | '.link__icon': 'link', 10 | '.link__text': 'link' 11 | } 12 | } 13 | }, 14 | 15 | link(e, el) { 16 | el = el.classList.contains('link') ? el : el.parentNode; 17 | this.emit('open:addon-page(sb)', el.getAttribute('data-id')); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/mark-actions.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('mark-actions'), 6 | events: { 7 | ENV: { 8 | 'clicked:mark': 'activate', 9 | 'activated:mark': 'activate' 10 | }, 11 | DOM: { 12 | click: { 13 | '.action-button--mark': 'markAction' 14 | } 15 | } 16 | }, 17 | frame: 0, 18 | buttons: [], 19 | 20 | autoinit() { 21 | this.buttons = Array.from(this.el.getElementsByTagName('button')); 22 | }, 23 | markAction(e, el) { 24 | if (el.hasAttribute('disabled')) return; 25 | const action = el.getAttribute('data-action'); 26 | if (action === 'copy') { 27 | browser.permissions.contains({ permissions: ['clipboardWrite'] }).then(granted => { 28 | this.emit('sidebar:' + el.getAttribute('data-action'), granted, null, { tab: 'active', frameId: this.frame }); 29 | }); 30 | } else { 31 | this.emit('sidebar:' + el.getAttribute('data-action'), null, null, { tab: 'active', frameId: this.frame }); 32 | } 33 | }, 34 | activate(markInfos, sender) { 35 | this.frame = sender && sender.frameId ? sender.frameId : 0; 36 | 37 | this.buttons.forEach(btn => { 38 | let type = btn.getAttribute('data-action'); 39 | if ( 40 | type === 'copy' || 41 | type === 'delete-highlight' || 42 | (typeof markInfos[type] === 'boolean' && !markInfos[type]) || 43 | (type === 'delete-bookmark' && markInfos.bookmark) 44 | ) { 45 | btn.removeAttribute('disabled'); 46 | btn.parentNode.classList.remove('disabled'); 47 | } 48 | }); 49 | }, 50 | deactivate() { 51 | this.buttons.forEach(btn => { 52 | btn.setAttribute('disabled', true); 53 | btn.parentNode.classList.add('disabled'); 54 | }); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/meta-infos.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('tab--meta'), 6 | events: { 7 | ENV: { 8 | 'stored:entry': 'render', 9 | 'updated:stored-entry': 'render' 10 | } 11 | }, 12 | 13 | render(entry) { 14 | if (entry && !Array.isArray(entry)) { 15 | const yes = browser.i18n.getMessage('yes'); 16 | const no = browser.i18n.getMessage('no'); 17 | 18 | document.getElementById('meta__number-marks').innerText = entry.marks.length; 19 | document.getElementById('meta__created').innerText = this.optimizeDateString(new Date(entry.first).toLocaleString()); 20 | document.getElementById('meta__last-modified').innerText = this.optimizeDateString(new Date(entry.last).toLocaleString()); 21 | 22 | ['synced', 'immut'] 23 | .forEach(field => document.getElementById('meta__' + field).innerText = entry[field] ? yes : no); 24 | } 25 | }, 26 | 27 | optimizeDateString(date) { 28 | return (date 29 | .replace(/^(\d{1})(\D{1})/, (m, p, q)=> '0' + p + q) 30 | .replace(/(\D{1})(\d{1}\D{1})/g, (m, p, q) => p + '0' + q)); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/page-actions.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('page-actions'), 6 | events: { 7 | ENV: { 8 | 'added:bookmark': 'activateBookmark', 9 | 'removed:bookmark': 'deactivateBookmark', 10 | 'added:note': 'activateNotes', 11 | 'removed:last-note': 'deactivateNotes', 12 | 'page-state': 'onPageState', 13 | 'notes-state': 'onNotesState' 14 | }, 15 | DOM: { 16 | click: { 17 | '.action-button--page': 'pageAction' 18 | } 19 | } 20 | }, 21 | 22 | activateBookmark() { 23 | this.activate('scroll', true); 24 | }, 25 | deactivateBookmark() { 26 | this.activate('scroll', false); 27 | }, 28 | activateNotes() { 29 | this.activate('notes', true); 30 | }, 31 | deactivateNotes() { 32 | this.activate('notes', false); 33 | }, 34 | activate(type, on) { 35 | const btn = document.getElementById('page-action--' + type); 36 | if (on) { 37 | btn.removeAttribute('disabled'); 38 | btn.parentNode.classList.remove('disabled'); 39 | } 40 | else { 41 | btn.setAttribute('disabled', true); 42 | btn.parentNode.classList.add('disabled'); 43 | } 44 | }, 45 | pageAction(e, el) { 46 | this.emit('sidebar:' + el.getAttribute('data-action'), { tab: 'active' }); 47 | }, 48 | onPageState(state) { 49 | if (state.bookmark) this.activateBookmark(); 50 | }, 51 | onNotesState(notes) { 52 | if (notes) this.activateNotes(); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/tabs.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('textmarker-sidebar'), 6 | events: { 7 | ENV: { 8 | 'stored:entry': 'showEntrySpecificTabs' 9 | }, 10 | DOM: { 11 | click: { 12 | '.tab__title': 'toggle', 13 | '.tab__name': 'toggle', 14 | '.tab__toggle': 'toggle' 15 | } 16 | } 17 | }, 18 | 19 | tabs: {}, 20 | 21 | autoinit() { 22 | _STORE.get('settings').then(settings => { 23 | if (!settings || !settings.sb) return; 24 | const tabSettings = settings.sb.tabs; 25 | for (let tab in tabSettings) { 26 | this.tabs[tab] = document.getElementById('tab--' + tab); 27 | if (tabSettings[tab].unfolded) this.open(tab); 28 | else this.close(tab); 29 | } 30 | }); 31 | }, 32 | 33 | open(tab) { 34 | this.tabs[tab].classList.remove('tab--folded'); 35 | }, 36 | close(tab) { 37 | this.tabs[tab].classList.add('tab--folded'); 38 | }, 39 | toggle(e, el) { 40 | el = el.nodeName === 'H2' ? el : el.parentNode; 41 | const id = el.getAttribute('data-target'); 42 | const tab = id.split('--').pop(); 43 | const tabEl = document.getElementById(id); 44 | tabEl.classList.toggle('tab--folded'); 45 | this.emit('toggled:sidebar-tab', tab, !tabEl.classList.contains('tab--folded')); 46 | }, 47 | showEntrySpecificTabs() { 48 | Array.from(document.getElementsByClassName('tab--entry')) 49 | .forEach(tab => tab.classList.remove('u-display--none')); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/tags.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('tab--tags'), 6 | events: { 7 | ENV: { 8 | 'stored:entry': 'render' 9 | }, 10 | DOM: { 11 | click: { 12 | '.tags__remove': 'removeTag', 13 | '.add-tag': 'addTag' 14 | } 15 | } 16 | }, 17 | 18 | render(entry) { 19 | if (entry && !Array.isArray(entry)) { 20 | const tags = entry.tag ? entry.tag.split(' ') : []; 21 | document.getElementById('tags').innerText = ''; 22 | tags.forEach(tag => this.renderTag(tag)); 23 | } 24 | }, 25 | renderTag(tag) { 26 | const container = document.getElementById('tags'); 27 | const el = document.createElement('div'); 28 | const del = document.createElement('span'); 29 | const x = document.createTextNode(String.fromCharCode(10005)); 30 | el.className = 'tags__item u-overflow--ellipsis'; 31 | del.className = 'tags__remove'; 32 | del.setAttribute('data-tag', tag); 33 | el.innerText = tag; 34 | del.appendChild(x); 35 | el.appendChild(del); 36 | container.appendChild(el); 37 | }, 38 | addTag() { 39 | const inp = document.getElementById('new-tag'); 40 | let tag = inp.value.trim(); 41 | if (!tag) return; 42 | this.emit('add:tag', tag, _STORE.entry); 43 | inp.value = ''; 44 | tag.split(' ').forEach(tag => this.renderTag(tag)); 45 | }, 46 | removeTag(e, el) { 47 | this.emit('remove:tag', el.getAttribute('data-tag'), _STORE.entry); 48 | el.parentNode.parentNode.removeChild(el.parentNode); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/content/sidebar/modules/themes.js: -------------------------------------------------------------------------------- 1 | import { _DOMMODULE } from './../../_shared/utils' 2 | import _STORE from './../_store' 3 | 4 | new _DOMMODULE({ 5 | el: document.getElementById('tab--themes'), 6 | events: { 7 | DOM: { 8 | change: { 9 | '.theme-opt': 'onChange' 10 | } 11 | } 12 | }, 13 | 14 | theme: 'default', 15 | 16 | autoinit() { 17 | _STORE.get('settings').then(settings => { 18 | const theme = settings && settings.sb && settings.sb.theme ? settings.sb.theme : 'default'; 19 | document.getElementById(`theme--${theme}`).checked = true; 20 | this.update(theme); 21 | }); 22 | }, 23 | 24 | onChange(e, el) { 25 | const theme = el.getAttribute('data-value'); 26 | this.emit('changed:sidebar-theme', theme); 27 | this.update(theme); 28 | }, 29 | update(theme) { 30 | this.theme = theme; 31 | document.getElementById('textmarker-sidebar').className = `textmarker-sidebar--${theme}`; 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/content/sidebar/port.js: -------------------------------------------------------------------------------- 1 | import { _PRIVPORT } from './../_shared/utils' 2 | 3 | export default new _PRIVPORT({ 4 | name: 'sidebar', 5 | type: 'privileged', 6 | id: Math.random().toString().slice(2, 16), 7 | events: { 8 | CONNECTION: [ 9 | 'change:bg-setting', 10 | 'error:browser-console', 11 | 'sidebar:highlight', 12 | 'sidebar:delete-highlight', 13 | 'sidebar:bookmark', 14 | 'sidebar:delete-bookmark', 15 | 'sidebar:note', 16 | 'sidebar:immut', 17 | 'sidebar:save-changes', 18 | 'sidebar:retry-restoration', 19 | 'sidebar:delete-entry', 20 | 'sidebar:undo', 21 | 'sidebar:redo', 22 | 'sidebar:copy', 23 | 'sidebar:scroll-to-bookmark', 24 | 'sidebar:toggle-notes', 25 | 'sidebar:next-mark', 26 | 'remove:tag', 27 | 'add:tag', 28 | 'open:addon-page(sb)', 29 | 'opened:sidebar', 30 | 'updated:page-note', 31 | 'toggled:sidebar-tab', 32 | 'sidebar:selected-marker', 33 | 'changed:sidebar-theme' 34 | ] 35 | } 36 | }) 37 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/_buttons.scss: -------------------------------------------------------------------------------- 1 | button { 2 | border: 1px solid $color-border; 3 | border-radius: 50%; 4 | transition: background 0.5s; 5 | float: right; 6 | width: 24px; 7 | height: 24px; 8 | cursor: pointer; 9 | background-color: transparent; 10 | background-repeat: no-repeat; 11 | background-position: center, 0 0; 12 | opacity: 0.75; 13 | 14 | &:hover { 15 | opacity: 1; 16 | } 17 | &:last-child { 18 | margin: 0; 19 | } 20 | &[disabled] { 21 | opacity: 0.2 !important; 22 | cursor: default; 23 | } 24 | 25 | &.action-button--add { 26 | width: 20px; 27 | height: 20px; 28 | font-size: 16px; 29 | padding: 0; 30 | position: relative; 31 | top: 2px; 32 | background: $gradient--button; 33 | color: $color-text--grey; 34 | 35 | &:hover { 36 | background: $gradient--button-hover; 37 | } 38 | 39 | span { 40 | position: absolute; 41 | top: -1px; 42 | left: 3px; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/_form-elements.scss: -------------------------------------------------------------------------------- 1 | ::-moz-placeholder, 2 | :placeholder-shown { 3 | color: $color-text--disabled; 4 | } 5 | select, input { 6 | font-family: inherit !important; 7 | } 8 | select { 9 | -moz-appearance: none; 10 | height: 24px; 11 | color: $color-text--grey; 12 | float: left; 13 | width: auto; 14 | border: 0; 15 | } 16 | input[type="text"] { 17 | border: 0; 18 | box-shadow: 0 0 3px -1px #c8c8c8 inset; 19 | color: inherit; 20 | padding: 0; 21 | height: 24px; 22 | width: calc(100% - 27px); 23 | padding: 0 4px; 24 | } 25 | input[type="radio"] { 26 | display: none; 27 | } 28 | label.fake-rb { 29 | width: auto; 30 | } 31 | input[type="radio"] + label span { 32 | border-radius: 50%; 33 | line-height: 1; 34 | vertical-align: -2px; 35 | } 36 | input[type="radio"]:checked + label span { 37 | text-indent: 0; 38 | } 39 | label span { 40 | display: inline-block; 41 | line-height: 1.2; 42 | height: 20px; 43 | width: 20px; 44 | text-align: center; 45 | color: $color-text--turquoise; 46 | text-indent: -1000px; 47 | overflow: hidden; 48 | border: 1px solid $color-border; 49 | border-radius: 2px; 50 | vertical-align: -5px; 51 | margin-right: 8px; 52 | } 53 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/_general.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: $font-size--base-small; 3 | color: $color-text--grey; 4 | font-family: inherit; 5 | padding: 0; 6 | } 7 | #header { 8 | margin-bottom: 15px; 9 | } 10 | h1 { 11 | width: 100%; 12 | font-size: 100%; 13 | font-weight: normal; 14 | margin: 0 0 10px; 15 | color: $color-text--turquoise; 16 | } 17 | h2 { 18 | font-size: 100%; 19 | margin: 0 0 8px; 20 | font-weight: normal; 21 | position: relative; 22 | } 23 | h3 { 24 | font-size: 80%; 25 | margin: 0 0 8px; 26 | color: $color-grey--medium; 27 | font-weight: normal; 28 | text-align: right; 29 | } 30 | label { 31 | float: left; 32 | padding-top: 2px; 33 | cursor: pointer; 34 | } 35 | svg { 36 | height: 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_actions.scss: -------------------------------------------------------------------------------- 1 | .action-button { 2 | width: 28px; 3 | height: 28px; 4 | } 5 | 6 | $action-buttons: ( 7 | '#mark-action--delete-highlight' : 'bin', 8 | '#mark-action--bookmark' : 'bm3', 9 | '#mark-action--delete-bookmark': 'del-bm', 10 | '#mark-action--add-note': 'note2', 11 | '#mark-action--copy': 'copy', 12 | '#page-action--delete': 'bin', 13 | '#page-action--save': 'save', 14 | '#page-action--retry': 'retry', 15 | '#page-action--undo': 'undo', 16 | '#page-action--redo': 'redo', 17 | '#page-action--scroll': 'to-bm', 18 | '#page-action--notes': 'toggle-notes', 19 | '#page-action--copy-all': 'copy', 20 | '#nav-action--up': 'up', 21 | '#nav-action--down': 'down', 22 | 23 | '.marker__apply': 'highlight' 24 | ); 25 | 26 | @each $selector, $icon in $action-buttons { 27 | #{$selector} { 28 | @include button_icon($icon, $gradient: true); 29 | } 30 | } 31 | 32 | #nav-action--up, #nav-action--down { 33 | background-size: 12px, 100%; 34 | } 35 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_links.scss: -------------------------------------------------------------------------------- 1 | .link { 2 | opacity: .5; 3 | 4 | &__icon { 5 | display: inline-block; 6 | width: 12px; 7 | height: 12px; 8 | top: 2px; 9 | margin-right: 8px; 10 | } 11 | &__text { 12 | display: inline-block; 13 | } 14 | &:hover { 15 | opacity: 1; 16 | } 17 | } 18 | 19 | $links: ( 20 | 'settings': 'tools', 21 | 'history': 'clock', 22 | 'help': 'book', 23 | 'troubleshooting': 'debug', 24 | 'contact': 'ghost' 25 | ); 26 | 27 | @each $link, $icon in $links { 28 | .link--#{$link} .link__icon { 29 | @include icon(#{$icon}, 0 0, 12px); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_marker.scss: -------------------------------------------------------------------------------- 1 | .marker { 2 | margin-bottom: 8px; 3 | width: 78px; 4 | 5 | .auto & { 6 | width: 85px; 7 | } 8 | 9 | &__cb-box { 10 | float: right; 11 | margin: 0 0 0 10px; 12 | display: none; 13 | .auto & { 14 | display: block; 15 | } 16 | } 17 | 18 | &__apply { 19 | margin: 0 0 0 10px; 20 | border-radius: 0; 21 | .auto & { 22 | display: none; 23 | } 24 | } 25 | &__label { 26 | display: inline-block; 27 | } 28 | &__text { 29 | position: relative; 30 | margin-top: -24px; 31 | left: 10px; 32 | color: $color-text--dark; 33 | font-size: 11px !important; 34 | } 35 | &__color { 36 | padding: 0; 37 | width: 44px; 38 | border: 0; 39 | -moz-appearance: none; 40 | cursor: pointer; 41 | 42 | &[disabled] { 43 | opacity: .2; 44 | cursor: default; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_marks.scss: -------------------------------------------------------------------------------- 1 | #nav-action--up, 2 | #nav-action--down { 3 | width: 20px; 4 | height: 20px; 5 | background-size: 14px; 6 | } 7 | 8 | .mark { 9 | position: relative; 10 | width: 100%; 11 | margin-bottom: 5px; 12 | 13 | &__text-container { 14 | position: relative; 15 | z-index: 2; 16 | background: #fff; 17 | border: 1px solid $color-border--light; 18 | border-radius: 2px 0 0 2px; 19 | 20 | .mark--note & { 21 | width: calc(100% - 16px); 22 | float: left; 23 | } 24 | 25 | .mark--active &, 26 | &:hover { 27 | border: 1px solid $color-border; 28 | } 29 | } 30 | 31 | &__text { 32 | border-left: 2px solid; 33 | padding: 2px 8px 2px 5px; 34 | 35 | &.unfolded { 36 | border-left: 0; 37 | border-top: 1px solid; 38 | padding-bottom: 10px; 39 | text-overflow: inherit; 40 | white-space: normal; 41 | } 42 | } 43 | 44 | &__note-btn { 45 | @include icon('note4'); 46 | position: relative; 47 | z-index: 2; 48 | float: right; 49 | width: 16px; 50 | height: 16px; 51 | 52 | &--red { 53 | filter: url(#filter--red); 54 | &:hover { 55 | filter: url(#filter--red) brightness(.75); 56 | } 57 | } 58 | &--orange { 59 | filter: url(#filter--orange); 60 | &:hover { 61 | filter: url(#filter--orange) brightness(.75); 62 | } 63 | } 64 | &--yellow { 65 | filter: url(#filter--yellow); 66 | &:hover { 67 | filter: url(#filter--yellow) brightness(.75); 68 | } 69 | } 70 | &--white { 71 | filter: url(#filter--white); 72 | &:hover { 73 | filter: url(#filter--white) brightness(.75); 74 | } 75 | } 76 | &--green { 77 | filter: url(#filter--green); 78 | &:hover { 79 | filter: url(#filter--green) brightness(.75); 80 | } 81 | } 82 | &--blue { 83 | filter: url(#filter--blue); 84 | &:hover { 85 | filter: url(#filter--blue) brightness(.75); 86 | } 87 | } 88 | &--purple { 89 | filter: url(#filter--purple); 90 | &:hover { 91 | filter: url(#filter--purple) brightness(.75); 92 | } 93 | } 94 | &--turquoise { 95 | filter: url(#filter--turquoise); 96 | &:hover { 97 | filter: url(#filter--turquoise) brightness(.75); 98 | } 99 | } 100 | } 101 | 102 | &__note { 103 | display: none; 104 | position: relative; 105 | top: 2px; 106 | padding: 5px; 107 | margin-left: 3px; 108 | border: 1px solid $color-border--light; 109 | 110 | &.unfolded { 111 | display: block; 112 | } 113 | 114 | &--red { 115 | background: #fee; 116 | } 117 | &--orange { 118 | background: #fed; 119 | } 120 | &--yellow { 121 | background: #ffe; 122 | } 123 | &--white { 124 | background: #f9f9f9; 125 | } 126 | &--green { 127 | background: #efe; 128 | } 129 | &--blue { 130 | background: #def; 131 | } 132 | &--purple { 133 | background: #eef; 134 | } 135 | &--turquoise { 136 | background: #d5f0f7; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_page-notes.scss: -------------------------------------------------------------------------------- 1 | tmnotesave { 2 | display: none; 3 | position: absolute; 4 | background: #fff; 5 | bottom: -1px; 6 | left: 5px; 7 | border: 1px solid $color-border--light; 8 | border-bottom: 0; 9 | border-radius: 3px 3px 0 0; 10 | width: auto; 11 | padding: 0 4px; 12 | color: $color-text--turquoise; 13 | 14 | tmnote & { 15 | display: block; 16 | } 17 | tmnote.tmnote--min & { 18 | display: none; 19 | } 20 | } 21 | 22 | tmnote { 23 | display: block; 24 | position: relative; 25 | width: 100%; 26 | border: 1px solid $color-border--light !important; 27 | margin-bottom: 4px; 28 | 29 | textarea { 30 | @extend .__note_textarea; 31 | padding: 5px 5px 10px; 32 | width: 100%; 33 | max-width: 100%; 34 | margin: 1px 0 0 0; 35 | } 36 | &.tmnote--min { 37 | margin-bottom: 2px; 38 | border: 0 !important; 39 | textarea, 40 | tmnotepalette { 41 | display: none !important; 42 | } 43 | tmnotebuttons { 44 | display: block !important; 45 | } 46 | .tmnote__header { 47 | background: none; 48 | &::placeholder { 49 | color: transparent; 50 | } 51 | } 52 | } 53 | .tmnote__header { 54 | width: calc(100% - 70px); 55 | height:22px !important; 56 | border: 0; 57 | color: $color-text--grey; 58 | } 59 | } 60 | tmnotepalette { 61 | position: absolute; 62 | display: block; 63 | background: none !important; 64 | padding: 7px 3px 0; 65 | width: auto; 66 | top: 22px; 67 | left: 0; 68 | border: 0 !important; 69 | } 70 | tmnotecolor { 71 | float: left; 72 | width: 22px; 73 | height: 14px; 74 | margin: 0 2px; 75 | } 76 | tmnotebuttons { 77 | display: block; 78 | } 79 | tmnotedelete { 80 | padding-top: 4px; 81 | } 82 | tmnoteminimize { 83 | font-size: 20px; 84 | } 85 | tmnotecustomize, 86 | tmnoteminimize, 87 | tmnotedelete { 88 | float: right; 89 | position: relative; 90 | height: 18px; 91 | width: 18px; 92 | margin: 2px 2px 0 0; 93 | text-align: center; 94 | } 95 | tmnoteminimize { 96 | &:before { 97 | content: '\2012'; 98 | position: absolute; 99 | top: -1px; 100 | left: 3px; 101 | 102 | .tmnote--min & { 103 | content: '\25A1'; 104 | top: -4px; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_tabs.scss: -------------------------------------------------------------------------------- 1 | .tab { 2 | &__header { 3 | position: relative; 4 | color: $color-grey--medium-light; 5 | 6 | &:hover { 7 | color: $color-black; 8 | } 9 | } 10 | 11 | &__title { 12 | border-bottom: 1px solid $color-border--light; 13 | 14 | .tab--folded & { 15 | border-top: 1px solid $color-border--light; 16 | border-bottom: 0; 17 | } 18 | } 19 | 20 | &__toggle { 21 | border: 1px solid $color-border--light; 22 | line-height: 1; 23 | position: absolute; 24 | bottom: -1px; 25 | right: 0; 26 | text-align: center; 27 | width:15px; 28 | 29 | &:before { 30 | content: '\2212'; 31 | display: inline-block; 32 | position: relative; 33 | top: -1px; 34 | 35 | .tab--folded & { 36 | content: '\002B'; 37 | } 38 | } 39 | 40 | .tab--folded & { 41 | top: -1px; 42 | bottom: auto; 43 | } 44 | } 45 | 46 | &__content { 47 | padding: 10px 0 25px; 48 | 49 | .tab--folded & { 50 | display: none; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/components/_tags.scss: -------------------------------------------------------------------------------- 1 | .tags { 2 | 3 | &__item { 4 | position: relative; 5 | margin: 0 5px 5px 0; 6 | display: inline-block; 7 | border-radius: 2px; 8 | padding: 0 20px 2px 4px; 9 | background: $color-bg--light; 10 | border: 1px solid transparentize($color-bg--turquoise, .75); 11 | max-width: 100%; 12 | } 13 | 14 | &__remove { 15 | position: absolute; 16 | right: 4px; 17 | top: 3px; 18 | font-size: 10px; 19 | margin-left: 10px; 20 | cursor: pointer; 21 | 22 | &:hover { 23 | color: $color-text--red; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/general'; 3 | @import '../../_shared/sass/links'; 4 | @import '../../_shared/sass/helpers'; 5 | @import '../../_shared/sass/icons'; 6 | @import '../../_shared/sass/shadows'; 7 | @import '../../_shared/sass/switches'; 8 | @import '../../_shared/sass/notes'; 9 | 10 | @import './general'; 11 | @import './buttons'; 12 | @import './form-elements'; 13 | 14 | @import './components/tabs'; 15 | @import './components/tags'; 16 | @import './components/marks'; 17 | @import './components/marker'; 18 | @import './components/actions'; 19 | @import './components/page-notes'; 20 | @import './components/links'; 21 | 22 | @import './themes/dark'; 23 | 24 | @include links(inherit); 25 | 26 | #textmarker-sidebar { 27 | min-height: 100vh; 28 | padding: 10px 15px; 29 | } 30 | #textmarker-sidebar--paused { 31 | padding: 10px 20px; 32 | color: $color-text--red; 33 | } 34 | .textmarker-sidebar--locked { 35 | #header, 36 | #tab--meta, 37 | #tab--tags, 38 | #switch-box, 39 | #mark-action--delete-highlight, 40 | #mark-action--bookmark, 41 | #page-action--scroll, 42 | #page-action--undo, 43 | #page-action--redo { 44 | display: none !important; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/content/sidebar/sass/themes/_dark.scss: -------------------------------------------------------------------------------- 1 | .textmarker-sidebar { 2 | 3 | &--dark, &--tm { 4 | background: $color-bg--dark-ff; 5 | color: $color-text--light; 6 | 7 | button { 8 | border-color: transparent; 9 | color: $color-bg--dark-ff; 10 | background-color: $color-grey--medium-light !important; 11 | 12 | &:hover { 13 | background-color: $color-grey--light !important; 14 | } 15 | } 16 | 17 | $action-buttons: ( 18 | '#mark-action--delete-highlight' : 'bin', 19 | '#mark-action--bookmark' : 'bm3', 20 | '#mark-action--delete-bookmark': 'del-bm', 21 | '#mark-action--add-note': 'note2', 22 | '#page-action--save': 'save', 23 | '#page-action--retry': 'retry', 24 | '#page-action--undo': 'undo', 25 | '#page-action--redo': 'redo', 26 | '#page-action--scroll': 'to-bm', 27 | '#page-action--notes': 'toggle-notes', 28 | '#nav-action--up': 'up', 29 | '#nav-action--down': 'down', 30 | 31 | '.marker__apply': 'highlight' 32 | ); 33 | 34 | @each $selector, $icon in $action-buttons { 35 | #{$selector} { 36 | @include button_icon($icon, $gradient: false); 37 | } 38 | } 39 | 40 | input[type="text"] { 41 | background-color: rgba(0, 0, 0, .1); 42 | } 43 | 44 | select { 45 | background-color: transparent; 46 | color: $color-grey--medium; 47 | } 48 | 49 | label span { 50 | background-color: rgba(0, 0, 0, .1); 51 | } 52 | 53 | .switch__indicator { 54 | background-color: rgba(255, 255, 255, .1); 55 | } 56 | .switch--immut { 57 | background-color: rgba(0, 0, 0, .1); 58 | } 59 | 60 | .tab { 61 | &__header { 62 | position: relative; 63 | color: $color-grey--medium-light; 64 | 65 | &:hover { 66 | color: $color-white; 67 | } 68 | } 69 | &__title { 70 | border-color: $color-grey--medium; 71 | 72 | .tab--folded & { 73 | border-color: $color-grey--medium; 74 | } 75 | } 76 | 77 | &__toggle { 78 | border-color: $color-grey--medium; 79 | } 80 | } 81 | 82 | .tags { 83 | &__item { 84 | background: $color-bg--dark; 85 | } 86 | &__remove { 87 | &:hover { 88 | color: lighten($color-text--red, 20); 89 | } 90 | } 91 | } 92 | 93 | .mark { 94 | &__text-container { 95 | border-color: $color-border--dark !important; 96 | background-color: rgba(0, 0, 0, .1); 97 | color: $color-text--light-grey; 98 | } 99 | &__text { 100 | &.unfolded { 101 | background-color: $color-bg--dark-ff; 102 | } 103 | } 104 | &__note { 105 | color: $color-bg--dark-ff; 106 | } 107 | } 108 | 109 | .open-note { 110 | .mark__text { 111 | background-color: $color-bg--dark-ff; 112 | } 113 | } 114 | 115 | .link { 116 | opacity: 1; 117 | color: #fff; 118 | 119 | &:hover { 120 | color: $color-text--turquoise; 121 | } 122 | } 123 | } 124 | 125 | &--tm { 126 | background: $color-grey--darker; 127 | 128 | button { 129 | color: $color-bg--dark; 130 | } 131 | 132 | .mark { 133 | &__text { 134 | &.unfolded { 135 | background-color: $color-bg--dark; 136 | } 137 | } 138 | 139 | &__note { 140 | color: $color-bg--dark; 141 | } 142 | } 143 | 144 | .open-note { 145 | .mark__text { 146 | background-color: $color-bg--dark; 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/content/tbb-menu/index.js: -------------------------------------------------------------------------------- 1 | import './sass/index.scss' 2 | 3 | import _ERRORTRACKER from './../_shared/utils' 4 | import { _DOMMODULE } from './../_shared/utils' 5 | import { _L10N } from './../_shared/utils' 6 | import _PORT from './port' 7 | _L10N(); 8 | 9 | new _DOMMODULE({ 10 | el: document.getElementById('menu'), 11 | events: { 12 | DOM: { 13 | click: { 14 | '#activate': 'toggle', 15 | '.send': 'open', 16 | 'span': 'open' 17 | } 18 | } 19 | }, 20 | port: null, 21 | disabled: false, 22 | 23 | autoinit() { 24 | browser.storage.local.get().then(storage => { 25 | return storage && storage.settings ? storage.settings.addon.active : true; 26 | }) 27 | .then(active => this.setActivateText(active)); 28 | }, 29 | toggle() { 30 | this.emit('toggle:addon'); 31 | window.close(); 32 | }, 33 | open(e, el) { 34 | const page = el.nodeName === 'SPAN' ? el.parentNode.id : el.id; 35 | this.emit('open:addon-page(tbb)', page); 36 | window.close(); 37 | }, 38 | setActivateText(active) { 39 | const btn = document.getElementById('activate'); 40 | btn.title = browser.i18n.getMessage('tbb_mitem_1_' + active); 41 | btn.textContent = browser.i18n.getMessage('tbb_pause_' + active); 42 | if (active) { 43 | btn.classList.remove('menu__icon--start'); 44 | btn.classList.add('menu__icon--pause'); 45 | } else { 46 | btn.classList.add('menu__icon--start'); 47 | btn.classList.remove('menu__icon--pause'); 48 | } 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/content/tbb-menu/port.js: -------------------------------------------------------------------------------- 1 | import { _PRIVPORT } from './../_shared/utils' 2 | 3 | export default new _PRIVPORT({ 4 | name: 'tbbmenu', 5 | type: 'privileged', 6 | id: Math.random().toString().slice(2, 16), 7 | events: { 8 | CONNECTION: [ 9 | 'toggle:addon', 10 | 'open:addon-page(tbb)', 11 | 'error:browser-console' 12 | ] 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /src/content/tbb-menu/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../_shared/sass/vars'; 2 | @import '../../_shared/sass/reset'; 3 | @import '../../_shared/sass/icons'; 4 | 5 | body { 6 | width: 200px; 7 | font-family: system, 'Segoe UI', Tahoma; 8 | } 9 | a { 10 | text-decoration: none; 11 | } 12 | ul { 13 | list-style-type: none; 14 | font-size: 12px; 15 | padding: 5px 0 8px; 16 | } 17 | li { 18 | padding: 5px 10px; 19 | 20 | &:hover { 21 | background: transparentize($color-bg--turquoise, .9); 22 | 23 | .menu__icon { 24 | opacity: 1; 25 | } 26 | } 27 | } 28 | .menu__icon { 29 | position: relative; 30 | display: inline-block; 31 | width: 16px; 32 | height: 16px; 33 | top: 3px; 34 | margin-right: 8px; 35 | opacity: .5; 36 | } 37 | .partingline { 38 | height: 1px; 39 | padding: 0; 40 | background: $color-border--light; 41 | margin: 5px 0; 42 | } 43 | .partingline:hover { 44 | background: $color-border--light; 45 | } 46 | 47 | $links: ( 48 | 'settings': 'tools', 49 | 'history': 'clock', 50 | 'help': 'book', 51 | 'contact': 'ghost', 52 | 'news': 'forklift', 53 | 'sync': 'sync2', 54 | 'export': 'export', 55 | 'troubleshooting': 'debug', 56 | 'logs': 'dna', 57 | 'donate': 'donate', 58 | 'github': 'github' 59 | ); 60 | 61 | @each $link, $icon in $links { 62 | ##{$link} .menu__icon { 63 | @include icon(#{$icon}); 64 | } 65 | } 66 | #activate { 67 | padding-left: 22px; 68 | color: #fff; 69 | 70 | &.menu__icon--start { 71 | @include icon('start'); 72 | } 73 | 74 | &.menu__icon--pause { 75 | @include icon('pause'); 76 | } 77 | } 78 | 79 | #donate, #github { 80 | float: right; 81 | } 82 | 83 | footer { 84 | background: $color-bg--turquoise; 85 | padding: 7px 6px 8px 12px; 86 | 87 | .menu__icon { 88 | opacity: .85; 89 | 90 | &:hover { 91 | opacity: 1; 92 | } 93 | } 94 | } 95 | 96 | #depricated { 97 | background: #3aad74; 98 | color: #fbfbfb; 99 | padding: 5px 8px 8px; 100 | } 101 | -------------------------------------------------------------------------------- /src/data/default-storage.js: -------------------------------------------------------------------------------- 1 | export default { 2 | version: browser.runtime.getManifest().version, 3 | settings:{ 4 | shortcuts: { 5 | z: ['ctrlKey-altKey', true], 6 | y: ['ctrlKey-altKey', true], 7 | s: ['ctrlKey-altKey', true], 8 | c: ['ctrlKey-altKey', false, true], 9 | b: ['ctrlKey-altKey', true, true], 10 | d: ['shiftKey', false, true], 11 | '-b': ['', '', true], 12 | m: ['', true, true], 13 | w: ['', true, true], 14 | n: ['shiftKey', false, true], 15 | '2': ['', true], 16 | '3': ['', true], 17 | arrowup: ['altKey', false, false], 18 | arrowdown: ['altKey', false, false], 19 | sb: ['', '', true], 20 | cm: ['', true] 21 | }, 22 | markers: { 23 | '1': { style: 'background-color:#dd99ff;' }, 24 | '2': { style: 'background-color:#66bbff;' }, 25 | '3': { style: 'background-color:#55ff55;' }, 26 | '4': { style: 'background-color:#ff6666;color:#ffffff;' }, 27 | '5': { style: 'background-color:#ffcc00;' }, 28 | m: { style: 'background-color:#ffee00;' } 29 | }, 30 | history: { 31 | autosave: true, 32 | saveInPriv: false, 33 | dropLosses: true, 34 | immut: false, 35 | naming: 'title', 36 | download: 'json', 37 | copy: 'text', 38 | saveNote: true, 39 | sorted: 'date-last', 40 | view: 'list', 41 | pp: 10, 42 | ignoreHash: true, 43 | autoRetry: true 44 | }, 45 | addon: { 46 | active: true, 47 | autocs: true, 48 | iframes: true 49 | }, 50 | misc: { 51 | bmicon: true, 52 | noteicon: true, 53 | noteonclick: true, 54 | notetransp: false, 55 | noteplainview: false, 56 | notefontsize: 12, 57 | overwrite: false, 58 | failureNote: true, 59 | successNote: true, 60 | pbmNote: true, 61 | changedNote: false, 62 | errorNote: true, 63 | vipNote: true, 64 | loadNote: false, 65 | customSearch: false, 66 | tmuipos: 'top-right', 67 | markmethod: 'popup', 68 | progressbar: true, 69 | tbbpower: false 70 | }, 71 | sb: { 72 | tabs: { 73 | meta: { unfolded: false }, 74 | tags: { unfolded: false }, 75 | notes: { unfolded: false }, 76 | markers: { unfolded: true }, 77 | actions: { unfolded: true }, 78 | marks: { unfolded: false }, 79 | links: { unfolded: false }, 80 | themes: { unfolded: false } 81 | }, 82 | theme: 'tm' 83 | } 84 | }, 85 | history: { 86 | entries: {} 87 | }, 88 | pagenotes: {}, 89 | sync: { 90 | settings: false, 91 | history: false, 92 | pagenotes: false 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/data/global-settings.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | MAX_ENTRY_NAME_CHARS: 70, 4 | 5 | MAX_LOG_ENTRIES: 20, 6 | 7 | NOTE_COLORS: { 8 | TURQUOISE: '#b9e4ec', 9 | GREEN: '#ccffcc', 10 | YELLOW: '#ffffcc', 11 | ORANGE: '#ffeebb', 12 | RED: '#ffcccc', 13 | PURPLE: '#eeccff', 14 | BLUE: '#bbeeff', 15 | WHITE: '#eeeeee' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/data/log-keys.js: -------------------------------------------------------------------------------- 1 | export default { 2 | note_pbm: 1, 3 | note_restoration_failure: 2, 4 | note_url: 3, 5 | error_save_style: 4, 6 | error_save__toggle_sc: 5, 7 | error_save_change_sc: 6, 8 | error_save_ctm: 7, 9 | error_save_autosave: 8, 10 | error_save_naming: 9, 11 | error_save_notify: 10, 12 | error_save_download: 11, 13 | error_save_bmicon: 12, 14 | error_clean_history: 13, 15 | error_add_marker: 14, 16 | error_remove_marker: 15, 17 | error_save_entry: 16, 18 | error_update_entry: 17, 19 | error_del_entry: 18, 20 | error_empty_synced_storage_onstart: 19, 21 | error_empty_synced_storage_onupdate: 20, 22 | error_empty_local_storage_onstart: 21, 23 | error_import_empty: 22, 24 | error_import_history: 23, 25 | error_import_settings: 24, 26 | error_import_outdated: 25, 27 | error_import_history_not_found: 26, 28 | error_import_settings_not_found: 27, 29 | error_naming: 28, 30 | error_storage_migration: 29, 31 | error_empty_local_storage_onupdate: 30, 32 | error_toggle_sync: 31, 33 | error_save_priv: 32, 34 | note_restoration_warning_1: 33, 35 | note_restoration_warning_2: 34, 36 | error_save_change_autonote: 35, 37 | error_save_mark_method: 36, 38 | js_injection_failure: 37, 39 | css_injection_failure: 38, 40 | missing_permission_wn: 39, 41 | 42 | getKeyByValue(val) { 43 | for (let key in this) { 44 | if (this[key] == val) { 45 | return key; 46 | break; 47 | } 48 | } 49 | return ''; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/icons/arrow-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/arrow-down.png -------------------------------------------------------------------------------- /src/icons/bin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/bin.png -------------------------------------------------------------------------------- /src/icons/bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/bm.png -------------------------------------------------------------------------------- /src/icons/bm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/bm2.png -------------------------------------------------------------------------------- /src/icons/bm3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/bm3.png -------------------------------------------------------------------------------- /src/icons/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/book.png -------------------------------------------------------------------------------- /src/icons/clipboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/clipboard.png -------------------------------------------------------------------------------- /src/icons/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/clock.png -------------------------------------------------------------------------------- /src/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/copy.png -------------------------------------------------------------------------------- /src/icons/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/debug.png -------------------------------------------------------------------------------- /src/icons/del-bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/del-bm.png -------------------------------------------------------------------------------- /src/icons/dna.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/dna.png -------------------------------------------------------------------------------- /src/icons/donate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/donate.png -------------------------------------------------------------------------------- /src/icons/double-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/double-arrow.png -------------------------------------------------------------------------------- /src/icons/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/down.png -------------------------------------------------------------------------------- /src/icons/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/download.png -------------------------------------------------------------------------------- /src/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/edit.png -------------------------------------------------------------------------------- /src/icons/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/email.png -------------------------------------------------------------------------------- /src/icons/export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/export.png -------------------------------------------------------------------------------- /src/icons/forklift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/forklift.png -------------------------------------------------------------------------------- /src/icons/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/ghost.png -------------------------------------------------------------------------------- /src/icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/github.png -------------------------------------------------------------------------------- /src/icons/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/highlight.png -------------------------------------------------------------------------------- /src/icons/m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/m.png -------------------------------------------------------------------------------- /src/icons/menu-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/menu-blue.png -------------------------------------------------------------------------------- /src/icons/note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/note.png -------------------------------------------------------------------------------- /src/icons/note2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/note2.png -------------------------------------------------------------------------------- /src/icons/note4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/note4.png -------------------------------------------------------------------------------- /src/icons/off16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/off16.png -------------------------------------------------------------------------------- /src/icons/off18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/off18.png -------------------------------------------------------------------------------- /src/icons/on16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/on16.png -------------------------------------------------------------------------------- /src/icons/on18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/on18.png -------------------------------------------------------------------------------- /src/icons/on32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/on32.png -------------------------------------------------------------------------------- /src/icons/on36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/on36.png -------------------------------------------------------------------------------- /src/icons/on64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/on64.png -------------------------------------------------------------------------------- /src/icons/pageaction16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/pageaction16.png -------------------------------------------------------------------------------- /src/icons/pageaction32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/pageaction32.png -------------------------------------------------------------------------------- /src/icons/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/pause.png -------------------------------------------------------------------------------- /src/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/redo.png -------------------------------------------------------------------------------- /src/icons/retry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/retry.png -------------------------------------------------------------------------------- /src/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/save.png -------------------------------------------------------------------------------- /src/icons/save2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/save2.png -------------------------------------------------------------------------------- /src/icons/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/search.png -------------------------------------------------------------------------------- /src/icons/sel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/sel.jpg -------------------------------------------------------------------------------- /src/icons/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/start.png -------------------------------------------------------------------------------- /src/icons/submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/submit.png -------------------------------------------------------------------------------- /src/icons/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/sync.png -------------------------------------------------------------------------------- /src/icons/sync2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/sync2.png -------------------------------------------------------------------------------- /src/icons/tm48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/tm48.png -------------------------------------------------------------------------------- /src/icons/tm64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/tm64.png -------------------------------------------------------------------------------- /src/icons/to-bm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/to-bm.png -------------------------------------------------------------------------------- /src/icons/toggle-notes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/toggle-notes.png -------------------------------------------------------------------------------- /src/icons/tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/tools.png -------------------------------------------------------------------------------- /src/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/undo.png -------------------------------------------------------------------------------- /src/icons/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/up.png -------------------------------------------------------------------------------- /src/icons/view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ufb/Textmarker/4d22cac2036ed6db8f7fbc32ad6ff854a1b3dac6/src/icons/view.png -------------------------------------------------------------------------------- /src/utils/copy.js: -------------------------------------------------------------------------------- 1 | const _COPY = function(src) { 2 | const target = Array.isArray(src) ? [] : {}; 3 | let val; 4 | for (let prop in src) { 5 | if (src.hasOwnProperty(prop)) { 6 | val = src[prop]; 7 | if (val !== null && typeof val === 'object') { 8 | target[prop] = _COPY(val); 9 | } else 10 | target[prop] = val; 11 | } 12 | } 13 | return target; 14 | } 15 | 16 | export { _COPY } 17 | -------------------------------------------------------------------------------- /src/utils/dommodule.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './module' 2 | 3 | export class _DOMMODULE extends _MODULE { 4 | 5 | constructor(obj) { 6 | obj._bound = {}; 7 | obj._extraBound = []; 8 | 9 | super(obj) 10 | 11 | this.addListenersManually || this.addListeners(); 12 | !this.autoPause || this.setAutoPause(); 13 | } 14 | generalHandler(subMap, type, e) { 15 | let el = e.target, 16 | selector, _sel, meth, f, isId, isClass, isDoc; 17 | 18 | for (selector in subMap) { 19 | f = selector[0]; 20 | _sel = selector; 21 | isId = f === '#'; 22 | isClass = f === '.'; 23 | isDoc = f === '*'; 24 | 25 | if (isId || isClass) selector = selector.substr(1); 26 | 27 | if (isDoc || 28 | isClass && el.classList.contains(selector) || 29 | isId && el.id === selector || 30 | el.nodeName.toLowerCase() === selector 31 | ) { 32 | 33 | meth = subMap[_sel]; 34 | 35 | if (typeof meth === 'function') meth(e, el); 36 | else if (this[meth]) this[meth](e, el); 37 | 38 | break; 39 | } 40 | } 41 | } 42 | setAutoPause() { 43 | this.on('toggled:addon', on => { 44 | if (on) this.addListeners(); 45 | else this.removeListeners(); 46 | }); 47 | } 48 | addListener(type, meth, el) { 49 | el = el || this.el; 50 | const handler = typeof meth === 'function' ? meth : this[meth]; 51 | 52 | el.addEventListener(type, handler, false); 53 | 54 | if (el === this.el) { 55 | if (!this._bound[type]) this._bound[type] = []; 56 | this._bound[type].push(handler); 57 | } else { 58 | this._extraBound.push([el, type, handler]); 59 | } 60 | } 61 | addListeners() { 62 | let events = this.events, 63 | domEvents, el, subMap, type, handler; 64 | 65 | if (!events || !(domEvents = events.DOM) || !(el = this.el)) return false; 66 | 67 | for (type in domEvents) { 68 | subMap = domEvents[type]; 69 | handler = this.proxy(this, this.generalHandler, subMap, type); 70 | el.addEventListener(type, handler, false); 71 | if (!this._bound[type]) this._bound[type] = []; 72 | this._bound[type].push(handler); 73 | } 74 | } 75 | removeListeners() { 76 | let type, _bound, l; 77 | 78 | for (type in this._bound) { 79 | _bound = this._bound[type]; 80 | l = _bound.length; 81 | while (l--) { 82 | this.el.removeEventListener(type, _bound[l], false); 83 | _bound.splice(l, 1); 84 | } 85 | } 86 | 87 | let _extra = this._extraBound, i = 0, set; 88 | l = _extra.length; 89 | 90 | for (; i < l; i++) { 91 | set = _extra[i]; 92 | set[0].removeEventListener(set[1], set[2], false); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/utils/error-tracker.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './module' 2 | 3 | const _ERRORTRACKER = new _MODULE({ 4 | autoinit() { 5 | window.addEventListener('error', error => { 6 | const file = error.filename.split('/').pop(); 7 | if (['sidebar.wp.js', 'tbb-menu.wp.js', 'options.wp.js', 'addon-page.wp.js'].includes(file)) { 8 | this.emit('error:browser-console', { 9 | message: error.message, 10 | location: error.filename.split('/').pop().split('.').shift() + ':' + error.lineno + ':' + error.colno, 11 | time: (new Date()).getTime() 12 | }); 13 | } 14 | }, false); 15 | } 16 | }); 17 | 18 | export default _ERRORTRACKER; 19 | -------------------------------------------------------------------------------- /src/utils/extend.js: -------------------------------------------------------------------------------- 1 | export default function(obj1, obj2) { 2 | for (var i in obj2) 3 | if (!obj1[i]) obj1[i] = obj2[i]; 4 | 5 | return obj1; 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/getActiveTab.js: -------------------------------------------------------------------------------- 1 | const _GET_ACTIVE_TAB = function() { 2 | 3 | return browser.tabs.query({ currentWindow: true, active: true }).then(tabs => tabs[0]); 4 | } 5 | 6 | export { _GET_ACTIVE_TAB } 7 | -------------------------------------------------------------------------------- /src/utils/hashless.js: -------------------------------------------------------------------------------- 1 | const _HASHLESS = function(url) { 2 | if (!url) return ''; 3 | const h = url.lastIndexOf('#'); 4 | if (h === -1) return url; 5 | else return url.substr(0, h); 6 | } 7 | 8 | export { _HASHLESS } 9 | -------------------------------------------------------------------------------- /src/utils/l10n.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Translates a HTMl page in the web l10n style from the Add-on SDK with WebExtensions strings. 3 | * Modified by underflyingbirches 4 | * 5 | * @author Martin Giger 6 | * @see {@link https://gist.github.com/freaktechnik/4a72bc0711d9bc82cf3b075bcc292953} 7 | * @license MPL-2.0 8 | */ 9 | function translateDocument() { 10 | let el, data, dataset; 11 | // Set the language attribute of the document. 12 | document.documentElement.setAttribute('lang', browser.i18n.getUILanguage().replace('_', '-')); 13 | // Get all elements that are marked as being translateable. 14 | const textElements = document.querySelectorAll('*[data-l10n-id]'); 15 | const attrElements = document.querySelectorAll('*[data-l10n-attr]'); 16 | 17 | for(el of textElements) { 18 | dataset = el.dataset; 19 | const l10nId = dataset.l10nId; 20 | 21 | if (l10nId) { 22 | data = browser.i18n.getMessage(l10nId, ''); 23 | 24 | if(data && data != '??') { 25 | el.textContent = data; 26 | } 27 | } 28 | } 29 | for(el of attrElements) { 30 | dataset = el.dataset; 31 | 32 | ['Title', 'Placeholder', 'Href'].forEach(attr => { 33 | const l10nAttr = dataset['l10n' + attr]; 34 | 35 | if (l10nAttr) { 36 | data = browser.i18n.getMessage(l10nAttr); 37 | 38 | if (data && data != '??') { 39 | el.setAttribute(attr.toLowerCase(), data); 40 | } 41 | } 42 | }); 43 | } 44 | } 45 | 46 | export default function() { 47 | document.addEventListener('DOMContentLoaded', () => translateDocument(), { 48 | capture: false, 49 | passive: true, 50 | once: true 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/mediator.js: -------------------------------------------------------------------------------- 1 | let topics = {}; 2 | 3 | export default class { 4 | 5 | on(event, handler) { 6 | if (!topics[event]) topics[event] = []; 7 | 8 | topics[event].push(handler); 9 | } 10 | emit(events, ...args) { 11 | events = events.split(' '); 12 | 13 | let i = 0, l = events.length, topic; 14 | 15 | for (; i < l; i++) { 16 | topic = topics[events[i]]; 17 | 18 | if (topic) 19 | topic.forEach(handler => handler.apply(this, args)) 20 | } 21 | } 22 | request(event, ...args) { 23 | if (this.type === 'background') { 24 | return browser.tabs.sendMessage(args[0].tabId, { ev: event, wait: true }, { frameId: args[0].frameId }); 25 | } else { 26 | return browser.runtime.sendMessage({ ev: event, args: args, wait: true }).catch(() => {}); 27 | } 28 | } 29 | proxy(context, func, ...args1) { 30 | return function(...args2) { 31 | return func.apply(context, args1.concat(args2)); 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/module.js: -------------------------------------------------------------------------------- 1 | import _MEDIATOR from './mediator' 2 | 3 | export class _MODULE extends _MEDIATOR { 4 | 5 | constructor(obj) { 6 | super() 7 | 8 | for (o in obj) this[o] = obj[o]; 9 | 10 | let events = this.events, 11 | envEvents, o, e, handler; 12 | 13 | if (events && (envEvents = events.ENV)) { 14 | for (e in envEvents) { 15 | handler = envEvents[e]; 16 | if (this[handler]) 17 | this.on(e, this.proxy(this, this[handler])); 18 | } 19 | } 20 | !this.autoinit || this.autoinit(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/store.js: -------------------------------------------------------------------------------- 1 | import { _MODULE } from './module' 2 | import _DEFAULT_STORAGE from '../data/default-storage' 3 | 4 | export class _STORE extends _MODULE { 5 | 6 | constructor(obj) { 7 | super(obj) 8 | 9 | this.initialized = false; 10 | this.initializing = false; 11 | 12 | this.area_settings = _DEFAULT_STORAGE.sync.settings ? 'sync' : 'local'; 13 | this.area_history = _DEFAULT_STORAGE.sync.history ? 'sync' : 'local'; 14 | this.area_pagenotes = _DEFAULT_STORAGE.sync.pagenotes ? 'sync' : 'local'; 15 | } 16 | 17 | init() { 18 | return browser.storage.local.get().then(storage => { 19 | if (storage && storage.sync) { 20 | this.setAreas(storage.sync); 21 | } 22 | }); 23 | } 24 | 25 | setAreas(sync) { 26 | for (let area in sync) { 27 | this['area_' + area] = sync[area] ? 'sync' : 'local'; 28 | } 29 | } 30 | 31 | onToggledSync() { 32 | this.init().then(() => this.emit('set-areas-after-sync-change')); 33 | } 34 | 35 | get(field = 'storage') { 36 | if (this.initializing) { 37 | return (new Promise(r => window.setTimeout(() => r(this.get(field)), 10))); 38 | } 39 | const meth = this['_get_' + field]; 40 | if (!meth) throw('field ' + field + ' doesn\'t exist'); 41 | 42 | if (!this.initialized) { 43 | this.initializing = true; 44 | this.initialized = true; 45 | 46 | return this.init().then(() => { 47 | this.initializing = false; 48 | return this['_get_' + field](); 49 | }); 50 | } 51 | return this['_get_' + field](); 52 | } 53 | 54 | _get_storage() { 55 | return browser.storage.local.get().then(localStorage => { 56 | return browser.storage.sync.get().then(syncedStorage => { 57 | ['version', 'logs'].forEach(field => { 58 | localStorage[field] = localStorage[field] || syncedStorage[field]; 59 | }); 60 | if (this.area_settings === 'sync') localStorage.settings = syncedStorage.settings; 61 | return this._get_history().then(history => { 62 | localStorage.history = history; 63 | return localStorage; 64 | }); 65 | }); 66 | }); 67 | } 68 | _get_local_storage() { 69 | return browser.storage.local.get(); 70 | } 71 | _get_synced_storage() { 72 | return browser.storage.sync.get(); 73 | } 74 | _get_history() { 75 | return browser.storage.sync.get().then(syncedStorage => { 76 | const syncedHistory = syncedStorage.history; 77 | 78 | return browser.storage.local.get().then(localStorage => { 79 | const localHistory = localStorage.history; 80 | if (!syncedHistory) return localHistory; 81 | if (!localHistory) return syncedHistory; 82 | 83 | //syncedHistory.order = syncedHistory.order.concat(localHistory.order); 84 | for (let e in localHistory.entries) syncedHistory.entries[e] = localHistory.entries[e]; 85 | 86 | return syncedHistory; 87 | }); 88 | }); 89 | } 90 | _get_settings() { 91 | return browser.storage[this.area_settings].get().then(storage => storage.settings || _DEFAULT_STORAGE.settings); 92 | } 93 | _get_logs() { 94 | return browser.storage.local.get().then(localStorage => { 95 | if (!localStorage || !localStorage.logs) return []; 96 | return localStorage.logs; 97 | }); 98 | } 99 | _get_version() { 100 | return browser.storage.local.get().then(localStorage => { 101 | if (!localStorage || !localStorage.version) { 102 | return browser.storage.sync.get().then(syncedStorage => syncedStorage.version || ''); 103 | } 104 | return localStorage.version; 105 | }); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const process = require('process'); 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | 5 | const PROD = (process.env.NODE_ENV === 'PROD'); 6 | 7 | module.exports = { 8 | context: path.resolve(__dirname, 'src'), 9 | entry: { 10 | 'background/background': './background/index.js', 11 | 'content/tbb-menu/tbb-menu': './content/tbb-menu/index.js', 12 | 'content/addon-page/addon-page': './content/addon-page/index.js', 13 | 'content/page-injections/injection': './content/page-injections/index.js', 14 | 'content/sidebar/sidebar': './content/sidebar/index.js', 15 | 'content/options-ui/options': './content/options-ui/index.js', 16 | 'content/detail-view/detail-view': './content/detail-view/index.js' 17 | }, 18 | output: { 19 | path: path.resolve(__dirname, 'extension'), 20 | filename: '[name].wp.js' 21 | }, 22 | // avoid UglifyJS (-> https://developer.mozilla.org/en-US/Add-ons/Source_Code_Submission#Requirements_for_the_source_package) 23 | mode: 'development',//PROD ? 'production' : 'development', 24 | devtool: PROD ? false : 'npm inline-source-map', 25 | module: { 26 | rules: [{ 27 | test: /\.js$/, 28 | include: [path.resolve(__dirname, 'src')], 29 | exclude: /node_modules/, 30 | use: { 31 | loader: 'babel-loader', 32 | options: { 33 | presets: ['@babel/preset-env'] 34 | } 35 | } 36 | }, { 37 | test: /\.png$/, 38 | exclude: /node_modules/, 39 | loader: 'file-loader', 40 | options: { 41 | name: '[name].[ext]', 42 | outputPath: 'icons/', 43 | publicPath: '../../icons/' 44 | } 45 | }, { 46 | test: /\.scss$/, 47 | exclude: /node_modules/, 48 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] 49 | }] 50 | }, 51 | plugins: [ 52 | new MiniCssExtractPlugin({ filename: '[name].css' }) 53 | ] 54 | } 55 | --------------------------------------------------------------------------------