browser-extension-settings 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Pipecraft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # browser-extension-settings
2
+
3
+ Settings module for developing browser extensions and userscripts
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm i browser-extension-settings
9
+ # or
10
+ pnpm add browser-extension-settings
11
+ # or
12
+ yarn add browser-extension-settings
13
+ ```
14
+
15
+ ## License
16
+
17
+ Copyright (c) 2023 [Pipecraft](https://www.pipecraft.net). Licensed under the [MIT License](LICENSE).
18
+
19
+ ## >\_
20
+
21
+ [![Pipecraft](https://img.shields.io/badge/site-pipecraft-brightgreen)](https://www.pipecraft.net)
22
+ [![UTags](https://img.shields.io/badge/site-UTags-brightgreen)](https://utags.pipecraft.net)
23
+ [![DTO](https://img.shields.io/badge/site-DTO-brightgreen)](https://dto.pipecraft.net)
24
+ [![BestXTools](https://img.shields.io/badge/site-bestxtools-brightgreen)](https://www.bestxtools.com)
package/lib/index.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ export const doc: Document
2
+
3
+ export const win: Window
4
+
5
+ export function uniq(array: any[]): any[]
6
+
7
+ export function toCamelCase(text: string): string
8
+
9
+ export function $(
10
+ selectors: string,
11
+ element?: HTMLElement | Document
12
+ ): HTMLElement | undefined
13
+
14
+ export function querySelector(
15
+ selectors: string,
16
+ element?: HTMLElement | Document
17
+ ): HTMLElement | undefined
18
+
19
+ export function $$(
20
+ selectors: string,
21
+ element?: HTMLElement | Document
22
+ ): HTMLElement[]
23
+
24
+ export function querySelectorAll(
25
+ selectors: string,
26
+ element?: HTMLElement | Document
27
+ ): HTMLElement[]
28
+
29
+ export function createElement(
30
+ tagName: string,
31
+ attributes?: Record<string, unknown>
32
+ ): HTMLElement
33
+
34
+ export function addElement(
35
+ tagName: string,
36
+ attributes?: Record<string, unknown>
37
+ ): HTMLElement
38
+
39
+ export function addElement(
40
+ parentNode: HTMLElement,
41
+ tagName: string | HTMLElement,
42
+ attributes?: Record<string, unknown>
43
+ ): HTMLElement
44
+
45
+ export function addStyle(styleText: string): HTMLElement
46
+
47
+ export function addEventListener(
48
+ element: HTMLElement | Document | EventTarget,
49
+ type: string,
50
+ listener: EventListenerOrEventListenerObject,
51
+ options?: boolean | AddEventListenerOptions
52
+ ): void
53
+
54
+ export function addEventListener(
55
+ element: HTMLElement | Document | EventTarget,
56
+ type: string | Record<string, unknown>
57
+ ): void
58
+
59
+ export function removeEventListener(
60
+ element: HTMLElement | Document | EventTarget,
61
+ type: string,
62
+ listener: EventListenerOrEventListenerObject,
63
+ options?: boolean | AddEventListenerOptions
64
+ ): void
65
+
66
+ export function removeEventListener(
67
+ element: HTMLElement | Document | EventTarget,
68
+ type: string | Record<string, unknown>
69
+ ): void
70
+
71
+ export function getAttribute(element: HTMLElement, name: string): string
72
+
73
+ export function setAttribute(
74
+ element: HTMLElement,
75
+ name: string,
76
+ value: string
77
+ ): void
78
+
79
+ export function setAttributes(
80
+ element: HTMLElement,
81
+ attributes: Record<string, unknown>
82
+ ): void
83
+
84
+ export function addAttribute(
85
+ element: HTMLElement,
86
+ name: string,
87
+ value: string
88
+ ): void
89
+
90
+ export function addClass(element: HTMLElement, className: string): void
91
+
92
+ export function removeClass(element: HTMLElement, className: string): void
93
+
94
+ export function hasClass(element: HTMLElement, className: string): boolean
95
+
96
+ export type SetStyle = (
97
+ element: HTMLElement,
98
+ style: string | Record<string, unknown>,
99
+ overwrite?: boolean
100
+ ) => void
101
+
102
+ export function setStyle(
103
+ element: HTMLElement,
104
+ style: string | Record<string, unknown>,
105
+ overwrite?: boolean
106
+ ): void
107
+
108
+ export function toStyleMap(styleText: string): Record<string, string>
109
+
110
+ export function noStyleSpace(text: string): string
111
+
112
+ export function createSetStyle(styleText: string): SetStyle
113
+
114
+ export function isUrl(text: string): boolean
115
+
116
+ // eslint-disable-next-line @typescript-eslint/ban-types
117
+ export function throttle(func: Function, interval: number): Function
118
+
119
+ export type MenuCallback = (event?: MouseEvent | KeyboardEvent) => void
120
+ export function registerMenuCommand(
121
+ name: string,
122
+ callback: MenuCallback,
123
+ accessKey?: string
124
+ ): void
125
+
126
+ export function extendHistoryApi(): void
127
+
128
+ export const actionHref: string
129
+
130
+ export function getOffsetPosition(
131
+ element: HTMLElement | undefined,
132
+ referElement?: HTMLElement | undefined
133
+ ): {
134
+ top: number
135
+ left: number
136
+ }
137
+
138
+ // eslint-disable-next-line @typescript-eslint/ban-types
139
+ export function runOnce(key: string, func: Function): any
package/lib/index.js ADDED
@@ -0,0 +1,335 @@
1
+ export const doc = document
2
+
3
+ export const win = window
4
+
5
+ export const uniq = (array) => [...new Set(array)]
6
+
7
+ export const toCamelCase = function (text) {
8
+ return text.replace(/^([A-Z])|[\s-_](\w)/g, function (match, p1, p2) {
9
+ if (p2) return p2.toUpperCase()
10
+ return p1.toLowerCase()
11
+ })
12
+ }
13
+
14
+ export const $ = (selectors, element) =>
15
+ (element || doc).querySelector(selectors)
16
+ export const $$ = (selectors, element) => [
17
+ ...(element || doc).querySelectorAll(selectors),
18
+ ]
19
+ export const querySelector = $
20
+ export const querySelectorAll = $$
21
+
22
+ export const createElement = (tagName, attributes) =>
23
+ setAttributes(doc.createElement(tagName), attributes)
24
+
25
+ export const addElement = (parentNode, tagName, attributes) => {
26
+ if (!parentNode) {
27
+ return
28
+ }
29
+
30
+ if (typeof parentNode === "string") {
31
+ attributes = tagName
32
+ tagName = parentNode
33
+ parentNode = doc.head
34
+ }
35
+
36
+ if (typeof tagName === "string") {
37
+ const element = createElement(tagName, attributes)
38
+ parentNode.append(element)
39
+ return element
40
+ }
41
+
42
+ // tagName: HTMLElement
43
+ setAttributes(tagName, attributes)
44
+ parentNode.append(tagName)
45
+ return tagName
46
+ }
47
+
48
+ export const addStyle = (styleText) => {
49
+ const element = createElement("style", { textContent: styleText })
50
+ doc.head.append(element)
51
+ return element
52
+ }
53
+
54
+ export const addEventListener = (element, type, listener, options) => {
55
+ if (!element) {
56
+ return
57
+ }
58
+
59
+ if (typeof type === "object") {
60
+ for (const type1 in type) {
61
+ if (Object.hasOwn(type, type1)) {
62
+ element.addEventListener(type1, type[type1])
63
+ }
64
+ }
65
+ } else if (typeof type === "string" && typeof listener === "function") {
66
+ element.addEventListener(type, listener, options)
67
+ }
68
+ }
69
+
70
+ export const removeEventListener = (element, type, listener, options) => {
71
+ if (!element) {
72
+ return
73
+ }
74
+
75
+ if (typeof type === "object") {
76
+ for (const type1 in type) {
77
+ if (Object.hasOwn(type, type1)) {
78
+ element.removeEventListener(type1, type[type1])
79
+ }
80
+ }
81
+ } else if (typeof type === "string" && typeof listener === "function") {
82
+ element.removeEventListener(type, listener, options)
83
+ }
84
+ }
85
+
86
+ export const getAttribute = (element, name) =>
87
+ element ? element.getAttribute(name) : null
88
+ export const setAttribute = (element, name, value) =>
89
+ element ? element.setAttribute(name, value) : undefined
90
+
91
+ export const setAttributes = (element, attributes) => {
92
+ if (element && attributes) {
93
+ for (const name in attributes) {
94
+ if (Object.hasOwn(attributes, name)) {
95
+ const value = attributes[name]
96
+ if (value === undefined) {
97
+ continue
98
+ }
99
+
100
+ if (/^(value|textContent|innerText|innerHTML)$/.test(name)) {
101
+ element[name] = value
102
+ } else if (name === "style") {
103
+ setStyle(element, value, true)
104
+ } else if (/on\w+/.test(name)) {
105
+ const type = name.slice(2)
106
+ addEventListener(element, type, value)
107
+ } else {
108
+ setAttribute(element, name, value)
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ return element
115
+ }
116
+
117
+ export const addAttribute = (element, name, value) => {
118
+ const orgValue = getAttribute(element, name)
119
+ if (!orgValue) {
120
+ setAttribute(element, name, value)
121
+ } else if (!orgValue.includes(value)) {
122
+ setAttribute(element, name, orgValue + " " + value)
123
+ }
124
+ }
125
+
126
+ export const addClass = (element, className) => {
127
+ if (!element || !element.classList) {
128
+ return
129
+ }
130
+
131
+ element.classList.add(className)
132
+ }
133
+
134
+ export const removeClass = (element, className) => {
135
+ if (!element || !element.classList) {
136
+ return
137
+ }
138
+
139
+ element.classList.remove(className)
140
+ }
141
+
142
+ export const hasClass = (element, className) => {
143
+ if (!element || !element.classList) {
144
+ return false
145
+ }
146
+
147
+ return element.classList.contains(className)
148
+ }
149
+
150
+ export const setStyle = (element, values, overwrite) => {
151
+ if (!element) {
152
+ return
153
+ }
154
+
155
+ // element.setAttribute("style", value) -> Fail when violates CSP
156
+ const style = element.style
157
+
158
+ if (typeof values === "string") {
159
+ style.cssText = overwrite ? values : style.cssText + ";" + values
160
+ return
161
+ }
162
+
163
+ if (overwrite) {
164
+ style.cssText = ""
165
+ }
166
+
167
+ for (const key in values) {
168
+ if (Object.hasOwn(values, key)) {
169
+ style[key] = values[key].replace("!important", "")
170
+ }
171
+ }
172
+ }
173
+
174
+ // convert `font-size: 12px; color: red` to `{"fontSize": "12px"; "color": "red"}`
175
+ // eslint-disable-next-line no-unused-vars
176
+ const toStyleKeyValues = (styleText) => {
177
+ const result = {}
178
+ const keyValues = styleText.split(/\s*;\s*/)
179
+ for (const keyValue of keyValues) {
180
+ const kv = keyValue.split(/\s*:\s*/)
181
+ // TODO: fix when key is such as -webkit-xxx
182
+ const key = toCamelCase(kv[0])
183
+ if (key) {
184
+ result[key] = kv[1]
185
+ }
186
+ }
187
+
188
+ return result
189
+ }
190
+
191
+ export const toStyleMap = (styleText) => {
192
+ styleText = noStyleSpace(styleText)
193
+ const map = {}
194
+ const keyValues = styleText.split("}")
195
+ for (const keyValue of keyValues) {
196
+ const kv = keyValue.split("{")
197
+ if (kv[0] && kv[1]) {
198
+ map[kv[0]] = kv[1]
199
+ }
200
+ }
201
+
202
+ return map
203
+ }
204
+
205
+ export const noStyleSpace = (text) => text.replace(/\s*([^\w-+%!])\s*/gm, "$1")
206
+
207
+ export const createSetStyle = (styleText) => {
208
+ const styleMap = toStyleMap(styleText)
209
+ return (element, value, overwrite) => {
210
+ if (typeof value === "object") {
211
+ setStyle(element, value, overwrite)
212
+ } else if (typeof value === "string") {
213
+ const key = noStyleSpace(value)
214
+ const value2 = styleMap[key]
215
+ setStyle(element, value2 || value, overwrite)
216
+ }
217
+ }
218
+ }
219
+
220
+ export const isUrl = (text) => /^https?:\/\//.test(text)
221
+
222
+ /**
223
+ *
224
+ * @param { function } func
225
+ * @param { number } interval
226
+ * @returns
227
+ */
228
+ export const throttle = (func, interval) => {
229
+ let timeoutId = null
230
+ let next = false
231
+ const handler = (...args) => {
232
+ if (timeoutId) {
233
+ next = true
234
+ } else {
235
+ func.apply(this, args)
236
+ timeoutId = setTimeout(() => {
237
+ timeoutId = null
238
+ if (next) {
239
+ next = false
240
+ handler()
241
+ }
242
+ }, interval)
243
+ }
244
+ }
245
+
246
+ return handler
247
+ }
248
+
249
+ if (typeof Object.hasOwn !== "function") {
250
+ Object.hasOwn = (instance, prop) =>
251
+ Object.prototype.hasOwnProperty.call(instance, prop)
252
+ }
253
+
254
+ export const registerMenuCommand = () => undefined
255
+
256
+ export const extendHistoryApi = () => {
257
+ // https://dirask.com/posts/JavaScript-on-location-changed-event-on-url-changed-event-DKeyZj
258
+ const pushState = history.pushState
259
+ const replaceState = history.replaceState
260
+
261
+ history.pushState = function () {
262
+ // eslint-disable-next-line prefer-rest-params
263
+ pushState.apply(history, arguments)
264
+ window.dispatchEvent(new Event("pushstate"))
265
+ window.dispatchEvent(new Event("locationchange"))
266
+ }
267
+
268
+ history.replaceState = function () {
269
+ // eslint-disable-next-line prefer-rest-params
270
+ replaceState.apply(history, arguments)
271
+ window.dispatchEvent(new Event("replacestate"))
272
+ window.dispatchEvent(new Event("locationchange"))
273
+ }
274
+
275
+ window.addEventListener("popstate", function () {
276
+ window.dispatchEvent(new Event("locationchange"))
277
+ })
278
+
279
+ // Usage example:
280
+ // window.addEventListener("locationchange", function () {
281
+ // console.log("onlocationchange event occurred!")
282
+ // })
283
+ }
284
+
285
+ // eslint-disable-next-line no-script-url
286
+ export const actionHref = "javascript:;"
287
+
288
+ export const getOffsetPosition = (element, referElement) => {
289
+ const position = { top: 0, left: 0 }
290
+ referElement = referElement || doc.body
291
+
292
+ while (element && element !== referElement) {
293
+ position.top += element.offsetTop
294
+ position.left += element.offsetLeft
295
+ element = element.offsetParent
296
+ }
297
+
298
+ return position
299
+ }
300
+
301
+ const runOnceCache = {}
302
+ export const runOnce = (key, func) => {
303
+ if (!key) {
304
+ return func()
305
+ }
306
+
307
+ if (Object.hasOwn(runOnceCache, key)) {
308
+ return runOnceCache[key]
309
+ }
310
+
311
+ const result = func()
312
+ runOnceCache[key] = result
313
+ return result
314
+ }
315
+
316
+ const cacheStore = {}
317
+ const makeKey = (key: string | any[]) =>
318
+ Array.isArray(key) ? key.join(":") : key
319
+ // eslint-disable-next-line @typescript-eslint/naming-convention
320
+ export const Cache = {
321
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
322
+ get: (key: string | any[]) => cacheStore[makeKey(key)],
323
+ add(key: string | any[], value: any) {
324
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
325
+ cacheStore[makeKey(key)] = value
326
+ },
327
+ }
328
+
329
+ export const sleep = async (time: number) => {
330
+ return new Promise((resolve) => {
331
+ setTimeout(() => {
332
+ resolve(1)
333
+ }, time)
334
+ })
335
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "browser-extension-settings",
3
+ "version": "0.0.1",
4
+ "description": "Settings module for developing browser extensions and userscripts ",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js"
9
+ },
10
+ "scripts": {
11
+ "p": "prettier --write .",
12
+ "lint": "prettier --write . && xo --fix",
13
+ "test": "echo \"Error: no test specified\" && exit 0"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/utags/browser-extension-settings.git"
18
+ },
19
+ "keywords": [
20
+ "extensions",
21
+ "userscripts",
22
+ "settings"
23
+ ],
24
+ "author": "Pipecraft",
25
+ "license": "MIT",
26
+ "bugs": {
27
+ "url": "https://github.com/utags/browser-extension-settings/issues"
28
+ },
29
+ "homepage": "https://github.com/utags/browser-extension-settings#readme",
30
+ "devDependencies": {
31
+ "prettier": "^2.8.8",
32
+ "xo": "^0.54.2"
33
+ },
34
+ "files": [
35
+ "lib/",
36
+ "LICENSE",
37
+ "README.md"
38
+ ],
39
+ "engines": {
40
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
41
+ },
42
+ "xo": {
43
+ "space": 2,
44
+ "prettier": true,
45
+ "globals": [
46
+ "document"
47
+ ],
48
+ "rules": {
49
+ "prefer-destructuring": 0,
50
+ "capitalized-comments": 0
51
+ }
52
+ }
53
+ }