diff --git a/.env.example b/.env.example index ce9481c5..cdd5e401 100644 --- a/.env.example +++ b/.env.example @@ -83,4 +83,6 @@ CADDY_AUTHORIZED_IPS= GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= -GOOGLE_REDIRECT_URL=http://localhost:3000/settings/connections/callback/google \ No newline at end of file +GOOGLE_REDIRECT_URL=http://localhost:3000/settings/connections/callback/google + +GOOGLE_FONTS_API_KEY= \ No newline at end of file diff --git a/app/Http/Controllers/FontsController.php b/app/Http/Controllers/FontsController.php new file mode 100644 index 00000000..cfff8532 --- /dev/null +++ b/app/Http/Controllers/FontsController.php @@ -0,0 +1,27 @@ +successful()) { + $fonts = collect($response->json()['items'])->filter(function ($font) { + return !in_array($font['category'], ['monospace']); + })->map(function ($font) { + return $font['family']; + })->toArray(); + return response()->json($fonts); + } + + return []; + }); + } +} diff --git a/app/Http/Requests/UserFormRequest.php b/app/Http/Requests/UserFormRequest.php index ded6a14b..c7ce75d6 100644 --- a/app/Http/Requests/UserFormRequest.php +++ b/app/Http/Requests/UserFormRequest.php @@ -29,6 +29,7 @@ abstract class UserFormRequest extends \Illuminate\Foundation\Http\FormRequest 'visibility' => ['required', Rule::in(Form::VISIBILITY)], // Customization + 'font_family' => 'string|nullable', 'theme' => ['required', Rule::in(Form::THEMES)], 'width' => ['required', Rule::in(Form::WIDTHS)], 'size' => ['required', Rule::in(Form::SIZES)], diff --git a/app/Models/Forms/Form.php b/app/Models/Forms/Form.php index 2088a724..cbd5f34c 100644 --- a/app/Models/Forms/Form.php +++ b/app/Models/Forms/Form.php @@ -31,9 +31,9 @@ class Form extends Model implements CachableAttributes public const DARK_MODE_VALUES = ['auto', 'light', 'dark']; - public const SIZES = ['sm','md','lg']; + public const SIZES = ['sm', 'md', 'lg']; - public const BORDER_RADIUS = ['none','small','full']; + public const BORDER_RADIUS = ['none', 'small', 'full']; public const THEMES = ['default', 'simple', 'notion']; @@ -53,6 +53,7 @@ class Form extends Model implements CachableAttributes 'visibility', // Customization + 'font_family', 'custom_domain', 'size', 'border_radius', diff --git a/client/components/open/editors/FontCard.vue b/client/components/open/editors/FontCard.vue new file mode 100644 index 00000000..09a5c67b --- /dev/null +++ b/client/components/open/editors/FontCard.vue @@ -0,0 +1,68 @@ + + + diff --git a/client/components/open/editors/GoogleFontPicker.vue b/client/components/open/editors/GoogleFontPicker.vue new file mode 100644 index 00000000..61d784bc --- /dev/null +++ b/client/components/open/editors/GoogleFontPicker.vue @@ -0,0 +1,141 @@ + + + diff --git a/client/components/open/forms/OpenCompleteForm.vue b/client/components/open/forms/OpenCompleteForm.vue index acc89c42..fc9b7686 100644 --- a/client/components/open/forms/OpenCompleteForm.vue +++ b/client/components/open/forms/OpenCompleteForm.vue @@ -2,7 +2,14 @@
+ +

.open-complete-form { + * { + font-family: var(--font-family) !important; + } .form-description, .nf-text { ol { @apply list-decimal list-inside; diff --git a/client/components/open/forms/components/form-components/FormCustomization.vue b/client/components/open/forms/components/form-components/FormCustomization.vue index b93ee83f..0117c535 100644 --- a/client/components/open/forms/components/form-components/FormCustomization.vue +++ b/client/components/open/forms/components/form-components/FormCustomization.vue @@ -53,6 +53,24 @@ label="Form Theme" /> + + + + {{ form.font_family || 'Default' }} + + + +
import { useWorkingFormStore } from "../../../../../stores/working_form" import EditorOptionsPanel from "../../../editors/EditorOptionsPanel.vue" +import GoogleFontPicker from "../../../editors/GoogleFontPicker.vue" import ProTag from "~/components/global/ProTag.vue" const workingFormStore = useWorkingFormStore() const form = storeToRefs(workingFormStore).content const isMounted = ref(false) const confetti = useConfetti() +const showGoogleFontPicker = ref(false) onMounted(() => { isMounted.value = true @@ -190,4 +210,9 @@ const onChangeConfettiOnSubmission = (val) => { confetti.play() } } + +const onApplyFont = (val) => { + form.value.font_family = val + showGoogleFontPicker.value = false +} diff --git a/client/composables/forms/initForm.js b/client/composables/forms/initForm.js index c9d4777a..a75ed6cf 100644 --- a/client/composables/forms/initForm.js +++ b/client/composables/forms/initForm.js @@ -9,6 +9,7 @@ export const initForm = (defaultValue = {}, withDefaultProperties = false) => { properties: withDefaultProperties ? getDefaultProperties() : [], // Customization + font_family: null, theme: "default", width: "centered", dark_mode: "auto", diff --git a/client/pages/forms/[slug]/index.vue b/client/pages/forms/[slug]/index.vue index c6224113..48b1254b 100644 --- a/client/pages/forms/[slug]/index.vue +++ b/client/pages/forms/[slug]/index.vue @@ -178,6 +178,39 @@ const pageMeta = computed(() => { } return {} }) + +const getFontUrl = computed(() => { + if(!form.value || !form.value.font_family) return null + const family = form.value.font_family.replace(/ /g, '+') + return `https://fonts.googleapis.com/css?family=${family}:wght@400,500,700,800,900&display=swap` +}) + +const headLinks = computed(() => { + const links = [] + if (form.value && form.value.font_family) { + links.push({ + rel: 'stylesheet', + href: getFontUrl.value + }) + } + if (pageMeta.value.page_favicon) { + links.push({ + rel: 'icon', type: 'image/x-icon', + href: pageMeta.value.page_favicon + }) + links.push({ + rel: 'apple-touch-icon', + type: 'image/png', + href: pageMeta.value.page_favicon + }) + links.push({ + rel: 'shortcut icon', + href: pageMeta.value.page_favicon + }) + } + return links +}) + useOpnSeoMeta({ title: () => { if (pageMeta.value.page_title) { @@ -209,21 +242,7 @@ useHead({ } return titleChunk ? `${titleChunk} - OpnForm` : 'OpnForm' }, - link: pageMeta.value.page_favicon ? [ - { - rel: 'icon', type: 'image/x-icon', - href: pageMeta.value.page_favicon - }, - { - rel: 'apple-touch-icon', - type: 'image/png', - href: pageMeta.value.page_favicon - }, - { - rel: 'shortcut icon', - href: pageMeta.value.page_favicon - } - ] : {}, + link: headLinks.value, meta: pageMeta.value.page_favicon ? [ { name: 'apple-mobile-web-app-capable', diff --git a/config/services.php b/config/services.php index f056acb6..163f10c7 100644 --- a/config/services.php +++ b/config/services.php @@ -69,5 +69,7 @@ return [ 'client_id' => env('GOOGLE_CLIENT_ID'), 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 'redirect' => env('GOOGLE_REDIRECT_URL'), - ] + ], + + 'google_fonts_api_key' => env('GOOGLE_FONTS_API_KEY'), ]; diff --git a/database/migrations/2024_08_12_091853_add_font_family_to_forms.php b/database/migrations/2024_08_12_091853_add_font_family_to_forms.php new file mode 100644 index 00000000..281a23d2 --- /dev/null +++ b/database/migrations/2024_08_12_091853_add_font_family_to_forms.php @@ -0,0 +1,27 @@ +string('font_family')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('forms', function (Blueprint $table) { + $table->dropColumn(['font_family']); + }); + } +}; diff --git a/routes/api.php b/routes/api.php index 5ef45b09..bc57d58d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -313,6 +313,9 @@ Route::prefix('content')->name('content.')->group(function () { Route::get('/sitemap-urls', [\App\Http\Controllers\SitemapController::class, 'index'])->name('sitemap.index'); +// Fonts +Route::get('/fonts', [\App\Http\Controllers\FontsController::class, 'index'])->name('fonts.index'); + // Templates Route::prefix('templates')->group(function () { Route::get('/', [TemplateController::class, 'index'])->name('templates.index');