refac
This commit is contained in:
@@ -16,7 +16,8 @@
|
|||||||
deleteSkillById,
|
deleteSkillById,
|
||||||
toggleSkillById
|
toggleSkillById
|
||||||
} from '$lib/apis/skills';
|
} from '$lib/apis/skills';
|
||||||
import { capitalizeFirstLetter } from '$lib/utils';
|
import { capitalizeFirstLetter, parseFrontmatter, formatSkillName } from '$lib/utils';
|
||||||
|
import TagInput from '$lib/components/common/Tags/TagInput.svelte';
|
||||||
|
|
||||||
import Tooltip from '../common/Tooltip.svelte';
|
import Tooltip from '../common/Tooltip.svelte';
|
||||||
import ConfirmDialog from '../common/ConfirmDialog.svelte';
|
import ConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
@@ -35,6 +36,9 @@
|
|||||||
let shiftKey = false;
|
let shiftKey = false;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
|
let importFiles;
|
||||||
|
let importInputElement: HTMLInputElement;
|
||||||
|
|
||||||
let query = '';
|
let query = '';
|
||||||
let searchDebounceTimer: ReturnType<typeof setTimeout>;
|
let searchDebounceTimer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
@@ -180,6 +184,51 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full justify-end gap-1.5">
|
<div class="flex w-full justify-end gap-1.5">
|
||||||
|
<input
|
||||||
|
bind:this={importInputElement}
|
||||||
|
bind:files={importFiles}
|
||||||
|
type="file"
|
||||||
|
accept=".md"
|
||||||
|
hidden
|
||||||
|
on:change={() => {
|
||||||
|
if (importFiles && importFiles.length > 0) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
const mdContent = event.target?.result;
|
||||||
|
if (typeof mdContent === 'string') {
|
||||||
|
const fm = parseFrontmatter(mdContent);
|
||||||
|
const fileName = importFiles[0].name.replace(/\.md$/, '');
|
||||||
|
const rawName = fm.name || fileName;
|
||||||
|
const displayName = formatSkillName(rawName);
|
||||||
|
sessionStorage.skill = JSON.stringify({
|
||||||
|
name: displayName,
|
||||||
|
id: fm.name || '', // Use raw frontmatter name as ID if available
|
||||||
|
description: fm.description || '',
|
||||||
|
content: mdContent,
|
||||||
|
is_active: true,
|
||||||
|
access_grants: []
|
||||||
|
});
|
||||||
|
goto('/workspace/skills/create');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(importFiles[0]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if $user?.role === 'admin' || $user?.permissions?.workspace?.skills}
|
||||||
|
<button
|
||||||
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
on:click={() => {
|
||||||
|
importInputElement.click();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class=" self-center font-medium line-clamp-1">
|
||||||
|
{$i18n.t('Import')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if skills.length && ($user?.role === 'admin' || $user?.permissions?.workspace?.skills)}
|
{#if skills.length && ($user?.role === 'admin' || $user?.permissions?.workspace?.skills)}
|
||||||
<button
|
<button
|
||||||
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-200 transition"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
|
import ChevronLeft from '$lib/components/icons/ChevronLeft.svelte';
|
||||||
import AccessControlModal from '../common/AccessControlModal.svelte';
|
import AccessControlModal from '../common/AccessControlModal.svelte';
|
||||||
import { user } from '$lib/stores';
|
import { user } from '$lib/stores';
|
||||||
import { slugify } from '$lib/utils';
|
import { slugify, parseFrontmatter, formatSkillName } from '$lib/utils';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import { updateSkillAccessGrants } from '$lib/apis/skills';
|
import { updateSkillAccessGrants } from '$lib/apis/skills';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
@@ -31,8 +31,32 @@
|
|||||||
let accessGrants = [];
|
let accessGrants = [];
|
||||||
let showAccessControlModal = false;
|
let showAccessControlModal = false;
|
||||||
let hasManualEdit = false;
|
let hasManualEdit = false;
|
||||||
|
let hasManualName = false;
|
||||||
|
let hasManualDescription = false;
|
||||||
|
let isFrontmatterDetected = false;
|
||||||
|
|
||||||
$: if (!edit && !hasManualEdit) {
|
// Auto-detect frontmatter and fill name/description in create mode
|
||||||
|
$: if (!edit && content) {
|
||||||
|
const fm = parseFrontmatter(content);
|
||||||
|
if (fm.name) {
|
||||||
|
isFrontmatterDetected = true;
|
||||||
|
if (!hasManualName) {
|
||||||
|
name = formatSkillName(fm.name);
|
||||||
|
}
|
||||||
|
if (!hasManualEdit) {
|
||||||
|
id = fm.name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isFrontmatterDetected = false;
|
||||||
|
}
|
||||||
|
if (fm.description && !hasManualDescription) {
|
||||||
|
description = fm.description;
|
||||||
|
}
|
||||||
|
} else if (!edit && !content) {
|
||||||
|
isFrontmatterDetected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$: if (!edit && !hasManualEdit && !isFrontmatterDetected) {
|
||||||
id = name !== '' ? slugify(name) : '';
|
id = name !== '' ? slugify(name) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +64,14 @@
|
|||||||
hasManualEdit = true;
|
hasManualEdit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleNameInput(e: Event) {
|
||||||
|
hasManualName = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDescriptionInput(e: Event) {
|
||||||
|
hasManualDescription = true;
|
||||||
|
}
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
toast.error($i18n.t('You do not have permission to edit this skill.'));
|
toast.error($i18n.t('You do not have permission to edit this skill.'));
|
||||||
@@ -68,6 +100,10 @@
|
|||||||
description = skill.description || '';
|
description = skill.description || '';
|
||||||
content = skill.content || '';
|
content = skill.content || '';
|
||||||
accessGrants = skill?.access_grants === undefined ? [] : skill?.access_grants;
|
accessGrants = skill?.access_grants === undefined ? [] : skill?.access_grants;
|
||||||
|
|
||||||
|
if (name) hasManualName = true;
|
||||||
|
if (description) hasManualDescription = true;
|
||||||
|
if (id) hasManualEdit = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -120,6 +156,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder={$i18n.t('Skill Name')}
|
placeholder={$i18n.t('Skill Name')}
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
|
on:input={handleNameInput}
|
||||||
required
|
required
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
@@ -176,6 +213,7 @@
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder={$i18n.t('Skill Description')}
|
placeholder={$i18n.t('Skill Description')}
|
||||||
bind:value={description}
|
bind:value={description}
|
||||||
|
on:input={handleDescriptionInput}
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -1699,3 +1699,21 @@ export const getCodeBlockContents = (content: string): object => {
|
|||||||
js: jsContent.trim()
|
js: jsContent.trim()
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export const parseFrontmatter = (content) => {
|
||||||
|
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
||||||
|
if (match) {
|
||||||
|
const frontmatter = {};
|
||||||
|
match[1].split('\n').forEach((line) => {
|
||||||
|
const [key, ...value] = line.split(':');
|
||||||
|
if (key && value) {
|
||||||
|
frontmatter[key.trim()] = value.join(':').trim().replace(/^["']|["']$/g, '');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return frontmatter;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatSkillName = (name) => {
|
||||||
|
return name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user