browser-extension-utils 0.2.2 → 0.3.2

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.
@@ -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,160 @@
1
+ declare module 'css:*' {
2
+ const cssText: string
3
+ export default cssText
4
+ }
5
+
6
+ declare const browser: typeof chrome
7
+
8
+ // eslint-disable-next-line @typescript-eslint/naming-convention
9
+ declare const GM_info: {
10
+ scriptHandler: string
11
+ }
12
+
13
+ declare function GM_addValueChangeListener(
14
+ key: string,
15
+ cb: (key: string, oldValue: any, newValue: any, remote: boolean) => void
16
+ ): number
17
+
18
+ declare type RegisterMenuCommandOptions = Parameters<
19
+ typeof GM_registerMenuCommand
20
+ >[2]
21
+
22
+ declare function GM_registerMenuCommand(
23
+ caption: string,
24
+ onClick: () => void,
25
+ options_or_accessKey?:
26
+ | string
27
+ | {
28
+ id?: string | number
29
+ accessKey?: string
30
+ autoClose?: boolean
31
+ // Tampermonkey-specific
32
+ title?: string
33
+ // ScriptCat-specific
34
+ nested?: boolean
35
+ // ScriptCat-specific
36
+ individual?: boolean
37
+ }
38
+ ): number
39
+
40
+ declare function GM_unregisterMenuCommand(menuId: number): void
41
+
42
+ declare const GM: {
43
+ info: {
44
+ scriptHandler: string
45
+ version: string
46
+ }
47
+ getValue<T = unknown>(key: string, defaultValue: T): Promise<T>
48
+ setValue(key: string, value: unknown): Promise<void>
49
+ deleteValue(key: string): Promise<void>
50
+ addValueChangeListener(
51
+ key: string,
52
+ cb: (key: string, oldValue: any, newValue: any, remote: boolean) => void
53
+ ): Promise<number>
54
+ removeValueChangeListener(id: number): Promise<void>
55
+ xmlHttpRequest(options: {
56
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE'
57
+ url: string
58
+ headers?: Record<string, string>
59
+ data?: string | FormData | ArrayBuffer
60
+ responseType?: 'text' | 'json' | 'blob'
61
+ onload?: (response: {
62
+ status: number
63
+ responseText?: string
64
+ response?: any
65
+ responseHeaders?: string
66
+ }) => void
67
+ onerror?: (error: any) => void
68
+ }): Promise<void>
69
+ setClipboard(text: string): Promise<void>
70
+ addStyle(css: string): Promise<HTMLStyleElement>
71
+ addElement(
72
+ tag: string,
73
+ attributes?: Record<string, string>
74
+ ): Promise<HTMLElement>
75
+ addElement(
76
+ parentNode: Element,
77
+ tag: string,
78
+ attributes?: Record<string, string>
79
+ ): Promise<HTMLElement>
80
+ registerMenuCommand(
81
+ caption: string,
82
+ onClick: () => void,
83
+ options_or_accessKey?:
84
+ | string
85
+ | {
86
+ id?: string | number
87
+ accessKey?: string
88
+ autoClose?: boolean
89
+ // Tampermonkey-specific
90
+ title?: string
91
+ // ScriptCat-specific
92
+ nested?: boolean
93
+ // ScriptCat-specific
94
+ individual?: boolean
95
+ }
96
+ ): Promise<number>
97
+ unregisterMenuCommand(menuId: number): Promise<void>
98
+ download(options: {
99
+ url: string
100
+ name: string
101
+ onload?: () => void
102
+ }): Promise<void>
103
+ openInTab(
104
+ url: string,
105
+ options?: { active?: boolean; insert?: boolean }
106
+ ): Promise<void>
107
+ notification(options: {
108
+ text: string
109
+ title?: string
110
+ image?: string
111
+ onclick?: () => void
112
+ }): Promise<void>
113
+ }
114
+
115
+ declare function GM_getValue<T = unknown>(key: string, defaultValue: T): any
116
+ declare function GM_setValue(key: string, value: any): void
117
+ declare function GM_deleteValue(key: string): void
118
+ declare function GM_addStyle(css: string): HTMLStyleElement
119
+ declare function GM_addElement(
120
+ tag: string,
121
+ attributes?: Record<string, string>
122
+ ): HTMLElement
123
+ declare function GM_addElement(
124
+ parentNode: Element,
125
+ tag: string,
126
+ attributes?: Record<string, string>
127
+ ): HTMLElement
128
+ declare function GM_openInTab(
129
+ url: string,
130
+ options?: { active?: boolean; insert?: boolean }
131
+ ): void
132
+ declare function GM_removeValueChangeListener(id: number): void
133
+ declare function GM_download(options: {
134
+ url: string
135
+ name: string
136
+ onload?: () => void
137
+ }): void
138
+ declare function GM_notification(options: {
139
+ text: string
140
+ title?: string
141
+ image?: string
142
+ onclick?: () => void
143
+ }): void
144
+
145
+ declare function GM_xmlhttpRequest(options: {
146
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE'
147
+ url: string
148
+ headers?: Record<string, string>
149
+ data?: string | FormData | ArrayBuffer
150
+ responseType?: 'text' | 'json' | 'blob'
151
+ onload?: (response: {
152
+ status: number
153
+ responseText?: string
154
+ response?: any
155
+ responseHeaders?: string
156
+ }) => void
157
+ onerror?: (error: any) => void
158
+ }): void
159
+
160
+ declare function GM_setClipboard(text: string): void