browser-extension-utils 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js DELETED
@@ -1,548 +0,0 @@
1
- export const doc = document
2
-
3
- export const win = globalThis
4
-
5
- export const uniq = (array) => [...new Set(array)]
6
-
7
- // Polyfill for String.prototype.replaceAll()
8
- // eslint-disable-next-line no-use-extend-native/no-use-extend-native
9
- if (typeof String.prototype.replaceAll !== "function") {
10
- // eslint-disable-next-line no-use-extend-native/no-use-extend-native, no-extend-native
11
- String.prototype.replaceAll = String.prototype.replace
12
- }
13
-
14
- export const toCamelCase = function (text) {
15
- return text.replaceAll(/^([A-Z])|[\s-_](\w)/g, function (match, p1, p2) {
16
- if (p2) return p2.toUpperCase()
17
- return p1.toLowerCase()
18
- })
19
- }
20
-
21
- export const $ = (selectors, element) =>
22
- (element || doc).querySelector(selectors) || undefined
23
- export const $$ = (selectors, element) => [
24
- ...(element || doc).querySelectorAll(selectors),
25
- ]
26
- export const querySelector = $
27
- export const querySelectorAll = $$
28
-
29
- export const getRootElement = (type) =>
30
- type === 1
31
- ? doc.head || doc.body || doc.documentElement
32
- : type === 2
33
- ? doc.body || doc.documentElement
34
- : doc.documentElement
35
-
36
- export const createElement = (tagName, attributes) =>
37
- setAttributes(doc.createElement(tagName), attributes)
38
-
39
- export const addElement = (parentNode, tagName, attributes) => {
40
- if (typeof parentNode === "string") {
41
- return addElement(null, parentNode, tagName)
42
- }
43
-
44
- if (!tagName) {
45
- return
46
- }
47
-
48
- if (!parentNode) {
49
- parentNode = /^(script|link|style|meta)$/.test(tagName)
50
- ? getRootElement(1)
51
- : getRootElement(2)
52
- }
53
-
54
- if (typeof tagName === "string") {
55
- const element = createElement(tagName, attributes)
56
- parentNode.append(element)
57
- return element
58
- }
59
-
60
- // tagName: HTMLElement
61
- setAttributes(tagName, attributes)
62
- parentNode.append(tagName)
63
- return tagName
64
- }
65
-
66
- export const addStyle = (styleText) => {
67
- const element = createElement("style", { textContent: styleText })
68
- getRootElement(1).append(element)
69
- return element
70
- }
71
-
72
- export const addEventListener = (element, type, listener, options) => {
73
- if (!element) {
74
- return
75
- }
76
-
77
- if (typeof type === "object") {
78
- for (const type1 in type) {
79
- if (Object.hasOwn(type, type1)) {
80
- element.addEventListener(type1, type[type1])
81
- }
82
- }
83
- } else if (typeof type === "string" && typeof listener === "function") {
84
- element.addEventListener(type, listener, options)
85
- }
86
- }
87
-
88
- export const removeEventListener = (element, type, listener, options) => {
89
- if (!element) {
90
- return
91
- }
92
-
93
- if (typeof type === "object") {
94
- for (const type1 in type) {
95
- if (Object.hasOwn(type, type1)) {
96
- element.removeEventListener(type1, type[type1])
97
- }
98
- }
99
- } else if (typeof type === "string" && typeof listener === "function") {
100
- element.removeEventListener(type, listener, options)
101
- }
102
- }
103
-
104
- export const getAttribute = (element, name) =>
105
- element && element.getAttribute ? element.getAttribute(name) : undefined
106
- export const setAttribute = (element, name, value) =>
107
- element && element.setAttribute
108
- ? element.setAttribute(name, value)
109
- : undefined
110
- export const removeAttribute = (element, name) =>
111
- element && element.removeAttribute ? element.removeAttribute(name) : undefined
112
-
113
- export const setAttributes = (element, attributes) => {
114
- if (element && attributes) {
115
- for (const name in attributes) {
116
- if (Object.hasOwn(attributes, name)) {
117
- const value = attributes[name]
118
- if (value === undefined) {
119
- continue
120
- }
121
-
122
- if (/^(value|textContent|innerText)$/.test(name)) {
123
- element[name] = value
124
- } else if (/^(innerHTML)$/.test(name)) {
125
- element[name] = createHTML(value)
126
- } else if (name === "style") {
127
- setStyle(element, value, true)
128
- } else if (/on\w+/.test(name)) {
129
- const type = name.slice(2)
130
- addEventListener(element, type, value)
131
- } else {
132
- setAttribute(element, name, value)
133
- }
134
- }
135
- }
136
- }
137
-
138
- return element
139
- }
140
-
141
- export const addAttribute = (element, name, value) => {
142
- const orgValue = getAttribute(element, name)
143
- if (!orgValue) {
144
- setAttribute(element, name, value)
145
- } else if (!orgValue.includes(value)) {
146
- setAttribute(element, name, orgValue + " " + value)
147
- }
148
- }
149
-
150
- export const addClass = (element, className) => {
151
- if (!element || !element.classList) {
152
- return
153
- }
154
-
155
- element.classList.add(className)
156
- }
157
-
158
- export const removeClass = (element, className) => {
159
- if (!element || !element.classList) {
160
- return
161
- }
162
-
163
- element.classList.remove(className)
164
- }
165
-
166
- export const hasClass = (element, className) => {
167
- if (!element || !element.classList) {
168
- return false
169
- }
170
-
171
- return element.classList.contains(className)
172
- }
173
-
174
- export const setStyle = (element, values, overwrite) => {
175
- if (!element) {
176
- return
177
- }
178
-
179
- // element.setAttribute("style", value) -> Fail when violates CSP
180
- const style = element.style
181
-
182
- if (typeof values === "string") {
183
- style.cssText = overwrite ? values : style.cssText + ";" + values
184
- return
185
- }
186
-
187
- if (overwrite) {
188
- style.cssText = ""
189
- }
190
-
191
- for (const key in values) {
192
- if (Object.hasOwn(values, key)) {
193
- style[key] = values[key].replace("!important", "")
194
- }
195
- }
196
- }
197
-
198
- // convert `font-size: 12px; color: red` to `{"fontSize": "12px"; "color": "red"}`
199
- // eslint-disable-next-line no-unused-vars
200
- const toStyleKeyValues = (styleText) => {
201
- const result = {}
202
- const keyValues = styleText.split(/\s*;\s*/)
203
- for (const keyValue of keyValues) {
204
- const kv = keyValue.split(/\s*:\s*/)
205
- // TODO: fix when key is such as -webkit-xxx
206
- const key = toCamelCase(kv[0])
207
- if (key) {
208
- result[key] = kv[1]
209
- }
210
- }
211
-
212
- return result
213
- }
214
-
215
- export const toStyleMap = (styleText) => {
216
- styleText = noStyleSpace(styleText)
217
- const map = {}
218
- const keyValues = styleText.split("}")
219
- for (const keyValue of keyValues) {
220
- const kv = keyValue.split("{")
221
- if (kv[0] && kv[1]) {
222
- map[kv[0]] = kv[1]
223
- }
224
- }
225
-
226
- return map
227
- }
228
-
229
- export const noStyleSpace = (text) =>
230
- text.replaceAll(/\s*([^\w-+%!])\s*/gm, "$1")
231
-
232
- export const createSetStyle = (styleText) => {
233
- const styleMap = toStyleMap(styleText)
234
- return (element, value, overwrite) => {
235
- if (typeof value === "object") {
236
- setStyle(element, value, overwrite)
237
- } else if (typeof value === "string") {
238
- const key = noStyleSpace(value)
239
- const value2 = styleMap[key]
240
- setStyle(element, value2 || value, overwrite)
241
- }
242
- }
243
- }
244
-
245
- export const isUrl = (text) => /^https?:\/\//.test(text)
246
-
247
- /**
248
- *
249
- * @param { function } func
250
- * @param { number } interval
251
- * @returns
252
- */
253
- export const throttle = (func, interval) => {
254
- let timeoutId = null
255
- let next = false
256
- const handler = (...args) => {
257
- if (timeoutId) {
258
- next = true
259
- } else {
260
- func.apply(this, args)
261
- timeoutId = setTimeout(() => {
262
- timeoutId = null
263
- if (next) {
264
- next = false
265
- handler()
266
- }
267
- }, interval)
268
- }
269
- }
270
-
271
- return handler
272
- }
273
-
274
- // Polyfill for Object.hasOwn()
275
- if (typeof Object.hasOwn !== "function") {
276
- Object.hasOwn = (instance, prop) =>
277
- // eslint-disable-next-line prefer-object-has-own
278
- Object.prototype.hasOwnProperty.call(instance, prop)
279
- }
280
-
281
- export const registerMenuCommand = () => undefined
282
-
283
- export const extendHistoryApi = () => {
284
- // https://dirask.com/posts/JavaScript-on-location-changed-event-on-url-changed-event-DKeyZj
285
- const pushState = history.pushState
286
- const replaceState = history.replaceState
287
-
288
- history.pushState = function () {
289
- // eslint-disable-next-line prefer-rest-params
290
- pushState.apply(history, arguments)
291
- globalThis.dispatchEvent(new Event("pushstate"))
292
- globalThis.dispatchEvent(new Event("locationchange"))
293
- }
294
-
295
- history.replaceState = function () {
296
- // eslint-disable-next-line prefer-rest-params
297
- replaceState.apply(history, arguments)
298
- globalThis.dispatchEvent(new Event("replacestate"))
299
- globalThis.dispatchEvent(new Event("locationchange"))
300
- }
301
-
302
- globalThis.addEventListener("popstate", function () {
303
- globalThis.dispatchEvent(new Event("locationchange"))
304
- })
305
-
306
- // Usage example:
307
- // window.addEventListener("locationchange", function () {
308
- // console.log("onlocationchange event occurred!")
309
- // })
310
- }
311
-
312
- // eslint-disable-next-line no-script-url
313
- export const actionHref = "javascript:;"
314
-
315
- export const getOffsetPosition = (element, referElement) => {
316
- const position = { top: 0, left: 0 }
317
- referElement = referElement || doc.body
318
-
319
- while (element && element !== referElement) {
320
- position.top += element.offsetTop
321
- position.left += element.offsetLeft
322
- element = element.offsetParent
323
- }
324
-
325
- return position
326
- }
327
-
328
- const runOnceCache = {}
329
- export const runOnce = async (key, func) => {
330
- if (Object.hasOwn(runOnceCache, key)) {
331
- return runOnceCache[key]
332
- }
333
-
334
- const result = await func()
335
-
336
- if (key) {
337
- runOnceCache[key] = result
338
- }
339
-
340
- return result
341
- }
342
-
343
- const cacheStore = {}
344
- const makeKey = (key /* string | any[] */) =>
345
- Array.isArray(key) ? key.join(":") : key
346
- export const cache = {
347
- get: (key /* string | any[] */) => cacheStore[makeKey(key)],
348
- add(key /* string | any[] */, value /* any */) {
349
- cacheStore[makeKey(key)] = value
350
- },
351
- }
352
-
353
- export const sleep = async (time) => {
354
- return new Promise((resolve) => {
355
- setTimeout(() => {
356
- resolve(1)
357
- }, time)
358
- })
359
- }
360
-
361
- export const parseInt10 = (number, defaultValue) => {
362
- if (typeof number === "number" && !Number.isNaN(number)) {
363
- return number
364
- }
365
-
366
- if (typeof defaultValue !== "number") {
367
- defaultValue = Number.NaN
368
- }
369
-
370
- if (!number) {
371
- return defaultValue
372
- }
373
-
374
- const result = Number.parseInt(number, 10)
375
- return Number.isNaN(result) ? defaultValue : result
376
- }
377
-
378
- const rootFuncArray = []
379
- const headFuncArray = []
380
- const bodyFuncArray = []
381
- let headBodyObserver
382
-
383
- const startObserveHeadBodyExists = () => {
384
- if (headBodyObserver) {
385
- return
386
- }
387
-
388
- headBodyObserver = new MutationObserver(() => {
389
- if (doc.head && doc.body) {
390
- headBodyObserver.disconnect()
391
- }
392
-
393
- if (doc.documentElement && rootFuncArray.length > 0) {
394
- for (const func of rootFuncArray) {
395
- func()
396
- }
397
-
398
- rootFuncArray.length = 0
399
- }
400
-
401
- if (doc.head && headFuncArray.length > 0) {
402
- for (const func of headFuncArray) {
403
- func()
404
- }
405
-
406
- headFuncArray.length = 0
407
- }
408
-
409
- if (doc.body && bodyFuncArray.length > 0) {
410
- for (const func of bodyFuncArray) {
411
- func()
412
- }
413
-
414
- bodyFuncArray.length = 0
415
- }
416
- })
417
-
418
- headBodyObserver.observe(doc, {
419
- childList: true,
420
- subtree: true,
421
- })
422
- }
423
-
424
- /**
425
- * Run function when document.documentElement exsits.
426
- */
427
- export const runWhenRootExists = (func) => {
428
- if (!doc.documentElement) {
429
- rootFuncArray.push(func)
430
- startObserveHeadBodyExists()
431
- return
432
- }
433
-
434
- func()
435
- }
436
-
437
- /**
438
- * Run function when document.head exsits.
439
- */
440
- export const runWhenHeadExists = (func) => {
441
- if (!doc.head) {
442
- headFuncArray.push(func)
443
- startObserveHeadBodyExists()
444
- return
445
- }
446
-
447
- func()
448
- }
449
-
450
- /**
451
- * Run function when document.body exsits. The function executed before DOMContentLoaded.
452
- */
453
- export const runWhenBodyExists = (func) => {
454
- if (!doc.body) {
455
- bodyFuncArray.push(func)
456
- startObserveHeadBodyExists()
457
- return
458
- }
459
-
460
- func()
461
- }
462
-
463
- /**
464
- * Equals to jQuery.domready
465
- */
466
- export const runWhenDomReady = (func) => {
467
- if (doc.readyState === "interactive" || doc.readyState === "complete") {
468
- return func()
469
- }
470
-
471
- const handler = () => {
472
- if (doc.readyState === "interactive" || doc.readyState === "complete") {
473
- func()
474
- removeEventListener(doc, "readystatechange", handler)
475
- }
476
- }
477
-
478
- addEventListener(doc, "readystatechange", handler)
479
- }
480
-
481
- export const isVisible = (element) => {
482
- if (typeof element.checkVisibility === "function") {
483
- return element.checkVisibility()
484
- }
485
-
486
- return element.offsetParent !== null
487
- }
488
-
489
- export const isTouchScreen = () => "ontouchstart" in win
490
-
491
- const escapeHTMLPolicy =
492
- typeof trustedTypes !== "undefined" &&
493
- typeof trustedTypes.createPolicy === "function"
494
- ? trustedTypes.createPolicy("beuEscapePolicy", {
495
- createHTML: (string) => string,
496
- })
497
- : undefined
498
-
499
- export const createHTML = (html) => {
500
- return escapeHTMLPolicy ? escapeHTMLPolicy.createHTML(html) : html
501
- }
502
-
503
- /**
504
- * Compare two semantic version strings
505
- * @param {string} v1 - First version string (e.g., "1.2.0")
506
- * @param {string} v2 - Second version string (e.g., "1.1.5")
507
- * @returns {number} - Returns 1 if v1 > v2, -1 if v1 < v2, 0 if equal
508
- * @throws {Error} - Throws error for invalid version strings
509
- */
510
- export function compareVersions(v1, v2) {
511
- // Input validation
512
- if (typeof v1 !== "string" || typeof v2 !== "string") {
513
- throw new TypeError("Version strings must be of type string")
514
- }
515
-
516
- if (!v1.trim() || !v2.trim()) {
517
- throw new Error("Version strings cannot be empty")
518
- }
519
-
520
- // Validate version format (basic semantic versioning)
521
- const versionRegex = /^\d+(\.\d+)*$/
522
- if (!versionRegex.test(v1) || !versionRegex.test(v2)) {
523
- throw new Error(
524
- "Invalid version format. Use semantic versioning (e.g., '1.2.3')"
525
- )
526
- }
527
-
528
- const v1Parts = v1.split(".").map(Number)
529
- const v2Parts = v2.split(".").map(Number)
530
- const maxLength = Math.max(v1Parts.length, v2Parts.length)
531
-
532
- for (let i = 0; i < maxLength; i++) {
533
- const num1 = v1Parts[i] || 0 // Use logical OR for cleaner default assignment
534
- const num2 = v2Parts[i] || 0
535
-
536
- if (num1 !== num2) {
537
- return num1 > num2 ? 1 : -1 // Simplified comparison
538
- }
539
- }
540
-
541
- return 0 // Versions are equal
542
- }
543
-
544
- // Usage:
545
- // console.log(compareVersions("1.2.0", "1.1.5")); // Output: 1
546
- // console.log(compareVersions("1.0", "1.0.0")); // Output: 0
547
- // console.log(compareVersions("2.0", "1.5.10")); // Output: 1
548
- // console.log(compareVersions("1.0.0", "1.0.0.1")); // Output: -1
package/lib/userscript.js DELETED
@@ -1,86 +0,0 @@
1
- import {
2
- getRootElement,
3
- setAttributes,
4
- addElement as _addElement,
5
- } from "./index.js"
6
-
7
- export * from "./index.js"
8
-
9
- // eslint-disable-next-line no-unused-expressions, n/prefer-global/process
10
- process.env.PLASMO_TAG === "dev" &&
11
- (() => {
12
- const functions = document.GMFunctions
13
- if (typeof functions === "object") {
14
- for (const key in functions) {
15
- if (Object.hasOwn(functions, key)) {
16
- window[key] = functions[key]
17
- }
18
- }
19
- }
20
- })()
21
-
22
- /* eslint-disable new-cap, camelcase */
23
- export const addElement =
24
- typeof GM_addElement === "function"
25
- ? (parentNode, tagName, attributes) => {
26
- if (typeof parentNode === "string") {
27
- return addElement(null, parentNode, tagName)
28
- }
29
-
30
- if (!tagName) {
31
- return
32
- }
33
-
34
- if (!parentNode) {
35
- parentNode = /^(script|link|style|meta)$/.test(tagName)
36
- ? getRootElement(1)
37
- : getRootElement(2)
38
- }
39
-
40
- if (typeof tagName === "string") {
41
- let attributes2
42
- if (attributes) {
43
- const entries1 = []
44
- const entries2 = []
45
- for (const entry of Object.entries(attributes)) {
46
- if (/^(on\w+|innerHTML)$/.test(entry[0])) {
47
- entries2.push(entry)
48
- } else {
49
- entries1.push(entry)
50
- }
51
- }
52
-
53
- attributes = Object.fromEntries(entries1)
54
- attributes2 = Object.fromEntries(entries2)
55
- }
56
-
57
- const element = GM_addElement(null, tagName, attributes)
58
- setAttributes(element, attributes2)
59
- parentNode.append(element)
60
- return element
61
- }
62
-
63
- // tagName: HTMLElement
64
- setAttributes(tagName, attributes)
65
- parentNode.append(tagName)
66
- return tagName
67
- }
68
- : _addElement
69
-
70
- export const addStyle = (styleText) =>
71
- addElement(null, "style", { textContent: styleText })
72
-
73
- // Only register menu on top frame
74
- export const registerMenuCommand = (name, callback, options) => {
75
- if (globalThis !== top) {
76
- return
77
- }
78
-
79
- if (typeof GM.registerMenuCommand !== "function") {
80
- console.warn("Do not support GM.registerMenuCommand!")
81
- return
82
- }
83
-
84
- return GM.registerMenuCommand(name, callback, options)
85
- }
86
- /* eslint-enable new-cap, camelcase */