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/README.md CHANGED
@@ -19,6 +19,4 @@ Copyright (c) 2023 [Pipecraft](https://www.pipecraft.net). Licensed under the [M
19
19
  ## >\_
20
20
 
21
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)
22
+ [![UTags](https://img.shields.io/badge/site-UTags-brightgreen)](https://utags.link)
@@ -0,0 +1,38 @@
1
+ import { getRootElement } from './dom-utils'
2
+ import { createElement } from './create-element'
3
+ import { setAttributes } from './set-attributes'
4
+
5
+ export const addElement = (
6
+ parentNode: HTMLElement | string | null | undefined,
7
+ tagName: string | HTMLElement,
8
+ attributes?: Record<string, unknown>
9
+ ): HTMLElement | undefined => {
10
+ if (typeof parentNode === 'string') {
11
+ return addElement(
12
+ null,
13
+ parentNode,
14
+ tagName as unknown as Record<string, unknown>
15
+ )
16
+ }
17
+
18
+ if (!tagName) {
19
+ return undefined
20
+ }
21
+
22
+ if (!parentNode) {
23
+ parentNode = /^(script|link|style|meta)$/.test(tagName as string)
24
+ ? getRootElement(1)
25
+ : getRootElement(2)
26
+ }
27
+
28
+ if (typeof tagName === 'string') {
29
+ const element = createElement(tagName, attributes)
30
+ parentNode.append(element)
31
+ return element
32
+ }
33
+
34
+ // tagName: HTMLElement
35
+ setAttributes(tagName, attributes)
36
+ parentNode.append(tagName)
37
+ return tagName
38
+ }
@@ -0,0 +1,11 @@
1
+ import { doc } from './dom-utils'
2
+ import { setAttributes } from './set-attributes'
3
+
4
+ export const createElement = (
5
+ tagName: string,
6
+ attributes?: Record<string, unknown>
7
+ ): HTMLElement =>
8
+ setAttributes(
9
+ doc.createElement(tagName),
10
+ attributes
11
+ ) as unknown as HTMLElement
@@ -0,0 +1,232 @@
1
+ export const doc = document
2
+
3
+ export const win = globalThis
4
+
5
+ // Polyfill for String.prototype.replaceAll()
6
+ if (typeof String.prototype.replaceAll !== 'function') {
7
+ // eslint-disable-next-line no-extend-native
8
+ String.prototype.replaceAll = String.prototype.replace
9
+ }
10
+
11
+ // Polyfill for Object.hasOwn()
12
+ if (typeof Object.hasOwn !== 'function') {
13
+ Object.hasOwn = (instance, prop) =>
14
+ Object.prototype.hasOwnProperty.call(instance, prop)
15
+ }
16
+
17
+ export const toCamelCase = function (text: string): string {
18
+ return text.replaceAll(
19
+ /^([A-Z])|[\s-_](\w)/g,
20
+ (_match: string, p1: string, p2: string) => {
21
+ if (p2) return p2.toUpperCase()
22
+ return p1.toLowerCase()
23
+ }
24
+ )
25
+ }
26
+
27
+ export const addEventListener = (
28
+ element: HTMLElement | Document | EventTarget | null | undefined,
29
+ type: string | Record<string, any>,
30
+ listener?: EventListenerOrEventListenerObject,
31
+ options?: boolean | AddEventListenerOptions
32
+ ): void => {
33
+ if (!element) {
34
+ return
35
+ }
36
+
37
+ if (typeof type === 'object') {
38
+ for (const type1 in type) {
39
+ if (Object.hasOwn(type, type1)) {
40
+ element.addEventListener(
41
+ type1,
42
+ (type as any)[type1] as EventListenerOrEventListenerObject
43
+ )
44
+ }
45
+ }
46
+ } else if (typeof type === 'string' && typeof listener === 'function') {
47
+ element.addEventListener(type, listener, options)
48
+ }
49
+ }
50
+
51
+ export const removeEventListener = (
52
+ element: HTMLElement | Document | EventTarget | null | undefined,
53
+ type: string | Record<string, any>,
54
+ listener?: EventListenerOrEventListenerObject,
55
+ options?: boolean | AddEventListenerOptions
56
+ ): void => {
57
+ if (!element) {
58
+ return
59
+ }
60
+
61
+ if (typeof type === 'object') {
62
+ for (const type1 in type) {
63
+ if (Object.hasOwn(type, type1)) {
64
+ element.removeEventListener(
65
+ type1,
66
+ (type as any)[type1] as EventListenerOrEventListenerObject
67
+ )
68
+ }
69
+ }
70
+ } else if (typeof type === 'string' && typeof listener === 'function') {
71
+ element.removeEventListener(type, listener, options)
72
+ }
73
+ }
74
+
75
+ export const getAttribute = (
76
+ element: HTMLElement | null | undefined,
77
+ name: string
78
+ ): string | undefined =>
79
+ element && element.getAttribute
80
+ ? element.getAttribute(name) || undefined
81
+ : undefined
82
+
83
+ export const setAttribute = (
84
+ element: HTMLElement | null | undefined,
85
+ name: string,
86
+ value: string
87
+ ): void => {
88
+ if (element && element.setAttribute) {
89
+ element.setAttribute(name, value)
90
+ }
91
+ }
92
+
93
+ export const removeAttribute = (
94
+ element: HTMLElement | null | undefined,
95
+ name: string
96
+ ): void => {
97
+ if (element && element.removeAttribute) {
98
+ element.removeAttribute(name)
99
+ }
100
+ }
101
+
102
+ export const addAttribute = (
103
+ element: HTMLElement | null | undefined,
104
+ name: string,
105
+ value: string
106
+ ): void => {
107
+ const orgValue = getAttribute(element, name)
108
+ if (!orgValue) {
109
+ setAttribute(element, name, value)
110
+ } else if (!orgValue.includes(value)) {
111
+ setAttribute(element, name, orgValue + ' ' + value)
112
+ }
113
+ }
114
+
115
+ export const addClass = (
116
+ element: HTMLElement | null | undefined,
117
+ className: string
118
+ ): void => {
119
+ if (!element || !element.classList) {
120
+ return
121
+ }
122
+
123
+ element.classList.add(className)
124
+ }
125
+
126
+ export const removeClass = (
127
+ element: HTMLElement | null | undefined,
128
+ className: string
129
+ ): void => {
130
+ if (!element || !element.classList) {
131
+ return
132
+ }
133
+
134
+ element.classList.remove(className)
135
+ }
136
+
137
+ export const hasClass = (
138
+ element: HTMLElement | null | undefined,
139
+ className: string
140
+ ): boolean => {
141
+ if (!element || !element.classList) {
142
+ return false
143
+ }
144
+
145
+ return element.classList.contains(className)
146
+ }
147
+
148
+ export type SetStyle = (
149
+ element: HTMLElement | null | undefined,
150
+ style: string | Record<string, unknown>,
151
+ overwrite?: boolean
152
+ ) => void
153
+
154
+ export const setStyle = (
155
+ element: HTMLElement | null | undefined,
156
+ values: string | Record<string, any>,
157
+ overwrite?: boolean
158
+ ): void => {
159
+ if (!element) {
160
+ return
161
+ }
162
+
163
+ // element.setAttribute("style", value) -> Fail when violates CSP
164
+ const style = element.style
165
+
166
+ if (typeof values === 'string') {
167
+ style.cssText = overwrite ? values : style.cssText + ';' + values
168
+ return
169
+ }
170
+
171
+ if (overwrite) {
172
+ style.cssText = ''
173
+ }
174
+
175
+ for (const key in values) {
176
+ if (Object.hasOwn(values, key)) {
177
+ ;(style as any)[key] = String(values[key]).replace('!important', '')
178
+ }
179
+ }
180
+ }
181
+
182
+ export const noStyleSpace = (text: string): string =>
183
+ text.replaceAll(/\s*([^\w-+%!])\s*/gm, '$1')
184
+
185
+ export const toStyleMap = (styleText: string): Record<string, string> => {
186
+ styleText = noStyleSpace(styleText)
187
+ const map: Record<string, string> = {}
188
+ const keyValues = styleText.split('}')
189
+ for (const keyValue of keyValues) {
190
+ const kv = keyValue.split('{')
191
+ if (kv[0] && kv[1]) {
192
+ map[kv[0]] = kv[1]
193
+ }
194
+ }
195
+
196
+ return map
197
+ }
198
+
199
+ export const createSetStyle = (styleText: string): SetStyle => {
200
+ const styleMap = toStyleMap(styleText)
201
+ return (element, value, overwrite) => {
202
+ if (typeof value === 'object') {
203
+ setStyle(element, value, overwrite)
204
+ } else if (typeof value === 'string') {
205
+ const key = noStyleSpace(value)
206
+ const value2 = styleMap[key]
207
+ setStyle(element, value2 || value, overwrite)
208
+ }
209
+ }
210
+ }
211
+
212
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
213
+ const tt = (globalThis as any).trustedTypes
214
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
215
+ const escapeHTMLPolicy =
216
+ tt !== undefined && typeof tt.createPolicy === 'function'
217
+ ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call
218
+ tt.createPolicy('beuEscapePolicy', {
219
+ createHTML: (string: string) => string,
220
+ })
221
+ : undefined
222
+
223
+ export const createHTML = (html: string): string =>
224
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
225
+ escapeHTMLPolicy ? (escapeHTMLPolicy.createHTML(html) as string) : html
226
+
227
+ export const getRootElement = (type?: number): HTMLElement =>
228
+ type === 1
229
+ ? doc.head || doc.body || doc.documentElement
230
+ : type === 2
231
+ ? doc.body || doc.documentElement
232
+ : doc.documentElement
@@ -0,0 +1,154 @@
1
+ declare module 'css:*' {
2
+ const cssText: string
3
+ export default cssText
4
+ }
5
+
6
+ // eslint-disable-next-line @typescript-eslint/naming-convention
7
+ declare const GM_info: {
8
+ scriptHandler: string
9
+ }
10
+
11
+ declare function GM_addValueChangeListener(
12
+ key: string,
13
+ cb: (key: string, oldValue: any, newValue: any, remote: boolean) => void
14
+ ): number
15
+
16
+ declare function GM_registerMenuCommand(
17
+ caption: string,
18
+ onClick: () => void,
19
+ options_or_accessKey?:
20
+ | string
21
+ | {
22
+ id?: string | number
23
+ accessKey?: string
24
+ autoClose?: boolean
25
+ // Tampermonkey-specific
26
+ title?: string
27
+ // ScriptCat-specific
28
+ nested?: boolean
29
+ // ScriptCat-specific
30
+ individual?: boolean
31
+ }
32
+ ): number
33
+
34
+ declare function GM_unregisterMenuCommand(menuId: number): void
35
+
36
+ declare const GM: {
37
+ info: {
38
+ scriptHandler: string
39
+ version: string
40
+ }
41
+ getValue<T = unknown>(key: string, defaultValue: T): Promise<T>
42
+ setValue(key: string, value: unknown): Promise<void>
43
+ deleteValue(key: string): Promise<void>
44
+ addValueChangeListener(
45
+ key: string,
46
+ cb: (key: string, oldValue: any, newValue: any, remote: boolean) => void
47
+ ): Promise<number>
48
+ removeValueChangeListener(id: number): Promise<void>
49
+ xmlHttpRequest(options: {
50
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE'
51
+ url: string
52
+ headers?: Record<string, string>
53
+ data?: string | FormData | ArrayBuffer
54
+ responseType?: 'text' | 'json' | 'blob'
55
+ onload?: (response: {
56
+ status: number
57
+ responseText?: string
58
+ response?: any
59
+ responseHeaders?: string
60
+ }) => void
61
+ onerror?: (error: any) => void
62
+ }): Promise<void>
63
+ setClipboard(text: string): Promise<void>
64
+ addStyle(css: string): Promise<HTMLStyleElement>
65
+ addElement(
66
+ tag: string,
67
+ attributes?: Record<string, string>
68
+ ): Promise<HTMLElement>
69
+ addElement(
70
+ parentNode: Element,
71
+ tag: string,
72
+ attributes?: Record<string, string>
73
+ ): Promise<HTMLElement>
74
+ registerMenuCommand(
75
+ caption: string,
76
+ onClick: () => void,
77
+ options_or_accessKey?:
78
+ | string
79
+ | {
80
+ id?: string | number
81
+ accessKey?: string
82
+ autoClose?: boolean
83
+ // Tampermonkey-specific
84
+ title?: string
85
+ // ScriptCat-specific
86
+ nested?: boolean
87
+ // ScriptCat-specific
88
+ individual?: boolean
89
+ }
90
+ ): Promise<number>
91
+ unregisterMenuCommand(menuId: number): Promise<void>
92
+ download(options: {
93
+ url: string
94
+ name: string
95
+ onload?: () => void
96
+ }): Promise<void>
97
+ openInTab(
98
+ url: string,
99
+ options?: { active?: boolean; insert?: boolean }
100
+ ): Promise<void>
101
+ notification(options: {
102
+ text: string
103
+ title?: string
104
+ image?: string
105
+ onclick?: () => void
106
+ }): Promise<void>
107
+ }
108
+
109
+ declare function GM_getValue<T = unknown>(key: string, defaultValue: T): any
110
+ declare function GM_setValue(key: string, value: any): void
111
+ declare function GM_deleteValue(key: string): void
112
+ declare function GM_addStyle(css: string): HTMLStyleElement
113
+ declare function GM_addElement(
114
+ tag: string,
115
+ attributes?: Record<string, string>
116
+ ): HTMLElement
117
+ declare function GM_addElement(
118
+ parentNode: Element,
119
+ tag: string,
120
+ attributes?: Record<string, string>
121
+ ): HTMLElement
122
+ declare function GM_openInTab(
123
+ url: string,
124
+ options?: { active?: boolean; insert?: boolean }
125
+ ): void
126
+ declare function GM_removeValueChangeListener(id: number): void
127
+ declare function GM_download(options: {
128
+ url: string
129
+ name: string
130
+ onload?: () => void
131
+ }): void
132
+ declare function GM_notification(options: {
133
+ text: string
134
+ title?: string
135
+ image?: string
136
+ onclick?: () => void
137
+ }): void
138
+
139
+ declare function GM_xmlhttpRequest(options: {
140
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE'
141
+ url: string
142
+ headers?: Record<string, string>
143
+ data?: string | FormData | ArrayBuffer
144
+ responseType?: 'text' | 'json' | 'blob'
145
+ onload?: (response: {
146
+ status: number
147
+ responseText?: string
148
+ response?: any
149
+ responseHeaders?: string
150
+ }) => void
151
+ onerror?: (error: any) => void
152
+ }): void
153
+
154
+ declare function GM_setClipboard(text: string): void