Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lang/en/messages.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
'section_defaults' => 'Section Defaults',
'section_defaults_description' => 'Override default meta, social, sitemap, and other settings for any particular section.',
'configure_site_defaults' => 'Configure Site Defaults',
'configure_section_defaults' => 'Configure Section Defaults',
'meta' => 'Meta',
'opengraph' => 'Open Graph',
'social' => 'Social',
Expand Down
133 changes: 133 additions & 0 deletions resources/js/components/section-defaults/ConfigureModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup>
import { Modal, Icon, Field, Heading, Select, Button, } from '@statamic/cms/ui';
import { ref, onMounted, getCurrentInstance } from 'vue';

const instance = getCurrentInstance();
const { $axios } = instance.appContext.config.globalProperties;

const emit = defineEmits(['closed', 'saved']);
const props = defineProps({ route: String });

const open = ref(true);
const busy = ref(false);
const sites = ref(null);
const error = ref(null);

const siteOriginOptions = (site) => {
return sites.value
.map((s) => ({ value: s.handle, label: __(s.name) }))
.filter((s) => s.value !== site.handle);
};

const save = () => {
busy.value = true;
error.value = null;

axios.patch(props.route, { sites: sites.value })
.then(() => {
Statamic.$toast.success(__('Saved'));
emit('saved');
close();
})
.catch((e) => {
error.value = e.response.data.errors?.sites[0];
Statamic.$toast.error(__('Something went wrong'));
})
.finally(() => {
busy.value = false;
});
};

const close = () => {
open.value = false;
setTimeout(() => emit('closed'), 200);
};

onMounted(() => {
busy.value = true;

$axios.get(props.route)
.then((response) => {
sites.value = response.data.sites;
})
.catch((error) => {
Statamic.$toast.error(__('Something went wrong'));
close();
})
.finally(() => {
busy.value = false;
});
});
</script>

<template>
<Modal
:title="__('seo-pro::messages.configure_section_defaults')"
:open
:dismissable="!busy"
@dismissed="close"
@update:model-value="close"
>
<div
v-if="!sites"
class="pointer-events-none absolute inset-0 flex select-none items-center justify-center bg-white bg-opacity-75 dark:bg-dark-700"
>
<Icon name="loading" />
</div>

<Field v-else :label="__('Sites')" :error required>
<table class="grid-table">
<thead>
<tr>
<th scope="col">
<div class="flex items-center justify-between">
{{ __('Site') }}
</div>
</th>
<th scope="col">
<div class="flex items-center justify-between">
{{ __('Origin') }}
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="site in sites" :key="site.handle">
<td class="grid-cell">
<div class="flex items-center gap-2">
<Heading :text="__(site.name)" />
</div>
</td>
<td class="grid-cell">
<Select
class="w-full"
:options="siteOriginOptions(site)"
:clearable="true"
:model-value="site.origin"
@update:model-value="site.origin = $event"
/>
</td>
</tr>
</tbody>
</table>
</Field>

<template #footer>
<div class="flex items-center justify-end space-x-3 pt-3 pb-1">
<Button
variant="ghost"
:disabled="busy"
:text="__('Cancel')"
@click="close"
/>
<Button
type="submit"
variant="primary"
:disabled="busy"
:text="__('Save')"
@click="save"
/>
</div>
</template>
</Modal>
</template>
2 changes: 2 additions & 0 deletions resources/js/cp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ReportsIndex from './pages/reports/Index.vue';
import ReportsEmpty from './pages/reports/Empty.vue';
import ReportsShow from './pages/reports/Show.vue';
import SectionDefaultsIndex from './pages/section-defaults/Index.vue';
import SectionDefaultsEdit from './pages/section-defaults/Edit.vue';
import SiteDefaultsEdit from './pages/site-defaults/Edit.vue';
import SeoProFieldtype from './components/fieldtypes/SeoProFieldtype.vue';
import PreviewsFieldtype from "./components/fieldtypes/PreviewsFieldtype.vue";
Expand All @@ -15,6 +16,7 @@ Statamic.booting(() => {
Statamic.$inertia.register('seo-pro::Reports/Empty', ReportsEmpty);
Statamic.$inertia.register('seo-pro::Reports/Show', ReportsShow);
Statamic.$inertia.register('seo-pro::SectionDefaults/Index', SectionDefaultsIndex);
Statamic.$inertia.register('seo-pro::SectionDefaults/Edit', SectionDefaultsEdit);
Statamic.$inertia.register('seo-pro::SiteDefaults/Edit', SiteDefaultsEdit);

Statamic.$components.register('seo_pro-fieldtype', SeoProFieldtype);
Expand Down
174 changes: 174 additions & 0 deletions resources/js/pages/section-defaults/Edit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<script setup>
import { onMounted, onUnmounted, ref, useTemplateRef, computed, nextTick, getCurrentInstance } from 'vue';
import { Header, Dropdown, DropdownMenu, DropdownItem, Button, PublishContainer } from '@statamic/cms/ui';
import { Pipeline, Request, BeforeSaveHooks, AfterSaveHooks } from '@statamic/cms/save-pipeline';
import { Head, router } from '@statamic/cms/inertia';
import SiteSelector from '../../components/SiteSelector.vue';
import ConfigureModal from '../../components/section-defaults/ConfigureModal.vue';

const instance = getCurrentInstance();
const { $axios } = instance.appContext.config.globalProperties;

const props = defineProps({
blueprint: Object,
initialReference: String,
initialValues: Object,
initialMeta: Object,
initialEnabled: Boolean,
initialLocalizations: Array,
initialLocalizedFields: Array,
initialHasOrigin: Boolean,
initialOriginValues: Object,
initialOriginMeta: Object,
initialSite: String,
action: String,
title: String,
configureUrl: String,
});

const container = useTemplateRef('container');
const reference = ref(props.initialReference);
const values = ref(props.initialValues);
const meta = ref(props.initialMeta);
const errors = ref({});
const localizing = ref(false);
const localizations = ref(props.initialLocalizations);
const localizedFields = ref(props.initialLocalizedFields);
const hasOrigin = ref(props.initialHasOrigin);
const originValues = ref(props.initialOriginValues);
const originMeta = ref(props.initialOriginMeta);
const site = ref(props.initialSite);
const syncFieldConfirmationText = ref(__('messages.sync_entry_field_confirmation_text'));
const pendingLocalization = ref(null);
const saving = ref(false);
const configureModalOpen = ref(false);

function save() {
new Pipeline()
.provide({ container, errors, saving })
.through([
new BeforeSaveHooks('section-defaults'),
new Request(props.action, 'patch', {
site: site.value,
_localized: localizedFields.value,
}),
new AfterSaveHooks('section-defaults'),
])
.then((response) => {
Statamic.$toast.success(__('Saved'));
});
}

let saveKeyBinding;

onMounted(() => {
saveKeyBinding = Statamic.$keys.bindGlobal(['mod+s'], (e) => {
e.preventDefault();
save();
});
});

onUnmounted(() => saveKeyBinding.destroy());

const isDirty = computed(() => Statamic.$dirty.has('section-defaults'));
const showLocalizationSelector = computed(() => localizations.value.length > 1);

const localizationSelected = (localizationHandle) => {
let localization = localizations.value.find((localization) => localization.handle === localizationHandle);

if (localization.active) return;

if (isDirty.value) {
pendingLocalization.value = localization;
return;
}

switchToLocalization(localization);
};

const confirmSwitchLocalization = () => {
switchToLocalization(pendingLocalization.value);
pendingLocalization.value = null;
};

const switchToLocalization = (localization) => {
localizing.value = localization.handle;

window.history.replaceState({}, '', localization.url);

$axios.get(localization.url).then((response) => {
const data = response.data;
reference.value = data.initialReference;
values.value = data.initialValues;
originValues.value = data.initialOriginValues;
originMeta.value = data.initialOriginMeta;
meta.value = data.initialMeta;
localizations.value = data.initialLocalizations;
localizedFields.value = data.initialLocalizedFields;
hasOrigin.value = data.initialHasOrigin;
site.value = localization.handle;
localizing.value = false;
nextTick(() => container.value.clearDirtyState());
});
};
</script>

<template>
<Head :title="title" />

<div class="max-w-5xl mx-auto">
<Header :title="title" icon="folder">
<Dropdown v-if="showLocalizationSelector && configureUrl">
<template #trigger>
<Button icon="dots" variant="ghost" :aria-label="__('Open dropdown menu')" />
</template>
<DropdownMenu>
<DropdownItem :text="__('Configure')" icon="cog" @click="configureModalOpen = true" />
</DropdownMenu>
</Dropdown>

<SiteSelector
v-if="showLocalizationSelector"
:sites="localizations"
:model-value="site"
@update:modelValue="localizationSelected"
/>

<Button variant="primary" :text="__('Save')" @click="save" :disabled="saving" />
</Header>

<PublishContainer
ref="container"
name="section-defaults"
:reference
:blueprint
v-model="values"
:meta
:errors
:site
:origin-values
:origin-meta
v-model:modified-fields="localizedFields"
:sync-field-confirmation-text
:track-dirty-state="true"
as-config
/>

<ConfigureModal
v-if="configureModalOpen"
:route="configureUrl"
@saved="() => router.reload()"
@closed="configureModalOpen = false"
/>

<confirmation-modal
v-if="pendingLocalization"
:title="__('Unsaved Changes')"
:body-text="__('Are you sure? Unsaved changes will be lost.')"
:button-text="__('Continue')"
:danger="true"
@confirm="confirmSwitchLocalization"
@cancel="pendingLocalization = null"
/>
</div>
</template>
3 changes: 3 additions & 0 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
Route::patch('site-defaults/configure', [Controllers\CP\ConfigureSiteDefaultsController::class, 'update'])->name('site-defaults.configure.update');

Route::get('section-defaults', [Controllers\CP\SectionDefaultsController::class, 'index'])->name('section-defaults.index');
Route::get('section-defaults/configure', [Controllers\CP\ConfigureSectionDefaultsController::class, 'edit'])->name('section-defaults.configure.edit');
Route::patch('section-defaults/configure', [Controllers\CP\ConfigureSectionDefaultsController::class, 'update'])->name('section-defaults.configure.update');
Route::get('section-defaults/collections/{seo_pro_collection}/edit', [Controllers\CP\CollectionDefaultsController::class, 'edit'])->name('section-defaults.collections.edit');

Route::patch('section-defaults/collections/{seo_pro_collection}', [Controllers\CP\CollectionDefaultsController::class, 'update'])->name('section-defaults.collections.update');
Route::get('section-defaults/taxonomies/{seo_pro_taxonomy}/edit', [Controllers\CP\TaxonomyDefaultsController::class, 'edit'])->name('section-defaults.taxonomies.edit');
Route::patch('section-defaults/taxonomies/{seo_pro_taxonomy}', [Controllers\CP\TaxonomyDefaultsController::class, 'update'])->name('section-defaults.taxonomies.update');
Expand Down
11 changes: 11 additions & 0 deletions src/Events/SectionDefaultsSaved.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Statamic\SeoPro\Events;

use Statamic\Events\Event;
use Statamic\SeoPro\SectionDefaults\LocalizedSectionDefaults;

class SectionDefaultsSaved extends Event
{
public function __construct(public LocalizedSectionDefaults $defaults) {}
}
Loading