dictate-button 0.2.0 → 1.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.
@@ -1,35 +0,0 @@
1
- function i() {
2
- const a = document.querySelectorAll(
3
- 'textarea[data-dictate-button-on]:not([data-dictate-button-enabled]), input[type="text"][data-dictate-button-on]:not([data-dictate-button-enabled]), textarea[data-dictate-button-target]:not([data-dictate-button-enabled]), input[type="text"][data-dictate-button-target]:not([data-dictate-button-enabled])'
4
- );
5
- for (const o of a) {
6
- const n = document.createElement("div");
7
- n.style.position = "relative", n.style.display = "inline-block", n.style.width = "auto", n.style.color = "inherit", o.parentNode.insertBefore(n, o), o.setAttribute("data-dictate-button-enabled", ""), n.appendChild(o), o.style.boxSizing = "border-box";
8
- const t = document.createElement("dictate-button");
9
- t.size = 24, t.style.position = "absolute", t.style.right = "0", t.style.top = "0", t.addEventListener("recording:started", (e) => {
10
- console.log("recording:started", e);
11
- }), t.addEventListener("recording:stopped", (e) => {
12
- console.log("recording:stopped", e);
13
- }), t.addEventListener("recording:failed", (e) => {
14
- console.log("recording:failed", e);
15
- }), t.addEventListener("transcribing:started", (e) => {
16
- console.log("transcribing:started", e);
17
- }), t.addEventListener("transcribing:finished", (e) => {
18
- console.log("transcribing:finished", e);
19
- const d = e.detail;
20
- r(o, d);
21
- }), t.addEventListener("transcribing:failed", (e) => {
22
- console.log("transcribing:failed", e);
23
- }), n.appendChild(t);
24
- }
25
- }
26
- function r(a, o) {
27
- const n = a.selectionStart || 0, t = a.selectionEnd || 0;
28
- a.value = a.value.substring(0, n) + o + a.value.substring(t);
29
- }
30
- document.addEventListener("DOMContentLoaded", () => {
31
- i(), new MutationObserver(i).observe(document.body, {
32
- childList: !0,
33
- subtree: !0
34
- });
35
- });
@@ -1 +0,0 @@
1
- "use strict";function s(){const i=document.querySelectorAll('textarea:not([data-dictate-button-off]):not([data-dictate-button-enabled]), input[type="text"]:not([data-dictate-button-off]):not([data-dictate-button-enabled])');for(const o of i){const n=document.createElement("div");n.style.position="relative",n.style.display="inline-block",n.style.width="auto",n.style.color="inherit",o.parentNode.insertBefore(n,o),o.setAttribute("data-dictate-button-enabled",""),n.appendChild(o),o.style.boxSizing="border-box";const t=document.createElement("dictate-button");t.size=24,t.style.position="absolute",t.style.right="0",t.style.top="0",t.addEventListener("recording:started",e=>{console.log("recording:started",e)}),t.addEventListener("recording:stopped",e=>{console.log("recording:stopped",e)}),t.addEventListener("recording:failed",e=>{console.log("recording:failed",e)}),t.addEventListener("transcribing:started",e=>{console.log("transcribing:started",e)}),t.addEventListener("transcribing:finished",e=>{console.log("transcribing:finished",e);const d=e.detail;r(o,d)}),t.addEventListener("transcribing:failed",e=>{console.log("transcribing:failed",e)}),n.appendChild(t)}}function r(i,o){const n=i.selectionStart||0,t=i.selectionEnd||0;i.value=i.value.substring(0,n)+o+i.value.substring(t)}document.addEventListener("DOMContentLoaded",()=>{s(),new MutationObserver(s).observe(document.body,{childList:!0,subtree:!0})});
@@ -1,35 +0,0 @@
1
- function d() {
2
- const i = document.querySelectorAll(
3
- 'textarea:not([data-dictate-button-off]):not([data-dictate-button-enabled]), input[type="text"]:not([data-dictate-button-off]):not([data-dictate-button-enabled])'
4
- );
5
- for (const o of i) {
6
- const n = document.createElement("div");
7
- n.style.position = "relative", n.style.display = "inline-block", n.style.width = "auto", n.style.color = "inherit", o.parentNode.insertBefore(n, o), o.setAttribute("data-dictate-button-enabled", ""), n.appendChild(o), o.style.boxSizing = "border-box";
8
- const t = document.createElement("dictate-button");
9
- t.size = 24, t.style.position = "absolute", t.style.right = "0", t.style.top = "0", t.addEventListener("recording:started", (e) => {
10
- console.log("recording:started", e);
11
- }), t.addEventListener("recording:stopped", (e) => {
12
- console.log("recording:stopped", e);
13
- }), t.addEventListener("recording:failed", (e) => {
14
- console.log("recording:failed", e);
15
- }), t.addEventListener("transcribing:started", (e) => {
16
- console.log("transcribing:started", e);
17
- }), t.addEventListener("transcribing:finished", (e) => {
18
- console.log("transcribing:finished", e);
19
- const s = e.detail;
20
- r(o, s);
21
- }), t.addEventListener("transcribing:failed", (e) => {
22
- console.log("transcribing:failed", e);
23
- }), n.appendChild(t);
24
- }
25
- }
26
- function r(i, o) {
27
- const n = i.selectionStart || 0, t = i.selectionEnd || 0;
28
- i.value = i.value.substring(0, n) + o + i.value.substring(t);
29
- }
30
- document.addEventListener("DOMContentLoaded", () => {
31
- d(), new MutationObserver(d).observe(document.body, {
32
- childList: !0,
33
- subtree: !0
34
- });
35
- });
@@ -1 +0,0 @@
1
- "use strict";require("./inject-exclusive.cjs.js");
package/dist/inject.d.ts DELETED
@@ -1 +0,0 @@
1
- export {}
package/dist/inject.es.js DELETED
@@ -1 +0,0 @@
1
- import "./inject-exclusive.es.js";
package/firebase.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "hosting": {
3
- "predeploy": [
4
- "pnpm run build"
5
- ],
6
- "public": "dist",
7
- "ignore": [
8
- "firebase.json",
9
- "**/.*",
10
- "**/node_modules/**"
11
- ],
12
- "headers": [
13
- {
14
- "source": "**/*.@(js|ts)",
15
- "headers": [
16
- {
17
- "key": "Access-Control-Allow-Origin",
18
- "value": "*"
19
- }
20
- ]
21
- },
22
- {
23
- "source": "**/*.@(js|ts)",
24
- "headers": [
25
- {
26
- "key": "Cache-Control",
27
- "value": "no-cache"
28
- }
29
- ]
30
- },
31
- {
32
- "source": "404.html",
33
- "headers": [
34
- {
35
- "key": "Cache-Control",
36
- "value": "max-age=300"
37
- }
38
- ]
39
- }
40
- ]
41
- }
42
- }
@@ -1,2 +0,0 @@
1
- onlyBuiltDependencies:
2
- - esbuild
@@ -1,36 +0,0 @@
1
- export const dictateButtonStyles = `
2
- :host([theme="dark"]) {
3
- color-scheme: only dark;
4
- }
5
- :host([theme="light"]) {
6
- color-scheme: only light;
7
- }
8
-
9
- :host .dictate-button__container {
10
- margin: 5px;
11
- }
12
-
13
- :host .dictate-button__button {
14
- cursor: pointer;
15
- padding: 2px;
16
- display: inline-flex;
17
- align-items: center;
18
- justify-content: center;
19
- opacity: 0.8;
20
- transition: opacity 0.2s ease-in-out;
21
- }
22
-
23
- :host .dictate-button__button .dictate-button__icon {
24
- width: 100%;
25
- height: 100%;
26
- }
27
-
28
- :host .dictate-button__button .dictate-button__icon.dictate-button__icon--processing {
29
- animation: dictate-button-rotate 1s linear infinite;
30
- }
31
-
32
- @keyframes dictate-button-rotate {
33
- 0% { transform: rotate(0deg); }
34
- 100% { transform: rotate(360deg); }
35
- }
36
- `
@@ -1,240 +0,0 @@
1
- import { customElement } from 'solid-element'
2
- import { createSignal } from 'solid-js'
3
- import { dictateButtonStyles } from './dictate-button.styles'
4
-
5
- console.debug('dictate-button version:', __APP_VERSION__)
6
-
7
- export interface DictateButtonProps {
8
- size?: number
9
- apiEndpoint?: string
10
- // The props below are for types only. We don't use them inside the component.
11
- theme?: 'light' | 'dark'
12
- class?: string
13
- }
14
-
15
- declare module 'solid-js' {
16
- namespace JSX {
17
- interface IntrinsicElements {
18
- 'dictate-button': Element & DictateButtonProps
19
- }
20
- }
21
- }
22
-
23
- type DictateButtonStatus = 'idle' | 'recording' | 'processing' | 'error'
24
-
25
- const DEFAULT_TRANSCRIBE_API_ENDPOINT =
26
- 'https://api.dictate-button.io/transcribe'
27
- const APP_NAME = 'dictate-button.io'
28
-
29
- customElement(
30
- 'dictate-button',
31
- {
32
- size: 24,
33
- apiEndpoint: DEFAULT_TRANSCRIBE_API_ENDPOINT,
34
- },
35
- (props: DictateButtonProps, { element }) => {
36
- const { size, apiEndpoint } = props
37
-
38
- console.debug('api', apiEndpoint)
39
-
40
- const [status, setStatus] = createSignal<DictateButtonStatus>('idle')
41
-
42
- let mediaRecorder: MediaRecorder | null = null
43
- let audioChunks: Blob[] = []
44
-
45
- const cleanup = () => {
46
- if (mediaRecorder && mediaRecorder.state !== 'inactive') {
47
- mediaRecorder.stop()
48
- }
49
- audioChunks = []
50
- }
51
-
52
- element.addEventListener('disconnected', cleanup)
53
-
54
- const toggleRecording = async () => {
55
- cleanup()
56
-
57
- if (status() === 'idle') {
58
- try {
59
- const stream = await navigator.mediaDevices.getUserMedia({
60
- audio: true,
61
- })
62
- mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' })
63
- audioChunks = []
64
-
65
- mediaRecorder.ondataavailable = (event) => {
66
- audioChunks.push(event.data)
67
- }
68
-
69
- mediaRecorder.onstop = async () => {
70
- setStatus('processing')
71
-
72
- event(element, 'transcribing:started', 'Started transcribing')
73
-
74
- const audioBlob = new Blob(audioChunks, { type: 'audio/webm' })
75
-
76
- try {
77
- const response = await fetch(apiEndpoint!, {
78
- method: 'POST',
79
- body: audioBlob,
80
- })
81
-
82
- if (!response.ok) throw new Error('Failed to transcribe audio')
83
-
84
- const data = await response.json()
85
-
86
- // If user cancelled processing, don't emit transcribing:finished event.
87
- if (status() !== 'processing') return
88
-
89
- event(element, 'transcribing:finished', data.text)
90
-
91
- setStatus('idle')
92
- } catch (error) {
93
- console.error('Failed to transcribe audio:', error)
94
-
95
- event(
96
- element,
97
- 'transcribing:failed',
98
- 'Failed to transcribe audio'
99
- )
100
-
101
- setErrorStatus()
102
- }
103
- }
104
-
105
- mediaRecorder.start()
106
-
107
- event(element, 'recording:started', 'Started recording')
108
-
109
- setStatus('recording')
110
- } catch (error) {
111
- console.error('Failed to start recording:', error)
112
-
113
- event(element, 'recording:failed', 'Failed to start recording')
114
-
115
- setErrorStatus()
116
- }
117
- } else {
118
- event(element, 'recording:stopped', 'Stopped recording')
119
-
120
- setStatus('idle')
121
- }
122
- }
123
-
124
- const setErrorStatus = () => {
125
- setStatus('error')
126
- setTimeout(() => setStatus('idle'), 2000)
127
- }
128
-
129
- return (
130
- <div part="container" class="dictate-button__container">
131
- <style>{dictateButtonStyles}</style>
132
- <button
133
- part="button"
134
- style={`width:${size}px;height:${size}px"`}
135
- class="dictate-button__button"
136
- onClick={toggleRecording}
137
- title={buttonTitle(status())}
138
- >
139
- {status() === 'idle' && <IdleIcon />}
140
- {status() === 'recording' && <RecordingIcon />}
141
- {status() === 'processing' && <ProcessingIcon />}
142
- {status() === 'error' && <ErrorIcon />}
143
- </button>
144
- </div>
145
- )
146
- }
147
- )
148
-
149
- const buttonTitle = (status: DictateButtonStatus) => {
150
- switch (status) {
151
- case 'idle':
152
- return `Start dictation (${APP_NAME})`
153
- case 'recording':
154
- return `Stop dictation (${APP_NAME})`
155
- case 'processing':
156
- return `Stop processing (${APP_NAME})`
157
- case 'error':
158
- return `Click to reset (${APP_NAME})`
159
- }
160
- }
161
-
162
- const event = (element: any, eventName: string, detail: string) => {
163
- element.dispatchEvent(
164
- new CustomEvent(eventName, {
165
- detail,
166
- bubbles: true,
167
- composed: true,
168
- })
169
- )
170
- }
171
-
172
- const IdleIcon = () => (
173
- <svg
174
- // @ts-ignore
175
- part="icon"
176
- class={`dictate-button__icon dictate-button__icon--idle`}
177
- fill="none"
178
- viewBox="0 0 24 24"
179
- stroke-width="1.5"
180
- stroke="currentColor"
181
- >
182
- <path
183
- stroke-linecap="round"
184
- stroke-linejoin="round"
185
- d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"
186
- />
187
- </svg>
188
- )
189
-
190
- const RecordingIcon = () => (
191
- <svg
192
- // @ts-ignore
193
- part="icon"
194
- class="dictate-button__icon dictate-button__icon--recording"
195
- viewBox="0 0 24 24"
196
- fill="currentColor"
197
- >
198
- <path
199
- fill-rule="evenodd"
200
- d="M4.5 7.5a3 3 0 0 1 3-3h9a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-9a3 3 0 0 1-3-3v-9Z"
201
- clip-rule="evenodd"
202
- />
203
- </svg>
204
- )
205
-
206
- const ProcessingIcon = () => (
207
- <svg
208
- // @ts-ignore
209
- part="icon"
210
- class="dictate-button__icon dictate-button__icon--processing"
211
- fill="none"
212
- viewBox="0 0 24 24"
213
- stroke-width="1.5"
214
- stroke="currentColor"
215
- >
216
- <path
217
- stroke-linecap="round"
218
- stroke-linejoin="round"
219
- d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
220
- />
221
- </svg>
222
- )
223
-
224
- const ErrorIcon = () => (
225
- <svg
226
- // @ts-ignore
227
- part="icon"
228
- class="dictate-button__icon dictate-button__icon--error"
229
- fill="none"
230
- viewBox="0 0 24 24"
231
- stroke-width="1.5"
232
- stroke="currentColor"
233
- >
234
- <path
235
- stroke-linecap="round"
236
- stroke-linejoin="round"
237
- d="M6 18 18 6M6 6l12 12"
238
- />
239
- </svg>
240
- )
@@ -1,76 +0,0 @@
1
- const BUTTONS_SIZE = 24 // px
2
- const WATCH_DOM_CHANGES = true
3
-
4
- function injectDictateButton() {
5
- const textFields = document.querySelectorAll(
6
- 'textarea[data-dictate-button-on]:not([data-dictate-button-enabled]), input[type="text"][data-dictate-button-on]:not([data-dictate-button-enabled]), textarea[data-dictate-button-target]:not([data-dictate-button-enabled]), input[type="text"][data-dictate-button-target]:not([data-dictate-button-enabled])'
7
- )
8
-
9
- for (const textField of textFields) {
10
- // Add a wrapper div with relative positioning.
11
- const container = document.createElement('div')
12
- container.style.position = 'relative'
13
- container.style.display = 'inline-block'
14
- container.style.width = 'auto'
15
- container.style.color = 'inherit'
16
- textField.parentNode.insertBefore(container, textField)
17
-
18
- textField.setAttribute('data-dictate-button-enabled', '')
19
-
20
- container.appendChild(textField)
21
-
22
- // Ensure textarea fills container
23
- textField.style.boxSizing = 'border-box'
24
- // textarea.style.paddingRight = `${BUTTONS_SIZE + 2 * 2 + 6}px`
25
-
26
- // Add the dictate-button component.
27
- const dictateBtn = document.createElement('dictate-button')
28
- dictateBtn.size = BUTTONS_SIZE
29
- dictateBtn.style.position = 'absolute'
30
- dictateBtn.style.right = '0'
31
- dictateBtn.style.top = '0'
32
-
33
- // Add event listeners for the dictate-button component.
34
- dictateBtn.addEventListener('recording:started', (e) => {
35
- console.log('recording:started', e)
36
- })
37
- dictateBtn.addEventListener('recording:stopped', (e) => {
38
- console.log('recording:stopped', e)
39
- })
40
- dictateBtn.addEventListener('recording:failed', (e) => {
41
- console.log('recording:failed', e)
42
- })
43
-
44
- dictateBtn.addEventListener('transcribing:started', (e) => {
45
- console.log('transcribing:started', e)
46
- })
47
- dictateBtn.addEventListener('transcribing:finished', (e) => {
48
- console.log('transcribing:finished', e)
49
- const customEvent = e
50
- const text = customEvent.detail
51
- receiveText(textField, text)
52
- })
53
- dictateBtn.addEventListener('transcribing:failed', (e) => {
54
- console.log('transcribing:failed', e)
55
- })
56
-
57
- container.appendChild(dictateBtn)
58
- }
59
- }
60
-
61
- function receiveText(textField, text) {
62
- const start = textField.selectionStart || 0
63
- const end = textField.selectionEnd || 0
64
- textField.value =
65
- textField.value.substring(0, start) + text + textField.value.substring(end)
66
- }
67
-
68
- document.addEventListener('DOMContentLoaded', () => {
69
- injectDictateButton()
70
- if (WATCH_DOM_CHANGES) {
71
- new MutationObserver(injectDictateButton).observe(document.body, {
72
- childList: true,
73
- subtree: true,
74
- })
75
- }
76
- })
@@ -1,76 +0,0 @@
1
- const BUTTONS_SIZE = 24 // px
2
- const WATCH_DOM_CHANGES = true
3
-
4
- function injectDictateButton() {
5
- const textFields = document.querySelectorAll(
6
- 'textarea:not([data-dictate-button-off]):not([data-dictate-button-enabled]), input[type="text"]:not([data-dictate-button-off]):not([data-dictate-button-enabled])'
7
- )
8
-
9
- for (const textField of textFields) {
10
- // Add a wrapper div with relative positioning.
11
- const container = document.createElement('div')
12
- container.style.position = 'relative'
13
- container.style.display = 'inline-block'
14
- container.style.width = 'auto'
15
- container.style.color = 'inherit'
16
- textField.parentNode.insertBefore(container, textField)
17
-
18
- textField.setAttribute('data-dictate-button-enabled', '')
19
-
20
- container.appendChild(textField)
21
-
22
- // Ensure textarea fills container
23
- textField.style.boxSizing = 'border-box'
24
- // textarea.style.paddingRight = `${BUTTONS_SIZE + 2 * 2 + 6}px`
25
-
26
- // Add the dictate-button component.
27
- const dictateBtn = document.createElement('dictate-button')
28
- dictateBtn.size = BUTTONS_SIZE
29
- dictateBtn.style.position = 'absolute'
30
- dictateBtn.style.right = '0'
31
- dictateBtn.style.top = '0'
32
-
33
- // Add event listeners for the dictate-button component.
34
- dictateBtn.addEventListener('recording:started', (e) => {
35
- console.log('recording:started', e)
36
- })
37
- dictateBtn.addEventListener('recording:stopped', (e) => {
38
- console.log('recording:stopped', e)
39
- })
40
- dictateBtn.addEventListener('recording:failed', (e) => {
41
- console.log('recording:failed', e)
42
- })
43
-
44
- dictateBtn.addEventListener('transcribing:started', (e) => {
45
- console.log('transcribing:started', e)
46
- })
47
- dictateBtn.addEventListener('transcribing:finished', (e) => {
48
- console.log('transcribing:finished', e)
49
- const customEvent = e
50
- const text = customEvent.detail
51
- receiveText(textField, text)
52
- })
53
- dictateBtn.addEventListener('transcribing:failed', (e) => {
54
- console.log('transcribing:failed', e)
55
- })
56
-
57
- container.appendChild(dictateBtn)
58
- }
59
- }
60
-
61
- function receiveText(textField, text) {
62
- const start = textField.selectionStart || 0
63
- const end = textField.selectionEnd || 0
64
- textField.value =
65
- textField.value.substring(0, start) + text + textField.value.substring(end)
66
- }
67
-
68
- document.addEventListener('DOMContentLoaded', () => {
69
- injectDictateButton()
70
- if (WATCH_DOM_CHANGES) {
71
- new MutationObserver(injectDictateButton).observe(document.body, {
72
- childList: true,
73
- subtree: true,
74
- })
75
- }
76
- })
package/src/inject.js DELETED
@@ -1,2 +0,0 @@
1
- // Enhance only specific fields.
2
- import './inject-exclusive'
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "declaration": true,
4
- "declarationDir": "dist",
5
- "outDir": "dist",
6
- "module": "ESNext",
7
- "target": "ESNext",
8
- "moduleResolution": "Node",
9
- "strict": true,
10
- "esModuleInterop": true,
11
- "skipLibCheck": true,
12
- "allowSyntheticDefaultImports": true,
13
- "jsx": "preserve",
14
- "jsxImportSource": "solid-js"
15
- },
16
- "include": ["src", "vite-env.d.ts"]
17
- }
package/vite-env.d.ts DELETED
@@ -1,3 +0,0 @@
1
- /// <reference types="vite/client" />
2
-
3
- declare const __APP_VERSION__: string;
package/vite.config.ts DELETED
@@ -1,37 +0,0 @@
1
- import { defineConfig } from "vite";
2
- import dts from "vite-plugin-dts";
3
- import solidPlugin from "vite-plugin-solid";
4
- import { viteStaticCopy } from 'vite-plugin-static-copy'
5
- import pkg from './package.json'
6
-
7
- export default defineConfig({
8
- define: {
9
- __APP_VERSION__: JSON.stringify(pkg.version),
10
- },
11
- plugins: [
12
- solidPlugin(),
13
- dts({
14
- insertTypesEntry: true,
15
- }),
16
- viteStaticCopy({
17
- targets: [
18
- {
19
- src: '404.html',
20
- dest: '',
21
- },
22
- ],
23
- }),
24
- ],
25
- build: {
26
- lib: {
27
- entry: {
28
- 'dictate-button': 'src/dictate-button.tsx',
29
- inject: 'src/inject.js',
30
- 'inject-exclusive': 'src/inject-exclusive.js',
31
- 'inject-inclusive': 'src/inject-inclusive.js',
32
- },
33
- formats: ['es', 'cjs'],
34
- fileName: (format, entryName) => `${entryName}.${format}.js`,
35
- },
36
- },
37
- })