browser-extension-utils 0.2.2 → 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/add-element.ts +38 -0
- package/lib/create-element.ts +11 -0
- package/lib/dom-utils.ts +232 -0
- package/lib/global.d.ts +154 -0
- package/lib/index.ts +358 -0
- package/lib/set-attributes.ts +42 -0
- package/lib/userscript.ts +94 -0
- package/package.json +20 -31
- package/lib/index.d.ts +0 -207
- package/lib/index.js +0 -548
- package/lib/userscript.js +0 -86
|
@@ -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
|
package/lib/dom-utils.ts
ADDED
|
@@ -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
|
package/lib/global.d.ts
ADDED
|
@@ -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
|