browser-extension-settings 0.0.1 → 0.0.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.
package/lib/index.d.ts CHANGED
@@ -1,139 +1,5 @@
1
- export const doc: Document
1
+ export async function initSettings(options: Record<string, unknown>): void
2
2
 
3
- export const win: Window
3
+ export async function showSettings(): void
4
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
5
+ export function getSettingsValue(key: string): boolean | string | undefined
package/lib/index.js CHANGED
@@ -1,335 +1 @@
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
- }
1
+ export { initSettings, showSettings, getSettingsValue } from "./settings.js"
@@ -0,0 +1,224 @@
1
+ import {
2
+ addValueChangeListener,
3
+ getValue,
4
+ setValue,
5
+ } from "browser-extension-storage"
6
+ import {
7
+ $,
8
+ addElement,
9
+ addEventListener,
10
+ addStyle,
11
+ doc,
12
+ removeEventListener,
13
+ } from "browser-extension-utils"
14
+ import styleText from "data-text:./style.scss"
15
+ import { createSwitchOption } from "./switch.js"
16
+
17
+ const settingsElementId =
18
+ "browser_extension_settings_" + String(Math.round(Math.random() * 10_000))
19
+ const getSettingsElement = () => $("#" + settingsElementId)
20
+ const getSettingsStyle = () =>
21
+ styleText.replace(/browser_extension_settings/gm, settingsElementId)
22
+ const storageKey = "settings"
23
+
24
+ let settingsOptions = {}
25
+ let settingsTable = {}
26
+ let settings = {}
27
+ async function getSettings() {
28
+ return (await getValue(storageKey)) ?? {}
29
+ }
30
+
31
+ async function saveSattingsValue(key, value) {
32
+ const settings = await getSettings()
33
+ settings[key] =
34
+ settingsTable[key] && settingsTable[key].defaultValue === value
35
+ ? undefined
36
+ : value
37
+
38
+ await setValue(storageKey, settings)
39
+ }
40
+
41
+ export function getSettingsValue(key) {
42
+ return Object.hasOwn(settings, key)
43
+ ? settings[key]
44
+ : settingsTable[key]?.defaultValue
45
+ }
46
+
47
+ const modalHandler = (event) => {
48
+ let target = event.target
49
+ const settingsLayer = getSettingsElement()
50
+ if (settingsLayer) {
51
+ while (target !== settingsLayer && target) {
52
+ target = target.parentNode
53
+ }
54
+
55
+ if (target === settingsLayer) {
56
+ return
57
+ }
58
+
59
+ settingsLayer.style.display = "none"
60
+ }
61
+
62
+ removeEventListener(document, "click", modalHandler)
63
+ }
64
+
65
+ async function updateOptions() {
66
+ if (!getSettingsElement()) {
67
+ return
68
+ }
69
+
70
+ for (const key in settingsTable) {
71
+ if (Object.hasOwn(settingsTable, key)) {
72
+ const checkbox = $(
73
+ `#${settingsElementId} .option_groups .switch_option[data-key="${key}"] input`
74
+ )
75
+ if (checkbox) {
76
+ checkbox.checked = getSettingsValue(key)
77
+ }
78
+ }
79
+ }
80
+
81
+ const host = location.host
82
+ const group2 = $(`#${settingsElementId} .option_groups:nth-of-type(2)`)
83
+ if (group2) {
84
+ group2.style.display = getSettingsValue(
85
+ `enableCustomRulesForCurrentSite_${host}`
86
+ )
87
+ ? "block"
88
+ : "none"
89
+ }
90
+
91
+ const customStyleValue = $(`#${settingsElementId} .option_groups textarea`)
92
+ if (customStyleValue) {
93
+ customStyleValue.value = settings[`customRulesForCurrentSite_${host}`] || ""
94
+ }
95
+ }
96
+
97
+ function createSettingsElement() {
98
+ let settingsLayer = getSettingsElement()
99
+ if (!settingsLayer) {
100
+ settingsLayer = addElement(document.body, "div", {
101
+ id: settingsElementId,
102
+ })
103
+
104
+ if (settingsOptions.title) {
105
+ addElement(settingsLayer, "h2", { textContent: settingsOptions.title })
106
+ }
107
+
108
+ const options = addElement(settingsLayer, "div", { class: "option_groups" })
109
+ for (const key in settingsTable) {
110
+ if (Object.hasOwn(settingsTable, key)) {
111
+ const item = settingsTable[key]
112
+ if (!item.type || item.type === "switch") {
113
+ const switchOption = createSwitchOption(item.title, {
114
+ async onchange(event) {
115
+ await saveSattingsValue(key, event.target.checked)
116
+ },
117
+ })
118
+
119
+ switchOption.dataset.key = key
120
+
121
+ addElement(options, switchOption)
122
+ }
123
+ }
124
+ }
125
+
126
+ const options2 = addElement(settingsLayer, "div", {
127
+ class: "option_groups",
128
+ })
129
+ let timeoutId
130
+ addElement(options2, "textarea", {
131
+ placeholder: `/* Custom rules for internal URLs, matching URLs will be opened in new tabs */`,
132
+ onkeyup(event) {
133
+ if (timeoutId) {
134
+ clearTimeout(timeoutId)
135
+ timeoutId = null
136
+ }
137
+
138
+ timeoutId = setTimeout(async () => {
139
+ const host = location.host
140
+ await saveSattingsValue(
141
+ `customRulesForCurrentSite_${host}`,
142
+ event.target.value.trim()
143
+ )
144
+ }, 100)
145
+ },
146
+ })
147
+
148
+ const tip = addElement(options2, "div", {
149
+ class: "tip",
150
+ })
151
+ addElement(tip, "a", {
152
+ class: "tip_anchor",
153
+ textContent: "Examples",
154
+ })
155
+ const tipContent = addElement(tip, "div", {
156
+ class: "tip_content",
157
+ innerHTML: `<p>Custom rules for internal URLs, matching URLs will be opened in new tabs</p>
158
+ <p>
159
+ - One line per url pattern<br>
160
+ - All URLs contains '/posts' or '/users/'<br>
161
+ <pre>/posts/
162
+ /users/</pre>
163
+
164
+ - Regex is supported<br>
165
+ <pre>^/(posts|members)/d+</pre>
166
+
167
+ - '*' for all URLs
168
+ </p>`,
169
+ })
170
+
171
+ if (settingsOptions.footer) {
172
+ const footer = addElement(settingsLayer, "footer")
173
+ footer.innerHTML =
174
+ typeof settingsOptions.footer === "string"
175
+ ? settingsOptions.footer
176
+ : `<p>Made with ❤️ by
177
+ <a href="https://www.pipecraft.net/" target="_blank">
178
+ Pipecraft
179
+ </a></p>`
180
+ }
181
+ }
182
+
183
+ return settingsLayer
184
+ }
185
+
186
+ function addSideMenu(options) {
187
+ const menu =
188
+ $("#browser_extension_side_menu") ||
189
+ addElement(doc.body, "div", {
190
+ id: "browser_extension_side_menu",
191
+ "data-version": 1,
192
+ })
193
+ addElement(menu, "button", {
194
+ type: "button",
195
+ title: options.title ? "设置 - " + options.title : "设置",
196
+ onclick() {
197
+ setTimeout(showSettings, 1)
198
+ },
199
+ })
200
+ }
201
+
202
+ export async function showSettings() {
203
+ const settingsLayer = createSettingsElement()
204
+ await updateOptions()
205
+ settingsLayer.style.display = "block"
206
+
207
+ addEventListener(document, "click", modalHandler)
208
+ }
209
+
210
+ export const initSettings = async (options) => {
211
+ settingsOptions = options
212
+ settingsTable = options.settingsTable || {}
213
+ addValueChangeListener(storageKey, async () => {
214
+ settings = await getSettings()
215
+ await updateOptions()
216
+ if (typeof options.onValueChange === "function") {
217
+ options.onValueChange()
218
+ }
219
+ })
220
+
221
+ settings = await getSettings()
222
+ addStyle(getSettingsStyle())
223
+ addSideMenu(options)
224
+ }
package/lib/style.scss ADDED
@@ -0,0 +1,263 @@
1
+ #browser_extension_settings {
2
+ --browser-extension-settings-background-color: #f3f3f3;
3
+ --browser-extension-settings-text-color: #444444;
4
+ --browser-extension-settings-link-color: #217dfc;
5
+ --sb-track-color: #00000000;
6
+ --sb-thumb-color: #33334480;
7
+ --sb-size: 2px;
8
+
9
+ position: fixed;
10
+ top: 10px;
11
+ right: 30px;
12
+ min-width: 250px;
13
+ max-height: 90%;
14
+ overflow-y: auto;
15
+ overflow-x: hidden;
16
+ scrollbar-color: var(--sb-thumb-color) var(--sb-track-color);
17
+ /* for Firefox */
18
+ scrollbar-width: thin;
19
+ display: none;
20
+ box-sizing: border-box;
21
+ padding: 10px 15px;
22
+ background-color: var(--browser-extension-settings-background-color);
23
+ color: var(--browser-extension-settings-text-color);
24
+ z-index: 100000;
25
+ border-radius: 5px;
26
+ -webkit-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22);
27
+ -moz-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22);
28
+ box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22) !important;
29
+
30
+ &::-webkit-scrollbar {
31
+ width: var(--sb-size);
32
+ }
33
+
34
+ &::-webkit-scrollbar-track {
35
+ background: var(--sb-track-color);
36
+ border-radius: 10px;
37
+ }
38
+
39
+ &::-webkit-scrollbar-thumb {
40
+ background: var(--sb-thumb-color);
41
+ border-radius: 10px;
42
+ }
43
+
44
+ h2 {
45
+ text-align: center;
46
+ margin: 5px 0 0;
47
+ font-size: 18px;
48
+ font-weight: 600;
49
+ border: none;
50
+ }
51
+
52
+ footer {
53
+ display: flex;
54
+ justify-content: center;
55
+ flex-direction: column;
56
+ font-size: 11px;
57
+ margin: 10px auto 0px;
58
+ background-color: var(--browser-extension-settings-background-color);
59
+ color: var(--browser-extension-settings-text-color);
60
+ }
61
+
62
+ footer a {
63
+ color: var(--browser-extension-settings-link-color) !important;
64
+ text-decoration: none;
65
+ padding: 0;
66
+ }
67
+
68
+ footer p {
69
+ text-align: center;
70
+ padding: 0;
71
+ margin: 2px;
72
+ line-height: 13px;
73
+ }
74
+
75
+ .option_groups {
76
+ background-color: #fff;
77
+ padding: 6px 15px 6px 15px;
78
+ border-radius: 10px;
79
+ display: flex;
80
+ flex-direction: column;
81
+ margin: 10px 0 0;
82
+ }
83
+
84
+ .option_groups .action {
85
+ font-size: 14px;
86
+ border-top: 1px solid #cccccc;
87
+ padding: 6px 0 6px 0;
88
+ color: var(--browser-extension-settings-link-color);
89
+ cursor: pointer;
90
+ }
91
+
92
+ .option_groups textarea {
93
+ margin: 10px 0 10px 0;
94
+ height: 100px;
95
+ width: 100%;
96
+ border: 1px solid darkgray;
97
+ border-radius: 4px;
98
+ box-sizing: border-box;
99
+ }
100
+
101
+ .switch_option {
102
+ display: flex;
103
+ justify-content: space-between;
104
+ align-items: center;
105
+ border-top: 1px solid #cccccc;
106
+ padding: 6px 0 6px 0;
107
+ font-size: 14px;
108
+ }
109
+ .switch_option:first-of-type,
110
+ .option_groups .action:first-of-type {
111
+ border-top: none;
112
+ }
113
+
114
+ .switch_option > span {
115
+ margin-right: 10px;
116
+ }
117
+
118
+ .option_groups .tip {
119
+ position: relative;
120
+ margin: 0;
121
+ padding: 0 15px 0 0;
122
+ border: none;
123
+ max-width: none;
124
+ font-size: 14px;
125
+
126
+ .tip_anchor {
127
+ cursor: help;
128
+ text-decoration: underline;
129
+ }
130
+ .tip_content {
131
+ position: absolute;
132
+ bottom: 15px;
133
+ left: 0;
134
+ background-color: #ffffff;
135
+ color: var(--browser-extension-settings-text-color);
136
+ text-align: left;
137
+ padding: 10px;
138
+ display: none;
139
+ border-radius: 5px;
140
+ -webkit-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22);
141
+ -moz-box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22);
142
+ box-shadow: 0px 10px 39px 10px rgba(62, 66, 66, 0.22) !important;
143
+ }
144
+
145
+ .tip_anchor:hover + .tip_content,
146
+ .tip_content:hover {
147
+ display: block;
148
+ }
149
+
150
+ p,
151
+ pre {
152
+ margin: revert;
153
+ padding: revert;
154
+ }
155
+
156
+ pre {
157
+ font-family: Consolas, panic sans, bitstream vera sans mono, Menlo,
158
+ microsoft yahei, monospace;
159
+ font-size: 13px;
160
+ letter-spacing: 0.015em;
161
+ line-height: 120%;
162
+ white-space: pre;
163
+ overflow: auto;
164
+ background-color: #f5f5f5;
165
+ word-break: normal;
166
+ overflow-wrap: normal;
167
+ padding: 0.5em;
168
+ border: none;
169
+ }
170
+ }
171
+
172
+ /* https://uiverse.io/zanina-yassine/afraid-eel-50 */
173
+ /* The switch - the box around the slider */
174
+ .container {
175
+ --button-width: 51px;
176
+ --button-height: 24px;
177
+ --toggle-diameter: 20px;
178
+ --color-off: #e9e9eb;
179
+ --color-on: #34c759;
180
+ width: var(--button-width);
181
+ height: var(--button-height);
182
+ position: relative;
183
+ padding: 0;
184
+ margin: 0;
185
+ flex: none;
186
+ }
187
+
188
+ /* Hide default HTML checkbox */
189
+ input[type="checkbox"] {
190
+ opacity: 0;
191
+ width: 0;
192
+ height: 0;
193
+ position: absolute;
194
+ }
195
+
196
+ .switch {
197
+ width: 100%;
198
+ height: 100%;
199
+ display: block;
200
+ background-color: var(--color-off);
201
+ border-radius: calc(var(--button-height) / 2);
202
+ border: none;
203
+ cursor: pointer;
204
+ transition: all 0.2s ease-out;
205
+ }
206
+
207
+ .switch::before {
208
+ display: none;
209
+ }
210
+
211
+ /* The slider */
212
+ .slider {
213
+ width: var(--toggle-diameter);
214
+ height: var(--toggle-diameter);
215
+ position: absolute;
216
+ left: 2px;
217
+ top: calc(50% - var(--toggle-diameter) / 2);
218
+ border-radius: 50%;
219
+ background: #ffffff;
220
+ box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.15), 0px 3px 1px rgba(0, 0, 0, 0.06);
221
+ transition: all 0.2s ease-out;
222
+ cursor: pointer;
223
+ }
224
+
225
+ input[type="checkbox"]:checked + .switch {
226
+ background-color: var(--color-on);
227
+ }
228
+
229
+ input[type="checkbox"]:checked + .switch .slider {
230
+ left: calc(var(--button-width) - var(--toggle-diameter) - 2px);
231
+ }
232
+ }
233
+
234
+ #browser_extension_side_menu {
235
+ min-height: 200px;
236
+ width: 40px;
237
+ opacity: 0;
238
+ position: fixed;
239
+ top: 80px;
240
+ right: 0;
241
+ padding-top: 20px;
242
+ }
243
+
244
+ #browser_extension_side_menu:hover {
245
+ opacity: 1;
246
+ }
247
+
248
+ #browser_extension_side_menu button {
249
+ cursor: pointer;
250
+ width: 24px;
251
+ height: 24px;
252
+ border: none;
253
+ background-color: transparent;
254
+ background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M12.0002 16C14.2094 16 16.0002 14.2091 16.0002 12C16.0002 9.79086 14.2094 8 12.0002 8C9.79109 8 8.00023 9.79086 8.00023 12C8.00023 14.2091 9.79109 16 12.0002 16ZM12.0002 14C13.1048 14 14.0002 13.1046 14.0002 12C14.0002 10.8954 13.1048 10 12.0002 10C10.8957 10 10.0002 10.8954 10.0002 12C10.0002 13.1046 10.8957 14 12.0002 14Z' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15.1182 1.86489L15.5203 4.81406C15.8475 4.97464 16.1621 5.1569 16.4623 5.35898L19.2185 4.23223C19.6814 4.043 20.2129 4.2248 20.463 4.65787L22.5901 8.34213C22.8401 8.77521 22.7318 9.3264 22.3365 9.63266L19.9821 11.4566C19.9941 11.6362 20.0002 11.8174 20.0002 12C20.0002 12.1826 19.9941 12.3638 19.9821 12.5434L22.3365 14.3673C22.7318 14.6736 22.8401 15.2248 22.5901 15.6579L20.463 19.3421C20.2129 19.7752 19.6814 19.957 19.2185 19.7678L16.4623 18.641C16.1621 18.8431 15.8475 19.0254 15.5203 19.1859L15.1182 22.1351C15.0506 22.6306 14.6274 23 14.1273 23H9.87313C9.37306 23 8.94987 22.6306 8.8823 22.1351L8.48014 19.1859C8.15296 19.0254 7.83835 18.8431 7.53818 18.641L4.78195 19.7678C4.31907 19.957 3.78756 19.7752 3.53752 19.3421L1.41042 15.6579C1.16038 15.2248 1.26869 14.6736 1.66401 14.3673L4.01841 12.5434C4.00636 12.3638 4.00025 12.1826 4.00025 12C4.00025 11.8174 4.00636 11.6362 4.01841 11.4566L1.66401 9.63266C1.26869 9.3264 1.16038 8.77521 1.41041 8.34213L3.53752 4.65787C3.78755 4.2248 4.31906 4.043 4.78195 4.23223L7.53818 5.35898C7.83835 5.1569 8.15296 4.97464 8.48014 4.81406L8.8823 1.86489C8.94987 1.3694 9.37306 1 9.87313 1H14.1273C14.6274 1 15.0506 1.3694 15.1182 1.86489ZM13.6826 6.14004L14.6392 6.60948C14.8842 6.72975 15.1201 6.86639 15.3454 7.01805L16.231 7.61423L19.1674 6.41382L20.4216 8.58619L17.9153 10.5278L17.9866 11.5905C17.9956 11.7255 18.0002 11.8621 18.0002 12C18.0002 12.1379 17.9956 12.2745 17.9866 12.4095L17.9153 13.4722L20.4216 15.4138L19.1674 17.5862L16.231 16.3858L15.3454 16.982C15.1201 17.1336 14.8842 17.2702 14.6392 17.3905L13.6826 17.86L13.2545 21H10.746L10.3178 17.86L9.36131 17.3905C9.11626 17.2702 8.88037 17.1336 8.6551 16.982L7.76954 16.3858L4.83313 17.5862L3.57891 15.4138L6.0852 13.4722L6.01392 12.4095C6.00487 12.2745 6.00024 12.1379 6.00024 12C6.00024 11.8621 6.00487 11.7255 6.01392 11.5905L6.0852 10.5278L3.57891 8.58619L4.83312 6.41382L7.76953 7.61423L8.6551 7.01805C8.88037 6.86639 9.11625 6.72976 9.36131 6.60949L10.3178 6.14004L10.746 3H13.2545L13.6826 6.14004Z' fill='black'/%3E%3C/svg%3E");
255
+ }
256
+
257
+ #browser_extension_side_menu button:hover {
258
+ opacity: 70%;
259
+ }
260
+
261
+ #browser_extension_side_menu button:active {
262
+ opacity: 100%;
263
+ }
package/lib/switch.js ADDED
@@ -0,0 +1,29 @@
1
+ import {
2
+ addElement,
3
+ addEventListener,
4
+ createElement,
5
+ } from "browser-extension-utils"
6
+
7
+ export function createSwitch(options = {}) {
8
+ const container = createElement("label", { class: "container" })
9
+ const checkbox = createElement(
10
+ "input",
11
+ options.checked ? { type: "checkbox", checked: "" } : { type: "checkbox" }
12
+ )
13
+ addElement(container, checkbox)
14
+ const switchElm = createElement("span", { class: "switch" })
15
+ addElement(switchElm, "span", { class: "slider" })
16
+ addElement(container, switchElm)
17
+ if (options.onchange) {
18
+ addEventListener(checkbox, "change", options.onchange)
19
+ }
20
+
21
+ return container
22
+ }
23
+
24
+ export function createSwitchOption(text, options) {
25
+ const div = createElement("div", { class: "switch_option" })
26
+ addElement(div, "span", { textContent: text })
27
+ div.append(createSwitch(options))
28
+ return div
29
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "browser-extension-settings",
3
- "version": "0.0.1",
4
- "description": "Settings module for developing browser extensions and userscripts ",
3
+ "version": "0.0.2",
4
+ "description": "Settings module for developing browser extensions and userscripts",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "exports": {
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "engines": {
40
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
40
+ "node": "^14.13.1 || >=16.0.0"
41
41
  },
42
42
  "xo": {
43
43
  "space": 2,
@@ -49,5 +49,9 @@
49
49
  "prefer-destructuring": 0,
50
50
  "capitalized-comments": 0
51
51
  }
52
+ },
53
+ "dependencies": {
54
+ "browser-extension-storage": "^0.1.2",
55
+ "browser-extension-utils": "^0.1.4"
52
56
  }
53
57
  }