feat: bookmarklet to blog stuff easily
This commit is contained in:
@@ -2,7 +2,7 @@ import React, { useEffect } from 'react';
|
||||
import { ActivityBar, Sidebar, Editor, StatusBar, Panel, TabBar, ToastContainer, showToast, ResizablePanel, WindowTitleBar } from './components';
|
||||
import { useAppStore, PostData, MediaData, TaskProgress } from './store';
|
||||
import { loadTabsForProject, saveTabsForProject } from './utils';
|
||||
import { openSingletonToolTab } from './navigation/tabPolicy';
|
||||
import { openEntityTab, openSingletonToolTab } from './navigation/tabPolicy';
|
||||
import { persistSiteValidationReport } from './navigation/siteValidationPersistence';
|
||||
import { executeActivityClick } from './navigation/activityExecution';
|
||||
import { createAndFocusPost } from './navigation/postCreation';
|
||||
@@ -214,6 +214,33 @@ const App: React.FC = () => {
|
||||
}) || (() => {})
|
||||
);
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('blogmark:created', (post: unknown) => {
|
||||
const created = post as { id?: string } | null;
|
||||
if (!created?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = useAppStore.getState();
|
||||
executeActivityClick(
|
||||
{
|
||||
activeView: state.activeView,
|
||||
sidebarVisible: state.sidebarVisible,
|
||||
tabs: state.tabs,
|
||||
activeTabId: state.activeTabId,
|
||||
},
|
||||
'posts',
|
||||
{
|
||||
setActiveView: state.setActiveView,
|
||||
toggleSidebar: state.toggleSidebar,
|
||||
},
|
||||
);
|
||||
|
||||
state.setSelectedPost(created.id);
|
||||
openEntityTab(state.openTab, 'post', created.id, 'preview');
|
||||
}) || (() => {})
|
||||
);
|
||||
|
||||
unsubscribers.push(
|
||||
window.electronAPI?.on('menu:importMedia', () => {
|
||||
window.electronAPI?.media.importDialog();
|
||||
|
||||
@@ -76,6 +76,15 @@ const DEFAULT_CATEGORY_METADATA: Record<string, CategoryMetadata> = {
|
||||
// Standard categories that cannot be deleted
|
||||
const PROTECTED_CATEGORIES = ['article', 'aside', 'page', 'picture'];
|
||||
|
||||
function normalizeBlogmarkCategory(value: unknown): string | undefined {
|
||||
if (typeof value !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
// Individual setting row component (VS Code style)
|
||||
const SettingRow: React.FC<{
|
||||
id: string;
|
||||
@@ -140,6 +149,7 @@ export const SettingsView: React.FC = () => {
|
||||
const [projectMainLanguage, setProjectMainLanguage] = useState<SupportedLanguage>('en');
|
||||
const [projectDefaultAuthor, setProjectDefaultAuthor] = useState('');
|
||||
const [projectMaxPostsPerPage, setProjectMaxPostsPerPage] = useState(50);
|
||||
const [projectBlogmarkCategory, setProjectBlogmarkCategory] = useState('article');
|
||||
|
||||
// Post categories management
|
||||
const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES);
|
||||
@@ -195,6 +205,9 @@ export const SettingsView: React.FC = () => {
|
||||
: 50;
|
||||
setProjectMaxPostsPerPage(maxPostsPerPage);
|
||||
|
||||
const incomingBlogmarkCategory = normalizeBlogmarkCategory((metadata as { blogmarkCategory?: unknown } | null)?.blogmarkCategory);
|
||||
setProjectBlogmarkCategory(incomingBlogmarkCategory || 'article');
|
||||
|
||||
const incomingCategoryMetadata = (metadata as any)?.categoryMetadata as Record<string, CategoryMetadata> | undefined;
|
||||
const incomingLegacyCategorySettings = (metadata as any)?.categorySettings as Record<string, { renderInLists: boolean; showTitle: boolean }> | undefined;
|
||||
setCategoryMetadata((current) => {
|
||||
@@ -232,6 +245,7 @@ export const SettingsView: React.FC = () => {
|
||||
const categories = await window.electronAPI?.meta.getCategories();
|
||||
if (categories && categories.length > 0) {
|
||||
setPostCategories(categories);
|
||||
setProjectBlogmarkCategory((current) => categories.includes(current) ? current : categories[0]);
|
||||
setCategoryMetadata((current) => {
|
||||
const next = { ...DEFAULT_CATEGORY_METADATA, ...current };
|
||||
for (const category of categories) {
|
||||
@@ -244,6 +258,7 @@ export const SettingsView: React.FC = () => {
|
||||
} else {
|
||||
// Initialize with defaults if no categories exist
|
||||
setPostCategories(DEFAULT_POST_CATEGORIES);
|
||||
setProjectBlogmarkCategory((current) => DEFAULT_POST_CATEGORIES.includes(current) ? current : DEFAULT_POST_CATEGORIES[0]);
|
||||
setCategoryMetadata(DEFAULT_CATEGORY_METADATA);
|
||||
}
|
||||
|
||||
@@ -326,6 +341,7 @@ export const SettingsView: React.FC = () => {
|
||||
mainLanguage: resolveSupportedRenderLanguage(projectMainLanguage),
|
||||
defaultAuthor: projectDefaultAuthor.trim() || undefined,
|
||||
maxPostsPerPage: Math.min(500, Math.max(1, Math.floor(projectMaxPostsPerPage || 50))),
|
||||
blogmarkCategory: normalizeBlogmarkCategory(projectBlogmarkCategory) || undefined,
|
||||
categoryMetadata,
|
||||
});
|
||||
}
|
||||
@@ -347,8 +363,29 @@ export const SettingsView: React.FC = () => {
|
||||
setProjectDataPath('');
|
||||
};
|
||||
|
||||
const handleCopyBlogmarkBookmarklet = async () => {
|
||||
try {
|
||||
const bookmarkletSource = await window.electronAPI?.app.getBlogmarkBookmarklet();
|
||||
if (!bookmarkletSource) {
|
||||
showToast.error(t('settings.toast.blogmarkBookmarkletGenerateFailed'));
|
||||
return;
|
||||
}
|
||||
|
||||
const copied = await window.electronAPI?.app.copyToClipboard(bookmarkletSource);
|
||||
if (copied) {
|
||||
showToast.success(t('settings.toast.blogmarkBookmarkletCopied'));
|
||||
return;
|
||||
}
|
||||
|
||||
showToast.error(t('settings.toast.blogmarkBookmarkletCopyFailed'));
|
||||
} catch (error) {
|
||||
console.error('Failed to copy blogmark bookmarklet:', error);
|
||||
showToast.error(t('settings.toast.blogmarkBookmarkletCopyFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// Keywords for each section for search filtering
|
||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'url', 'public', 'path', 'folder', 'location', 'data', 'language', 'author', 'default', 'preview', 'max', 'posts', 'page'];
|
||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'url', 'public', 'path', 'folder', 'location', 'data', 'language', 'author', 'default', 'preview', 'max', 'posts', 'page', 'bookmarklet', 'blogmark'];
|
||||
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
|
||||
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
|
||||
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode'];
|
||||
@@ -480,6 +517,32 @@ export const SettingsView: React.FC = () => {
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="project-blogmark-category"
|
||||
label={t('settings.project.blogmarkCategoryLabel')}
|
||||
description={t('settings.project.blogmarkCategoryDescription')}
|
||||
>
|
||||
<select
|
||||
id="project-blogmark-category"
|
||||
value={projectBlogmarkCategory}
|
||||
onChange={(e) => setProjectBlogmarkCategory(e.target.value)}
|
||||
>
|
||||
{postCategories.map((category) => (
|
||||
<option key={category} value={category}>{category}</option>
|
||||
))}
|
||||
</select>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="project-blogmark-bookmarklet"
|
||||
label={t('settings.project.blogmarkBookmarkletLabel')}
|
||||
description={t('settings.project.blogmarkBookmarkletDescription')}
|
||||
>
|
||||
<button className="secondary" onClick={handleCopyBlogmarkBookmarklet}>
|
||||
{t('settings.project.blogmarkBookmarkletCopyButton')}
|
||||
</button>
|
||||
</SettingRow>
|
||||
|
||||
<div className="setting-actions">
|
||||
<button className="primary" onClick={handleSaveProject}>
|
||||
{t('settings.project.saveButton')}
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"settings.toast.credentialsCleared": "{type}-Anmeldedaten gelöscht",
|
||||
"settings.toast.projectSaved": "Projekteinstellungen gespeichert",
|
||||
"settings.toast.projectSaveFailed": "Projekteinstellungen konnten nicht gespeichert werden",
|
||||
"settings.toast.blogmarkBookmarkletCopied": "Blogmark-Bookmarklet in die Zwischenablage kopiert",
|
||||
"settings.toast.blogmarkBookmarkletCopyFailed": "Blogmark-Bookmarklet konnte nicht kopiert werden",
|
||||
"settings.toast.blogmarkBookmarkletGenerateFailed": "Blogmark-Bookmarklet konnte nicht erzeugt werden",
|
||||
"settings.toast.categoryAdded": "Kategorie \"{category}\" hinzugefügt",
|
||||
"settings.toast.categoryAddFailed": "Kategorie konnte nicht hinzugefügt werden",
|
||||
"settings.toast.categoryExists": "Kategorie existiert bereits",
|
||||
@@ -435,6 +438,11 @@
|
||||
"settings.project.defaultAuthorPlaceholder": "Autorenname",
|
||||
"settings.project.maxPostsPerPageLabel": "Maximale Beiträge pro Seite",
|
||||
"settings.project.maxPostsPerPageDescription": "Maximale Anzahl von Beiträgen pro Vorschau-Routenseite.",
|
||||
"settings.project.blogmarkCategoryLabel": "Blogmark-Kategorie",
|
||||
"settings.project.blogmarkCategoryDescription": "Kategorie für Beiträge, die über Bookmarklet-Deep-Links erstellt werden.",
|
||||
"settings.project.blogmarkBookmarkletLabel": "Browser-Bookmarklet",
|
||||
"settings.project.blogmarkBookmarkletDescription": "Kopiere ein Bookmarklet, das du in die Browser-Lesezeichenleiste einfügen kannst, um Seitenlinks an bDS zu senden.",
|
||||
"settings.project.blogmarkBookmarkletCopyButton": "Blogmark-Bookmarklet kopieren",
|
||||
"settings.project.saveButton": "Projekteinstellungen speichern",
|
||||
"editor.loadingPost": "Beitrag wird geladen...",
|
||||
"editor.unsavedChanges": "Ungespeicherte Änderungen (wird beim Wechsel automatisch gespeichert)",
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"settings.toast.credentialsCleared": "{type} credentials cleared",
|
||||
"settings.toast.projectSaved": "Project settings saved",
|
||||
"settings.toast.projectSaveFailed": "Failed to save project settings",
|
||||
"settings.toast.blogmarkBookmarkletCopied": "Blogmark bookmarklet copied to clipboard",
|
||||
"settings.toast.blogmarkBookmarkletCopyFailed": "Failed to copy blogmark bookmarklet",
|
||||
"settings.toast.blogmarkBookmarkletGenerateFailed": "Failed to generate blogmark bookmarklet",
|
||||
"settings.toast.categoryAdded": "Category \"{category}\" added",
|
||||
"settings.toast.categoryAddFailed": "Failed to add category",
|
||||
"settings.toast.categoryExists": "Category already exists",
|
||||
@@ -435,6 +438,11 @@
|
||||
"settings.project.defaultAuthorPlaceholder": "Author Name",
|
||||
"settings.project.maxPostsPerPageLabel": "Max Posts Per Page",
|
||||
"settings.project.maxPostsPerPageDescription": "Maximum number of posts shown per preview route page.",
|
||||
"settings.project.blogmarkCategoryLabel": "Blogmark Category",
|
||||
"settings.project.blogmarkCategoryDescription": "Category assigned to posts created via bookmarklet deep links.",
|
||||
"settings.project.blogmarkBookmarkletLabel": "Browser Bookmarklet",
|
||||
"settings.project.blogmarkBookmarkletDescription": "Copy a bookmarklet you can paste into your browser bookmarks bar to send page links into bDS.",
|
||||
"settings.project.blogmarkBookmarkletCopyButton": "Copy Blogmark Bookmarklet",
|
||||
"settings.project.saveButton": "Save Project Settings",
|
||||
"editor.loadingPost": "Loading post...",
|
||||
"editor.unsavedChanges": "Unsaved changes (auto-saves on switch)",
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"settings.toast.credentialsCleared": "Credenciales de {type} borradas",
|
||||
"settings.toast.projectSaved": "Configuración del proyecto guardada",
|
||||
"settings.toast.projectSaveFailed": "No se pudo guardar la configuración del proyecto",
|
||||
"settings.toast.blogmarkBookmarkletCopied": "Bookmarklet de blogmark copiado al portapapeles",
|
||||
"settings.toast.blogmarkBookmarkletCopyFailed": "No se pudo copiar el bookmarklet de blogmark",
|
||||
"settings.toast.blogmarkBookmarkletGenerateFailed": "No se pudo generar el bookmarklet de blogmark",
|
||||
"settings.toast.categoryAdded": "Categoría \"{category}\" agregada",
|
||||
"settings.toast.categoryAddFailed": "No se pudo agregar la categoría",
|
||||
"settings.toast.categoryExists": "La categoría ya existe",
|
||||
@@ -435,6 +438,11 @@
|
||||
"settings.project.defaultAuthorPlaceholder": "Nombre del autor",
|
||||
"settings.project.maxPostsPerPageLabel": "Máx. entradas por página",
|
||||
"settings.project.maxPostsPerPageDescription": "Número máximo de entradas mostradas por página de ruta de vista previa.",
|
||||
"settings.project.blogmarkCategoryLabel": "Categoría de blogmark",
|
||||
"settings.project.blogmarkCategoryDescription": "Categoría asignada a entradas creadas mediante deep links del bookmarklet.",
|
||||
"settings.project.blogmarkBookmarkletLabel": "Bookmarklet del navegador",
|
||||
"settings.project.blogmarkBookmarkletDescription": "Copia un bookmarklet para pegarlo en la barra de marcadores del navegador y enviar enlaces de páginas a bDS.",
|
||||
"settings.project.blogmarkBookmarkletCopyButton": "Copiar bookmarklet de blogmark",
|
||||
"settings.project.saveButton": "Guardar configuración del proyecto",
|
||||
"editor.loadingPost": "Cargando entrada...",
|
||||
"editor.unsavedChanges": "Cambios sin guardar (se guarda automáticamente al cambiar)",
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"settings.toast.credentialsCleared": "Identifiants {type} effacés",
|
||||
"settings.toast.projectSaved": "Paramètres du projet enregistrés",
|
||||
"settings.toast.projectSaveFailed": "Impossible d’enregistrer les paramètres du projet",
|
||||
"settings.toast.blogmarkBookmarkletCopied": "Bookmarklet blogmark copié dans le presse-papiers",
|
||||
"settings.toast.blogmarkBookmarkletCopyFailed": "Impossible de copier le bookmarklet blogmark",
|
||||
"settings.toast.blogmarkBookmarkletGenerateFailed": "Impossible de générer le bookmarklet blogmark",
|
||||
"settings.toast.categoryAdded": "Catégorie \"{category}\" ajoutée",
|
||||
"settings.toast.categoryAddFailed": "Impossible d’ajouter la catégorie",
|
||||
"settings.toast.categoryExists": "La catégorie existe déjà",
|
||||
@@ -435,6 +438,11 @@
|
||||
"settings.project.defaultAuthorPlaceholder": "Nom de l’auteur",
|
||||
"settings.project.maxPostsPerPageLabel": "Nombre max d’articles par page",
|
||||
"settings.project.maxPostsPerPageDescription": "Nombre maximum d’articles affichés par page de route d’aperçu.",
|
||||
"settings.project.blogmarkCategoryLabel": "Catégorie blogmark",
|
||||
"settings.project.blogmarkCategoryDescription": "Catégorie attribuée aux articles créés via les deep links du bookmarklet.",
|
||||
"settings.project.blogmarkBookmarkletLabel": "Bookmarklet navigateur",
|
||||
"settings.project.blogmarkBookmarkletDescription": "Copiez un bookmarklet à coller dans la barre de favoris de votre navigateur pour envoyer les liens de page vers bDS.",
|
||||
"settings.project.blogmarkBookmarkletCopyButton": "Copier le bookmarklet blogmark",
|
||||
"settings.project.saveButton": "Enregistrer les paramètres du projet",
|
||||
"editor.loadingPost": "Chargement de l’article...",
|
||||
"editor.unsavedChanges": "Modifications non enregistrées (enregistrement auto au changement)",
|
||||
|
||||
@@ -133,6 +133,9 @@
|
||||
"settings.toast.credentialsCleared": "Credenziali {type} cancellate",
|
||||
"settings.toast.projectSaved": "Impostazioni progetto salvate",
|
||||
"settings.toast.projectSaveFailed": "Impossibile salvare le impostazioni del progetto",
|
||||
"settings.toast.blogmarkBookmarkletCopied": "Bookmarklet blogmark copiato negli appunti",
|
||||
"settings.toast.blogmarkBookmarkletCopyFailed": "Impossibile copiare il bookmarklet blogmark",
|
||||
"settings.toast.blogmarkBookmarkletGenerateFailed": "Impossibile generare il bookmarklet blogmark",
|
||||
"settings.toast.categoryAdded": "Categoria \"{category}\" aggiunta",
|
||||
"settings.toast.categoryAddFailed": "Impossibile aggiungere la categoria",
|
||||
"settings.toast.categoryExists": "La categoria esiste già",
|
||||
@@ -435,6 +438,11 @@
|
||||
"settings.project.defaultAuthorPlaceholder": "Nome autore",
|
||||
"settings.project.maxPostsPerPageLabel": "Max post per pagina",
|
||||
"settings.project.maxPostsPerPageDescription": "Numero massimo di post mostrati per pagina di anteprima.",
|
||||
"settings.project.blogmarkCategoryLabel": "Categoria blogmark",
|
||||
"settings.project.blogmarkCategoryDescription": "Categoria assegnata ai post creati tramite deep link del bookmarklet.",
|
||||
"settings.project.blogmarkBookmarkletLabel": "Bookmarklet del browser",
|
||||
"settings.project.blogmarkBookmarkletDescription": "Copia un bookmarklet da incollare nella barra dei preferiti del browser per inviare link di pagina a bDS.",
|
||||
"settings.project.blogmarkBookmarkletCopyButton": "Copia bookmarklet blogmark",
|
||||
"settings.project.saveButton": "Salva impostazioni progetto",
|
||||
"editor.loadingPost": "Caricamento post...",
|
||||
"editor.unsavedChanges": "Modifiche non salvate (salvataggio automatico al cambio)",
|
||||
|
||||
Reference in New Issue
Block a user