"
33 | ],
34 | "js": [
35 | "content.js"
36 | ],
37 | "css": [
38 | "index.css"
39 | ],
40 | "run_at": "document_start",
41 | "all_frames": true
42 | }
43 | ],
44 | "options_ui": {
45 | "page": "options/options.html",
46 | "open_in_tab": false
47 | },
48 | "action": {
49 | "default_icon": "assets/icons/logo-new.png",
50 | "default_title": "Selecton",
51 | "default_popup": "popup/popup.html"
52 | },
53 | "permissions": [
54 | "storage",
55 | "notifications",
56 | "clipboardRead"
57 | ],
58 | "host_permissions": [
59 | "https://translate.googleapis.com/*",
60 | "https://api.exchangerate.host/*",
61 | "https://min-api.cryptocompare.com/data/*",
62 | "https://*.wikipedia.org/w/*"
63 | ]
64 | }
--------------------------------------------------------------------------------
/src/options/icons/border-color.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/highlighter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/network.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/options/icons/palette.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/select-end.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/select-start.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/settings.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/split-screen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/sync.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/icons/text-field.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/options/options.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 9px;
3 | font-size: 14px;
4 | }
5 |
6 | .option {
7 | padding: 4px;
8 | }
9 |
10 | .option:hover {
11 | background-color: rgb(0, 0, 0, 0.1);
12 | border-radius: 4px;
13 | transition: all 200ms ease-in-out;
14 | }
15 |
16 | label {
17 | cursor: pointer;
18 | }
19 |
20 | hr {
21 | opacity: 0.2;
22 | color: grey;
23 | }
24 |
25 | #customSearchUrl {
26 | max-width: 100%;
27 | }
28 |
29 | .option input {
30 | max-width: 65px;
31 | margin-right: 6px;
32 | }
33 |
34 | .option select {
35 | margin-right: 6px;
36 | }
37 |
38 | input[type="checkbox"] {
39 | cursor: pointer;
40 | }
41 |
42 | .disabled-option {
43 | opacity: 0.5;
44 | transition: opacity 150ms ease-in-out;
45 | pointer-events: none;
46 | }
47 |
48 | .enabled-option {
49 | opacity: 1.0;
50 | transition: opacity 150ms ease-in-out;
51 | }
52 |
53 | .visible-option {
54 | opacity: 1.0;
55 | }
56 |
57 | .hidden-option {
58 | display: none;
59 | pointer-events: none;
60 | }
61 |
62 | h3 {
63 | color: grey;
64 | padding-top: 10px;
65 | }
66 |
67 | h4 {
68 | color: grey;
69 | opacity: 0.8;
70 | font-weight: 500;
71 | }
72 |
73 | #footer-buttons button {
74 | margin: 3px !important;
75 | width: 100%;
76 | text-align: center;
77 | cursor: pointer;
78 | }
79 |
80 | #customSearchButtonsContainer {
81 | margin-top: 15px;
82 | }
83 |
84 | select {
85 | margin: 5px 0px;
86 | cursor: pointer;
87 | }
88 |
89 | /* Collapsible settings headers */
90 | /* Style the button that is used to open and close the collapsible content */
91 | .collapsible-header {
92 | background-color: #eee;
93 | color: #444;
94 | cursor: pointer;
95 | padding: 18px 6px 18px 40px;
96 | border: none;
97 | text-align: left;
98 | outline: none;
99 | font-size: 15px;
100 | border-radius: 3px;
101 | position: relative;
102 |
103 | margin: 12px 0px;
104 | }
105 |
106 | /* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
107 | .active, .collapsible-header:hover {
108 | background-color: #ccc;
109 | }
110 |
111 | /* Style the collapsible content. Note: hidden by default */
112 | .collapsible-content {
113 | padding-left: 8px;
114 | max-height: 0;
115 | overflow: hidden;
116 | transition: max-height 0.2s ease-out;
117 | border-left: 1px solid lightgray;
118 | }
119 |
120 | #selecton-version {
121 | text-align: center;
122 | }
123 |
124 | #additional-settings-block {
125 | width: 100%;
126 | }
127 |
128 | #customSearchTooltipHint, #markerHintHeader {
129 | margin: 0px 0px 15px;
130 | }
131 |
132 | .custom-search-option-url-input {
133 | display: inline; min-width: 100%; max-width: 100%;
134 | }
135 |
136 | .custom-search-option-move-button {
137 | max-width: 25px; padding: 1px; align-items: center
138 | }
139 |
140 | .custom-search-option-url-label {
141 | display: inline; opacity: 0.5;
142 | }
143 |
144 | .custom-search-option-icon-input {
145 | min-width: 100%; max-width: 100%; margin-top: 3px !important;
146 | }
147 |
148 | /* Collapsible header arrow */
149 | .collapsible-header:after {
150 | content: '❮';
151 | font-size: 18px;
152 | font-weight: 50;
153 | float: right;
154 | opacity: 0.5;
155 | transform: translate(-7px, -2px) rotate(-90deg);
156 | transition: transform 150ms ease;
157 | }
158 |
159 | .active:after {
160 | transform: translate(-7px, -2px) rotate(-270deg);
161 | }
162 |
163 |
164 | /* Markers section */
165 | .marker-website-tile {
166 | cursor: pointer;
167 | position: relative !important;
168 | border: 1px solid lightgray;
169 | border-radius: 3px;
170 | margin: 3px 0px;
171 | }
172 |
173 | .marker-website-favicon {
174 | display: inline; margin-right: 5px; width: 15px; height: 15px; vertical-align: middle !important;
175 | }
176 |
177 | .markers-counter-circle {
178 | position: absolute;
179 | bottom: 1px;
180 | right: 1px;
181 | background: rgba(85, 85, 85, 0.438); color: white;
182 | font-size: 14px;
183 | padding: 0px 6px;
184 | border-bottom-right-radius: 3px;
185 | }
186 |
187 | .marker-tile {
188 | position: relative;
189 | padding: 6px 0px;
190 | overflow-y: auto;
191 | max-height: 60px; cursor: pointer;
192 | }
193 |
194 | .marker-color-preview {
195 | max-width: 4px; max-height: 4px; padding: 0px 8.5px; margin-right: 5px; margin-left: 2px; border-radius: 2px;
196 | border: 1px solid lightgray;
197 | display: inline;
198 | }
199 |
200 | /* Marker delete button */
201 | .marker-highlight-delete {
202 | position: absolute;
203 | top: 1px;
204 | right: 1px;
205 | background: #555;
206 | color: white;
207 | /* border-radius: 50%; */
208 | /* border: 1px solid lightgray; */
209 | border-top-right-radius: 3px;
210 | z-index: 999999999999;
211 | opacity: 0;
212 | font-size: 14px;
213 | transition: opacity 200ms;
214 | padding: 0px 4px;
215 | cursor: pointer;
216 | }
217 |
218 | .marker-tile:hover .marker-highlight-delete {
219 | opacity: 0.85;
220 | }
221 |
222 | .marker-highlight-delete:hover {
223 | background: red;
224 | }
225 |
226 | .header-icon {
227 | margin-right: 8px;
228 | opacity: 0.55;
229 | position: absolute;
230 | left: 8px;
231 | margin: auto;
232 | bottom: 0;
233 | top: -2px;
234 | }
235 |
236 | /* 'What's new' button color */
237 | /* #selecton-version a { color: #55b5ff !important; text-decoration: none; } */
238 | a {
239 | color: #55b5ff;
240 | }
241 | a:not(:hover) {
242 | text-decoration: none;
243 | }
244 |
245 |
246 | @media (min-width: 600px) {
247 | #footer-buttons { text-align: center; }
248 | #footer-buttons button { width: unset; display: inline; }
249 | #allChangesSavedAutomaticallyHeader { text-align: center; }
250 | }
251 |
252 | @-moz-document url-prefix() {
253 | body { font-family: sans-serif !important; line-height: 1.0 !important; }
254 | h3 {font-size: 18px; font-weight: 600;}
255 | }
256 |
257 |
258 | @media not all and (min-resolution:.001dpcm){ @supports (-webkit-appearance:none) {
259 | .collapsible-header {
260 | background-color: rgba(211,211,211, 0.6);
261 | cursor: auto;
262 | border-radius: 4px;
263 | padding: 16px 6px 16px 12px;
264 | }
265 |
266 | .custom-search-option-move-button{
267 | max-width: 40px; width: 40px; padding: 2.5px;
268 | }
269 |
270 | .active, .collapsible-header:hover {
271 | background-color: rgba(211,211,211, 0.6);
272 | }
273 |
274 | h4 {
275 | color: black;
276 | opacity: 0.6;
277 | }
278 |
279 | button {
280 | padding: 6px; cursor: auto;
281 | }
282 |
283 | button img { display: none; }
284 |
285 | .option, label { cursor: auto; }
286 |
287 | .option:hover {
288 | background-color: unset;
289 | }
290 |
291 | hr {
292 | opacity: 0.5;
293 | }
294 | }}
295 |
296 | @media (prefers-color-scheme: dark) {
297 | body {
298 | background: rgb(40,41,44);
299 | color: white;
300 | }
301 |
302 | .collapsible-header {
303 | background-color: rgba(238, 238, 238, 0.528);
304 | color: white;
305 | }
306 |
307 | .collapsible-header:hover {
308 | background-color: rgba(204, 204, 204, 0.503);
309 | }
310 |
311 | .collapsible-content {
312 | border-left: 1px solid gray;
313 | }
314 |
315 | .option:hover {
316 | background-color: rgb(256, 256, 256, 0.1);
317 | }
318 |
319 | .header-icon {
320 | filter: invert(100%);
321 | }
322 |
323 | h4 {
324 | color: lightgray;
325 | opacity: 0.75;
326 | }
327 |
328 | hr {
329 | color: lightgray;
330 | }
331 | }
--------------------------------------------------------------------------------
/src/options/test-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Test page — Selecton
9 |
10 |
11 |
40 |
41 |
42 |
43 |
44 |
Selecton test page
45 |
46 |
47 | You can test your current configs here.
48 | It may be needed to reload page to apply new configs (unless 'Apply configs immediately' is
49 | checked in settings)
50 |
51 |
52 | Dummy text
53 |
54 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin consequat vel turpis quis venenatis. Sed vitae nisl vel nunc rhoncus feugiat a vitae
55 | augue. Sed
56 | interdum eget arcu ultricies lobortis. Vivamus sodales et dolor ac vestibulum. Aliquam lobortis malesuada nisi nec sodales. Morbi tempus neque efficitur
57 | nunc
58 | eleifend, ac consectetur purus fringilla. Pellentesque vehicula nisl ultricies, venenatis libero non, vestibulum nisi. Vestibulum in risus et nisi
59 | bibendum
60 | porttitor.
61 |
62 |
63 | Phasellus bibendum euismod erat vitae semper. Donec pellentesque pretium massa eget fermentum. Nulla ut velit augue. In hac habitasse platea dictumst.
64 | Suspendisse potenti. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque fringilla varius risus,
65 | sit amet
66 | consectetur libero finibus non. Sed fringilla, urna at lobortis mattis, urna mauris porttitor metus, in pulvinar dui mi ut magna. Nam nec felis id mi
67 | maximus
68 | pellentesque.
69 |
70 |
71 | Cras pharetra ex ultrices massa semper gravida. Morbi fermentum, lacus iaculis vehicula commodo, eros orci ultricies magna, eu gravida lorem nulla id
72 | dui. Proin
73 | sed condimentum nunc. Sed porttitor ullamcorper ante eu volutpat. Nullam fringilla aliquam nulla. Praesent non ipsum non ipsum tempor egestas. Etiam id
74 | purus
75 | vel sem efficitur laoreet. Vestibulum ut auctor mi. Mauris bibendum, nisi et scelerisque efficitur, urna quam efficitur nisi, non mollis dui libero quis
76 | tortor.
77 | Suspendisse efficitur nisl vel magna accumsan, vitae ultrices quam suscipit. Ut malesuada tortor nec sem vulputate, sit amet placerat orci viverra.
78 | Phasellus
79 | turpis sem, sagittis a nulla non, rhoncus fringilla turpis. Nam nec cursus velit, quis molestie elit. Aenean eget leo aliquet, tincidunt est sed,
80 | interdum nibh.
81 | Quisque nec sodales nibh. Cras id nibh aliquam, tempus risus nec, ornare urna.
82 |
83 |
84 | Donec elementum pellentesque euismod. Aliquam commodo urna in purus lacinia pulvinar a vel metus. Ut iaculis ante nibh, id viverra lectus placerat
85 | vitae.
86 | Quisque congue nibh urna, vitae interdum justo varius malesuada. Proin tristique sem dictum massa auctor eleifend. Aenean auctor vitae risus id viverra.
87 | Nunc ut
88 | sapien non nibh pretium rhoncus ac sed ante. Vivamus sagittis quam lectus, quis vehicula urna tincidunt id. Cras eu nisl eget nisi dignissim maximus eu
89 | nec leo.
90 | Quisque neque dolor, imperdiet sit amet aliquam sit amet, vestibulum quis justo. Pellentesque non lectus quis diam tincidunt facilisis ut id neque.
91 | Fusce
92 | dignissim egestas neque rhoncus tincidunt. Proin lacus tellus, mattis id justo vitae, porta tempus nibh.
93 |
94 |
95 | Measure units
96 |
97 | Mass: 205lbs — 93kg
98 | Temperature: 102.2°F — 39°C
99 | Speed: 62.14mph — 100km/h
100 | Distance: 60 miles — 96.56 km
101 | Volume: 2.5 gal — 11.3652 liters
102 | Volume: 4 qt — 3.79 L
103 |
104 |
105 | Currencies
106 |
107 | 71,52 AUD
108 | $71,52
109 | 248 dollars
110 | 3334,29 RUB
111 | 2 million euros
112 |
113 |
114 | Links
115 |
116 | https://github.com/emvaized/selecton-extension
117 | google.com
118 | r/kde
119 |
120 |
121 | Various buttons
122 |
123 | #3590FF
124 | johndoe@example.com
125 | 25 May
126 |
127 |
128 | Text field
129 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/src/options/test-page.js:
--------------------------------------------------------------------------------
1 | document.getElementById('text-input').oninput = function (e) {
2 | document.getElementById('text-input-result').innerHTML = e.target.value;
3 | }
--------------------------------------------------------------------------------
/src/popup/popup.css:
--------------------------------------------------------------------------------
1 | #selecton-settings-label {
2 | display: inline; font-size: 18px;vertical-align: middle; color: grey;
3 | }
4 |
5 | .circleButton {
6 | opacity: 0.8;
7 | border-radius: 50%;
8 | padding: 3px;
9 | margin-left: 3px;
10 | }
11 |
12 | .float {
13 | float: right;
14 | }
15 |
16 | .circleButton:hover{
17 | background-color: lightGrey;
18 | cursor: pointer;
19 | }
20 |
21 | #popup-header {
22 | padding: 6px; border-bottom: 1px solid lightGrey; vertical-align: bottom;
23 | height: 25px;
24 | }
25 |
26 | #headerLogo {
27 | display: inline; vertical-align: middle;
28 | }
29 |
30 | iframe {
31 | display: block; margin: 0px !important; width: 100%;
32 | height: 525px;
33 | min-width: 400px;
34 | }
35 |
36 | @-moz-document url-prefix() {
37 | body { font-family: sans-serif !important; }
38 | iframe { min-width: 365px; }
39 | #headerLogo {vertical-align: bottom;}
40 | }
41 |
42 |
43 | @media not all and (min-resolution:.001dpcm){ @supports (-webkit-appearance:none) {
44 | #selecton-settings-label {
45 | color: black; opacity: 0.65;
46 | }
47 | }}
48 |
49 | @media (prefers-color-scheme: dark) {
50 | body {
51 | background: rgb(40,41,44);
52 | color: white;
53 | }
54 |
55 | #selecton-settings-label {
56 | color: lightgray;
57 | }
58 |
59 | #openSettingsInTabButton {
60 | filter: invert(100%);
61 | }
62 |
63 | #popup-header {
64 | border-bottom: 1px solid gray;
65 | }
66 | }
--------------------------------------------------------------------------------
/src/popup/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/popup/popup.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", function () {
2 | document.getElementById('selecton-settings-label').innerHTML = chrome.i18n.getMessage("selectonSettings") ?? 'Selecton settings';
3 |
4 | let openSettingsInTabButton = document.getElementById('openSettingsInTabButton');
5 | openSettingsInTabButton.setAttribute('title', chrome.i18n.getMessage("openInNewTab") ?? 'Open in new tab');
6 | openSettingsInTabButton.addEventListener('mouseup', function (e) {
7 | if (e.button == 0) {
8 | chrome.runtime.openOptionsPage();
9 | window.close();
10 | } else if (e.button == 1) {
11 | window.open(chrome.runtime.getURL('options/options.html'));
12 | }
13 | });
14 |
15 | });
--------------------------------------------------------------------------------
/src/ui/buttons/basic-buttons.js:
--------------------------------------------------------------------------------
1 |
2 | function addBasicTooltipButtons(layout) {
3 | // TODO: Provide option to use regular butttons instead; add text format buttons as one button
4 | if (layout == 'textfield') {
5 | const textField = document.activeElement;
6 | const isContentEditable = textField.getAttribute('contenteditable') !== null;
7 |
8 | if (selection.toString() !== '') {
9 | try {
10 | /// Add a cut button
11 | addBasicTooltipButton(cutLabel, cutButtonIcon, function () {
12 | document.execCommand('cut');
13 | }, true);
14 |
15 | /// Add copy button
16 | copyButton = addBasicTooltipButton(copyLabel, copyButtonIcon, function () {
17 | try {
18 | textField.focus();
19 | document.execCommand('copy');
20 | removeSelectionOnPage();
21 | } catch (e) { console.log(e); }
22 | });
23 |
24 | /// Add paste button
25 | addBasicTooltipButton(pasteLabel, pasteButtonIcon, function () {
26 | textField.focus();
27 | if (isContentEditable) {
28 | /// TODO: Rewrite this in order to ask for clipboardRead permission first
29 |
30 | // chrome.permissions.request({
31 | // permissions: ['clipboardRead'],
32 | // }, (granted) => {
33 | // if (granted) {
34 | let currentClipboardContent = getCurrentClipboard();
35 | if (currentClipboardContent !== null && currentClipboardContent !== undefined && currentClipboardContent != '')
36 | document.execCommand("insertHTML", false, currentClipboardContent);
37 | // } else {
38 | // chrome.runtime.sendMessage({ type: 'selecton-no-clipboard-permission-message' });
39 | // }
40 | // });
41 |
42 | } else
43 | document.execCommand('paste');
44 |
45 | removeSelectionOnPage();
46 | hideTooltip();
47 | });
48 |
49 | if (configs.addFontFormatButtons) {
50 |
51 | /// Italic button
52 | addBasicTooltipButton(italicLabel, italicTextIcon, function () {
53 | textField.focus();
54 | document.execCommand(isContentEditable ? "insertHTML" : "insertText", false, '' + selectedText + ' ');
55 | hideTooltip();
56 | });
57 |
58 | /// Bold button
59 | addBasicTooltipButton(boldLabel, boldTextIcon, function () {
60 | textField.focus();
61 | document.execCommand(isContentEditable ? "insertHTML" : "insertText", false, '' + selectedText + ' ');
62 | hideTooltip();
63 | });
64 |
65 | /// Strikethrough button
66 | addBasicTooltipButton(strikeLabel, strikeTextIcon, function () {
67 | textField.focus();
68 | document.execCommand(isContentEditable ? "insertHTML" : "insertText", false, '' + selectedText + ' ');
69 | hideTooltip();
70 | });
71 | }
72 |
73 | if (configs.collapseButtons)
74 | try {
75 | collapseButtons();
76 | } catch (e) { if (configs.debugMode) console.log(e); }
77 |
78 | setCopyButtonTitle(copyButton);
79 |
80 | } catch (e) { if (configs.debugMode) console.log(e) }
81 |
82 | } else {
83 | if (configs.addPasteButton)
84 | try {
85 | /// Add paste button
86 | addBasicTooltipButton(pasteLabel, pasteButtonIcon, function (e) {
87 | textField.focus();
88 |
89 | if (textField.getAttribute('contenteditable') !== null) {
90 | let currentClipboardContent = getCurrentClipboard();
91 |
92 | if (currentClipboardContent !== null && currentClipboardContent !== undefined && currentClipboardContent != '')
93 | document.execCommand("insertHTML", false, currentClipboardContent);
94 | } else
95 | document.execCommand('paste');
96 |
97 | removeSelectionOnPage();
98 | // hideTooltip();
99 | }, true);
100 |
101 | } catch (e) { if (configs.debugMode) console.log(e); }
102 |
103 | /// Add 'clear' button
104 | if (configs.addClearButton && isTextFieldEmpty == false)
105 | addBasicTooltipButton(clearLabel, clearIcon, function (e) {
106 | removeSelectionOnPage();
107 | textField.focus();
108 |
109 | if (textField.getAttribute('contenteditable') !== null)
110 | textField.innerHTML = '';
111 | else {
112 | textField.value = '';
113 | }
114 | });
115 | }
116 |
117 | setBorderRadiusForSideButtons(tooltip);
118 |
119 | } else {
120 | /// Add search button
121 | // let selectedText = selection.toString();
122 | searchButton = addLinkTooltipButton(searchLabel, searchButtonIcon, returnSearchUrl(selectedText.trim()), true);
123 |
124 | /// Populate panel with custom search buttons, when enabled
125 | if (configs.customSearchOptionsDisplay == 'panelCustomSearchStyle') {
126 | if (configs.customSearchButtons)
127 | for (var i = 0, l = configs.customSearchButtons.length; i < l; i++) {
128 | const item = configs.customSearchButtons[i];
129 |
130 | const url = item['url'].replace('%s', selectedText);
131 | const optionEnabled = item['enabled'];
132 | const domain = url.split('/')[2];
133 | const title = item['title'] ?? domain;
134 | const icon = item['icon'] ?? 'https://www.google.com/s2/favicons?domain=' + domain;
135 |
136 | if (optionEnabled) {
137 | let b = addLinkTooltipButton(title ?? url, icon, url);
138 | b.classList.add('custom-search-image-button')
139 | }
140 | }
141 | }
142 |
143 | /// Add copy button
144 | /// TODO: Add option to copy plain text
145 | copyButton = addBasicTooltipButton(copyLabel, copyButtonIcon, function () {
146 | document.execCommand('copy');
147 | // removeSelectionOnPage();
148 | // if (configs.hideTooltipOnActionButtonClick){
149 | // hideDragHandles();
150 | // hideTooltip();
151 | // }
152 | });
153 | }
154 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/calendar-button.js:
--------------------------------------------------------------------------------
1 | function checkToAddCalendarButton(text) {
2 | let weekday, day, month, year, time;
3 | let mayBeDate = false, showDateInsteadOfWeekday = false;
4 |
5 | const words = text.toLowerCase().split(' ');
6 | const todayDate = new Date();
7 |
8 | loop:
9 | for (let i = 0, n = words.length; i < n; i++) {
10 | const word = words[i];
11 |
12 | /// month
13 | for (j in dateKeywords.month) {
14 | const mon = dateKeywords.month[j];
15 | if (word.includes(mon)) {
16 | month = dateKeywords.month[j % 12];
17 | // mayBeDate = true;
18 | continue loop;
19 | }
20 | }
21 |
22 | /// weekday
23 | for (j in dateKeywords.weekday) {
24 | const weekd = dateKeywords.weekday[j];
25 | if (word.includes(weekd)) {
26 | weekday = dateKeywords.weekday[j % 7];
27 | mayBeDate = true;
28 | continue loop;
29 | }
30 | }
31 |
32 | /// check for 'tomorrow' keywords
33 | for (j in dateKeywords.tomorrow) {
34 | const tomorrowKeyword = dateKeywords.tomorrow[j];
35 | if (word.includes(tomorrowKeyword)) {
36 | const tomorrow = new Date(new Date().getTime() + (24 * 60 * 60 * 1000));
37 | weekday = dateKeywords.weekday[tomorrow.getDay()];
38 | day = tomorrow.getDate();
39 | month = dateKeywords.month[todayDate.getMonth()];
40 | mayBeDate = true;
41 | showDateInsteadOfWeekday = true;
42 | continue loop;
43 | }
44 | }
45 |
46 | const wordLength = word.length;
47 | const wordIsNumeric = isStringNumeric(word);
48 |
49 | /// check for day of month
50 | if (wordIsNumeric && wordLength >= 1 && wordLength < 3) {
51 | /// don't use if it's time in 12-hour format
52 | const nextWord = words[i + 1];
53 | if (nextWord && (nextWord.toLowerCase() == 'am' || nextWord.toLowerCase() == 'pm'))
54 | continue;
55 |
56 | if (day && !year) year = word;
57 | else {
58 | day = word;
59 | mayBeDate = true;
60 | }
61 | continue;
62 | }
63 |
64 | /// check for year
65 | if (wordIsNumeric && wordLength == 4) {
66 | year = word;
67 | if (month) mayBeDate = true;
68 | continue;
69 | }
70 |
71 | /// check for time
72 | if (word.includes(':')) {
73 | time = word;
74 | if (time.split(':').length == 2) time += ':00';
75 | continue;
76 | }
77 | }
78 |
79 | if (!mayBeDate) {
80 | /// If no success, try to parse string as date
81 | if (text.includes('/')) {
82 | const parsedDate = new Date(text);
83 | if (isNaN(parsedDate)) return;
84 | addCalendarButtonFromDate(parsedDate, todayDate, showDateInsteadOfWeekday);
85 | return;
86 | } else if (text.includes('.')) {
87 | const parts = text.split('.');
88 | const partsLength = parts.length;
89 |
90 | if (partsLength >= 2 && partsLength <= 3) {
91 | let d = parts[0], m = parts[1], y = partsLength < 3 ? todayDate.getFullYear() : parts[2];
92 | if (d == '' || m == '') return;
93 | if (parseInt(d) == 0) return;
94 | if (y.length < 2) return;
95 |
96 | const parsedDate = new Date(`${y}/${m}/${d}`);
97 | if (isNaN(parsedDate)) return;
98 | addCalendarButtonFromDate(parsedDate, todayDate, showDateInsteadOfWeekday);
99 | }
100 |
101 | return;
102 | } else return;
103 | }
104 |
105 | /// fill non found data
106 | if (!year) year = todayDate.getFullYear();
107 | if (!month && !day && weekday) {
108 | let date = new Date(), daytoset = dateKeywords.weekday.indexOf(weekday);
109 | let currentDay = date.getDay();
110 | let distance = (daytoset + 7 - currentDay) % 7;
111 | date.setDate(date.getDate() + distance);
112 | month = dateKeywords.month[date.getMonth()];
113 | day = date.getDate() + 1;
114 | // showDateInsteadOfWeekday = true;
115 | }
116 |
117 | /// gather collected data
118 | /// format: Wed, 09 Aug 1995 00:00:00 GMT'
119 | let dateString = '';
120 | if (weekday) dateString += `${weekday}, `;
121 | if (day) dateString += `${day} `;
122 | if (month) dateString += `${month} `;
123 | if (year) dateString += `${year} `;
124 | if (time) dateString += `${time} `;
125 |
126 | const returnedDate = new Date(Date.parse(dateString));
127 | if (isNaN(returnedDate)) return;
128 |
129 | addCalendarButtonFromDate(returnedDate, todayDate, showDateInsteadOfWeekday, time);
130 | }
131 |
132 |
133 | function addCalendarButtonFromDate(date, todayDate, showDateInsteadOfWeekday, time) {
134 | /// get difference in days
135 | const diffTime = todayDate - date;
136 | const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)) * -1;
137 | let buttonLabel;
138 |
139 | if (showDateInsteadOfWeekday) {
140 | buttonLabel = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
141 | } else {
142 | if (diffDays <= -360) {
143 | const years = -1 * Math.ceil(diffDays / 360);
144 | buttonLabel = years == 1 ? chrome.i18n.getMessage('yearAgo') : chrome.i18n.getMessage('yearsAgo', `${years}`);
145 | } else if (diffDays < -30 && diffDays > -360) {
146 | const months = -1 * Math.ceil(diffDays / 30);
147 | buttonLabel = months == 1 ? chrome.i18n.getMessage('monthAgo') : chrome.i18n.getMessage('monthsAgo', `${months}`);
148 | } else if (diffDays < 0 && diffDays >= -30) {
149 | const days = -1 * diffDays;
150 | buttonLabel = days == 1 ? chrome.i18n.getMessage('dayAgo') : chrome.i18n.getMessage('daysAgo', `${days}`);
151 | } else if (diffDays == 0) {
152 | buttonLabel = chrome.i18n.getMessage('today');
153 | } else if (diffDays > 0 && diffDays < 30) {
154 | buttonLabel = diffDays == 1 ? chrome.i18n.getMessage('inDay') : chrome.i18n.getMessage('inDays', `${diffDays}`);
155 | } else if (diffDays >= 29 && diffDays < 360) {
156 | const months = Math.floor(diffDays / 30);
157 | buttonLabel = months == 1 ? chrome.i18n.getMessage('inMonth') : chrome.i18n.getMessage('inMonths', `${months}`);
158 | } else if (diffDays >= 360) {
159 | const years = Math.floor(diffDays / 360);
160 | buttonLabel = years == 1 ? chrome.i18n.getMessage('inYear') : chrome.i18n.getMessage('inYears', `${years}`);
161 | } else {
162 | buttonLabel = date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
163 | }
164 | }
165 |
166 | /// If specific time is provided, create event – otherwise open day in calendar
167 | let calendarLink;
168 | if (time) {
169 | let dateString = date.toISOString().replaceAll(':', '').replaceAll('-', '');
170 | calendarLink = `https://calendar.google.com/calendar/u/0/r/eventedit?&dates=${dateString}/${dateString}&sf=true`;
171 | } else {
172 | calendarLink = `https://calendar.google.com/calendar/u/0/r/day/${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
173 | }
174 | const dateButton = addLinkTooltipButton(buttonLabel, calendarIcon, calendarLink);
175 | dateButton.title = date.toLocaleDateString();
176 | dateButton.classList.add('color-highlight');
177 |
178 | if (configs.buttonsStyle == 'onlyicon') dateButton.innerHTML += ' ' + buttonLabel;
179 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/collapse-button.js:
--------------------------------------------------------------------------------
1 | function collapseButtons() {
2 | const maxButtons = configs.maxTooltipButtonsToShow ?? 3;
3 | const buttonsCount = tooltip.children.length - 1; /// subtract the arrow
4 |
5 | if (buttonsCount > maxButtons) {
6 |
7 | let collapsedButtonsPanel, moreButton;
8 | if (configs.collapseAsSecondPanel){
9 | /// Show as secondary panel
10 | collapsedButtonsPanel = createHoverPanelForButton(undefined, undefined, undefined, false, false, true, false, true);
11 | collapsedButtonsPanel.style.right = '2px';
12 |
13 | setTimeout(()=> {
14 | collapsedButtonsPanel.style.transform = returnTooltipRevealTransform(false, false);
15 | collapsedButtonsPanel.style.zIndex = '-1';
16 | if(tooltip) tooltip.appendChild(collapsedButtonsPanel);
17 | }, 2)
18 |
19 | } else {
20 | /// Create 'more' button
21 | moreButton = document.createElement('button');
22 | moreButton.setAttribute('class', configs.showButtonBorders ? 'selection-popup-button button-with-border' : 'selection-popup-button');
23 | moreButton.classList.add('more-button');
24 | moreButton.innerText = configs.verticalLayoutTooltip ? '⋯' : '⋮';
25 |
26 | /// Show as hover button
27 | tooltip.appendChild(moreButton);
28 | collapsedButtonsPanel = createHoverPanelForButton(moreButton, undefined, undefined, false, true, true, false);
29 |
30 | /// Show buttons count
31 | const buttonsCountSpan = moreButton.querySelector('.selecton-hover-button-indicator');
32 | if (buttonsCountSpan){
33 | buttonsCountSpan.innerText = buttonsCount - maxButtons;
34 | buttonsCountSpan.classList.add('selecton-more-button-child-count')
35 | }
36 | }
37 |
38 | collapsedButtonsPanel.style.maxWidth = 'unset';
39 | collapsedButtonsPanel.style.zIndex = '2';
40 | collapsedButtonsPanel.classList.add('default-padding-tooltip');
41 |
42 | /// Append buttons to panel
43 | for (let i = buttonsCount; i > maxButtons; i--) {
44 | const button = tooltip.children[i];
45 | // if (!configs.verticalLayoutTooltip && i == (maxButtons * 1) + 1) button.classList.remove('button-with-border');
46 |
47 | // collapsedButtonsPanel.prepend(button);
48 | if (configs.verticalLayoutTooltip)
49 | collapsedButtonsPanel.prepend(button);
50 | else {
51 | collapsedButtonsPanel.appendChild(button);
52 | }
53 | }
54 |
55 | if (!configs.collapseAsSecondPanel)
56 | moreButton.appendChild(collapsedButtonsPanel);
57 | setBorderRadiusForSideButtons(collapsedButtonsPanel, false);
58 | }
59 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/dictionary-button.js:
--------------------------------------------------------------------------------
1 | function addDictionaryButton(selectionLength) {
2 | try {
3 |
4 | const locale = configs.languageToTranslate;
5 | const wikiUrl = 'https://' +
6 | (locale ? locale + '.' : '') +
7 | `wikipedia.org/w/index.php?search=${encodeURIComponent(selectedText)}`;
8 | // const wikiButton = addBasicTooltipButton(dictionaryLabel, dictionaryButtonIcon, wikiUrl);
9 | const wikiButton = addLinkTooltipButton(dictionaryLabel, dictionaryButtonIcon, wikiUrl);
10 | // wikiButton.setAttribute('id', 'selecton-translate-button');
11 |
12 | /// set fetch on hover listener
13 | if (selectionLength < 500) {
14 | setLiveWikiButton(selectedText, wikiButton);
15 | }
16 |
17 | } catch (e) {
18 | if (configs.debugMode)
19 | console.log(e);
20 | }
21 | }
22 |
23 |
24 | function setLiveWikiButton(word, wikiButton) {
25 | let fetched = false;
26 | let definitionPanel = createHoverPanelForButton(wikiButton, `${chrome.i18n.getMessage("searchingDefinitions") ?? 'Searching'}...`, onShow);
27 | wikiButton.appendChild(definitionPanel);
28 |
29 | function onShow() {
30 | if (fetched == false) {
31 | /// Fetch definition from Wikipedia
32 | fetched = true;
33 | fetchDefinition(word, definitionPanel, wikiButton);
34 | }
35 | }
36 | }
37 |
38 | async function fetchDefinition(text, definitionPanel, wikiButton) {
39 | let resultDefinition;
40 | let nothingFoundLabel = chrome.i18n.getMessage("noDefinitionFound") ?? 'No definition found';
41 | let locale = configs.languageToTranslate;
42 | let textToSearch = encodeURIComponent(text.replaceAll(' ', '_'));
43 |
44 | fetchFromWikipedia(locale, (res) => {
45 | if (resultDefinition == null || resultDefinition == undefined || resultDefinition == '') {
46 | /// try to fetch from english Wiki
47 | locale = 'en';
48 | fetchFromWikipedia('en', (res2)=>{
49 | if (resultDefinition == null || resultDefinition == undefined || resultDefinition == '') {
50 | /// no results found
51 | definitionPanel.innerText = nothingFoundLabel;
52 | return;
53 | } else {
54 | onFinish();
55 | }
56 | });
57 | } else {
58 | onFinish();
59 | }
60 |
61 | function onFinish(){
62 | /// Set definition view
63 | definitionPanel.innerText = resultDefinition;
64 | definitionPanel.classList.add('selecton-live-translation');
65 | definitionPanel.style.maxWidth = '450%';
66 |
67 | /// If text contains line breaks, align by the left side
68 | if (resultDefinition.includes(`
69 | `)) definitionPanel.style.textAlign = 'start';
70 |
71 | /// Create origin language label
72 | let originLabelWidth = configs.fontSize / 1.5;
73 | let originLabelPadding = 6;
74 | let langLabel;
75 | if (locale !== null && locale !== undefined && locale !== '') {
76 | langLabel = document.createElement('span');
77 | langLabel.textContent = locale;
78 | langLabel.setAttribute('style', `opacity: 0.7; position: absolute; right: ${originLabelPadding}px; bottom: ${originLabelPadding}px; font-size: ${originLabelWidth}px;color: var(--selection-button-foreground) !important`)
79 | definitionPanel.appendChild(langLabel);
80 | }
81 | }
82 |
83 | });
84 |
85 |
86 | function fetchFromWikipedia(locale, callback, onError) {
87 | try {
88 | /// exclude local language variations, such as ru-RU
89 | let langToFetch = locale;
90 | if (langToFetch.includes('-')) langToFetch = langToFetch.split('-')[0];
91 | if (langToFetch.includes('_')) langToFetch = langToFetch.split('_')[0];
92 |
93 | /// Fetch data from Wiktionary
94 |
95 | const wikiUrl = `https://${langToFetch}.wikipedia.org/w/api.php?action=query&exsectionformat=plain&prop=extracts&origin=*&exchars=${configs.dictionaryButtonResponseCharsAmount ?? 300}&exlimit=1&explaintext=0&formatversion=2&format=json&titles=${textToSearch}`;
96 |
97 | chrome.runtime.sendMessage({ type: 'background_fetch', url: wikiUrl }, (res) => {
98 | // let jsoned = res.json();
99 | let jsoned = res;
100 | let extract = jsoned.query.pages[0].extract;
101 |
102 | if (extract) {
103 | resultDefinition = extract;
104 | }
105 |
106 | if (jsoned.query.pages[0].pageid)
107 | // wikiButton.onmousedown = function (e) {
108 | // let url = `https://${locale}.wikipedia.org/?curid=${jsoned.query.pages[0].pageid}`;
109 | // onTooltipButtonClick(e, url);
110 | // }
111 | wikiButton.href = `https://${locale}.wikipedia.org/?curid=${jsoned.query.pages[0].pageid}`;
112 |
113 | callback(res)
114 | });
115 | } catch (e) { console.log(e); onError(e); }
116 | }
117 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/marker.js:
--------------------------------------------------------------------------------
1 | let possibleMarkerColors = [
2 | {
3 | 'color': 'yellow',
4 | 'foreground': 'black'
5 | },
6 | {
7 | 'color': 'lightblue',
8 | 'foreground': 'black'
9 | },
10 | {
11 | 'color': 'lightgreen',
12 | 'foreground': 'black'
13 | },
14 | {
15 | 'color': 'red',
16 | 'foreground': 'white'
17 | },
18 | ];
19 |
20 | function addMarkerButton() {
21 | /// Create button
22 | const markerButton = addBasicTooltipButton(markerLabel, markerIcon, function () {
23 | markTextSelection(undefined, undefined, selection.toString());
24 | saveAllMarkers();
25 | removeSelectionOnPage();
26 | });
27 | markerButton.classList.add('higher-z-index');
28 |
29 | /// Create color chooser panel
30 | setTimeout(function () {
31 | let colorChooserPanel = createHoverPanelForButton(markerButton, undefined, undefined, false, true, false, false);
32 | colorChooserPanel.style.maxWidth = '500%';
33 | colorChooserPanel.classList.add('default-padding-tooltip');
34 |
35 | /// Generate buttons to panel
36 | for (let i = 0, l = possibleMarkerColors.length; i < l; i++) {
37 | let selectedColor = possibleMarkerColors[i];
38 |
39 | const button = document.createElement('button');
40 | button.className = 'selection-popup-button';
41 | if (configs.showButtonBorders)
42 | button.classList.add('button-with-border');
43 |
44 | const colorCircle = document.createElement('div');
45 | colorCircle.setAttribute('class', 'selection-popup-color-preview-circle');
46 | colorCircle.style.background = selectedColor.color;
47 | button.appendChild(colorCircle);
48 |
49 | button.addEventListener("mousedown", function (e) {
50 | e.stopPropagation();
51 | markTextSelection(selectedColor.color, selectedColor.foreground, selection.toString());
52 | saveAllMarkers();
53 | removeSelectionOnPage();
54 | });
55 |
56 | colorChooserPanel.prepend(button);
57 | }
58 |
59 | setBorderRadiusForSideButtons(colorChooserPanel, false);
60 |
61 | /// Append panel
62 | markerButton.appendChild(colorChooserPanel);
63 | }, 5)
64 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/search-button.js:
--------------------------------------------------------------------------------
1 |
2 | function setHoverForSearchButton(searchButton) {
3 | /// Create search options panel
4 | let searchPanel = createHoverPanelForButton(searchButton, undefined, undefined, true);
5 | searchPanel.classList.add('no-padding-tooltip');
6 | searchPanel.style.textAlign = 'start';
7 |
8 | /// Generate buttons for panel
9 | let searchButtons = configs.customSearchButtons.filter((item, idx) => item['enabled']);
10 |
11 | const searchButtonsLength = searchButtons.length;
12 | if (searchButtonsLength == 0) return;
13 |
14 | const containerPrototype = document.createElement('a');
15 | containerPrototype.style.display = verticalSecondaryTooltip ? 'block' : 'inline-block';
16 | containerPrototype.style.textAlign = 'start';
17 | containerPrototype.className = 'custom-search-image-button';
18 | if (!verticalSecondaryTooltip) containerPrototype.style.padding = '0px';
19 | const maxIconsInRow = configs.maxIconsInRow;
20 |
21 | for (var i = 0; i < searchButtonsLength; i++) {
22 | const item = searchButtons[i];
23 |
24 | const url = item['url'];
25 | const optionEnabled = item['enabled'];
26 | const title = item['title'];
27 | const icon = item['icon'];
28 |
29 | if (optionEnabled && url !== '') {
30 | let imgButton = document.createElement('img');
31 | imgButton.setAttribute('class', 'selecton-search-tooltip-icon');
32 |
33 | imgButton.addEventListener('error', function () {
34 | if (configs.debugMode) {
35 | console.log('error loading favicon for: ' + url + ' because of security policies of website');
36 | }
37 | });
38 |
39 | imgButton.setAttribute('src', icon !== null && icon !== undefined && icon !== '' ? icon : 'https://www.google.com/s2/favicons?domain=' + url.split('/')[2])
40 |
41 | /// Set title
42 | let titleText = title !== null && title !== undefined && title !== '' ? title : returnDomainFromUrl(url);
43 | const container = containerPrototype.cloneNode(true);
44 |
45 | /// Add label in vertical style
46 | if (verticalSecondaryTooltip) {
47 | container.appendChild(imgButton);
48 |
49 | const labelSpan = document.createElement('span');
50 | labelSpan.textContent = titleText.charAt(0).toUpperCase() + titleText.slice(1);
51 | container.appendChild(labelSpan);
52 | } else {
53 | /// No label in horizontal style
54 | imgButton.style.margin = '3px 6px';
55 | imgButton.title = titleText;
56 | container.appendChild(imgButton);
57 | }
58 |
59 | searchPanel.appendChild(container);
60 |
61 | /// Set click listeners
62 | container.addEventListener("mousedown", function (e) {
63 | e.stopPropagation();
64 | // onSearchButtonClick(e, url);
65 | });
66 | container.href = returnSearchButtonUrl(url);
67 | container.target = '_blank';
68 | }
69 | }
70 |
71 | containerPrototype.remove();
72 |
73 | /// Create grid style to horizontal panel, to limit amount of icons in row
74 | if (!verticalSecondaryTooltip && searchButtonsLength > maxIconsInRow) {
75 | searchPanel.style.display = 'grid';
76 | searchPanel.style.gridTemplateColumns = `repeat(${maxIconsInRow}, 1fr)`;
77 | }
78 |
79 | /// Set border radius for first and last buttons
80 | // const borderRadiusForButton = configs.useCustomStyle ? configs.borderRadius : 3;
81 | // const firstSearchButtonBorderRadius = verticalSecondaryTooltip ?
82 | // `${borderRadiusForButton}px ${borderRadiusForButton}px 0px 0px`
83 | // : firstButtonBorderRadius;
84 | // const lastSearchButtonBorderRadius = verticalSecondaryTooltip ?
85 | // `0px 0px ${borderRadiusForButton}px ${borderRadiusForButton}px`
86 | // : lastButtonBorderRadius;
87 |
88 | // let buttons = searchPanel.children;
89 | // buttons[0].style.borderRadius = firstSearchButtonBorderRadius;
90 | // buttons[buttons.length - 1].style.borderRadius = lastSearchButtonBorderRadius;
91 |
92 | /// Append panel
93 | searchButton.appendChild(searchPanel);
94 | }
95 |
96 | function returnSearchButtonUrl(url){
97 | let selectedText = selection.toString();
98 | selectedText = encodeURI(selectedText);
99 | selectedText = selectedText.replaceAll('&', '%26').replaceAll('+', '%2B');
100 | let urlToOpen = url.replaceAll('%s', selectedText);
101 |
102 | if (urlToOpen.includes('%w'))
103 | try {
104 | let currentDomain = window.location.href.split('/')[2];
105 | urlToOpen = urlToOpen.replaceAll('%w', currentDomain);
106 | } catch (e) {
107 | if (configs.debugMode) console.log(e);
108 | }
109 |
110 | return urlToOpen;
111 | }
112 |
113 | function onSearchButtonClick(e, url) {
114 | let urlToOpen = returnSearchButtonUrl(url)
115 |
116 | try {
117 | let evt = e || window.event;
118 |
119 | if ("buttons" in evt) {
120 | if (evt.button == 0) {
121 | /// Left button click
122 | hideTooltip();
123 | removeSelectionOnPage();
124 | chrome.runtime.sendMessage({ type: 'selecton-open-new-tab', url: urlToOpen, focused: true });
125 | } else if (evt.button == 1) {
126 | /// Middle button click
127 | evt.preventDefault();
128 | if (configs.middleClickHidesTooltip) {
129 | hideTooltip();
130 | removeSelectionOnPage();
131 | }
132 |
133 | chrome.runtime.sendMessage({ type: 'selecton-open-new-tab', url: urlToOpen, focused: false });
134 | }
135 | }
136 |
137 | } catch (e) {
138 | window.open(urlToOpen, '_blank');
139 | }
140 | }
--------------------------------------------------------------------------------
/src/ui/buttons/hover-buttons/translate-button.js:
--------------------------------------------------------------------------------
1 | function addTranslateButton(onFinish, selectionLength, wordsCount) {
2 | try {
3 | if (!chrome.i18n.detectLanguage) proccessButton(true);
4 | else
5 | chrome.i18n.detectLanguage(selectedText, function (result) {
6 | if (configs.debugMode)
7 | console.log('Checking if its needed to add Translate button...');
8 |
9 | /// Show Translate button when language was not detected
10 | let shouldTranslate = false;
11 |
12 | if (configs.debugMode)
13 | console.log(`User language is: ${configs.languageToTranslate}`);
14 |
15 | let detectedLanguages = result;
16 | let languageOfSelectedText;
17 |
18 | if (detectedLanguages !== null && detectedLanguages !== undefined) {
19 | const langs = detectedLanguages.languages;
20 |
21 | if (langs.length > 0) {
22 | languageOfSelectedText = langs[0].language;
23 | if (configs.debugMode) console.log('Detected language: ' + languageOfSelectedText);
24 |
25 | /// Show detected language on info panel
26 | if (configs.showInfoPanel && detectedLanguages.isReliable && !configs.verticalLayoutTooltip)
27 | setTimeout(function () {
28 | if (infoPanel && infoPanel.isConnected) {
29 | infoPanel.innerText += ' · ' + languageOfSelectedText;
30 | // let languageNames = new Intl.DisplayNames([configs.languageToTranslate], { type: 'language' });
31 | // infoPanel.innerText += ' · ' + languageNames.of(languageOfSelectedText);
32 | }
33 | }, 5)
34 |
35 | // if (configs.debugMode)
36 | // console.log(`Detection is reliable: ${detectedLanguages.isReliable}`);
37 |
38 | /// Don't show translate button if selected language is the same as desired
39 | if (languageOfSelectedText == configs.languageToTranslate && configs.hideTranslateButtonForUserLanguage)
40 | shouldTranslate = false;
41 | else shouldTranslate = true;
42 | } else {
43 | if (configs.debugMode) console.log('Selecton failed to detect language of selected text');
44 | shouldTranslate = configs.showTranslateIfLanguageUnknown ?? false;
45 | }
46 | } else {
47 | if (configs.debugMode) console.log('Selecton failed to detect language of selected text');
48 | shouldTranslate = configs.showTranslateIfLanguageUnknown ?? false;
49 | }
50 |
51 | if (configs.debugMode)
52 | console.log(`Should translate: ${shouldTranslate}`);
53 |
54 | proccessButton(shouldTranslate, languageOfSelectedText);
55 |
56 | });
57 | } catch (e) {
58 | if (configs.debugMode)
59 | console.log(e);
60 | }
61 |
62 | function proccessButton(shouldTranslate, languageOfSelectedText) {
63 | if (shouldTranslate == true) {
64 | setRegularTranslateButton(languageOfSelectedText, selectionLength, wordsCount);
65 | }
66 | if (onFinish) onFinish();
67 | }
68 | }
69 |
70 |
71 | function setRegularTranslateButton(languageOfSelectedText, selectionLength, wordsCount) {
72 |
73 | const translateUrl = languageOfSelectedText == configs.languageToTranslate && !configs.hideTranslateButtonForUserLanguage ?
74 | returnTranslateUrl(selectedText, 'en') :
75 | returnTranslateUrl(selectedText);
76 | const translateButton = addLinkTooltipButton(translateLabel, translateButtonIcon, translateUrl);
77 |
78 | translateButton.setAttribute('id', 'selecton-translate-button');
79 |
80 | /// set live tranlsation listeners
81 | // if (configs.liveTranslation && configs.preferredTranslateService == 'google' && selectedText.length < 500) {
82 | if (configs.liveTranslation && selectionLength < 500) {
83 | setTimeout(function () {
84 | if (configs.translateSingleWordsImmediately && wordsCount == 1 && !/[:\/"'']/.test(selectedText)) {
85 | fetchTranslation(selectedText, 'auto', configs.languageToTranslate, undefined, translateButton, true)
86 | } else {
87 | setLiveTranslateOnHoverButton(selectedText, 'auto', configs.languageToTranslate, translateButton);
88 | }
89 | }, 5);
90 | }
91 | }
92 |
93 | function setLiveTranslateOnHoverButton(word, sourceLang, targetLang, translateButton) {
94 | let fetched = false;
95 | let liveTranslationPanel = createHoverPanelForButton(translateButton, `${chrome.i18n.getMessage("translating") ?? 'Translating'}...`, onShow);
96 | translateButton.appendChild(liveTranslationPanel);
97 |
98 | function onShow() {
99 | if (fetched == false) {
100 | /// Fetch definition from Google Translate
101 | fetched = true;
102 | fetchTranslation(word, sourceLang, targetLang, liveTranslationPanel, translateButton)
103 | }
104 | }
105 | }
106 |
107 | async function fetchTranslation(word, sourceLang, targetLang, liveTranslationPanel, translateButton, showResultInButton = false) {
108 | // let maxLengthForResult = 100;
109 | let noTranslationLabel = chrome.i18n.getMessage("noTranslationFound");
110 |
111 | const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${sourceLang}&tl=${targetLang}&dt=t&dt=bd&dj=1&q=${encodeURIComponent(word)}`;
112 | // const xhr = new XMLHttpRequest();
113 | // xhr.responseType = "json";
114 | // xhr.open("GET", url);
115 | // xhr.send();
116 |
117 | // let result = await new Promise((resolve, reject) => {
118 | // xhr.onload = () => {
119 | // resolve(xhr);
120 | // };
121 | // xhr.onerror = () => {
122 | // resolve(xhr);
123 | // };
124 | // });
125 |
126 | chrome.runtime.sendMessage({ type: 'background_fetch', url: url }, (response) => {
127 | let result = response;
128 |
129 | if (configs.debugMode) {
130 | console.log('Response from Google Translate:');
131 | console.log(result);
132 | }
133 |
134 | if (!result) {
135 | liveTranslationPanel.innerText = noTranslationLabel;
136 | return;
137 | }
138 |
139 | let resultOfLiveTranslation;
140 | let originLanguage;
141 |
142 | try {
143 | resultOfLiveTranslation = result.dict[0].terms[0];
144 | } catch (e) {
145 | // resultOfLiveTranslation = result.response.sentences[0].trans;
146 | resultOfLiveTranslation = '';
147 | result.sentences.forEach(function (sentenceObj) {
148 | resultOfLiveTranslation += sentenceObj.trans;
149 | })
150 | }
151 |
152 | try {
153 | originLanguage = result.src;
154 | } catch (e) { }
155 |
156 | /// Set translation view
157 | if (resultOfLiveTranslation !== null && resultOfLiveTranslation !== undefined && resultOfLiveTranslation !== '' && resultOfLiveTranslation.replaceAll(' ', '') !== word.replaceAll(' ', '')) {
158 | // if (resultOfLiveTranslation.length > maxLengthForResult)
159 | // resultOfLiveTranslation = resultOfLiveTranslation.substring(0, maxLengthForResult - 3) + '...';
160 |
161 | if (showResultInButton){
162 | let span = translateButton.querySelector('span');
163 | if (!span) span = translateButton
164 | span.innerText = resultOfLiveTranslation;
165 | span.classList.add('selecton-live-translation');
166 | translateButton.title = 'Provided by Google Translate';
167 | } else {
168 | liveTranslationPanel.innerText = resultOfLiveTranslation;
169 | liveTranslationPanel.classList.add('selecton-live-translation');
170 | }
171 |
172 | // setTimeout(function () {
173 | // /// check if panel goes off-screen on top
174 | // checkHoverPanelToOverflowOnTop(liveTranslationPanel);
175 | // }, 3);
176 |
177 | /// Create origin language label
178 | let originLabelWidth = configs.fontSize / 1.5;
179 | let originLabelPadding = 3.5;
180 | let langLabel;
181 | if (originLanguage !== null && originLanguage !== undefined && originLanguage !== '') {
182 | langLabel = document.createElement('span');
183 | langLabel.textContent = originLanguage;
184 | langLabel.setAttribute('style', `opacity: 0.7; position: relative; right: -${originLabelPadding}px; bottom: -2.5px; font-size: ${originLabelWidth}px;color: var(--selection-button-foreground) !important`)
185 |
186 | if (showResultInButton){
187 | translateButton.appendChild(langLabel);
188 | } else {
189 | liveTranslationPanel.appendChild(langLabel);
190 | }
191 | }
192 | } else {
193 | /// no translation found
194 | liveTranslationPanel.innerHTML = noTranslationLabel;
195 | }
196 | });
197 |
198 | }
--------------------------------------------------------------------------------
/src/ui/tooltip.js:
--------------------------------------------------------------------------------
1 | function createTooltip(e, recreated = false) {
2 |
3 | if (isDraggingTooltip) return;
4 | if (dontShowTooltip == true) return;
5 | if (e !== undefined && e !== null && e.button !== 0) return;
6 |
7 | setTimeout(function () {
8 | lastMouseUpEvent = e;
9 | if (selection == null || selection == undefined) return;
10 | // hideTooltip();
11 | tooltipOnBottom = false; /// reset the 'reverted' state of previous tooltip
12 |
13 | if (configs.snapSelectionToWord) {
14 | // if (isTextFieldFocused == true && configs.dontSnapTextfieldSelection == true) {
15 | if (isTextFieldFocused == true) {
16 | if (configs.debugMode)
17 | console.log('Word snapping rejected while textfield is focused');
18 | } else if (configs.disableWordSnappingOnCtrlKey && e !== undefined && (e.ctrlKey == true || e.metaKey == true)) {
19 | if (configs.debugMode)
20 | console.log('Word snapping rejected due to pressed CTRL key');
21 | } else {
22 | // selectedText = selection.toString();
23 |
24 | selectedTextIsCode = false;
25 | if (configs.disableWordSnapForCode || configs.showInfoPanel)
26 | for (let i = 0, l = codeMarkers.length; i < l; i++) {
27 | if (selectedText.includes(codeMarkers[i])) {
28 | selectedTextIsCode = true; break;
29 | }
30 | }
31 |
32 | /// dont snap if selection is modified by drag handle, or if it looks like code
33 | if (isDraggingDragHandle == false &&
34 | (selectedTextIsCode == false || !configs.disableWordSnapForCode)){
35 | if (domainIsBlacklistedForSnapping == false &&
36 | e.detail < 2 &&
37 | (timerToRecreateOverlays == null || timerToRecreateOverlays == undefined) &&
38 | e.target.id !== 'selecton-extend-selection-button' && (!e.target.parentNode || e.target.parentNode.id !== 'selecton-extend-selection-button')
39 | ) {
40 | snapSelectionByWords(selection);
41 | }
42 | }
43 |
44 | }
45 | }
46 |
47 | /// Special tooltip for text fields
48 | if (isTextFieldFocused) {
49 | if (configs.addActionButtonsForTextFields == false) return;
50 |
51 | /// Create text field tooltip
52 | setUpTooltip();
53 | addBasicTooltipButtons('textfield');
54 |
55 | if (tooltip.children.length < 2) {
56 | /// Don't add tooltip with no buttons
57 | tooltip.remove();
58 | return;
59 | }
60 |
61 | document.body.appendChild(tooltip);
62 |
63 | /// Check resulting DY to be out of view
64 | let resultDy = e.clientY - tooltip.clientHeight - arrow.clientHeight - 9;
65 | let vertOutOfView = resultDy <= 0;
66 | if (vertOutOfView) {
67 | resultDy = e.clientY + arrow.clientHeight;
68 | arrow.classList.add('arrow-on-bottom');
69 | tooltipOnBottom = true;
70 | }
71 |
72 | showTooltip(e.clientX, resultDy);
73 | return;
74 | }
75 |
76 | /// Hide previous tooltip if exists
77 | if (tooltip) hideTooltip();
78 |
79 | /// Check text selection again
80 | /// Fix for recreating tooltip when clicked inside selected area (noticed only in Firefox)
81 | selection = window.getSelection();
82 | selectedText = selection.toString().trim();
83 |
84 | if (selectedText == '') {
85 | hideDragHandles();
86 | return;
87 | }
88 |
89 | setUpTooltip(recreated);
90 |
91 | /// Add basic buttons (Copy, Search, etc)
92 | addBasicTooltipButtons(null);
93 |
94 | if (dontShowTooltip == false && selectedText !== null && selectedText !== '') {
95 | addContextualButtons(function () {
96 | /// Set border radius for first and last buttons
97 | setBorderRadiusForSideButtons(tooltip);
98 |
99 | /// Append tooltip to the DOM
100 | document.body.appendChild(tooltip);
101 |
102 | /// Calculate tooltip position and show tooltip
103 | calculateTooltipPosition(e);
104 |
105 | /// Create search tooltip for custom search options)
106 | if (configs.customSearchOptionsDisplay == 'hoverCustomSearchStyle')
107 | setTimeout(function () {
108 | if (configs.secondaryTooltipEnabled && configs.customSearchButtons)
109 | setHoverForSearchButton(searchButton);
110 | }, 5);
111 |
112 | /// Selection change listener
113 | setTimeout(function () {
114 | if (tooltipIsShown == false) return;
115 | document.addEventListener("selectionchange", selectionChangeListener);
116 | }, configs.animationDuration);
117 | });
118 |
119 | } else hideTooltip();
120 |
121 | }, 0);
122 | }
123 |
124 | function setUpTooltip(recreated = false) {
125 |
126 | /// Create tooltip and it's arrow
127 | tooltip = document.createElement('div');
128 | tooltip.className = 'selection-tooltip selecton-entity';
129 | if (configs.verticalLayoutTooltip) {
130 | tooltip.classList.add('vertical-layout-tooltip');
131 | tooltip.classList.add('reversed-order');
132 | }
133 | if (configs.buttonsStyle == 'onlyicon' || configs.buttonsStyle == 'iconlabel') tooltip.classList.add('tooltip-with-icons');
134 | tooltip.style.opacity = 0.0;
135 | tooltip.style.position = 'fixed';
136 | tooltip.style.pointerEvents = 'none';
137 | tooltip.style.transition = `opacity ${configs.animationDuration}ms ease-out, transform ${configs.animationDuration}ms ease-out`;
138 | if (recreated) tooltip.style.transition = `opacity ${configs.animationDuration}ms ease-out`;
139 | tooltip.style.transform = returnTooltipRevealTransform(false);
140 | // tooltip.style.transformOrigin = '50% 100% 0';
141 | tooltip.style.transformOrigin = configs.tooltipRevealEffect == 'scaleUpTooltipEffect' ? '50% 30% 0' : configs.tooltipRevealEffect == 'scaleUpFromBottomTooltipEffect' ? '50% 125% 0' : '50% 100% 0';
142 |
143 | if (configs.useCustomStyle && configs.tooltipOpacity != 1.0 && configs.tooltipOpacity != 1 && configs.fullOpacityOnHover) {
144 | tooltip.onmouseover = function () {
145 | setTimeout(function () {
146 | if (dontShowTooltip == true) return;
147 | try {
148 | tooltip.style.opacity = 1.0;
149 | } catch (e) { }
150 | }, 1);
151 | }
152 | tooltip.onmouseout = function () {
153 | setTimeout(function () {
154 | if (dontShowTooltip == true) return;
155 | try {
156 | tooltip.style.opacity = configs.tooltipOpacity;
157 | } catch (e) { }
158 | }, 1);
159 | }
160 | if (configs.debugMode) {
161 | console.log('Selecton tooltip inactive opacity: ' + configs.tooltipOpacity.toString());
162 | console.log('Set tooltip opacity listeners');
163 | }
164 | }
165 |
166 | /// Add tooltip arrow
167 | arrow = document.createElement('div');
168 | if (configs.showTooltipArrow) arrow.setAttribute('class', 'selection-tooltip-arrow');
169 | tooltip.appendChild(arrow);
170 |
171 | /// Make the tooltip draggable by arrow
172 | if (configs.showTooltipArrow && configs.draggableTooltip) {
173 | makeTooltipElementDraggable(arrow);
174 | }
175 |
176 | /// Apply custom stylings
177 | if (configs.useCustomStyle) {
178 | if (configs.addTooltipShadow) {
179 | tooltip.style.boxShadow = `0 2px 7px rgba(0,0,0,${configs.shadowOpacity})`;
180 | arrow.style.boxShadow = `1px 1px 3px rgba(0,0,0,${configs.shadowOpacity / 1.5})`;
181 | }
182 | /// Set rounded corners for buttons
183 | if (configs.verticalLayoutTooltip) {
184 | firstButtonBorderRadius = `0px 0px ${configs.borderRadius / 1.5}px ${configs.borderRadius / 1.5}px`;
185 | lastButtonBorderRadius = `${configs.borderRadius / 1.5}px ${configs.borderRadius / 1.5}px 0px 0px`;
186 | } else {
187 | firstButtonBorderRadius = `${configs.borderRadius / 1.5}px 0px 0px ${configs.borderRadius / 1.5}px`;
188 | lastButtonBorderRadius = `0px ${configs.borderRadius / 1.5}px ${configs.borderRadius / 1.5}px 0px`;
189 | }
190 |
191 | onlyButtonBorderRadius = `${configs.borderRadius / 1.5}px`;
192 | } else {
193 | /// Set default corners for buttons
194 | firstButtonBorderRadius = '3px 0px 0px 3px';
195 | lastButtonBorderRadius = '0px 3px 3px 0px';
196 | onlyButtonBorderRadius = '3px';
197 | }
198 |
199 | if (configs.debugMode)
200 | console.log('Selecton tooltip was created');
201 | }
202 |
203 | function calculateTooltipPosition(e) {
204 | const selStartDimensions = getSelectionCoordinates(true);
205 | const selEndDimensions = getSelectionCoordinates(false);
206 |
207 | let canAddDragHandles = true;
208 | if (selStartDimensions.dontAddDragHandles) canAddDragHandles = false;
209 | let dyForFloatingTooltip = 15;
210 | let dyWhenOffscreen = window.innerHeight / 3;
211 | let tooltipHeight = tooltip.clientHeight;
212 | let dxToShowTooltip, dyToShowTooltip;
213 |
214 | if (configs.tooltipPosition == 'overCursor' && e.clientX < window.innerWidth - 30) {
215 |
216 | /// Show it on top of selection, dx aligned to cursor
217 | dyToShowTooltip = selStartDimensions.dy - tooltipHeight - (arrow.clientHeight / 1.5) - 2;
218 | let vertOutOfView = dyToShowTooltip <= 0;
219 |
220 | if (vertOutOfView || (selEndDimensions.dy - selStartDimensions.dy > 2.0 && selEndDimensions.backwards !== true)) {
221 | /// show tooltip under selection
222 | let possibleDyToShowTooltip = selEndDimensions.dy + (selEndDimensions.lineHeight ?? 0) + arrow.clientHeight;
223 |
224 | if (possibleDyToShowTooltip < window.innerHeight) {
225 | dyToShowTooltip = possibleDyToShowTooltip;
226 | setTooltipOnBottom();
227 |
228 | if (configs.verticalLayoutTooltip) tooltip.classList.remove('reversed-order');
229 | }
230 | }
231 |
232 | /// Check to be off-screen on top
233 | if (dyToShowTooltip < 0 && tooltipOnBottom == false) dyToShowTooltip = dyWhenOffscreen;
234 |
235 | /// Calculating DX
236 | dxToShowTooltip = e.clientX;
237 |
238 | } else {
239 | /// Calculating DY
240 | dyToShowTooltip = selStartDimensions.dy - tooltipHeight - arrow.clientHeight;
241 |
242 | /// If tooltip is going off-screen on top...
243 | let vertOutOfView = dyToShowTooltip <= 0;
244 | if (vertOutOfView) {
245 | /// check to display on bottom
246 | let resultingDyOnBottom = selEndDimensions.dy + (selEndDimensions.lineHeight ?? 0) + arrow.clientHeight;
247 | if (resultingDyOnBottom < window.innerHeight) {
248 | dyToShowTooltip = resultingDyOnBottom;
249 | setTooltipOnBottom();
250 | if (configs.verticalLayoutTooltip) tooltip.classList.remove('reversed-order');
251 | } else {
252 | /// if it will be off-screen as well, use off-screen dy
253 | dyToShowTooltip = dyWhenOffscreen;
254 | }
255 | }
256 |
257 | /// Add small padding
258 | if (configs.showTooltipArrow) dyToShowTooltip = dyToShowTooltip + 2;
259 |
260 | /// Calculating DX
261 | try {
262 | /// New approach - place tooltip in horizontal center between two selection handles
263 | const delta = selEndDimensions.dx > selStartDimensions.dx ? selEndDimensions.dx - selStartDimensions.dx : selStartDimensions.dx - selEndDimensions.dx;
264 |
265 | if (selEndDimensions.dx > selStartDimensions.dx)
266 | dxToShowTooltip = selStartDimensions.dx + (delta / 2);
267 | else
268 | dxToShowTooltip = selEndDimensions.dx + (delta / 2);
269 | } catch (e) {
270 | if (configs.debugMode)
271 | console.log(e);
272 |
273 | /// Fall back to old approach - place tooltip in horizontal center selection rect,
274 | /// which may be in fact bigger than visible selection
275 | const selDimensions = getSelectionRectDimensions();
276 | dxToShowTooltip = selDimensions.dx + (selDimensions.width / 2);
277 | }
278 | }
279 |
280 | if (configs.floatingOffscreenTooltip) {
281 | /// Keep panel floating when off-screen
282 | floatingTooltipTop = false; floatingTooltipBottom = false;
283 | if (dyToShowTooltip < 0) {
284 | dyToShowTooltip = dyForFloatingTooltip;
285 | floatingTooltipTop = window.scrollY;
286 | } else if (dyToShowTooltip > window.innerHeight) {
287 | dyToShowTooltip = window.innerHeight - (tooltipHeight ?? 50) - dyForFloatingTooltip;
288 | floatingTooltipBottom = window.scrollY;
289 | }
290 | }
291 |
292 | showTooltip(dxToShowTooltip, dyToShowTooltip);
293 |
294 | if (configs.addDragHandles && canAddDragHandles)
295 | setDragHandles(selStartDimensions, selEndDimensions);
296 | }
297 |
298 | function showTooltip(dx, dy) {
299 | tooltip.style.pointerEvents = 'none';
300 | tooltip.style.opacity = configs.useCustomStyle ? configs.tooltipOpacity : 1.0;
301 | tooltip.style.top = `${dy}px`;
302 | tooltip.style.left = `${dx}px`;
303 |
304 | /// Set reveal animation type
305 | tooltip.style.transform = returnTooltipRevealTransform(true);
306 |
307 | /// Check for colliding with side edges
308 | checkTooltipForCollidingWithSideEdges();
309 |
310 | if (configs.debugMode)
311 | console.log('Selecton tooltip is shown');
312 | tooltipIsShown = true;
313 |
314 | /// Make tooltip interactive only after transition ends
315 | let currentTooltip = tooltip;
316 | setTimeout(function () {
317 | if (tooltipIsShown == false || tooltip == null) return;
318 | currentTooltip.style.pointerEvents = 'all';
319 | }, configs.animationDuration);
320 | }
321 |
322 | let oldTooltips;
323 | function hideTooltip(animated = true) {
324 | if (!tooltip) return;
325 |
326 | if (configs.debugMode) {
327 | console.log('--- Hiding Selecton tooltips ---');
328 | console.log('Checking for existing tooltips...');
329 | }
330 |
331 | /// Hide all tooltips
332 | if (!oldTooltips) oldTooltips = document.getElementsByClassName('selecton-entity');
333 |
334 | if (oldTooltips && oldTooltips.length) {
335 | tooltipIsShown = false;
336 |
337 | if (configs.debugMode)
338 | console.log(`Found ${oldTooltips.length} tooltips to hide`);
339 |
340 | for (let i = 0, l = oldTooltips.length; i < l; i++) {
341 | const oldTooltip = oldTooltips[i];
342 | if (!animated)
343 | oldTooltip.style.transition = '';
344 | oldTooltip.style.opacity = 0.0;
345 | oldTooltip.style.pointerEvents = 'none';
346 |
347 | setTimeout(function () {
348 | oldTooltip.remove();
349 | }, animated ? configs.animationDuration : 0);
350 | }
351 | } else {
352 | if (configs.debugMode)
353 | console.log('No existing tooltips found');
354 | }
355 |
356 | tooltip.style.pointerEvents = 'none';
357 | tooltip = null;
358 | secondaryTooltip = null;
359 | timerToRecreateOverlays = null;
360 | isTextFieldFocused = false;
361 |
362 | document.removeEventListener("selectionchange", selectionChangeListener);
363 | window.removeEventListener('mousemove', mouseMoveToHideListener);
364 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const TerserPlugin = require("terser-webpack-plugin");
3 | const CopyPlugin = require("copy-webpack-plugin");
4 | const ConcatPlugin = require('@mcler/webpack-concat-plugin');
5 | const JsonMinimizerPlugin = require("json-minimizer-webpack-plugin");
6 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
7 |
8 | module.exports = {
9 | /// background script
10 | entry: {
11 | background: "./src/functions/background.js"
12 | },
13 | output: {
14 | path: path.resolve(__dirname, 'dist'),
15 | filename: "[name].js"
16 | },
17 | plugins: [
18 | /// content scripts
19 | new ConcatPlugin({
20 | name: 'content',
21 | outputPath: './',
22 | fileName: '[name].js',
23 | filesToConcat: [
24 | "./src/data/**",
25 | [
26 | "./src/functions/**",
27 | "!./src/functions/background.js",
28 | ],
29 | "./src/ui/**/**",
30 | "./src/index.js",
31 | ]
32 | }),
33 | /// static files
34 | new CopyPlugin({
35 | patterns: [
36 | "src/manifest.json",
37 | "src/index.css",
38 | { from: "src/_locales", to: "_locales" },
39 | { from: "src/assets", to: "assets" },
40 | { from: "src/popup", to: "popup" },
41 | { from: "src/options", to: "options" },
42 | /// additional dependencies for toolbar popup and options page
43 | { from: "src/data/configs.js", to: "src/data/" },
44 | { from: "src/data/currencies.js", to: "src/data/" },
45 | ],
46 | }),
47 | ],
48 | mode: 'production',
49 | optimization: {
50 | minimize: true,
51 | minimizer: [
52 | new TerserPlugin(),
53 | new CssMinimizerPlugin(),
54 | new JsonMinimizerPlugin(),
55 | ],
56 | },
57 | };
--------------------------------------------------------------------------------