feat(app-form): icon picker, tag/category autocomplete, typography

- Replace AppIconPicker text input with visual IconPickerButton for
  lucide icons (grid with search)
- Add AutocompleteInput component for category field with existing
  category suggestions
- Add TagsInput component for tags field with tag pills, autocomplete
  from existing tags, and keyboard navigation
- Add GET /api/apps/suggestions endpoint returning all categories/tags
- Add getAllTags() to appService (merges Tag model + comma-separated)
- Install @tailwindcss/typography plugin to fix prose rendering
  (headings, lists, blockquotes now render in Note/Markdown widgets)
- Fix note widget validator test for new html format
This commit is contained in:
2026-04-03 00:24:08 +03:00
parent a6b09aae9c
commit c5f5f84c79
10 changed files with 405 additions and 28 deletions
+23 -6
View File
@@ -6,6 +6,8 @@
import AppIconPicker from './AppIconPicker.svelte';
import IntegrationConfigFields from './IntegrationConfigFields.svelte';
import AppUrlPreview from './AppUrlPreview.svelte';
import AutocompleteInput from '$lib/components/ui/AutocompleteInput.svelte';
import TagsInput from '$lib/components/ui/TagsInput.svelte';
import IconGrid from '$lib/components/ui/IconGrid.svelte';
import type { IconGridItem } from '$lib/components/ui/IconGrid.svelte';
@@ -25,6 +27,21 @@
let showAdvanced = $state(false);
let showIntegration = $state(false);
let categorySuggestions = $state<string[]>([]);
let tagSuggestions = $state<string[]>([]);
// Fetch autocomplete suggestions
$effect(() => {
fetch('/api/apps/suggestions')
.then((r) => r.json())
.then((json) => {
if (json.success) {
categorySuggestions = json.data?.categories ?? [];
tagSuggestions = json.data?.tags ?? [];
}
})
.catch(() => {});
});
let availableIntegrations = $state<Array<{ id: string; name: string; icon: string; authConfigFields: any[]; extraConfigFields: any[] }>>([]);
let integrationConfig = $state<Record<string, unknown>>({});
let testingConnection = $state(false);
@@ -148,13 +165,13 @@
<label for="category" class="mb-1 block text-sm font-medium text-card-foreground">
{$t('app.category')}
</label>
<input
<AutocompleteInput
id="category"
name="category"
type="text"
bind:value={$form.category}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
suggestions={categorySuggestions}
placeholder={$t('app.category_placeholder')}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
@@ -162,13 +179,13 @@
<label for="tags" class="mb-1 block text-sm font-medium text-card-foreground">
{$t('app.tags')}
</label>
<input
<TagsInput
id="tags"
name="tags"
type="text"
bind:value={$form.tags}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
suggestions={tagSuggestions}
placeholder={$t('app.tags_placeholder')}
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
</div>