web-extension 0.0.0-stage → 2.0.0
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/AI.md +270 -0
- package/DEVELOPER.md +122 -0
- package/LICENSE +21 -0
- package/README.md +140 -2
- package/dist/index.js +2125 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -4
- package/templates/base/_gitignore +6 -0
- package/templates/base/public/icons/128.png +0 -0
- package/templates/base/public/icons/16.png +0 -0
- package/templates/base/public/icons/48.png +0 -0
- package/templates/features/background/js/src/background/index.js +6 -0
- package/templates/features/background/ts/src/background/index.ts +6 -0
- package/templates/features/content/content-only/js/src/content/index.js +32 -0
- package/templates/features/content/content-only/ts/src/content/index.ts +32 -0
- package/templates/features/content/full/js/src/content/index.js +6 -0
- package/templates/features/content/full/ts/src/content/index.ts +6 -0
- package/templates/features/content/style.css +16 -0
- package/templates/features/devtools/js/src/devtools/devtools.html +9 -0
- package/templates/features/devtools/js/src/devtools/devtools.js +11 -0
- package/templates/features/devtools/js/src/devtools/panel.html +18 -0
- package/templates/features/devtools/ts/src/devtools/devtools.html +9 -0
- package/templates/features/devtools/ts/src/devtools/devtools.ts +11 -0
- package/templates/features/devtools/ts/src/devtools/panel.html +18 -0
- package/templates/features/dnr/rules.json +18 -0
- package/templates/features/extras/storage/js/src/lib/storage.js +31 -0
- package/templates/features/extras/storage/ts/src/lib/storage.ts +15 -0
- package/templates/features/newtab/js/src/newtab/index.html +17 -0
- package/templates/features/newtab/js/src/newtab/main.js +8 -0
- package/templates/features/newtab/js/src/newtab/style.css +30 -0
- package/templates/features/newtab/ts/src/newtab/index.html +17 -0
- package/templates/features/newtab/ts/src/newtab/main.ts +8 -0
- package/templates/features/newtab/ts/src/newtab/style.css +30 -0
- package/templates/features/options/js/src/options/index.html +21 -0
- package/templates/features/options/js/src/options/main.js +11 -0
- package/templates/features/options/js/src/options/style.css +37 -0
- package/templates/features/options/ts/src/options/index.html +21 -0
- package/templates/features/options/ts/src/options/main.ts +11 -0
- package/templates/features/options/ts/src/options/style.css +37 -0
- package/templates/features/popup/js/src/popup/index.html +18 -0
- package/templates/features/popup/js/src/popup/main.js +9 -0
- package/templates/features/popup/js/src/popup/style.css +39 -0
- package/templates/features/popup/ts/src/popup/index.html +18 -0
- package/templates/features/popup/ts/src/popup/main.ts +9 -0
- package/templates/features/popup/ts/src/popup/style.css +39 -0
- package/templates/features/sidepanel/js/src/sidepanel/index.html +17 -0
- package/templates/features/sidepanel/js/src/sidepanel/main.js +7 -0
- package/templates/features/sidepanel/js/src/sidepanel/style.css +25 -0
- package/templates/features/sidepanel/ts/src/sidepanel/index.html +17 -0
- package/templates/features/sidepanel/ts/src/sidepanel/main.ts +7 -0
- package/templates/features/sidepanel/ts/src/sidepanel/style.css +25 -0
- package/templates/js/src/lib/browser.js +9 -0
- package/templates/ts/src/lib/browser.ts +9 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { api } from './browser';
|
|
2
|
+
|
|
3
|
+
export async function getStorage<T = any>(key: string, defaultValue?: T): Promise<T | undefined> {
|
|
4
|
+
const result = await api.storage.local.get(key);
|
|
5
|
+
return (result[key] as T) ?? defaultValue;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function setStorage<T = any>(key: string, value: T): Promise<void> {
|
|
9
|
+
await api.storage.local.set({ [key]: value });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function removeStorage(key: string): Promise<void> {
|
|
13
|
+
await api.storage.local.remove(key);
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>New Tab</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>Welcome to {{name}}</h1>
|
|
12
|
+
<p id="time"></p>
|
|
13
|
+
</div>
|
|
14
|
+
<script type="module" src="./main.js"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
display: flex;
|
|
4
|
+
min-height: 100vh;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
8
|
+
color: #1f2937;
|
|
9
|
+
background-color: #f3f4f6;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.container {
|
|
13
|
+
text-align: center;
|
|
14
|
+
padding: 32px;
|
|
15
|
+
background: white;
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 {
|
|
21
|
+
margin: 0 0 12px 0;
|
|
22
|
+
font-size: 1.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#time {
|
|
26
|
+
font-size: 1.25rem;
|
|
27
|
+
color: #4b5563;
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>New Tab</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>Welcome to {{name}}</h1>
|
|
12
|
+
<p id="time"></p>
|
|
13
|
+
</div>
|
|
14
|
+
<script type="module" src="./main.ts"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
display: flex;
|
|
4
|
+
min-height: 100vh;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
8
|
+
color: #1f2937;
|
|
9
|
+
background-color: #f3f4f6;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.container {
|
|
13
|
+
text-align: center;
|
|
14
|
+
padding: 32px;
|
|
15
|
+
background: white;
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 {
|
|
21
|
+
margin: 0 0 12px 0;
|
|
22
|
+
font-size: 1.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#time {
|
|
26
|
+
font-size: 1.25rem;
|
|
27
|
+
color: #4b5563;
|
|
28
|
+
margin: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{name}} Options</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}} Options</h1>
|
|
12
|
+
<label>
|
|
13
|
+
<input type="checkbox" id="enabled" />
|
|
14
|
+
Enable feature
|
|
15
|
+
</label>
|
|
16
|
+
<div id="status"></div>
|
|
17
|
+
</div>
|
|
18
|
+
<script type="module" src="./main.js"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { api } from '../lib/browser.js';
|
|
2
|
+
|
|
3
|
+
const checkbox = /** @type {HTMLInputElement | null} */ (document.getElementById('enabled'));
|
|
4
|
+
const status = document.getElementById('status');
|
|
5
|
+
|
|
6
|
+
checkbox?.addEventListener('change', () => {
|
|
7
|
+
if (status) {
|
|
8
|
+
status.textContent = checkbox.checked ? 'Setting enabled' : 'Setting disabled';
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
body {
|
|
2
|
+
max-width: 600px;
|
|
3
|
+
margin: 32px auto;
|
|
4
|
+
padding: 0 16px;
|
|
5
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
6
|
+
color: #1f2937;
|
|
7
|
+
background-color: #f9fafb;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: 16px;
|
|
14
|
+
background-color: #ffffff;
|
|
15
|
+
padding: 24px;
|
|
16
|
+
border-radius: 8px;
|
|
17
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 {
|
|
21
|
+
font-size: 1.25rem;
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
label {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
font-size: 0.95rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#status {
|
|
34
|
+
font-size: 0.875rem;
|
|
35
|
+
color: #4b5563;
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{name}} Options</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}} Options</h1>
|
|
12
|
+
<label>
|
|
13
|
+
<input type="checkbox" id="enabled" />
|
|
14
|
+
Enable feature
|
|
15
|
+
</label>
|
|
16
|
+
<div id="status"></div>
|
|
17
|
+
</div>
|
|
18
|
+
<script type="module" src="./main.ts"></script>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { api } from '../lib/browser';
|
|
2
|
+
|
|
3
|
+
const checkbox = document.getElementById('enabled') as HTMLInputElement | null;
|
|
4
|
+
const status = document.getElementById('status');
|
|
5
|
+
|
|
6
|
+
checkbox?.addEventListener('change', () => {
|
|
7
|
+
if (status) {
|
|
8
|
+
status.textContent = checkbox.checked ? 'Setting enabled' : 'Setting disabled';
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
body {
|
|
2
|
+
max-width: 600px;
|
|
3
|
+
margin: 32px auto;
|
|
4
|
+
padding: 0 16px;
|
|
5
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
6
|
+
color: #1f2937;
|
|
7
|
+
background-color: #f9fafb;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: 16px;
|
|
14
|
+
background-color: #ffffff;
|
|
15
|
+
padding: 24px;
|
|
16
|
+
border-radius: 8px;
|
|
17
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
h1 {
|
|
21
|
+
font-size: 1.25rem;
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
label {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
font-size: 0.95rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#status {
|
|
34
|
+
font-size: 0.875rem;
|
|
35
|
+
color: #4b5563;
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Popup</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}}</h1>
|
|
12
|
+
<p id="status">Extension popup loaded.</p>
|
|
13
|
+
<button id="btn">Click me</button>
|
|
14
|
+
</div>
|
|
15
|
+
<script type="module" src="./main.js"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
body {
|
|
2
|
+
width: 280px;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 16px;
|
|
5
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
6
|
+
color: #1f2937;
|
|
7
|
+
background-color: #f9fafb;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: 12px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1 {
|
|
17
|
+
font-size: 1.1rem;
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
p {
|
|
22
|
+
font-size: 0.875rem;
|
|
23
|
+
margin: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
button {
|
|
27
|
+
padding: 8px 12px;
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
color: #ffffff;
|
|
30
|
+
background-color: #3b82f6;
|
|
31
|
+
border: none;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
button:hover {
|
|
37
|
+
background-color: #2563eb;
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Popup</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}}</h1>
|
|
12
|
+
<p id="status">Extension popup loaded.</p>
|
|
13
|
+
<button id="btn">Click me</button>
|
|
14
|
+
</div>
|
|
15
|
+
<script type="module" src="./main.ts"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
18
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
body {
|
|
2
|
+
width: 280px;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 16px;
|
|
5
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
6
|
+
color: #1f2937;
|
|
7
|
+
background-color: #f9fafb;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: 12px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1 {
|
|
17
|
+
font-size: 1.1rem;
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
p {
|
|
22
|
+
font-size: 0.875rem;
|
|
23
|
+
margin: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
button {
|
|
27
|
+
padding: 8px 12px;
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
color: #ffffff;
|
|
30
|
+
background-color: #3b82f6;
|
|
31
|
+
border: none;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
button:hover {
|
|
37
|
+
background-color: #2563eb;
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Side Panel</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}} Side Panel</h1>
|
|
12
|
+
<p id="info">Side panel content loaded.</p>
|
|
13
|
+
</div>
|
|
14
|
+
<script type="module" src="./main.js"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 16px;
|
|
4
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
5
|
+
color: #1f2937;
|
|
6
|
+
background-color: #f9fafb;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.container {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 12px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1 {
|
|
16
|
+
font-size: 1.1rem;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
p {
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
margin: 0;
|
|
23
|
+
color: #4b5563;
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Side Panel</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="container">
|
|
11
|
+
<h1>{{name}} Side Panel</h1>
|
|
12
|
+
<p id="info">Side panel content loaded.</p>
|
|
13
|
+
</div>
|
|
14
|
+
<script type="module" src="./main.ts"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
17
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 16px;
|
|
4
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
5
|
+
color: #1f2937;
|
|
6
|
+
background-color: #f9fafb;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.container {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 12px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1 {
|
|
16
|
+
font-size: 1.1rem;
|
|
17
|
+
margin: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
p {
|
|
21
|
+
font-size: 0.875rem;
|
|
22
|
+
margin: 0;
|
|
23
|
+
color: #4b5563;
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-browser WebExtension API accessor.
|
|
3
|
+
* Supports standard browser.* and chrome.* namespaces.
|
|
4
|
+
*/
|
|
5
|
+
export const api: typeof chrome =
|
|
6
|
+
typeof (globalThis as any).browser !== 'undefined'
|
|
7
|
+
? (globalThis as any).browser
|
|
8
|
+
: (globalThis as any).chrome;
|
|
9
|
+
|