feat: add form_saver
This commit is contained in:
parent
1a7e70272c
commit
e7b316be7a
74
form_saver.ts
Normal file
74
form_saver.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { CommunicateMessage, FormSavedDataContent } from './types'
|
||||
|
||||
class FormSaverExtension {
|
||||
private readonly api = browser ?? chrome
|
||||
|
||||
public startListen = (): void =>
|
||||
this.api.runtime.onMessage.addListener(this.messageHandler.bind(this))
|
||||
|
||||
// ---
|
||||
|
||||
private readonly messageHandler = (message: unknown, _: any, sendResponse: (message: CommunicateMessage) => void): undefined => {
|
||||
const receivedMessage = message as CommunicateMessage
|
||||
|
||||
if (receivedMessage.type === 'SAVE_FORM') {
|
||||
return sendResponse(this.saveForm()) as undefined
|
||||
}
|
||||
|
||||
if (receivedMessage.type === 'LOAD_FORM') {
|
||||
return this.loadForm(receivedMessage.data?.contents ?? []) as undefined
|
||||
}
|
||||
}
|
||||
|
||||
private readonly saveForm = (): CommunicateMessage => {
|
||||
const inputElements = this.findInputElements()
|
||||
|
||||
return {
|
||||
type: 'FORM_SAVED',
|
||||
data: {
|
||||
url: window.location.href,
|
||||
contents: [
|
||||
...this.retrieveTextInputValues(inputElements)
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly loadForm = (formSaved: FormSavedDataContent[]): void => {
|
||||
this.loadTextInputValues(formSaved)
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
private readonly findInputElements = (): HTMLInputElement[] =>
|
||||
[...document.querySelectorAll('input')]
|
||||
|
||||
private readonly retrieveTextInputValues = (elements: HTMLInputElement[]): FormSavedDataContent[] =>
|
||||
elements
|
||||
.filter((v) => v.type === 'text')
|
||||
.map((v) => ({
|
||||
selector: `div#${v.parentElement?.id ?? ''}>input`,
|
||||
type: 'TEXT' as const,
|
||||
value: v.value
|
||||
}))
|
||||
|
||||
// ---
|
||||
|
||||
private readonly loadTextInputValues = (formSaved: FormSavedDataContent[]): void =>
|
||||
formSaved
|
||||
.filter((v) => v.type === 'TEXT')
|
||||
.forEach(this.loadTextInputValue.bind(this))
|
||||
|
||||
private readonly loadTextInputValue = (formSaved: FormSavedDataContent): void => {
|
||||
const element = document.querySelector(formSaved.selector)
|
||||
if (element === null || !(element instanceof HTMLInputElement) || element.type !== 'text') { return }
|
||||
|
||||
element.value = formSaved.value
|
||||
element.dispatchEvent(new Event('input', {
|
||||
bubbles: true
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
new FormSaverExtension()
|
||||
.startListen()
|
BIN
icons/koishi.webp
Normal file
BIN
icons/koishi.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
@ -1,14 +1,32 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "force_delete",
|
||||
"name": "Koishi ~~ Hello- I'm behind you right now. ~~",
|
||||
"version": "1.0",
|
||||
|
||||
"description": "automatically types AWS's delete-safe words",
|
||||
"description": "Hello- I'm behind you right now.",
|
||||
|
||||
"icons": {
|
||||
"48": "icons/koishi.webp"
|
||||
},
|
||||
|
||||
"action": {
|
||||
"default_icon": "icons/koishi.webp",
|
||||
"default_title": "Koishi",
|
||||
"default_popup": "popup/index.html"
|
||||
},
|
||||
|
||||
"host_permissions": [
|
||||
"*://*.console.aws.amazon.com/*"
|
||||
],
|
||||
|
||||
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["*://*.console.aws.amazon.com/*"],
|
||||
"js": ["main.js"],
|
||||
"js": [
|
||||
"force_delete.js",
|
||||
"form_saver.js"
|
||||
],
|
||||
"all_frames": true,
|
||||
"match_about_blank": true,
|
||||
"match_origin_as_fallback": true
|
||||
|
779
pnpm-lock.yaml
779
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
29
popup/index.html
Normal file
29
popup/index.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="/popup/main.css">
|
||||
<title>Koishi Form Saver</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h2>Form Saver</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>url</th>
|
||||
<th>content</th>
|
||||
<th>action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="render_point">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button id="save">Save!!</button>
|
||||
<script src="/popup/main.js"></script>
|
||||
</body>
|
||||
</html>
|
34
popup/main.css
Normal file
34
popup/main.css
Normal file
@ -0,0 +1,34 @@
|
||||
:root {
|
||||
background-color: #212121;
|
||||
color: #fafafa;
|
||||
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
table {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #626262;
|
||||
padding: 2px 5px;
|
||||
color: #fafafa;
|
||||
border: none;
|
||||
outline: 1px solid #af7d85;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #838383;
|
||||
}
|
83
popup/main.ts
Normal file
83
popup/main.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { CommunicateMessage, FormSavedData } from '../types'
|
||||
|
||||
void (async () => {
|
||||
const api = browser ?? chrome
|
||||
|
||||
const [tab] = await api.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true
|
||||
})
|
||||
|
||||
if (tab.url === undefined) {
|
||||
document.body.innerText = 'Extension disabled on this page'
|
||||
return
|
||||
}
|
||||
|
||||
if (localStorage.getItem('form_saved') === null) { localStorage.setItem('form_saved', '[]') }
|
||||
|
||||
document.getElementById('save')?.addEventListener('click', () => {
|
||||
void (async () => {
|
||||
const formSaved: CommunicateMessage = await api.tabs.sendMessage(tab.id ?? 0, {
|
||||
type: 'SAVE_FORM'
|
||||
})
|
||||
|
||||
if (formSaved.type !== 'FORM_SAVED') { return }
|
||||
|
||||
const prevSaved = JSON.parse(localStorage.getItem('form_saved') ?? '') as Array<FormSavedData & { id: string }>
|
||||
const newSaved = JSON.stringify([
|
||||
{
|
||||
id: crypto.randomUUID(),
|
||||
...formSaved.data
|
||||
},
|
||||
...prevSaved.slice(0, 10)
|
||||
])
|
||||
|
||||
localStorage.setItem('form_saved', newSaved)
|
||||
|
||||
renderTable()
|
||||
})()
|
||||
})
|
||||
|
||||
renderTable()
|
||||
function renderTable (): void {
|
||||
const formSaved = JSON.parse(localStorage.getItem('form_saved') ?? '') as Array<FormSavedData & { id: string }>
|
||||
const renderPoint = document.getElementById('render_point')
|
||||
|
||||
if (renderPoint === null) {
|
||||
return
|
||||
}
|
||||
|
||||
renderPoint.innerHTML = ''
|
||||
|
||||
for (const form of formSaved) {
|
||||
renderPoint.innerHTML += `
|
||||
<tr>
|
||||
<td>${form.id.substring(0, 8)}</td>
|
||||
<td>${new URL(form.url).pathname}${new URL(form.url).hash}</td>
|
||||
<td>${form.contents.filter((v) => v.type === 'TEXT').length} Texts</td>
|
||||
<td>
|
||||
<button id="load-${form.id}">Load</button>
|
||||
<button id="delete-${form.id}">x</button>
|
||||
</td>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
|
||||
for (const form of formSaved) {
|
||||
document.getElementById(`load-${form.id}`)?.addEventListener('click', () => {
|
||||
void api.tabs.sendMessage<CommunicateMessage>(tab.id ?? 0, {
|
||||
type: 'LOAD_FORM',
|
||||
data: form
|
||||
})
|
||||
})
|
||||
|
||||
document.getElementById(`delete-${form.id}`)?.addEventListener('click', () => {
|
||||
const prevSaved = JSON.parse(localStorage.getItem('form_saved') ?? '') as Array<FormSavedData & { id: string }>
|
||||
const newSaved = JSON.stringify(prevSaved.filter((v) => v.id !== form.id))
|
||||
|
||||
localStorage.setItem('form_saved', newSaved)
|
||||
renderTable()
|
||||
})
|
||||
}
|
||||
}
|
||||
})()
|
@ -25,7 +25,7 @@
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"module": "Preserve", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
@ -108,6 +108,6 @@
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"main.ts"
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
|
22
types.d.ts
vendored
Normal file
22
types.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Browser } from 'webextension-polyfill'
|
||||
|
||||
declare global {
|
||||
const browser: Browser | undefined
|
||||
const chrome: Browser
|
||||
}
|
||||
|
||||
interface CommunicateMessage {
|
||||
type: 'SAVE_FORM' | 'LOAD_FORM' | 'FORM_SAVED',
|
||||
data?: FormSavedData
|
||||
}
|
||||
|
||||
interface FormSavedData {
|
||||
url: string
|
||||
contents: FormSavedDataContent[]
|
||||
}
|
||||
|
||||
interface FormSavedDataContent {
|
||||
selector: string,
|
||||
type: 'TEXT',
|
||||
value: string
|
||||
}
|
Loading…
Reference in New Issue
Block a user