Skip to content

Commit da40914

Browse files
authored
Overhauls the Filename Prefix Settings (#16)
* First pass for converting the file name inputs to react components * adds mini-debounce bc I don't want to write somethign janky myself * Fixes incorrect inclusion for compilation * Pulls filename settings into it's own component * Renames debounced save function for clarity and updates its usage in FileNameSettings * Bumps version
1 parent 139cfcb commit da40914

9 files changed

+169
-108
lines changed

manifest.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "scribe",
33
"name": "Scribe",
4-
"version": "1.1.1",
4+
"version": "1.1.2",
55
"minAppVersion": "0.15.0",
66
"description": "Record voice notes, Fill in lost thoughts, Transcribe the audio, Summarize & Visualize the text - All in one clip",
77
"author": "Mike Alicea",

package-lock.json

+9-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-scribe-plugin",
3-
"version": "1.1.1",
3+
"version": "1.1.2",
44
"description": "An Obsidian plugin for recording voice notes, transcribing the audio, and summarizing the text - All in one",
55
"main": "build/main.js",
66
"scripts": {
@@ -28,6 +28,7 @@
2828
"@langchain/openai": "^0.3.11",
2929
"assemblyai": "^4.8.0",
3030
"langchain": "^0.3.3",
31+
"mini-debounce": "^1.0.8",
3132
"openai": "^4.68.3",
3233
"react": "^18.3.1",
3334
"react-dom": "^18.3.1",

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export default class ScribePlugin extends Plugin {
9898
}
9999

100100
async saveSettings() {
101+
new Notice('Scribe: ✅ Settings saved');
101102
await this.saveData(this.settings);
102103
}
103104

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useState } from 'react';
2+
import type ScribePlugin from 'src/index';
3+
4+
import { formatFilenamePrefix } from 'src/util/filenameUtils';
5+
import { SettingsItem } from './SettingsItem';
6+
7+
export const FileNameSettings: React.FC<{
8+
plugin: ScribePlugin;
9+
saveSettings: () => void;
10+
}> = ({ plugin, saveSettings }) => {
11+
const [noteFilenamePrefix, setNoteFilenamePrefix] = useState(
12+
plugin.settings.noteFilenamePrefix,
13+
);
14+
const [recordingFilenamePrefix, setRecordingFilenamePrefix] = useState(
15+
plugin.settings.recordingFilenamePrefix,
16+
);
17+
const [dateFilenameFormat, setDateFilenameFormat] = useState(
18+
plugin.settings.dateFilenameFormat,
19+
);
20+
const isDateInPrefix =
21+
(noteFilenamePrefix || '').includes('{{date}}') ||
22+
(recordingFilenamePrefix || '').includes('{{date}}');
23+
return (
24+
<div>
25+
<h2>File name properties</h2>
26+
<SettingsItem
27+
name="Transcript filename prefix"
28+
description="This will be the prefix of the note filename, use {{date}} to include the date"
29+
control={
30+
<input
31+
type="text"
32+
placeholder="scribe-"
33+
value={noteFilenamePrefix}
34+
onChange={(e) => {
35+
setNoteFilenamePrefix(e.target.value);
36+
plugin.settings.noteFilenamePrefix = e.target.value;
37+
saveSettings();
38+
}}
39+
/>
40+
}
41+
/>
42+
<SettingsItem
43+
name="Audio recording filename prefix"
44+
description="This will be the prefix of the audio recording filename, use {{date}} to include the date"
45+
control={
46+
<input
47+
type="text"
48+
placeholder="scribe-"
49+
value={recordingFilenamePrefix}
50+
onChange={(e) => {
51+
setRecordingFilenamePrefix(e.target.value);
52+
plugin.settings.recordingFilenamePrefix = e.target.value;
53+
saveSettings();
54+
}}
55+
/>
56+
}
57+
/>
58+
<SettingsItem
59+
name="Date format"
60+
description="This will only be used if {{date}} is in the transcript or audio recording filename prefix above."
61+
control={
62+
<div>
63+
<input
64+
type="text"
65+
placeholder="YYYY-MM-DD"
66+
disabled={!isDateInPrefix}
67+
value={dateFilenameFormat}
68+
onChange={(e) => {
69+
setDateFilenameFormat(e.target.value);
70+
plugin.settings.dateFilenameFormat = e.target.value;
71+
saveSettings();
72+
}}
73+
/>
74+
</div>
75+
}
76+
/>
77+
{isDateInPrefix && (
78+
<div>
79+
<p>
80+
{formatFilenamePrefix(`${noteFilenamePrefix}`, dateFilenameFormat)}
81+
filename
82+
</p>
83+
<p>
84+
{formatFilenamePrefix(
85+
`${recordingFilenamePrefix}`,
86+
dateFilenameFormat,
87+
)}
88+
filename
89+
</p>
90+
</div>
91+
)}
92+
</div>
93+
);
94+
};
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const SettingsItem: React.FC<{
2+
name: string;
3+
description: string;
4+
control: React.ReactNode;
5+
}> = ({ name, description, control }) => {
6+
return (
7+
<div className="setting-item">
8+
<div className="setting-item-info">
9+
<div className="setting-item-name">{name}</div>
10+
<div className="setting-item-description">{description}</div>
11+
</div>
12+
<div className="setting-item-control">{control}</div>
13+
</div>
14+
);
15+
};

src/settings/settings.ts src/settings/settings.tsx

+23-103
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { type App, Notice, PluginSettingTab, Setting, moment } from 'obsidian';
2+
import { createRoot, type Root } from 'react-dom/client';
3+
import { useDebounce } from 'src/util/useDebounce';
4+
25
import type ScribePlugin from 'src';
3-
import { formatFilenamePrefix } from 'src/util/filenameUtils';
6+
47
import { LLM_MODELS } from 'src/util/openAiUtils';
58

9+
import { FileNameSettings } from './components/FileNameSettings';
10+
611
export enum TRANSCRIPT_PLATFORM {
712
assemblyAi = 'assemblyAi',
813
openAi = 'openAi',
@@ -41,6 +46,7 @@ export async function handleSettingsTab(plugin: ScribePlugin) {
4146

4247
export class ScribeSettingsTab extends PluginSettingTab {
4348
plugin: ScribePlugin;
49+
reactRoot: Root | null;
4450

4551
constructor(app: App, plugin: ScribePlugin) {
4652
super(app, plugin);
@@ -178,109 +184,12 @@ export class ScribeSettingsTab extends PluginSettingTab {
178184
component.setValue(this.plugin.settings.transcriptPlatform);
179185
});
180186

181-
containerEl.createEl('h2', { text: 'File name properties' });
182-
containerEl.createEl('sub', {
183-
text: 'These settings must be saved via the button for validation purposes',
184-
});
185-
186-
const isDateInPrefix = () =>
187-
this.plugin.settings.noteFilenamePrefix.includes('{{date}}') ||
188-
this.plugin.settings.recordingFilenamePrefix.includes('{{date}}');
189-
190-
new Setting(containerEl)
191-
.setName('Transcript filename prefix')
192-
.setDesc(
193-
'This will be the prefix of the note filename, use {{date}} to include the date',
194-
)
195-
.addText((text) => {
196-
text.setPlaceholder('scribe-');
197-
text.onChange((value) => {
198-
this.plugin.settings.noteFilenamePrefix = value;
199-
200-
dateInput.setDisabled(!isDateInPrefix());
201-
});
202-
203-
text.setValue(this.plugin.settings.noteFilenamePrefix);
204-
});
205-
206-
new Setting(containerEl)
207-
.setName('Audio recording filename prefix')
208-
.setDesc(
209-
'This will be the prefix of the audio recording filename, use {{date}} to include the date',
210-
)
211-
.addText((text) => {
212-
text.setPlaceholder('scribe-');
213-
text.onChange((value) => {
214-
this.plugin.settings.recordingFilenamePrefix = value;
215-
dateInput.setDisabled(!isDateInPrefix());
216-
});
217-
218-
text.setValue(this.plugin.settings.recordingFilenamePrefix);
219-
});
220-
221-
const dateInput = new Setting(containerEl)
222-
.setName('Date format')
223-
.setDesc(
224-
'This will only be used if {{date}} is in the transcript or audio recording filename prefix above.',
225-
)
226-
.addText((text) => {
227-
text.setDisabled(!isDateInPrefix());
228-
text.setPlaceholder('YYYY-MM-DD');
229-
text.onChange((value) => {
230-
this.plugin.settings.dateFilenameFormat = value;
231-
console.log(value);
232-
try {
233-
new Notice(
234-
`📆 Format: ${formatFilenamePrefix(
235-
'some-prefix-{{date}}',
236-
value,
237-
)}`,
238-
);
239-
} catch (error) {
240-
console.error('Invalid date format', error);
241-
new Notice(`Invalid date format: ${value}`);
242-
}
243-
});
244-
245-
text.setValue(this.plugin.settings.dateFilenameFormat);
246-
});
247-
248-
new Setting(containerEl).addButton((button) => {
249-
button.setButtonText('Save settings');
250-
button.onClick(async () => {
251-
if (!this.plugin.settings.noteFilenamePrefix) {
252-
new Notice(
253-
'⚠️ You must provide a note filename prefix, setting to default',
254-
);
255-
this.plugin.settings.noteFilenamePrefix =
256-
DEFAULT_SETTINGS.noteFilenamePrefix;
257-
}
258-
259-
if (!this.plugin.settings.recordingFilenamePrefix) {
260-
new Notice(
261-
'⚠️ You must provide a recording filename prefix, setting to default',
262-
);
263-
this.plugin.settings.recordingFilenamePrefix =
264-
DEFAULT_SETTINGS.recordingFilenamePrefix;
265-
}
266-
267-
if (
268-
this.plugin.settings.noteFilenamePrefix.includes('{{date}}') &&
269-
!this.plugin.settings.dateFilenameFormat
270-
) {
271-
new Notice('⚠️ You must provide a date format, setting to default');
272-
this.plugin.settings.dateFilenameFormat =
273-
DEFAULT_SETTINGS.dateFilenameFormat;
274-
}
275-
276-
this.saveSettings();
277-
this.display();
278-
});
187+
const reactTestWrapper = containerEl.createDiv({
188+
cls: 'scribe-settings-react',
279189
});
280190

281-
containerEl.createEl('sub', {
282-
text: 'This functionality will improve in future versions',
283-
});
191+
this.reactRoot = createRoot(reactTestWrapper);
192+
this.reactRoot.render(<ScribeSettings plugin={this.plugin} />);
284193

285194
new Setting(containerEl).addButton((button) => {
286195
button.setButtonText('Reset to default');
@@ -299,6 +208,17 @@ export class ScribeSettingsTab extends PluginSettingTab {
299208

300209
saveSettings() {
301210
this.plugin.saveSettings();
302-
new Notice('Scribe: ✅ Settings saved');
303211
}
304212
}
213+
214+
const ScribeSettings: React.FC<{ plugin: ScribePlugin }> = ({ plugin }) => {
215+
const debouncedSaveSettings = useDebounce(() => {
216+
plugin.saveSettings();
217+
}, 700);
218+
219+
return (
220+
<div>
221+
<FileNameSettings plugin={plugin} saveSettings={debouncedSaveSettings} />
222+
</div>
223+
);
224+
};

src/util/useDebounce.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { debounce } from 'mini-debounce';
2+
import { useEffect, useMemo, useRef } from 'react';
3+
4+
export const useDebounce = (
5+
callback: () => void,
6+
timeMs: number,
7+
): (() => void) => {
8+
const ref = useRef<() => void>();
9+
10+
useEffect(() => {
11+
ref.current = callback;
12+
}, [callback]);
13+
14+
const debouncedCallback = useMemo(() => {
15+
const func = () => {
16+
ref.current?.();
17+
};
18+
19+
return debounce(func, timeMs);
20+
}, [timeMs]);
21+
22+
return debouncedCallback;
23+
};

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@
1414
"strictNullChecks": true,
1515
"lib": ["DOM", "ES5", "ES6", "ES7"]
1616
},
17-
"include": ["**/*.ts", "src/modal/scribeControlsModal.tsx"]
17+
"include": ["src/*.ts", "src/*.tsx", "src/**/*.ts", "src/**/*.tsx"]
1818
}

0 commit comments

Comments
 (0)