211 lines
9 KiB
Svelte
211 lines
9 KiB
Svelte
|
|
<script>
|
||
|
|
/**
|
||
|
|
* Site Settings Administration Component
|
||
|
|
* Allows admins to configure Site Title, SEO, and Supported Languages.
|
||
|
|
*/
|
||
|
|
let { adminPath, settings = {} } = $props();
|
||
|
|
|
||
|
|
let siteTitle = $state('');
|
||
|
|
let seoDescription = $state('');
|
||
|
|
let seoKeywordsString = $state('');
|
||
|
|
let supportedLanguages = $state([{ name: 'English', abbreviation: 'en' }]);
|
||
|
|
let defaultLocale = $state('en');
|
||
|
|
let translationDriver = $state('mock');
|
||
|
|
let googleTranslateKey = $state('');
|
||
|
|
let deeplApiKey = $state('');
|
||
|
|
let openaiApiKey = $state('');
|
||
|
|
|
||
|
|
$effect(() => {
|
||
|
|
siteTitle = settings.site_title || '';
|
||
|
|
seoDescription = settings.seo_description || '';
|
||
|
|
seoKeywordsString = (settings.seo_keywords || []).join(', ');
|
||
|
|
supportedLanguages = settings.supported_languages || [{ name: 'English', abbreviation: 'en' }];
|
||
|
|
defaultLocale = settings.default_locale || 'en';
|
||
|
|
translationDriver = settings.translation_driver || 'mock';
|
||
|
|
googleTranslateKey = settings.google_translate_key || '';
|
||
|
|
deeplApiKey = settings.deepl_api_key || '';
|
||
|
|
openaiApiKey = settings.openai_api_key || '';
|
||
|
|
});
|
||
|
|
|
||
|
|
let isSaving = $state(false);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a new language to the supported list.
|
||
|
|
*/
|
||
|
|
function addLanguage() {
|
||
|
|
supportedLanguages = [...supportedLanguages, { name: '', abbreviation: '' }];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Remove a language from the supported list.
|
||
|
|
* @param {number} index
|
||
|
|
*/
|
||
|
|
function removeLanguage(index) {
|
||
|
|
if (supportedLanguages.length > 1) {
|
||
|
|
supportedLanguages = supportedLanguages.filter((_, i) => i !== index);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Submit the settings form to the backend.
|
||
|
|
*/
|
||
|
|
async function saveSettings() {
|
||
|
|
isSaving = true;
|
||
|
|
|
||
|
|
const formData = {
|
||
|
|
site_title: siteTitle,
|
||
|
|
seo_description: seoDescription,
|
||
|
|
seo_keywords: seoKeywordsString.split(',').map(k => k.trim()).filter(k => k !== ''),
|
||
|
|
supported_languages: supportedLanguages,
|
||
|
|
default_locale: defaultLocale,
|
||
|
|
translation_driver: translationDriver,
|
||
|
|
google_translate_key: googleTranslateKey,
|
||
|
|
deepl_api_key: deeplApiKey,
|
||
|
|
openai_api_key: openaiApiKey,
|
||
|
|
_token: document.querySelector('meta[name="csrf-token"]').getAttribute('content')
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(`${adminPath}/settings`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {
|
||
|
|
'Content-Type': 'application/json',
|
||
|
|
'Accept': 'application/json',
|
||
|
|
'X-CSRF-TOKEN': formData._token
|
||
|
|
},
|
||
|
|
body: JSON.stringify(formData)
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
window.location.reload();
|
||
|
|
} else {
|
||
|
|
const data = await response.json();
|
||
|
|
alert('Error saving settings: ' + (data.message || 'Unknown error'));
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Error:', error);
|
||
|
|
alert('An unexpected error occurred.');
|
||
|
|
} finally {
|
||
|
|
isSaving = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<div class="ui container">
|
||
|
|
<div class="ui segment">
|
||
|
|
<h2 class="ui header">
|
||
|
|
<i class="settings icon"></i>
|
||
|
|
<div class="content">
|
||
|
|
Site Settings
|
||
|
|
<div class="sub header">Configure your site's core properties and localization.</div>
|
||
|
|
</div>
|
||
|
|
</h2>
|
||
|
|
|
||
|
|
<form class="ui form" onsubmit={(e) => { e.preventDefault(); saveSettings(); }}>
|
||
|
|
<h4 class="ui dividing header">General Settings</h4>
|
||
|
|
<div class="field">
|
||
|
|
<label for="site-title">Site Title</label>
|
||
|
|
<input type="text" id="site-title" bind:value={siteTitle} placeholder="e.g. My Awesome Site" required>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h4 class="ui dividing header">SEO Settings</h4>
|
||
|
|
<div class="field">
|
||
|
|
<label for="seo-description">Meta Description</label>
|
||
|
|
<textarea id="seo-description" rows="3" bind:value={seoDescription} placeholder="Brief description for search engines..."></textarea>
|
||
|
|
</div>
|
||
|
|
<div class="field">
|
||
|
|
<label for="seo-keywords">Keywords (comma separated)</label>
|
||
|
|
<input type="text" id="seo-keywords" bind:value={seoKeywordsString} placeholder="cms, blog, site">
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h4 class="ui dividing header">Localization</h4>
|
||
|
|
<div class="field">
|
||
|
|
<label for="default-locale">Default Site Locale</label>
|
||
|
|
<select id="default-locale" class="ui dropdown" bind:value={defaultLocale}>
|
||
|
|
{#each supportedLanguages as lang}
|
||
|
|
{#if lang.abbreviation}
|
||
|
|
<option value={lang.abbreviation}>{lang.name} ({lang.abbreviation})</option>
|
||
|
|
{/if}
|
||
|
|
{/each}
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="field">
|
||
|
|
<span style="font-weight: bold; display: block; margin-bottom: 0.5em;">Supported Languages</span>
|
||
|
|
<div class="ui list">
|
||
|
|
{#each supportedLanguages as lang, index}
|
||
|
|
<div class="item">
|
||
|
|
<div class="fields" style="margin-bottom: 0.5em;">
|
||
|
|
<div class="eight wide field">
|
||
|
|
<input type="text" placeholder="Language Name (e.g. Spanish)" bind:value={lang.name} required aria-label="Language Name">
|
||
|
|
</div>
|
||
|
|
<div class="four wide field">
|
||
|
|
<input type="text" placeholder="Code (e.g. es)" bind:value={lang.abbreviation} maxlength="2" required aria-label="Language Code">
|
||
|
|
</div>
|
||
|
|
<div class="four wide field">
|
||
|
|
<button type="button" class="ui icon button basic red" onclick={() => removeLanguage(index)} disabled={supportedLanguages.length <= 1} title="Remove Language" aria-label="Remove Language">
|
||
|
|
<i class="trash icon"></i>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{/each}
|
||
|
|
</div>
|
||
|
|
<button type="button" class="ui button basic" onclick={addLanguage}>
|
||
|
|
<i class="plus icon"></i> Add Language
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h4 class="ui dividing header">Translation Services</h4>
|
||
|
|
<div class="field">
|
||
|
|
<label for="translation-driver">Translation Driver</label>
|
||
|
|
<select id="translation-driver" class="ui dropdown" bind:value={translationDriver}>
|
||
|
|
<option value="mock">Mock (Development Only)</option>
|
||
|
|
<option value="google">Google Translate</option>
|
||
|
|
<option value="deepl">DeepL</option>
|
||
|
|
<option value="openai">OpenAI (GPT-4o-mini)</option>
|
||
|
|
</select>
|
||
|
|
<div class="ui pointing label">
|
||
|
|
Choose "Mock" for testing without an API key, or select a provider for production.
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{#if translationDriver === 'google'}
|
||
|
|
<div class="field">
|
||
|
|
<label for="google-translate-key">Google Translate API Key</label>
|
||
|
|
<input type="password" id="google-translate-key" bind:value={googleTranslateKey} placeholder="Enter your Google Cloud Translation API Key">
|
||
|
|
<div class="ui pointing label">
|
||
|
|
Required for automated block translations using Google.
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
{#if translationDriver === 'deepl'}
|
||
|
|
<div class="field">
|
||
|
|
<label for="deepl-api-key">DeepL API Key</label>
|
||
|
|
<input type="password" id="deepl-api-key" bind:value={deeplApiKey} placeholder="Enter your DeepL API Auth Key">
|
||
|
|
<div class="ui pointing label">
|
||
|
|
Required for automated block translations using DeepL.
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
{#if translationDriver === 'openai'}
|
||
|
|
<div class="field">
|
||
|
|
<label for="openai-api-key">OpenAI API Key</label>
|
||
|
|
<input type="password" id="openai-api-key" bind:value={openaiApiKey} placeholder="Enter your OpenAI API Key">
|
||
|
|
<div class="ui pointing label">
|
||
|
|
Required for automated block translations using OpenAI (GPT-4o-mini).
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
{/if}
|
||
|
|
|
||
|
|
<div class="ui divider"></div>
|
||
|
|
|
||
|
|
<button class="ui primary button" class:loading={isSaving} disabled={isSaving}>
|
||
|
|
Save Settings
|
||
|
|
</button>
|
||
|
|
</form>
|
||
|
|
</div>
|
||
|
|
</div>
|