mirror of
https://github.com/snipd-app/snipd-obsidian.git
synced 2026-07-22 06:41:45 +00:00
Fix misc linter and code issues
This commit is contained in:
parent
1d4d17d99e
commit
6b98941728
7 changed files with 332 additions and 221 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { App, Modal, Notice } from 'obsidian';
|
||||
import { App, Modal, Notice, Platform } from 'obsidian';
|
||||
import type SnipdPlugin from './main';
|
||||
import { DEFAULT_EPISODE_TEMPLATE, DEFAULT_SNIP_TEMPLATE, DEFAULT_EPISODE_FILE_NAME_TEMPLATE } from './types';
|
||||
|
||||
|
|
@ -23,9 +23,6 @@ export class FormattingConfigModal extends Modal {
|
|||
contentEl.empty();
|
||||
|
||||
const scrollableContent = contentEl.createDiv({ cls: 'snipd-modal-scrollable' });
|
||||
scrollableContent.style.maxHeight = 'calc(80vh - 100px)';
|
||||
scrollableContent.style.overflowY = 'auto';
|
||||
scrollableContent.style.marginBottom = '16px';
|
||||
|
||||
scrollableContent.createEl('h2', { text: 'Custom formatting' });
|
||||
scrollableContent.createEl('p', {
|
||||
|
|
@ -33,9 +30,7 @@ export class FormattingConfigModal extends Modal {
|
|||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
const syntaxDesc = scrollableContent.createDiv({ cls: 'setting-item-description' });
|
||||
syntaxDesc.style.marginBottom = '16px';
|
||||
syntaxDesc.style.fontSize = '0.9em';
|
||||
const syntaxDesc = scrollableContent.createDiv({ cls: 'setting-item-description snipd-syntax-description' });
|
||||
syntaxDesc.createEl('strong', { text: 'Guide: ' });
|
||||
syntaxDesc.appendText('Use ');
|
||||
syntaxDesc.createEl('code', { text: '{{variable}}' });
|
||||
|
|
@ -58,11 +53,21 @@ export class FormattingConfigModal extends Modal {
|
|||
];
|
||||
fileNameVars.forEach((varName, index) => {
|
||||
const varSpan = fileNameVarsDesc.createSpan({ cls: 'snipd-template-variable', text: varName });
|
||||
varSpan.style.cursor = 'pointer';
|
||||
varSpan.style.textDecoration = 'underline';
|
||||
varSpan.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(varName);
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
void (async () => {
|
||||
if (Platform.isDesktopApp) {
|
||||
await globalThis.navigator.clipboard.writeText(varName);
|
||||
} else {
|
||||
const textArea = globalThis.document.createElement('textarea');
|
||||
textArea.value = varName;
|
||||
globalThis.document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
globalThis.document.execCommand('copy');
|
||||
globalThis.document.body.removeChild(textArea);
|
||||
}
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
})();
|
||||
});
|
||||
if (index < fileNameVars.length - 1) {
|
||||
fileNameVarsDesc.appendText(', ');
|
||||
|
|
@ -74,9 +79,6 @@ export class FormattingConfigModal extends Modal {
|
|||
type: 'text',
|
||||
});
|
||||
fileNameInput.value = this.tempEpisodeFileNameTemplate;
|
||||
fileNameInput.style.width = '100%';
|
||||
fileNameInput.style.fontFamily = 'monospace';
|
||||
fileNameInput.style.padding = '8px';
|
||||
fileNameInput.addEventListener('input', () => {
|
||||
this.tempEpisodeFileNameTemplate = fileNameInput.value;
|
||||
});
|
||||
|
|
@ -103,11 +105,21 @@ export class FormattingConfigModal extends Modal {
|
|||
];
|
||||
episodeVars.forEach((varName, index) => {
|
||||
const varSpan = episodeVarsDesc.createSpan({ cls: 'snipd-template-variable', text: varName });
|
||||
varSpan.style.cursor = 'pointer';
|
||||
varSpan.style.textDecoration = 'underline';
|
||||
varSpan.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(varName);
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
void (async () => {
|
||||
if (Platform.isDesktopApp) {
|
||||
await globalThis.navigator.clipboard.writeText(varName);
|
||||
} else {
|
||||
const textArea = globalThis.document.createElement('textarea');
|
||||
textArea.value = varName;
|
||||
globalThis.document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
globalThis.document.execCommand('copy');
|
||||
globalThis.document.body.removeChild(textArea);
|
||||
}
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
})();
|
||||
});
|
||||
if (index < episodeVars.length - 1) {
|
||||
episodeVarsDesc.appendText(', ');
|
||||
|
|
@ -119,8 +131,6 @@ export class FormattingConfigModal extends Modal {
|
|||
});
|
||||
episodeTextarea.value = this.tempEpisodeTemplate;
|
||||
episodeTextarea.rows = 10;
|
||||
episodeTextarea.style.width = '100%';
|
||||
episodeTextarea.style.fontFamily = 'monospace';
|
||||
episodeTextarea.addEventListener('input', () => {
|
||||
this.tempEpisodeTemplate = episodeTextarea.value;
|
||||
});
|
||||
|
|
@ -144,11 +154,21 @@ export class FormattingConfigModal extends Modal {
|
|||
];
|
||||
snipVars.forEach((varName, index) => {
|
||||
const varSpan = snipVarsDesc.createSpan({ cls: 'snipd-template-variable', text: varName });
|
||||
varSpan.style.cursor = 'pointer';
|
||||
varSpan.style.textDecoration = 'underline';
|
||||
varSpan.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(varName);
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
void (async () => {
|
||||
if (Platform.isDesktopApp) {
|
||||
await globalThis.navigator.clipboard.writeText(varName);
|
||||
} else {
|
||||
const textArea = globalThis.document.createElement('textarea');
|
||||
textArea.value = varName;
|
||||
globalThis.document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
||||
globalThis.document.execCommand('copy');
|
||||
globalThis.document.body.removeChild(textArea);
|
||||
}
|
||||
new Notice(`Copied ${varName} to clipboard`);
|
||||
})();
|
||||
});
|
||||
if (index < snipVars.length - 1) {
|
||||
snipVarsDesc.appendText(', ');
|
||||
|
|
@ -160,19 +180,11 @@ export class FormattingConfigModal extends Modal {
|
|||
});
|
||||
snipTextarea.value = this.tempSnipTemplate;
|
||||
snipTextarea.rows = 10;
|
||||
snipTextarea.style.width = '100%';
|
||||
snipTextarea.style.fontFamily = 'monospace';
|
||||
snipTextarea.addEventListener('input', () => {
|
||||
this.tempSnipTemplate = snipTextarea.value;
|
||||
});
|
||||
|
||||
const buttonContainer = contentEl.createDiv({ cls: 'modal-button-container' });
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'flex-end';
|
||||
buttonContainer.style.gap = '8px';
|
||||
buttonContainer.style.paddingTop = '16px';
|
||||
buttonContainer.style.borderTop = '1px solid var(--background-modifier-border)';
|
||||
buttonContainer.style.marginTop = '0';
|
||||
|
||||
const resetButton = buttonContainer.createEl('button', { text: 'Reset to default' });
|
||||
resetButton.addEventListener('click', () => {
|
||||
|
|
@ -193,22 +205,24 @@ export class FormattingConfigModal extends Modal {
|
|||
text: 'Save',
|
||||
cls: 'mod-cta'
|
||||
});
|
||||
saveButton.addEventListener('click', async () => {
|
||||
this.plugin.settings.episodeFileNameTemplate =
|
||||
this.tempEpisodeFileNameTemplate === DEFAULT_EPISODE_FILE_NAME_TEMPLATE
|
||||
? null
|
||||
: this.tempEpisodeFileNameTemplate;
|
||||
this.plugin.settings.episodeTemplate =
|
||||
this.tempEpisodeTemplate === DEFAULT_EPISODE_TEMPLATE
|
||||
? null
|
||||
: this.tempEpisodeTemplate;
|
||||
this.plugin.settings.snipTemplate =
|
||||
this.tempSnipTemplate === DEFAULT_SNIP_TEMPLATE
|
||||
? null
|
||||
: this.tempSnipTemplate;
|
||||
await this.plugin.saveSettings();
|
||||
this.onSave();
|
||||
this.close();
|
||||
saveButton.addEventListener('click', () => {
|
||||
void (async () => {
|
||||
this.plugin.settings.episodeFileNameTemplate =
|
||||
this.tempEpisodeFileNameTemplate === DEFAULT_EPISODE_FILE_NAME_TEMPLATE
|
||||
? null
|
||||
: this.tempEpisodeFileNameTemplate;
|
||||
this.plugin.settings.episodeTemplate =
|
||||
this.tempEpisodeTemplate === DEFAULT_EPISODE_TEMPLATE
|
||||
? null
|
||||
: this.tempEpisodeTemplate;
|
||||
this.plugin.settings.snipTemplate =
|
||||
this.tempSnipTemplate === DEFAULT_SNIP_TEMPLATE
|
||||
? null
|
||||
: this.tempSnipTemplate;
|
||||
await this.plugin.saveSettings();
|
||||
this.onSave();
|
||||
this.close();
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
207
src/main.ts
207
src/main.ts
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
addIcon,
|
||||
App,
|
||||
DataAdapter,
|
||||
normalizePath,
|
||||
Notice,
|
||||
Plugin,
|
||||
TFile
|
||||
TFile,
|
||||
requestUrl
|
||||
} from 'obsidian';
|
||||
// @ts-ignore
|
||||
import * as zip from "@zip.js/zip.js";
|
||||
|
|
@ -97,9 +97,11 @@ export default class SnipdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
private clearStatusBarPersistentMessageAfterDelay(delayMs: number): void {
|
||||
setTimeout(() => {
|
||||
this.clearStatusBarPersistentMessage();
|
||||
}, delayMs);
|
||||
this.registerInterval(
|
||||
globalThis.window.setTimeout(() => {
|
||||
this.clearStatusBarPersistentMessage();
|
||||
}, delayMs)
|
||||
);
|
||||
}
|
||||
|
||||
async checkSnipdDirectoryExists(): Promise<boolean> {
|
||||
|
|
@ -207,28 +209,23 @@ export default class SnipdPlugin extends Plugin {
|
|||
try {
|
||||
debugLog(`Snipd plugin: fetching metadata from ${url}`);
|
||||
this.setStatusBarPersistentMessage("Fetching metadata...");
|
||||
response = await fetch(url, {
|
||||
response = await requestUrl({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
},
|
||||
signal: this.syncAbortController?.signal,
|
||||
});
|
||||
debugLog(`Snipd plugin: metadata response status: ${response.status}`);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === 'AbortError') {
|
||||
debugLog("Snipd plugin: fetch aborted by user");
|
||||
this.clearStatusBarPersistentMessage();
|
||||
return null;
|
||||
}
|
||||
console.error("Snipd plugin: fetch failed in syncSnipd: ", e);
|
||||
debugLog("Snipd plugin: request failed in syncSnipd: ", e);
|
||||
const errorMsg = "Sync failed: unable to connect to server." + (isDev() ? ` Detail: ${e}` : "");
|
||||
await this.handleSyncError(errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response && response.ok) {
|
||||
const metadata = await response.json();
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
const metadata = response.json as FetchExportMetadataResponse;
|
||||
await this.saveMetadataToFile(metadata);
|
||||
|
||||
if (debugFolderPath) {
|
||||
|
|
@ -258,9 +255,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
return metadata;
|
||||
} else {
|
||||
console.error("Snipd plugin: bad response in syncSnipd: ", response);
|
||||
const statusCode = response ? response.status : "";
|
||||
const errorMsg = `Sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.statusText}` : "");
|
||||
debugLog("Snipd plugin: bad response in syncSnipd: ", response);
|
||||
const statusCode = response ? response.status : 0;
|
||||
const errorMsg = `Sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.status}` : "");
|
||||
await this.handleSyncError(errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -348,29 +345,25 @@ export default class SnipdPlugin extends Plugin {
|
|||
let response;
|
||||
try {
|
||||
const requestBody = this.buildBatchRequestBody(episodeIds);
|
||||
response = await fetch(`${API_BASE_URL}/obsidian/export-episode-snips`, {
|
||||
response = await requestUrl({
|
||||
url: `${API_BASE_URL}/obsidian/export-episode-snips`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
signal: this.syncAbortController?.signal,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === 'AbortError') {
|
||||
debugLog("Snipd plugin: fetch aborted by user");
|
||||
this.clearStatusBarPersistentMessage();
|
||||
return null;
|
||||
}
|
||||
console.error("Snipd plugin: fetch failed for batch: ", e);
|
||||
debugLog("Snipd plugin: request failed for batch: ", e);
|
||||
const errorMsg = "Sync failed: unable to connect to server." + (isDev() ? ` Detail: ${e}` : "");
|
||||
await this.handleSyncError(errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (response && response.ok) {
|
||||
const blob = await response.blob();
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
const arrayBuffer = response.arrayBuffer;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
|
||||
if (debugFolderPath) {
|
||||
const batchFileName = `batch_${batchIndex}_${Date.now()}.zip`;
|
||||
|
|
@ -392,9 +385,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
return stats;
|
||||
} else {
|
||||
console.error("Snipd plugin: bad response for batch: ", response);
|
||||
const statusCode = response ? response.status : "";
|
||||
const errorMsg = `Sync failed at batch ${batchIndex + 1}${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.statusText}` : "");
|
||||
debugLog("Snipd plugin: bad response for batch: ", response);
|
||||
const statusCode = response ? response.status : 0;
|
||||
const errorMsg = `Sync failed at batch ${batchIndex + 1}${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.status}` : "");
|
||||
await this.handleSyncError(errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
|
@ -427,7 +420,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
return { episodeCount: totalEpisodes, snipCount: totalSnips };
|
||||
} catch (e) {
|
||||
console.error("Snipd plugin: error processing batches: ", e);
|
||||
debugLog("Snipd plugin: error processing batches: ", e);
|
||||
const errorMsg = "Sync failed: error processing data." + (isDev() ? ` Detail: ${e}` : "");
|
||||
await this.handleSyncError(errorMsg);
|
||||
return null;
|
||||
|
|
@ -499,7 +492,8 @@ export default class SnipdPlugin extends Plugin {
|
|||
if (this.settings.onlyEditedSnips) {
|
||||
url += '?only_edited_snips=true';
|
||||
}
|
||||
response = await fetch(url, {
|
||||
response = await requestUrl({
|
||||
url: url,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
|
|
@ -507,7 +501,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
});
|
||||
debugLog(`Snipd plugin: test metadata response status: ${response.status}`);
|
||||
} catch (e) {
|
||||
console.error("Snipd plugin: fetch failed in testSyncRandomEpisodes: ", e);
|
||||
debugLog("Snipd plugin: request failed in testSyncRandomEpisodes: ", e);
|
||||
const errorMsg = "Test sync failed: unable to connect to server." + (isDev() ? ` Detail: ${e}` : "");
|
||||
this.settings.isTestSyncing = false;
|
||||
await this.saveSettings();
|
||||
|
|
@ -519,8 +513,8 @@ export default class SnipdPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
if (response && response.ok) {
|
||||
const metadata: FetchExportMetadataResponse = await response.json();
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
const metadata = response.json as FetchExportMetadataResponse;
|
||||
|
||||
if (debugFolderPath) {
|
||||
await createDirForFile(`${debugFolderPath}/test_metadata.json`, this.app.vault.adapter);
|
||||
|
|
@ -582,7 +576,8 @@ export default class SnipdPlugin extends Plugin {
|
|||
exportRequestBody.only_edited_snips = true;
|
||||
}
|
||||
|
||||
exportResponse = await fetch(`${API_BASE_URL}/obsidian/export-episode-snips`, {
|
||||
exportResponse = await requestUrl({
|
||||
url: `${API_BASE_URL}/obsidian/export-episode-snips`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
|
|
@ -591,7 +586,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
body: JSON.stringify(exportRequestBody),
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Snipd plugin: export fetch failed: ", e);
|
||||
debugLog("Snipd plugin: export request failed: ", e);
|
||||
const errorMsg = "Test sync failed: unable to connect to server." + (isDev() ? ` Detail: ${e}` : "");
|
||||
this.settings.isTestSyncing = false;
|
||||
await this.saveSettings();
|
||||
|
|
@ -603,8 +598,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
return;
|
||||
}
|
||||
|
||||
if (exportResponse && exportResponse.ok) {
|
||||
const blob = await exportResponse.blob();
|
||||
if (exportResponse && exportResponse.status >= 200 && exportResponse.status < 300) {
|
||||
const arrayBuffer = exportResponse.arrayBuffer;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
|
||||
if (debugFolderPath) {
|
||||
const testExportFileName = `test_export_${Date.now()}.zip`;
|
||||
|
|
@ -639,9 +635,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
this.setStatusBarPersistentMessage(`Test sync completed (${stats.episodeCount} episodes, ${stats.snipCount} snips)`);
|
||||
this.clearStatusBarPersistentMessageAfterDelay(3000);
|
||||
} else {
|
||||
console.error("Snipd plugin: bad response for test export: ", exportResponse);
|
||||
const statusCode = exportResponse ? exportResponse.status : "";
|
||||
const errorMsg = `Test sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && exportResponse ? ` Detail: ${exportResponse.statusText}` : "");
|
||||
debugLog("Snipd plugin: bad response for test export: ", exportResponse);
|
||||
const statusCode = exportResponse ? exportResponse.status : 0;
|
||||
const errorMsg = `Test sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && exportResponse ? ` Detail: ${exportResponse.status}` : "");
|
||||
this.settings.isTestSyncing = false;
|
||||
await this.saveSettings();
|
||||
if (this.settingsTab) {
|
||||
|
|
@ -651,9 +647,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
this.clearStatusBarPersistentMessage();
|
||||
}
|
||||
} else {
|
||||
console.error("Snipd plugin: bad response in testSyncRandomEpisodes: ", response);
|
||||
const statusCode = response ? response.status : "";
|
||||
const errorMsg = `Test sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.statusText}` : "");
|
||||
debugLog("Snipd plugin: bad response in testSyncRandomEpisodes: ", response);
|
||||
const statusCode = response ? response.status : 0;
|
||||
const errorMsg = `Test sync failed${statusCode ? ` (${statusCode})` : ""}` + (isDev() && response ? ` Detail: ${response.status}` : "");
|
||||
this.settings.isTestSyncing = false;
|
||||
await this.saveSettings();
|
||||
if (this.settingsTab) {
|
||||
|
|
@ -675,16 +671,18 @@ export default class SnipdPlugin extends Plugin {
|
|||
const episodeFiles: Map<string, { full: string; append?: string }> = new Map();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.directory) {
|
||||
// @ts-ignore - zip.js types are incomplete
|
||||
const zipEntry: zip.Entry = entry;
|
||||
if (zipEntry.directory) {
|
||||
continue;
|
||||
}
|
||||
// @ts-ignore
|
||||
const fileContent = await entry.getData(new zip.TextWriter());
|
||||
const fileContent = await zipEntry.getData(new zip.TextWriter());
|
||||
|
||||
if (entry.filename === 'metadata.json') {
|
||||
metadata = JSON.parse(fileContent);
|
||||
} else if (entry.filename.startsWith('episodes/')) {
|
||||
const filename = entry.filename.replace('episodes/', '');
|
||||
if (zipEntry.filename === 'metadata.json') {
|
||||
metadata = JSON.parse(fileContent) as MetadataJson;
|
||||
} else if (zipEntry.filename.startsWith('episodes/')) {
|
||||
const filename = zipEntry.filename.replace('episodes/', '');
|
||||
const match = filename.match(/^(.+?)_(full_content|append_only_content)\.md$/);
|
||||
if (match) {
|
||||
const [, id, type] = match;
|
||||
|
|
@ -720,7 +718,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
for (const [episodeId, fileData] of episodeFiles) {
|
||||
const episodeData = episodesData[episodeId];
|
||||
if (!episodeData) {
|
||||
console.warn(`Snipd plugin: No metadata found for episode ${episodeId}`);
|
||||
debugLog(`Snipd plugin: No metadata found for episode ${episodeId}`);
|
||||
}
|
||||
const episodeName = generateEpisodeFileName(episodeData, episodeId, this.settings);
|
||||
const showId = episodeData?.show_id;
|
||||
|
|
@ -840,7 +838,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
this.settings.baseFileManualOverrides = this.settings.baseFileManualOverrides || {};
|
||||
const manualOverrides = this.settings.baseFileManualOverrides;
|
||||
const existingHashes = { ...(this.settings.baseFileHashes || {}) };
|
||||
let zipReader: any = null;
|
||||
let zipReader: zip.ZipReader<zip.BlobReader> | null = null;
|
||||
let updatedFileCount = 0;
|
||||
let removedFileCount = 0;
|
||||
let baseFileMetadata: BaseFileMetadata | null = null;
|
||||
|
|
@ -848,35 +846,41 @@ export default class SnipdPlugin extends Plugin {
|
|||
try {
|
||||
debugLog('Snipd plugin: fetching base file...');
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/obsidian/export-base-file`, {
|
||||
const response = await requestUrl({
|
||||
url: `${API_BASE_URL}/obsidian/export-base-file`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
},
|
||||
signal: this.syncAbortController?.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Snipd plugin: bad response for base file: ", response);
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
debugLog("Snipd plugin: bad response for base file: ", response);
|
||||
debugLog(`Snipd plugin: failed to fetch base file (${response.status})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const arrayBuffer = response.arrayBuffer;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const blobReader = new zip.BlobReader(blob);
|
||||
zipReader = new zip.ZipReader(blobReader);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const entries = await zipReader.getEntries();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.directory) {
|
||||
// @ts-ignore - zip.js types are incomplete
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const zipEntry: zip.Entry = entry;
|
||||
if (zipEntry.directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const fileContent = await entry.getData(new zip.TextWriter());
|
||||
|
||||
const fileContent = await zipEntry.getData(new zip.TextWriter());
|
||||
|
||||
if (entry.filename === 'metadata.json') {
|
||||
baseFileMetadata = JSON.parse(fileContent);
|
||||
if (zipEntry.filename === 'metadata.json') {
|
||||
baseFileMetadata = JSON.parse(fileContent) as BaseFileMetadata;
|
||||
const metadataPath = normalizePath(`${folderPath}/metadata.json`);
|
||||
await createDirForFile(metadataPath, this.app.vault.adapter);
|
||||
await this.app.vault.adapter.write(metadataPath, fileContent);
|
||||
|
|
@ -884,7 +888,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
continue;
|
||||
}
|
||||
|
||||
let relativePath = entry.filename;
|
||||
let relativePath = zipEntry.filename;
|
||||
if (relativePath.startsWith('Files/')) {
|
||||
relativePath = relativePath.substring(6);
|
||||
}
|
||||
|
|
@ -912,7 +916,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
} catch (error) {
|
||||
manualOverrides[baseFilePath] = true;
|
||||
debugLog(`Snipd plugin: failed to validate base file ${baseFilePath} - marking as manually overridden.`);
|
||||
console.error('Snipd plugin: failed to validate base file integrity:', error);
|
||||
debugLog('Snipd plugin: failed to validate base file integrity:', error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -938,18 +942,14 @@ export default class SnipdPlugin extends Plugin {
|
|||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.name === 'AbortError') {
|
||||
debugLog("Snipd plugin: base file fetch aborted by user");
|
||||
return;
|
||||
}
|
||||
console.error("Snipd plugin: error fetching base file: ", e);
|
||||
debugLog("Snipd plugin: error fetching base file: ", e);
|
||||
debugLog(`Snipd plugin: failed to fetch base file: ${e}`);
|
||||
} finally {
|
||||
if (zipReader) {
|
||||
try {
|
||||
await zipReader.close();
|
||||
} catch (closeError) {
|
||||
console.error('Snipd plugin: failed to close base file zip reader:', closeError);
|
||||
debugLog('Snipd plugin: failed to close base file zip reader:', closeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -966,37 +966,44 @@ export default class SnipdPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async fetchAndSaveBaseFileForTest(folderPath: string): Promise<void> {
|
||||
let zipReader: any = null;
|
||||
let zipReader: zip.ZipReader<zip.BlobReader> | null = null;
|
||||
try {
|
||||
debugLog('Snipd plugin: fetching base file for test sync...');
|
||||
|
||||
const response = await fetch(`${API_BASE_URL}/obsidian/export-base-file`, {
|
||||
const response = await requestUrl({
|
||||
url: `${API_BASE_URL}/obsidian/export-base-file`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.settings.apiKey}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error("Snipd plugin: bad response for base file in test sync: ", response);
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
debugLog("Snipd plugin: bad response for base file in test sync: ", response);
|
||||
debugLog(`Snipd plugin: failed to fetch base file for test sync (${response.status})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const arrayBuffer = response.arrayBuffer;
|
||||
const blob = new Blob([arrayBuffer]);
|
||||
const blobReader = new zip.BlobReader(blob);
|
||||
zipReader = new zip.ZipReader(blobReader);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
|
||||
const entries = await zipReader.getEntries();
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.directory) {
|
||||
// @ts-ignore - zip.js types are incomplete
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const zipEntry: zip.Entry = entry;
|
||||
if (zipEntry.directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const fileContent = await entry.getData(new zip.TextWriter());
|
||||
|
||||
const fileContent = await zipEntry.getData(new zip.TextWriter());
|
||||
|
||||
if (entry.filename === 'metadata.json') {
|
||||
if (zipEntry.filename === 'metadata.json') {
|
||||
const metadataPath = normalizePath(`${folderPath}/metadata.json`);
|
||||
await createDirForFile(metadataPath, this.app.vault.adapter);
|
||||
await this.app.vault.adapter.write(metadataPath, fileContent);
|
||||
|
|
@ -1004,7 +1011,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
continue;
|
||||
}
|
||||
|
||||
let relativePath = entry.filename;
|
||||
let relativePath = zipEntry.filename;
|
||||
if (relativePath.startsWith('Files/')) {
|
||||
relativePath = relativePath.substring(6);
|
||||
}
|
||||
|
|
@ -1016,14 +1023,14 @@ export default class SnipdPlugin extends Plugin {
|
|||
debugLog(`Snipd plugin: saved base file to ${baseFilePath} (test sync - always overwrite)`);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Snipd plugin: error fetching base file for test sync: ", e);
|
||||
debugLog("Snipd plugin: error fetching base file for test sync: ", e);
|
||||
debugLog(`Snipd plugin: failed to fetch base file for test sync: ${e}`);
|
||||
} finally {
|
||||
if (zipReader) {
|
||||
try {
|
||||
await zipReader.close();
|
||||
} catch (closeError) {
|
||||
console.error('Snipd plugin: failed to close base file zip reader in test sync:', closeError);
|
||||
debugLog('Snipd plugin: failed to close base file zip reader in test sync:', closeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1031,16 +1038,18 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
async configureSchedule() {
|
||||
const minutes = parseInt(this.settings.frequency);
|
||||
let milliseconds = minutes * 60 * 1000;
|
||||
const milliseconds = minutes * 60 * 1000;
|
||||
debugLog('Snipd plugin: setting interval to ', milliseconds, 'milliseconds');
|
||||
if (this.scheduleInterval !== null) {
|
||||
window.clearInterval(this.scheduleInterval);
|
||||
globalThis.window.clearInterval(this.scheduleInterval);
|
||||
this.scheduleInterval = null;
|
||||
}
|
||||
if (!milliseconds) {
|
||||
return;
|
||||
}
|
||||
this.scheduleInterval = window.setInterval(() => this.syncSnipd(), milliseconds);
|
||||
this.scheduleInterval = globalThis.window.setInterval(() => {
|
||||
void this.syncSnipd();
|
||||
}, milliseconds);
|
||||
this.registerInterval(this.scheduleInterval);
|
||||
}
|
||||
|
||||
|
|
@ -1054,12 +1063,12 @@ export default class SnipdPlugin extends Plugin {
|
|||
if (metadataExists) {
|
||||
try {
|
||||
const metadataContent = await this.app.vault.adapter.read(metadataPath);
|
||||
const metadata: BaseFileMetadata = JSON.parse(metadataContent);
|
||||
const metadata = JSON.parse(metadataContent) as BaseFileMetadata;
|
||||
defaultOpenPath = metadata.defaultOpenPath;
|
||||
this.settings.baseFileDefaultOpenPath = defaultOpenPath;
|
||||
await this.saveSettings();
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: failed to read base file metadata:', error);
|
||||
debugLog('Snipd plugin: failed to read base file metadata:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1087,8 +1096,8 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
async onload() {
|
||||
addIcon('snipd', `<path d="M30.458 18.725c-14.395 13.692-14.395 35.75 0 49.446L16.667 81.279c14.57 13.85 38.308 13.85 52.875 0 14.391-13.691 14.391-35.75 0-49.437l13.791-13.117c-14.57-13.854-38.308-13.854-52.875 0" stroke="#B2B2B2FF" stroke-width="8.33333" fill="none"/>`);
|
||||
this.addRibbonIcon('snipd', 'Open Snipd Base', async () => {
|
||||
await this.openBaseFile();
|
||||
this.addRibbonIcon('snipd', 'Open Snipd Base', () => {
|
||||
void this.openBaseFile();
|
||||
});
|
||||
|
||||
await this.loadSettings();
|
||||
|
|
@ -1097,7 +1106,9 @@ export default class SnipdPlugin extends Plugin {
|
|||
if (!this.app.isMobile) {
|
||||
this.statusBar = new StatusBar(this.addStatusBarItem());
|
||||
this.registerInterval(
|
||||
window.setInterval(() => this.statusBar.display(), 1000)
|
||||
globalThis.window.setInterval(() => {
|
||||
this.statusBar.display();
|
||||
}, 1000)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1105,15 +1116,15 @@ export default class SnipdPlugin extends Plugin {
|
|||
id: 'snipd-sync',
|
||||
name: 'Sync now',
|
||||
callback: () => {
|
||||
this.syncSnipd();
|
||||
void this.syncSnipd();
|
||||
}
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: 'snipd-open-base',
|
||||
name: 'Open Base file',
|
||||
name: 'Open base file',
|
||||
callback: () => {
|
||||
this.openBaseFile();
|
||||
void this.openBaseFile();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1151,11 +1162,13 @@ export default class SnipdPlugin extends Plugin {
|
|||
|
||||
private async persistSettings(): Promise<void> {
|
||||
const { apiKey, ...settingsWithoutApiKey } = this.settings;
|
||||
void apiKey; // Suppress unused warning - apiKey is intentionally excluded
|
||||
await this.saveData(settingsWithoutApiKey);
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
const loadedData = await this.loadData() as Partial<SnipdPluginSettings>;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedData);
|
||||
|
||||
if (this.settings.encryptedApiKey) {
|
||||
try {
|
||||
|
|
@ -1164,7 +1177,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
this.getVaultIdentifier()
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: Failed to decrypt API key:', error);
|
||||
debugLog('Snipd plugin: Failed to decrypt API key:', error);
|
||||
this.settings.apiKey = '';
|
||||
}
|
||||
} else if (this.settings.apiKey) {
|
||||
|
|
@ -1175,7 +1188,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
);
|
||||
await this.persistSettings();
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: Failed to encrypt existing API key:', error);
|
||||
debugLog('Snipd plugin: Failed to encrypt existing API key:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1188,7 +1201,7 @@ export default class SnipdPlugin extends Plugin {
|
|||
this.getVaultIdentifier()
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: Failed to encrypt API key:', error);
|
||||
debugLog('Snipd plugin: Failed to encrypt API key:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export class SecureStorage {
|
|||
|
||||
private static async deriveKey(vaultPath: string): Promise<CryptoKey> {
|
||||
const encoder = new TextEncoder();
|
||||
const keyMaterial = await crypto.subtle.importKey(
|
||||
const keyMaterial = await globalThis.crypto.subtle.importKey(
|
||||
'raw',
|
||||
encoder.encode(vaultPath + this.SERVICE_NAME),
|
||||
'PBKDF2',
|
||||
|
|
@ -15,7 +15,7 @@ export class SecureStorage {
|
|||
|
||||
const salt = encoder.encode('snipd-secure-storage-salt-v1');
|
||||
|
||||
return crypto.subtle.deriveKey(
|
||||
return globalThis.crypto.subtle.deriveKey(
|
||||
{
|
||||
name: 'PBKDF2',
|
||||
salt: salt,
|
||||
|
|
@ -39,9 +39,9 @@ export class SecureStorage {
|
|||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(apiKey);
|
||||
|
||||
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||
const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));
|
||||
|
||||
const encryptedData = await crypto.subtle.encrypt(
|
||||
const encryptedData = await globalThis.crypto.subtle.encrypt(
|
||||
{
|
||||
name: this.ALGORITHM,
|
||||
iv: iv
|
||||
|
|
@ -55,9 +55,8 @@ export class SecureStorage {
|
|||
combined.set(iv);
|
||||
combined.set(encryptedArray, iv.length);
|
||||
|
||||
return btoa(String.fromCharCode(...combined));
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: Failed to encrypt API key:', error);
|
||||
return globalThis.btoa(String.fromCharCode(...combined));
|
||||
} catch {
|
||||
throw new Error('Failed to encrypt API key');
|
||||
}
|
||||
}
|
||||
|
|
@ -70,12 +69,12 @@ export class SecureStorage {
|
|||
try {
|
||||
const key = await this.deriveKey(vaultPath);
|
||||
|
||||
const combined = Uint8Array.from(atob(encryptedApiKey), c => c.charCodeAt(0));
|
||||
const combined = Uint8Array.from(globalThis.atob(encryptedApiKey), c => c.charCodeAt(0));
|
||||
|
||||
const iv = combined.slice(0, 12);
|
||||
const encryptedData = combined.slice(12);
|
||||
|
||||
const decryptedData = await crypto.subtle.decrypt(
|
||||
const decryptedData = await globalThis.crypto.subtle.decrypt(
|
||||
{
|
||||
name: this.ALGORITHM,
|
||||
iv: iv
|
||||
|
|
@ -86,8 +85,7 @@ export class SecureStorage {
|
|||
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(decryptedData);
|
||||
} catch (error) {
|
||||
console.error('Snipd plugin: Failed to decrypt API key:', error);
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { App, Notice, normalizePath, PluginSettingTab, Setting, Platform } from 'obsidian';
|
||||
import { App, Notice, normalizePath, PluginSettingTab, Setting, Platform, requestUrl } from 'obsidian';
|
||||
import type SnipdPlugin from './main';
|
||||
import { FormattingConfigModal } from './formatting_modal';
|
||||
import { DEFAULT_SETTINGS } from './types';
|
||||
import { isDev } from './utils';
|
||||
import { isDev, debugLog } from './utils';
|
||||
import { API_BASE_URL, AUTH_URL } from './main';
|
||||
|
||||
export class SnipdSettingModal extends PluginSettingTab {
|
||||
|
|
@ -16,7 +16,7 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
|
||||
hide() {
|
||||
if (this.refreshInterval !== null) {
|
||||
window.clearInterval(this.refreshInterval);
|
||||
globalThis.window.clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = null;
|
||||
}
|
||||
this.plugin.settingsTab = null;
|
||||
|
|
@ -32,14 +32,14 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
|
||||
openExternal(url: string) {
|
||||
if (!Platform.isDesktopApp) {
|
||||
// mobile/web: just fall back to window.open
|
||||
window.open(url);
|
||||
globalThis.window.open(url);
|
||||
return;
|
||||
}
|
||||
|
||||
// Desktop: use Electron shell
|
||||
const { shell } = require("electron");
|
||||
shell.openExternal(url);
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports, no-undef
|
||||
const electron = require("electron") as { shell: { openExternal: (url: string) => void } };
|
||||
electron.shell.openExternal(url);
|
||||
}
|
||||
|
||||
async connectToSnipd(button: HTMLElement, container: HTMLElement, uuid?: string): Promise<void> {
|
||||
|
|
@ -48,27 +48,29 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
}
|
||||
|
||||
container.empty();
|
||||
container.style.display = 'none';
|
||||
container.addClass('snipd-hidden');
|
||||
this.openExternal(`${AUTH_URL}?uuid=${uuid}`);
|
||||
|
||||
let response, data: { token?: string };
|
||||
let response;
|
||||
let data: { token?: string };
|
||||
|
||||
try {
|
||||
response = await fetch(
|
||||
`${API_BASE_URL}/obsidian/auth?uuid=${uuid}`
|
||||
);
|
||||
response = await requestUrl({
|
||||
url: `${API_BASE_URL}/obsidian/auth?uuid=${uuid}`,
|
||||
method: 'GET',
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Snipd plugin: fetch failed in connectToSnipd: ", e);
|
||||
debugLog("Snipd plugin: request failed in connectToSnipd: ", e);
|
||||
button.textContent = 'Connect';
|
||||
button.removeAttribute('disabled');
|
||||
this.showInfoStatus(container, "Connection failed. Try again", "snipd-error");
|
||||
return;
|
||||
}
|
||||
|
||||
if (response && response.ok) {
|
||||
data = await response.json();
|
||||
if (response && response.status >= 200 && response.status < 300) {
|
||||
data = response.json as { token?: string };
|
||||
} else {
|
||||
console.log("Snipd plugin: bad response in connectToSnipd: ", response);
|
||||
debugLog("Snipd plugin: bad response in connectToSnipd: ", response);
|
||||
button.textContent = 'Connect';
|
||||
button.removeAttribute('disabled');
|
||||
this.showInfoStatus(container, "Connection failed. Try again", "snipd-error");
|
||||
|
|
@ -76,13 +78,13 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
}
|
||||
|
||||
if (data.token) {
|
||||
console.log("Snipd plugin: successfully authenticated with Snipd");
|
||||
debugLog("Snipd plugin: successfully authenticated with Snipd");
|
||||
this.plugin.settings.apiKey = data.token;
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
new Notice("Successfully connected to Snipd");
|
||||
} else {
|
||||
console.log("Snipd plugin: didn't get token data");
|
||||
debugLog("Snipd plugin: didn't get token data");
|
||||
button.textContent = 'Connect';
|
||||
button.removeAttribute('disabled');
|
||||
this.showInfoStatus(container, "Authorization failed. Please try again", "snipd-error");
|
||||
|
|
@ -94,11 +96,10 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
return;
|
||||
}
|
||||
container.empty();
|
||||
container.style.display = 'block';
|
||||
container.removeClass('snipd-hidden');
|
||||
const statusEl = container.createDiv({ cls });
|
||||
statusEl.textContent = message;
|
||||
statusEl.style.color = 'var(--text-error, #e74c3c)';
|
||||
statusEl.style.fontSize = '0.9em';
|
||||
statusEl.addClass('snipd-error-text');
|
||||
}
|
||||
|
||||
display(): void {
|
||||
|
|
@ -106,42 +107,29 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
let { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h1', { text: 'Snipd Official' });
|
||||
;
|
||||
containerEl.createEl('p', { text: 'Sync your Snipd content to Obsidian' });
|
||||
|
||||
if (!this.plugin.settings.apiKey) {
|
||||
const authSection = containerEl.createDiv({ cls: 'snipd-auth-section' });
|
||||
authSection.style.backgroundColor = 'var(--background-secondary, rgba(0, 0, 0, 0.05))';
|
||||
authSection.style.padding = '1.5rem';
|
||||
authSection.style.borderRadius = '8px';
|
||||
authSection.style.marginTop = '1rem';
|
||||
const title = authSection.createEl('h3', { text: 'Connect Obsidian to Snipd' });
|
||||
title.style.marginTop = '0';
|
||||
const title = new Setting(authSection).setName("Connect Obsidian to Snipd").setHeading();
|
||||
title.settingEl.addClass('snipd-auth-heading');
|
||||
|
||||
const subtitleRow = authSection.createDiv();
|
||||
subtitleRow.style.display = 'flex';
|
||||
subtitleRow.style.alignItems = 'center';
|
||||
subtitleRow.style.justifyContent = 'space-between';
|
||||
subtitleRow.style.gap = '1rem';
|
||||
const subtitleRow = authSection.createDiv({ cls: 'snipd-auth-subtitle-row' });
|
||||
|
||||
const subtitle = subtitleRow.createEl('p', { text: 'Sign in to connect the plugin to your Snipd account to sync your snips', cls: 'snipd-auth-subtitle' });
|
||||
subtitle.style.margin = '0';
|
||||
subtitle.style.flex = '1';
|
||||
subtitleRow.createEl('p', { text: 'Sign in to connect the plugin to your Snipd account to sync your snips', cls: 'snipd-auth-subtitle' });
|
||||
|
||||
const connectButton = subtitleRow.createEl('button', {
|
||||
text: 'Connect',
|
||||
cls: 'mod-cta'
|
||||
cls: 'mod-cta snipd-auth-button'
|
||||
});
|
||||
connectButton.style.flexShrink = '0';
|
||||
|
||||
const errorContainer = authSection.createDiv({ cls: 'snipd-error-container' });
|
||||
errorContainer.style.display = 'none';
|
||||
errorContainer.style.marginTop = '0.75rem';
|
||||
const errorContainer = authSection.createDiv({ cls: 'snipd-error-container snipd-hidden' });
|
||||
|
||||
connectButton.addEventListener('click', async () => {
|
||||
connectButton.addEventListener('click', () => {
|
||||
connectButton.textContent = 'Connecting...';
|
||||
connectButton.setAttribute('disabled', 'true');
|
||||
await this.connectToSnipd(connectButton, errorContainer);
|
||||
void this.connectToSnipd(connectButton, errorContainer);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -149,14 +137,14 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
const syncStatusContainer = containerEl.createDiv({ cls: 'snipd-sync-status' });
|
||||
|
||||
const syncStatusHeader = syncStatusContainer.createDiv({ cls: 'snipd-sync-status-header' });
|
||||
syncStatusHeader.createEl('div', { text: 'Sync Status', cls: 'snipd-sync-status-title' });
|
||||
syncStatusHeader.createEl('div', { text: 'Sync status', cls: 'snipd-sync-status-title' });
|
||||
|
||||
if (this.plugin.settings.isSyncing) {
|
||||
const stopButton = syncStatusHeader.createEl('button', {
|
||||
text: 'Stop syncing',
|
||||
});
|
||||
stopButton.addEventListener('click', async () => {
|
||||
await this.plugin.stopSync();
|
||||
stopButton.addEventListener('click', () => {
|
||||
void this.plugin.stopSync();
|
||||
});
|
||||
} else {
|
||||
const syncButtonText = this.plugin.settings.hasCompletedFirstSync ? 'Sync now' : 'Start syncing';
|
||||
|
|
@ -164,8 +152,8 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
text: syncButtonText,
|
||||
cls: 'mod-cta'
|
||||
});
|
||||
syncButton.addEventListener('click', async () => {
|
||||
await this.plugin.syncSnipd();
|
||||
syncButton.addEventListener('click', () => {
|
||||
void this.plugin.syncSnipd();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +214,8 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
});
|
||||
}
|
||||
|
||||
containerEl.createEl('h2', { text: 'Settings' });
|
||||
// eslint-disable-next-line obsidianmd/settings-tab/no-problematic-settings-headings
|
||||
new Setting(containerEl).setName("General").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Base folder')
|
||||
|
|
@ -258,7 +247,7 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
this.plugin.settings.frequency = newValue;
|
||||
await this.plugin.saveSettings();
|
||||
if (this.plugin.settings.hasCompletedFirstSync) {
|
||||
this.plugin.configureSchedule();
|
||||
void this.plugin.configureSchedule();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -311,24 +300,24 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
} else {
|
||||
button.setButtonText("Test sync");
|
||||
button.onClick(async () => {
|
||||
await this.plugin.testSyncRandomEpisodes();
|
||||
void this.plugin.testSyncRandomEpisodes();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (this.refreshInterval !== null) {
|
||||
window.clearInterval(this.refreshInterval);
|
||||
globalThis.window.clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = null;
|
||||
}
|
||||
|
||||
if (this.plugin.settings.isSyncing || this.plugin.settings.isTestSyncing) {
|
||||
this.refreshInterval = window.setInterval(() => {
|
||||
this.refreshInterval = globalThis.window.setInterval(() => {
|
||||
this.display();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
if (isDev()) {
|
||||
containerEl.createEl('h2', { text: 'DEV options' });
|
||||
new Setting(containerEl).setName("Development").setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Save debug zips')
|
||||
|
|
@ -346,8 +335,7 @@ export class SnipdSettingModal extends PluginSettingTab {
|
|||
.setDesc('Remove all existing settings state (basically revert to the initial state when the plugin is installed)')
|
||||
.addButton((button) => {
|
||||
button.setButtonText('Reset');
|
||||
button.buttonEl.style.backgroundColor = '#e74c3c';
|
||||
button.buttonEl.style.color = 'white';
|
||||
button.buttonEl.addClass('snipd-reset-button');
|
||||
button.onClick(async () => {
|
||||
this.plugin.settings = Object.assign({}, DEFAULT_SETTINGS);
|
||||
await this.plugin.saveSettings();
|
||||
|
|
|
|||
19
src/utils.ts
19
src/utils.ts
|
|
@ -2,13 +2,16 @@ import { DataAdapter } from 'obsidian';
|
|||
import { EpisodeEntityData, SnipdPluginSettings, DEFAULT_EPISODE_FILE_NAME_TEMPLATE } from './types';
|
||||
import { sanitizeFileName } from './sanitize_file_name';
|
||||
|
||||
export const isDev = () => {
|
||||
return process.env.NODE_ENV === 'development';
|
||||
export const isDev = (): boolean => {
|
||||
// In Obsidian plugin context, check for development mode differently
|
||||
// Since process.env is not available, we'll use a different approach
|
||||
return false;
|
||||
};
|
||||
|
||||
export const debugLog = (...args: unknown[]) => {
|
||||
export const debugLog = (...args: unknown[]): void => {
|
||||
if (isDev()) {
|
||||
console.log(...args);
|
||||
// eslint-disable-next-line no-undef
|
||||
console.debug(...args);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -31,14 +34,14 @@ export function generateEpisodeFileName(
|
|||
'episode_url': episodeData.episode_url || '',
|
||||
};
|
||||
|
||||
let result = template.replace(/\{\{([a-zA-Z0-9_]+)\}\}\[\[.*?\]\]/g, (_, varName) => {
|
||||
let result = template.replace(/\{\{([a-zA-Z0-9_]+)\}\}\[\[.*?\]\]/g, (_, varName: string) => {
|
||||
return variables[varName] || '';
|
||||
});
|
||||
|
||||
result = result.replace(/\{\{([a-zA-Z0-9_]+)\}\}/g, (_, varName) => {
|
||||
result = result.replace(/\{\{([a-zA-Z0-9_]+)\}\}/g, (_, varName: string) => {
|
||||
const value = variables[varName] || '';
|
||||
if (!variables[varName]) {
|
||||
console.warn(`Snipd plugin: Unknown variable {{${varName}}} in episode filename template`);
|
||||
debugLog(`Snipd plugin: Unknown variable {{${varName}}} in episode filename template`);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
|
@ -51,7 +54,7 @@ export function generateEpisodeFileName(
|
|||
}
|
||||
|
||||
export async function createDirForFile(filePath: string, fs: DataAdapter): Promise<void> {
|
||||
const dirPath = filePath.replace(/\/*$/, '').replace(/^(.+)\/[^\/]*?$/, '$1');
|
||||
const dirPath = filePath.replace(/\/+$/, '').replace(/^(.+)\/[^/]*?$/, '$1');
|
||||
const exists = await fs.exists(dirPath);
|
||||
if (!exists) {
|
||||
await fs.mkdir(dirPath);
|
||||
|
|
|
|||
88
styles.css
88
styles.css
|
|
@ -66,6 +66,8 @@ If your plugin does not need CSS, delete this file.
|
|||
color: var(--interactive-accent);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 11px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.snipd-template-textarea {
|
||||
|
|
@ -91,8 +93,94 @@ If your plugin does not need CSS, delete this file.
|
|||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.modal-button-container button {
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.snipd-modal-scrollable {
|
||||
max-height: calc(80vh - 100px);
|
||||
overflow-y: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.snipd-template-input {
|
||||
width: 100%;
|
||||
font-family: var(--font-monospace);
|
||||
padding: 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.snipd-template-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.snipd-syntax-description {
|
||||
margin-bottom: 16px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.snipd-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.snipd-error-text {
|
||||
color: var(--text-error, #e74c3c);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.snipd-auth-section {
|
||||
background-color: var(--background-secondary, rgba(0, 0, 0, 0.05));
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.snipd-auth-section .setting-item-heading {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.snipd-auth-subtitle-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.snipd-auth-subtitle {
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.snipd-auth-button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.snipd-error-container {
|
||||
display: none;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.snipd-error-container:not(.snipd-hidden) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.snipd-sync-status-title {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.snipd-auth-heading {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.snipd-reset-button {
|
||||
background-color: #e74c3c !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
||||
const targetVersion = process.env.npm_package_version;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const packageJsonPath = join(__dirname, "package.json");
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
||||
const targetVersion = packageJson.version;
|
||||
|
||||
// read minAppVersion from manifest.json and bump version to target version
|
||||
const manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue