Merge pull request #97 from devonthesofa/feature/templates-caracterization

Templates caracterization
This commit is contained in:
Aleix Soler 2026-03-19 18:51:06 +01:00 committed by GitHub
commit fd7004d3a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 162 additions and 19 deletions

View file

@ -20,26 +20,85 @@ export const MarketplaceBrowseModal: React.FC<MarketplaceBrowseModalProps> = ({
onClose,
}) => {
const [searchQuery, setSearchQuery] = useState("");
const [languageFilter, setLanguageFilter] = useState("");
const [typeFilter, setTypeFilter] = useState("");
const allTemplates = useMemo(() => {
return [...PREDEFINED_TEMPLATES, ...COMMUNITY_TEMPLATES];
}, []);
const availableLanguages = useMemo(() => {
const langs = new Set<string>();
allTemplates.forEach((t) => {
if (t.language) langs.add(t.language);
});
return Array.from(langs).sort();
}, [allTemplates]);
const availableTypes = useMemo(() => {
const types = new Set<string>();
allTemplates.forEach((t) => {
if (t.type) types.add(t.type);
});
return Array.from(types).sort();
}, [allTemplates]);
const filteredTemplates = useMemo(() => {
const allTemplates = [...PREDEFINED_TEMPLATES, ...COMMUNITY_TEMPLATES];
return allTemplates.filter(
(t) =>
return allTemplates.filter((t) => {
const matchesSearch =
t.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
t.description.toLowerCase().includes(searchQuery.toLowerCase()),
);
}, [searchQuery]);
t.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesLanguage =
!languageFilter || t.language === languageFilter;
const matchesType = !typeFilter || t.type === typeFilter;
return matchesSearch && matchesLanguage && matchesType;
});
}, [allTemplates, searchQuery, languageFilter, typeFilter]);
return (
<div className="template-editor-modal marketplace-browse-modal">
<div className="template-editor-modal__header">
<div className="marketplace-header-content">
<h2>Template Marketplace</h2>
<SearchFilter
value={searchQuery}
onFilterChange={setSearchQuery}
placeholder="Search templates..."
/>
<div style={{ display: "flex", gap: "8px" }}>
<SearchFilter
value={searchQuery}
onFilterChange={setSearchQuery}
placeholder="Search templates..."
/>
{availableLanguages.length > 0 && (
<select
value={languageFilter}
onChange={(e) =>
setLanguageFilter(e.target.value)
}
className="dropdown"
>
<option value="">All Languages</option>
{availableLanguages.map((lang) => (
<option key={lang} value={lang}>
{lang}
</option>
))}
</select>
)}
{availableTypes.length > 0 && (
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
className="dropdown"
>
<option value="">All Types</option>
{availableTypes.map((type) => (
<option key={type} value={type}>
{type}
</option>
))}
</select>
)}
</div>
</div>
</div>
@ -65,6 +124,16 @@ export const MarketplaceBrowseModal: React.FC<MarketplaceBrowseModalProps> = ({
? "Built-in"
: "Community"}
</span>
{template.type && (
<span className="template-badge badge-neutral">
{template.type}
</span>
)}
{template.language && (
<span className="template-badge badge-neutral">
{template.language}
</span>
)}
{template.authorGithub && (
<div className="marketplace-card-author">
by{" "}

View file

@ -28,6 +28,8 @@ export const TemplateEditorModal: React.FC<TemplateEditorModalProps> = ({
const [isIdUnlocked, setIsIdUnlocked] = useState(false);
const [isAuthorUnlocked, setIsAuthorUnlocked] = useState(false);
const [description, setDescription] = useState(template?.description || "");
const [language, setLanguage] = useState(template?.language || "");
const [type, setType] = useState(template?.type || "");
const [authorGithub, setAuthorGithub] = useState(
template?.authorGithub || "",
);
@ -74,12 +76,24 @@ export const TemplateEditorModal: React.FC<TemplateEditorModalProps> = ({
id: templateId,
name: name.trim(),
description: description.trim(),
language: language.trim() || undefined,
type: type.trim() || undefined,
authorGithub: authorGithub.trim() || undefined,
statuses: statusesWithTemplateId,
};
onSave(savedTemplate);
}, [id, name, description, authorGithub, statuses, isValid, onSave]);
}, [
id,
name,
description,
language,
type,
authorGithub,
statuses,
isValid,
onSave,
]);
const handleNameChange = useCallback(
(val: string) => {
@ -218,6 +232,30 @@ export const TemplateEditorModal: React.FC<TemplateEditorModalProps> = ({
/>
</SettingItem>
<SettingItem
name="Template Type (Optional)"
description="Categorize your template (e.g. Academic, Project Management, Writing, General)"
>
<Input
variant="text"
value={type}
onChange={setType}
placeholder="e.g. Project Management"
/>
</SettingItem>
<SettingItem
name="Language (Optional)"
description="Primary language of the statuses in this template"
>
<Input
variant="text"
value={language}
onChange={setLanguage}
placeholder="e.g. English"
/>
</SettingItem>
<SettingItem
name="Author GitHub Username (Optional)"
description={

View file

@ -10,6 +10,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
id: "academic",
name: "Academic research",
description: "Status workflow for academic research and writing",
language: "English",
type: "Academic",
authorGithub: "soler1212",
statuses: [
{
@ -62,6 +64,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
name: "Colorful workflow",
description:
"A colorful set of workflow statuses with descriptive icons",
language: "English",
type: "General",
authorGithub: "soler1212",
statuses: [
{
@ -125,6 +129,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
id: "creative-writing",
name: "Creative Writing",
description: "Workflow for novelists and creative writers",
language: "English",
type: "Writing",
authorGithub: "soler1212",
statuses: [
{
@ -165,6 +171,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
name: "Starter Template",
description:
"A simplified set of essential workflow statuses to get you started.",
language: "English",
type: "General",
authorGithub: "soler1212",
statuses: [
{
@ -198,6 +206,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
id: "minimal",
name: "Minimal workflow",
description: "A simplified set of essential workflow statuses",
language: "English",
type: "General",
authorGithub: "soler1212",
statuses: [
{
@ -231,6 +241,8 @@ export const PREDEFINED_TEMPLATES: StatusTemplate[] = [
id: "project",
name: "Project management",
description: "Status workflow for project management and tracking",
language: "English",
type: "Project Management",
authorGithub: "soler1212",
statuses: [
{

View file

@ -2,6 +2,8 @@
"id": "academic",
"name": "Academic research",
"description": "Status workflow for academic research and writing",
"language": "English",
"type": "Academic",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -2,6 +2,8 @@
"id": "colorful",
"name": "Colorful workflow",
"description": "A colorful set of workflow statuses with descriptive icons",
"language": "English",
"type": "General",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -2,6 +2,8 @@
"id": "creative-writing",
"name": "Creative Writing",
"description": "Workflow for novelists and creative writers",
"language": "English",
"type": "Writing",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -2,6 +2,8 @@
"id": "default-starter",
"name": "Starter Template",
"description": "A simplified set of essential workflow statuses to get you started.",
"language": "English",
"type": "General",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -2,6 +2,8 @@
"id": "minimal",
"name": "Minimal workflow",
"description": "A simplified set of essential workflow statuses",
"language": "English",
"type": "General",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -2,6 +2,8 @@
"id": "project",
"name": "Project management",
"description": "Status workflow for project management and tracking",
"language": "English",
"type": "Project Management",
"authorGithub": "soler1212",
"statuses": [
{

View file

@ -20,6 +20,8 @@ export interface StatusTemplate {
name: string;
description: string;
authorGithub?: string;
language?: string;
type?: string;
isPredefined?: boolean;
statuses: NoteStatus[];
}

View file

@ -1,12 +1,15 @@
import { StatusTemplate } from "@/types/pluginSettings";
import settingsService from "@/core/settingsService";
import { PREDEFINED_TEMPLATES } from "@/constants/predefinedTemplates";
import {
PREDEFINED_TEMPLATES,
COMMUNITY_TEMPLATES,
} from "@/constants/predefinedTemplates";
/**
* Check if a template is a custom user template (not from marketplace)
*/
export const isCustomTemplate = (template: StatusTemplate): boolean => {
// Either matches an original marketplace template ID
// Either matches an original predefined template
const matchesPredefined = PREDEFINED_TEMPLATES.some(
(pt) =>
pt.id === template.id ||
@ -14,9 +17,13 @@ export const isCustomTemplate = (template: StatusTemplate): boolean => {
);
if (matchesPredefined) return false;
// Or has metadata from marketplace
const hasMetadata = !!template.authorGithub;
if (hasMetadata) return false;
// Or matches an original community template
const matchesCommunity = COMMUNITY_TEMPLATES.some(
(ct) =>
ct.id === template.id ||
ct.name.toLowerCase() === template.name.toLowerCase(),
);
if (matchesCommunity) return false;
return true;
};
@ -25,7 +32,11 @@ export const isCustomTemplate = (template: StatusTemplate): boolean => {
* Check if a marketplace template has been modified by the user
*/
export const isTemplateModified = (template: StatusTemplate): boolean => {
const original = PREDEFINED_TEMPLATES.find(
const allMarketplaceTemplates = [
...PREDEFINED_TEMPLATES,
...COMMUNITY_TEMPLATES,
];
const original = allMarketplaceTemplates.find(
(pt) =>
pt.id === template.id ||
pt.name.toLowerCase() === template.name.toLowerCase(),
@ -104,4 +115,3 @@ export const generateTemplateId = (
return id;
};