From 22ab62cd6bf639910beef09ad285030e7526d63a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 25 Sep 2025 14:36:03 -0400 Subject: [PATCH 01/59] wip --- composer.json | 1 + package-lock.json | 42 ++++++++++ package.json | 3 +- resources/js/bootstrap/App.vue | 4 - resources/js/bootstrap/statamic.js | 34 ++++++-- resources/js/pages/Dashboard.vue | 6 ++ resources/js/pages/collections/Show.vue | 6 ++ resources/js/pages/layout/Breadcrumbs.vue | 7 ++ resources/js/pages/layout/CommandPalette.vue | 7 ++ resources/js/pages/layout/Header.vue | 33 ++++++++ resources/js/pages/layout/Layout.vue | 68 +++++++++++++++ resources/js/pages/layout/Logo.vue | 30 +++++++ resources/js/pages/layout/Nav.vue | 62 ++++++++++++++ resources/js/pages/layout/SiteSelector.vue | 13 +++ resources/js/pages/layout/UserDropdown.vue | 83 +++++++++++++++++++ resources/js/pages/layout/ViewSiteButton.vue | 16 ++++ resources/views/layout.blade.php | 3 + resources/views/partials/head.blade.php | 1 + .../CP/Collections/CollectionsController.php | 3 + .../Controllers/CP/DashboardController.php | 3 + .../Middleware/CP/HandleInertiaRequests.php | 43 ++++++++++ src/Providers/CpServiceProvider.php | 1 + 22 files changed, 458 insertions(+), 11 deletions(-) create mode 100644 resources/js/pages/Dashboard.vue create mode 100644 resources/js/pages/collections/Show.vue create mode 100644 resources/js/pages/layout/Breadcrumbs.vue create mode 100644 resources/js/pages/layout/CommandPalette.vue create mode 100644 resources/js/pages/layout/Header.vue create mode 100644 resources/js/pages/layout/Layout.vue create mode 100644 resources/js/pages/layout/Logo.vue create mode 100644 resources/js/pages/layout/Nav.vue create mode 100644 resources/js/pages/layout/SiteSelector.vue create mode 100644 resources/js/pages/layout/UserDropdown.vue create mode 100644 resources/js/pages/layout/ViewSiteButton.vue create mode 100644 src/Http/Middleware/CP/HandleInertiaRequests.php diff --git a/composer.json b/composer.json index 89d507a3c0a..ffe4868bac7 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "bacon/bacon-qr-code": "^3.0", "composer/semver": "^3.4", "guzzlehttp/guzzle": "^6.3 || ^7.0", + "inertiajs/inertia-laravel": "^2.0", "james-heinrich/getid3": "^1.9.21", "laravel/framework": "^11.34 || ^12.0", "laravel/prompts": "^0.3.0", diff --git a/package-lock.json b/package-lock.json index 36b3ffdf204..387bb951f6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@floating-ui/dom": "^1.6.0", "@he-tree/vue": "^2.10.0-beta.2", "@hoppscotch/vue-toasted": "^0.1.0", + "@inertiajs/vue3": "^2.1.11", "@internationalized/date": "^3.7.0", "@shopify/draggable": "^1.0.0-beta.12", "@tiptap/core": "^3.0.0", @@ -931,6 +932,32 @@ "vue": "^3.2.37" } }, + "node_modules/@inertiajs/core": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.1.11.tgz", + "integrity": "sha512-TF1UE1UjNRGyxJZGYe5IwDZwgyTvviE2XoJRHOS4SjS8kVzIqeBb3disIkLiJqshRNSNP8syggOro4TLtVubvA==", + "license": "MIT", + "dependencies": { + "@types/lodash-es": "^4.17.12", + "axios": "^1.12.0", + "lodash-es": "^4.17.21", + "qs": "^6.9.0" + } + }, + "node_modules/@inertiajs/vue3": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.1.11.tgz", + "integrity": "sha512-9+ny7uaPwMeLMl01GOoNFmC4ssCZ/RPH8AeyAwMVdfIEnUaXVqWwf8epHeLdc/wH17hWYWlSTxAOLdSUbjvaDQ==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "2.1.11", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, "node_modules/@internationalized/date": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz", @@ -2385,6 +2412,21 @@ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "license": "MIT" }, + "node_modules/@types/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", diff --git a/package.json b/package.json index 1799edc634e..3be1c17cd89 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ "@floating-ui/dom": "^1.6.0", "@he-tree/vue": "^2.10.0-beta.2", "@hoppscotch/vue-toasted": "^0.1.0", + "@inertiajs/vue3": "^2.1.11", "@internationalized/date": "^3.7.0", "@shopify/draggable": "^1.0.0-beta.12", "@tiptap/core": "^3.0.0", - "@tiptap/extensions": "^3.0.0", "@tiptap/extension-blockquote": "^3.0.0", "@tiptap/extension-bold": "^3.0.0", "@tiptap/extension-bullet-list": "^3.0.0", @@ -48,6 +48,7 @@ "@tiptap/extension-text-align": "^3.0.0", "@tiptap/extension-typography": "^3.0.0", "@tiptap/extension-underline": "^3.0.0", + "@tiptap/extensions": "^3.0.0", "@tiptap/pm": "^3.0.0", "@tiptap/vue-3": "^3.0.0", "alpinejs": "^3.1.1", diff --git a/resources/js/bootstrap/App.vue b/resources/js/bootstrap/App.vue index fe3becafe8a..d9ba2727a9e 100644 --- a/resources/js/bootstrap/App.vue +++ b/resources/js/bootstrap/App.vue @@ -29,7 +29,6 @@ import PreferencesEditForm from '../components/preferences/EditForm.vue'; import NavigationView from '../components/navigation/View.vue'; import TaxonomyBlueprintListing from '../components/taxonomies/BlueprintListing.vue'; import Updater from '../components/updater/Updater.vue'; -import PortalTargets from '../components/portals/PortalTargets.vue'; import SitesEditForm from '../components/sites/EditForm.vue'; import CommandPalette from '../components/command-palette/CommandPalette.vue'; import ItemActions from '../components/actions/ItemActions.vue'; @@ -37,7 +36,6 @@ import BulkActions from '../components/actions/BulkActions.vue'; import LicensingAlert from '../components/LicensingAlert.vue'; import { defineAsyncComponent } from 'vue'; -import { ConfigProvider } from 'reka-ui'; export default { components: { @@ -73,9 +71,7 @@ export default { TaxonomyBlueprintListing, NavBuilder: defineAsyncComponent(() => import('../components/nav/Builder.vue')), Updater, - PortalTargets, SitesEditForm, - ConfigProvider, ItemActions, BulkActions, LicensingAlert, diff --git a/resources/js/bootstrap/statamic.js b/resources/js/bootstrap/statamic.js index 9df494eb192..0e184129182 100644 --- a/resources/js/bootstrap/statamic.js +++ b/resources/js/bootstrap/statamic.js @@ -1,4 +1,4 @@ -import { createApp, ref, markRaw } from 'vue'; +import { createApp, markRaw, h } from 'vue'; import App from './App.vue'; import { createPinia, defineStore } from 'pinia'; import axios from 'axios'; @@ -14,6 +14,7 @@ import useDirtyState from '../composables/dirty-state'; import VueClickAway from 'vue3-click-away'; import FloatingVue from 'floating-vue'; import 'floating-vue/dist/style.css'; +import { createInertiaApp } from '@inertiajs/vue3'; import Toasts from '../components/Toasts'; import PortalVue from 'portal-vue'; import Keys from '../components/keys/Keys'; @@ -38,6 +39,7 @@ import markdown from '@/util/markdown.js'; import VueComponentDebug from 'vue-component-debug'; import CommandPalette from '../components/CommandPalette.js'; import { registerIconSetFromStrings } from '@ui'; +import Layout from '@/pages/layout/Layout.vue'; let bootingCallbacks = []; let bootedCallbacks = []; @@ -149,7 +151,30 @@ export default { }, async start() { - this.$app = createApp(App); + const _this = this; + await createInertiaApp({ + id: 'statamic', + resolve: name => { + const pages = import.meta.glob('../pages/**/*.vue', { eager: true }) + let page = pages[`../pages/${name}.vue`]; + page.default.layout = Layout; + return page; + }, + async setup({ el, App: InertiaApp, props, plugin }) { + const app = await _this.configureApp(InertiaApp, props); + app.use(plugin).mount(el); + }, + }) + + bootedCallbacks.forEach((callback) => callback(this)); + bootedCallbacks = []; + }, + + async configureApp(InertiaApp, props, el) { + this.$app = createApp({ + ...App, + render: () => h(InertiaApp, props), + }); this.$app.config.silent = false; this.$app.config.devtools = true; @@ -246,10 +271,7 @@ export default { bootingCallbacks.forEach((callback) => callback(this)); bootingCallbacks = []; - this.$app.mount('#statamic'); - - bootedCallbacks.forEach((callback) => callback(this)); - bootedCallbacks = []; + return this.$app; }, }; diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue new file mode 100644 index 00000000000..c510f747fc7 --- /dev/null +++ b/resources/js/pages/Dashboard.vue @@ -0,0 +1,6 @@ + + + diff --git a/resources/js/pages/collections/Show.vue b/resources/js/pages/collections/Show.vue new file mode 100644 index 00000000000..30475abd607 --- /dev/null +++ b/resources/js/pages/collections/Show.vue @@ -0,0 +1,6 @@ + + + diff --git a/resources/js/pages/layout/Breadcrumbs.vue b/resources/js/pages/layout/Breadcrumbs.vue new file mode 100644 index 00000000000..b303f131ff8 --- /dev/null +++ b/resources/js/pages/layout/Breadcrumbs.vue @@ -0,0 +1,7 @@ + + + diff --git a/resources/js/pages/layout/CommandPalette.vue b/resources/js/pages/layout/CommandPalette.vue new file mode 100644 index 00000000000..c6412004a28 --- /dev/null +++ b/resources/js/pages/layout/CommandPalette.vue @@ -0,0 +1,7 @@ + + + diff --git a/resources/js/pages/layout/Header.vue b/resources/js/pages/layout/Header.vue new file mode 100644 index 00000000000..ab41070237a --- /dev/null +++ b/resources/js/pages/layout/Header.vue @@ -0,0 +1,33 @@ + + + diff --git a/resources/js/pages/layout/Layout.vue b/resources/js/pages/layout/Layout.vue new file mode 100644 index 00000000000..8d348968da2 --- /dev/null +++ b/resources/js/pages/layout/Layout.vue @@ -0,0 +1,68 @@ + + + diff --git a/resources/js/pages/layout/Logo.vue b/resources/js/pages/layout/Logo.vue new file mode 100644 index 00000000000..69a4b5b2d3e --- /dev/null +++ b/resources/js/pages/layout/Logo.vue @@ -0,0 +1,30 @@ + + + diff --git a/resources/js/pages/layout/Nav.vue b/resources/js/pages/layout/Nav.vue new file mode 100644 index 00000000000..3cc76a51057 --- /dev/null +++ b/resources/js/pages/layout/Nav.vue @@ -0,0 +1,62 @@ + + + diff --git a/resources/js/pages/layout/SiteSelector.vue b/resources/js/pages/layout/SiteSelector.vue new file mode 100644 index 00000000000..c99700b8871 --- /dev/null +++ b/resources/js/pages/layout/SiteSelector.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/pages/layout/UserDropdown.vue b/resources/js/pages/layout/UserDropdown.vue new file mode 100644 index 00000000000..295b2269900 --- /dev/null +++ b/resources/js/pages/layout/UserDropdown.vue @@ -0,0 +1,83 @@ + + + diff --git a/resources/js/pages/layout/ViewSiteButton.vue b/resources/js/pages/layout/ViewSiteButton.vue new file mode 100644 index 00000000000..4434cb361b1 --- /dev/null +++ b/resources/js/pages/layout/ViewSiteButton.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index 0442865ef5d..f8c642ebb08 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -11,6 +11,8 @@ class="bg-global-header-bg dark:bg-dark-global-header-bg font-sans leading-normal text-gray-900 dark:text-white" @if ($user->getPreference('strict_accessibility')) data-contrast="increased" @endif > + @inertia('statamic') + {{--
@include('statamic::partials.session-expiry') @@ -57,6 +59,7 @@ class="@yield('content-class') pt-14"
+ --}} @include('statamic::partials.scripts') @yield('scripts') diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index a6872e0b429..96cc33ee52d 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -36,6 +36,7 @@ {{ Statamic::cpViteScripts() }} +@inertiaHead @if (Statamic::pro() && config('statamic.cp.custom_css_url')) diff --git a/src/Http/Controllers/CP/Collections/CollectionsController.php b/src/Http/Controllers/CP/Collections/CollectionsController.php index 167fb0715ef..8359704b30e 100644 --- a/src/Http/Controllers/CP/Collections/CollectionsController.php +++ b/src/Http/Controllers/CP/Collections/CollectionsController.php @@ -3,6 +3,7 @@ namespace Statamic\Http\Controllers\CP\Collections; use Illuminate\Http\Request; +use Inertia\Inertia; use Statamic\Contracts\Entries\Collection as CollectionContract; use Statamic\Contracts\Entries\Entry as EntryContract; use Statamic\CP\Column; @@ -144,6 +145,8 @@ public function show(Request $request, $collection) 'actions' => Action::for($collection, ['view' => 'form']), ]; + return Inertia::render('collections/Show'); + if ($collection->queryEntries()->count() === 0) { return view('statamic::collections.empty', $viewData); } diff --git a/src/Http/Controllers/CP/DashboardController.php b/src/Http/Controllers/CP/DashboardController.php index d415f9676ce..afc229e1f17 100644 --- a/src/Http/Controllers/CP/DashboardController.php +++ b/src/Http/Controllers/CP/DashboardController.php @@ -2,6 +2,7 @@ namespace Statamic\Http\Controllers\CP; +use Inertia\Inertia; use Statamic\Facades\Preference; use Statamic\Facades\Site; use Statamic\Facades\User; @@ -23,6 +24,8 @@ public function index(Loader $loader) return view('statamic::dashboard.empty'); } + return Inertia::render('Dashboard'); + return view('statamic::dashboard.dashboard', [ 'widgets' => $widgets, ]); diff --git a/src/Http/Middleware/CP/HandleInertiaRequests.php b/src/Http/Middleware/CP/HandleInertiaRequests.php new file mode 100644 index 00000000000..8a75f9056e4 --- /dev/null +++ b/src/Http/Middleware/CP/HandleInertiaRequests.php @@ -0,0 +1,43 @@ + + */ + public function share(Request $request): array + { + return [ + ...parent::share($request), + // + ]; + } +} diff --git a/src/Providers/CpServiceProvider.php b/src/Providers/CpServiceProvider.php index 06dc7ec64cb..7806cb0781a 100644 --- a/src/Providers/CpServiceProvider.php +++ b/src/Providers/CpServiceProvider.php @@ -99,6 +99,7 @@ protected function registerMiddlewareGroups() \Statamic\Http\Middleware\CP\AuthGuard::class, \Statamic\Http\Middleware\CP\AddToasts::class, \Statamic\Http\Middleware\CP\TrimStrings::class, + \Statamic\Http\Middleware\CP\HandleInertiaRequests::class, ]); $router->middlewareGroup('statamic.cp.authenticated', [ From e7d6beb6aef4641e8360f63a3d2fa19822f97a26 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Thu, 25 Sep 2025 16:59:45 -0400 Subject: [PATCH 02/59] make non-inertia (blade) pages work --- resources/js/bootstrap/statamic.js | 21 +++++++++++++++++++++ resources/views/layout.blade.php | 12 +++++++++++- resources/views/partials/head.blade.php | 1 - 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/resources/js/bootstrap/statamic.js b/resources/js/bootstrap/statamic.js index 0e184129182..0b8a66b40f1 100644 --- a/resources/js/bootstrap/statamic.js +++ b/resources/js/bootstrap/statamic.js @@ -15,6 +15,7 @@ import VueClickAway from 'vue3-click-away'; import FloatingVue from 'floating-vue'; import 'floating-vue/dist/style.css'; import { createInertiaApp } from '@inertiajs/vue3'; +import { router } from '@inertiajs/vue3'; import Toasts from '../components/Toasts'; import PortalVue from 'portal-vue'; import Keys from '../components/keys/Keys'; @@ -151,10 +152,22 @@ export default { }, async start() { + const el = document.getElementById('statamic'); + const bladeContent = el?.innerHTML || ''; const _this = this; + await createInertiaApp({ id: 'statamic', resolve: name => { + if (name === 'NonInertiaPage') { + return { + default: { + layout: Layout, + template: `
${bladeContent}
`, + } + } + } + const pages = import.meta.glob('../pages/**/*.vue', { eager: true }) let page = pages[`../pages/${name}.vue`]; page.default.layout = Layout; @@ -166,6 +179,14 @@ export default { }, }) + // Handle non-Inertia responses with full page reload + router.on('invalid', (event) => { + if (event.detail.response.status === 200) { + event.preventDefault(); + window.location.href = event.detail.response.request.responseURL; + } + }); + bootedCallbacks.forEach((callback) => callback(this)); bootedCallbacks = []; }, diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index f8c642ebb08..45605468a3c 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -11,7 +11,17 @@ class="bg-global-header-bg dark:bg-dark-global-header-bg font-sans leading-normal text-gray-900 dark:text-white" @if ($user->getPreference('strict_accessibility')) data-contrast="increased" @endif > - @inertia('statamic') +
+ @yield('content') +
+ {{--
diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index 96cc33ee52d..a6872e0b429 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -36,7 +36,6 @@ {{ Statamic::cpViteScripts() }} -@inertiaHead @if (Statamic::pro() && config('statamic.cp.custom_css_url')) From e7b810f8c29a8f54aa366715b9e717b2820297e6 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 09:52:11 -0400 Subject: [PATCH 03/59] wangjangle so that addons can register inertia pages ... booting callbacks need to be first because the component needs to be registered in order to get resolved by inertia. components need to queued and then all actually registered later because they need be resolved by inertia before the vue app is started. --- resources/js/bootstrap/statamic.js | 23 ++++++++++++++++------- resources/js/components/Components.js | 25 ++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/resources/js/bootstrap/statamic.js b/resources/js/bootstrap/statamic.js index 0b8a66b40f1..a1c4f4b9b5d 100644 --- a/resources/js/bootstrap/statamic.js +++ b/resources/js/bootstrap/statamic.js @@ -44,7 +44,7 @@ import Layout from '@/pages/layout/Layout.vue'; let bootingCallbacks = []; let bootedCallbacks = []; -let components; +let components = new Components; export default { booting(callback) { @@ -152,6 +152,9 @@ export default { }, async start() { + bootingCallbacks.forEach((callback) => callback(this)); + bootingCallbacks = []; + const el = document.getElementById('statamic'); const bladeContent = el?.innerHTML || ''; const _this = this; @@ -168,8 +171,18 @@ export default { } } - const pages = import.meta.glob('../pages/**/*.vue', { eager: true }) + // Resolve core pages + const pages = import.meta.glob('../pages/**/*.vue', { eager: true }); let page = pages[`../pages/${name}.vue`]; + + // Resolve addon pages + if (!page) { + const addonPage = components.queue[`Pages/${name}`]; + if (addonPage) page = { default: addonPage }; + } + + if (!page) throw new Error(`Couldn't find Inertia component for the [${name}] page. Did you you register a [Pages/${name}] component?`); + page.default.layout = Layout; return page; }, @@ -208,8 +221,6 @@ export default { const portals = markRaw(new Portals()); - components = new Components(this.$app); - Object.assign(this.$app.config.globalProperties, { $config: new Config(this.initialConfig), }); @@ -277,6 +288,7 @@ export default { registerGlobalCommandPalette(); registerFieldtypes(this.$app); registerIconSets(this.initialConfig); + components.boot(this.$app); // Suppress the translation warnings this.$app.config.warnHandler = (msg, vm, trace) => { @@ -289,9 +301,6 @@ export default { axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; axios.defaults.headers.common['X-CSRF-TOKEN'] = Statamic.$config.get('csrfToken'); - bootingCallbacks.forEach((callback) => callback(this)); - bootingCallbacks = []; - return this.$app; }, }; diff --git a/resources/js/components/Components.js b/resources/js/components/Components.js index 8096d6c198f..faa795ca597 100644 --- a/resources/js/components/Components.js +++ b/resources/js/components/Components.js @@ -3,13 +3,32 @@ import uniqid from 'uniqid'; import Component from './Component'; class Components { - constructor(app) { - this.app = app; + #booted = false; + + constructor() { + this.queue = {}; this.components = ref([]); } + boot(app) { + if (this.#booted) return; + + this.app = app; + + Object.entries(this.queue).forEach(([name, component]) => { + this.app.component(name, component); + }); + + this.#booted = true; + } + register(name, component) { - this.app.component(name, component); + if (this.#booted) { + this.app.component(name, component); + return; + } + + this.queue[name] = component; } append(name, { props }) { From bb5e7a0b55e8e725519353f4180e2d23690ccdaa Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 10:54:27 -0400 Subject: [PATCH 04/59] Wire up page titles, including fallbacks for blade pages --- resources/js/bootstrap/statamic.js | 3 +++ resources/js/pages/Dashboard.vue | 2 ++ resources/js/pages/layout/Head.vue | 22 +++++++++++++++++++ resources/views/layout.blade.php | 4 ++++ resources/views/partials/head.blade.php | 5 ----- .../View/Composers/JavascriptComposer.php | 1 + 6 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 resources/js/pages/layout/Head.vue diff --git a/resources/js/bootstrap/statamic.js b/resources/js/bootstrap/statamic.js index a1c4f4b9b5d..df1dc535d1c 100644 --- a/resources/js/bootstrap/statamic.js +++ b/resources/js/bootstrap/statamic.js @@ -156,6 +156,8 @@ export default { bootingCallbacks = []; const el = document.getElementById('statamic'); + const titleEl = document.getElementById('blade-title'); + const bladeTitle = titleEl.dataset.title; const bladeContent = el?.innerHTML || ''; const _this = this; @@ -190,6 +192,7 @@ export default { const app = await _this.configureApp(InertiaApp, props); app.use(plugin).mount(el); }, + title: (title) => title || bladeTitle }) // Handle non-Inertia responses with full page reload diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue index c510f747fc7..9bee7c13835 100644 --- a/resources/js/pages/Dashboard.vue +++ b/resources/js/pages/Dashboard.vue @@ -1,6 +1,8 @@ diff --git a/resources/js/pages/layout/Head.vue b/resources/js/pages/layout/Head.vue new file mode 100644 index 00000000000..a73c342c004 --- /dev/null +++ b/resources/js/pages/layout/Head.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index 45605468a3c..74c3ed696db 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -19,6 +19,10 @@ class="bg-global-header-bg dark:bg-dark-global-header-bg font-sans leading-norma 'version' => inertia()->getVersion(), ]) }}" > +
@yield('content')
diff --git a/resources/views/partials/head.blade.php b/resources/views/partials/head.blade.php index a6872e0b429..c954d5e5ef1 100644 --- a/resources/views/partials/head.blade.php +++ b/resources/views/partials/head.blade.php @@ -8,11 +8,6 @@ - - @yield('title', $title ?? __('Here')) {{ Statamic::cpDirection() === 'ltr' ? '‹' : '›' }} - {{ __(Statamic::pro() ? config('statamic.cp.custom_cms_name', 'Statamic') : 'Statamic') }} - - @if (Statamic::pro() && config('statamic.cp.custom_favicon_url')) @include('statamic::partials.favicon', ['favicon_url' => config('statamic.cp.custom_favicon_url')]) @else diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index 6546a5e99b6..706008f9fbc 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -48,6 +48,7 @@ private function commonVariables() 'direction' => Statamic::cpDirection(), 'asciiReplaceExtraSymbols' => $replaceSymbols = config('statamic.system.ascii_replace_extra_symbols'), 'charmap' => ASCII::charsArray($replaceSymbols), + 'cmsName' => __(Statamic::pro() ? config('statamic.cp.custom_cms_name', 'Statamic') : 'Statamic'), ]; } From cec783591648f2239b1037653a237b5571ac8ed0 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 11:40:05 -0400 Subject: [PATCH 05/59] Move collection show blade view into inertia ... The View.vue was basically already the page component, but the props from the blade view are moved to the server. This can obviously be cleaned up, but I converted it with as few changes as I could. --- resources/js/bootstrap/App.vue | 2 - resources/js/components/collections/View.vue | 416 ----------------- resources/js/pages/collections/Show.vue | 421 +++++++++++++++++- resources/views/collections/show.blade.php | 38 -- .../CP/Collections/CollectionsController.php | 49 +- 5 files changed, 456 insertions(+), 470 deletions(-) delete mode 100644 resources/js/components/collections/View.vue delete mode 100644 resources/views/collections/show.blade.php diff --git a/resources/js/bootstrap/App.vue b/resources/js/bootstrap/App.vue index d9ba2727a9e..5685eb62772 100644 --- a/resources/js/bootstrap/App.vue +++ b/resources/js/bootstrap/App.vue @@ -21,7 +21,6 @@ import RolePublishForm from '../components/roles/PublishForm.vue'; import UserGroupListing from '../components/user-groups/Listing.vue'; import UserGroupPublishForm from '../components/user-groups/PublishForm.vue'; import CollectionScaffolder from '../components/collections/Scaffolder.vue'; -import CollectionView from '../components/collections/View.vue'; import CollectionBlueprintListing from '../components/collections/BlueprintListing.vue'; import SessionExpiry from '../components/SessionExpiry.vue'; import NavigationListing from '../components/navigation/Listing.vue'; @@ -62,7 +61,6 @@ export default { UserGroupListing, UserGroupPublishForm, CollectionScaffolder, - CollectionView, CollectionBlueprintListing, SessionExpiry, NavigationListing, diff --git a/resources/js/components/collections/View.vue b/resources/js/components/collections/View.vue deleted file mode 100644 index 5def42d01bb..00000000000 --- a/resources/js/components/collections/View.vue +++ /dev/null @@ -1,416 +0,0 @@ - - - diff --git a/resources/js/pages/collections/Show.vue b/resources/js/pages/collections/Show.vue index 30475abd607..bf6d0d20d2b 100644 --- a/resources/js/pages/collections/Show.vue +++ b/resources/js/pages/collections/Show.vue @@ -1,6 +1,419 @@ - - + + diff --git a/resources/views/collections/show.blade.php b/resources/views/collections/show.blade.php deleted file mode 100644 index 07507b2f87c..00000000000 --- a/resources/views/collections/show.blade.php +++ /dev/null @@ -1,38 +0,0 @@ -@extends("statamic::layout") -@section("title", Statamic::crumb($collection->title(), "Collections")) - -@section("content") - handle()) }}" - reorder-url="{{ cp_route("collections.entries.reorder", $collection->handle()) }}" - edit-url="{{ $collection->editUrl() }}" - blueprints-url="{{ cp_route("blueprints.collections.index", $collection) }}" - scaffold-url="{{ cp_route("collections.scaffold", $collection->handle()) }}" - :can-edit="{{ Statamic\Support\Str::bool($user->can("edit", $collection)) }}" - :can-edit-blueprints="{{ Statamic\Support\Str::bool($user->can("configure fields")) }}" - initial-site="{{ $site }}" - :sites="{{ json_encode($sites) }}" - :can-change-localization-delete-behavior="{{ Statamic\Support\Str::bool($canChangeLocalizationDeleteBehavior) }}" - @if ($collection->hasStructure()) - :structured="{{ Statamic\Support\Str::bool($user->can("reorder", $collection)) }}" - structure-pages-url="{{ cp_route("collections.tree.index", $structure->handle()) }}" - structure-submit-url="{{ cp_route("collections.tree.update", $collection->handle()) }}" - :structure-max-depth="{{ $structure->maxDepth() ?? "Infinity" }}" - :structure-expects-root="{{ Statamic\Support\Str::bool($structure->expectsRoot()) }}" - :structure-show-slugs="{{ Statamic\Support\Str::bool($structure->showSlugs()) }}" - @endif - > -@endsection diff --git a/src/Http/Controllers/CP/Collections/CollectionsController.php b/src/Http/Controllers/CP/Collections/CollectionsController.php index 8359704b30e..e97b033cd1c 100644 --- a/src/Http/Controllers/CP/Collections/CollectionsController.php +++ b/src/Http/Controllers/CP/Collections/CollectionsController.php @@ -145,22 +145,51 @@ public function show(Request $request, $collection) 'actions' => Action::for($collection, ['view' => 'form']), ]; - return Inertia::render('collections/Show'); - if ($collection->queryEntries()->count() === 0) { return view('statamic::collections.empty', $viewData); } - if (! $collection->hasStructure()) { - return view('statamic::collections.show', $viewData); - } + $user = \Statamic\Facades\User::current(); + $props = [ + 'title' => $collection->title(), + 'handle' => $collection->handle(), + 'icon' => $collection->icon(), + 'canCreate' => $viewData['canCreate'], + 'createUrls' => $viewData['createUrls'], + 'createLabel' => $collection->createLabel(), + 'blueprints' => $blueprints->all(), + 'sortColumn' => $collection->sortField(), + 'sortDirection' => $collection->sortDirection(), + 'columns' => $columns, + 'filters' => $viewData['filters'], + 'actions' => $viewData['actions'], + 'actionUrl' => cp_route('collections.actions.run'), + 'entriesActionUrl' => cp_route('collections.entries.actions.run', $collection->handle()), + 'reorderUrl' => cp_route('collections.entries.reorder', $collection->handle()), + 'editUrl' => $collection->editUrl(), + 'blueprintsUrl' => cp_route('blueprints.collections.index', $collection), + 'scaffoldUrl' => cp_route('collections.scaffold', $collection->handle()), + 'canEdit' => $user->can('edit', $collection), + 'canEditBlueprints' => $user->can('configure fields'), + 'initialSite' => $site->handle(), + 'sites' => $viewData['sites'], + 'canChangeLocalizationDeleteBehavior' => $viewData['canChangeLocalizationDeleteBehavior'], + ]; - $structure = $collection->structure(); + if ($collection->hasStructure()) { + $structure = $collection->structure(); + $props = [ + ...$props, + 'structured' => $user->can('reorder', $collection), + 'structurePagesUrl' => cp_route('collections.tree.index', $structure->handle()), + 'structureSubmitUrl' => cp_route('collections.tree.update', $collection->handle()), + 'structureMaxDepth' => $structure->maxDepth() ?? PHP_FLOAT_MAX, // "Infinity" + 'structureExpectsRoot' => $structure->expectsRoot(), + 'structureShowSlugs' => $structure->showSlugs(), + ]; + } - return view('statamic::collections.show', array_merge($viewData, [ - 'structure' => $structure, - 'expectsRoot' => $structure->expectsRoot(), - ])); + return Inertia::render('collections/Show', $props); } public function create() From e505ca894950337323a8c2b5c0f331c18a4e69cd Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 11:59:17 -0400 Subject: [PATCH 06/59] Move view site button to inertia --- resources/js/pages/layout/ViewSiteButton.vue | 21 +++++++++---------- .../components/view-site-button.blade.php | 8 ------- .../View/Composers/JavascriptComposer.php | 1 + 3 files changed, 11 insertions(+), 19 deletions(-) delete mode 100644 resources/views/components/view-site-button.blade.php diff --git a/resources/js/pages/layout/ViewSiteButton.vue b/resources/js/pages/layout/ViewSiteButton.vue index 4434cb361b1..d5788ba2a2b 100644 --- a/resources/js/pages/layout/ViewSiteButton.vue +++ b/resources/js/pages/layout/ViewSiteButton.vue @@ -1,16 +1,15 @@ diff --git a/resources/views/components/view-site-button.blade.php b/resources/views/components/view-site-button.blade.php deleted file mode 100644 index 970bef1d042..00000000000 --- a/resources/views/components/view-site-button.blade.php +++ /dev/null @@ -1,8 +0,0 @@ - - @cp_svg('icons/visit-website') - diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index 706008f9fbc..4339a1631cf 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -71,6 +71,7 @@ private function protectedVariables() 'multisiteEnabled' => Site::multiEnabled(), 'sites' => $this->sites(), 'selectedSite' => Site::selected()->handle(), + 'selectedSiteUrl' => Site::selected()->url(), 'preloadableFieldtypes' => FieldtypeRepository::preloadable()->keys(), 'livePreview' => config('statamic.live_preview'), 'permissions' => $this->permissions($user), From 2792304dfc55f9052fad6da9490ba2f9cf0f29d9 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 14:12:08 -0400 Subject: [PATCH 07/59] DropdownItem will use inertia links --- packages/ui/src/Dropdown/Item.vue | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/Dropdown/Item.vue b/packages/ui/src/Dropdown/Item.vue index a23a64f49da..52ebcb763f7 100644 --- a/packages/ui/src/Dropdown/Item.vue +++ b/packages/ui/src/Dropdown/Item.vue @@ -1,8 +1,9 @@ diff --git a/resources/views/components/user-dropdown.blade.php b/resources/views/components/user-dropdown.blade.php deleted file mode 100644 index 6f72bdf2430..00000000000 --- a/resources/views/components/user-dropdown.blade.php +++ /dev/null @@ -1,74 +0,0 @@ -@php($userJson = json_encode($user)) - -
- -
- - - - - -
- -
-
{{ $user->email() }}
- @if ($user->isSuper()) -
- {{ __('Super Admin') }} - @if (session()->get('statamic_impersonated_by')) - - @endif -
- @elseif (session()->get('statamic_impersonated_by')) - - @endif -
-
-
- - - - - @if (config('statamic.cp.support_url')) - - - @endif - @if (session()->get('statamic_impersonated_by')) - - @endif - - - - - - - - - - -
diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index 4339a1631cf..7186a72e669 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -72,6 +72,7 @@ private function protectedVariables() 'sites' => $this->sites(), 'selectedSite' => Site::selected()->handle(), 'selectedSiteUrl' => Site::selected()->url(), + 'supportUrl' => config('statamic.cp.support_url'), 'preloadableFieldtypes' => FieldtypeRepository::preloadable()->keys(), 'livePreview' => config('statamic.live_preview'), 'permissions' => $this->permissions($user), @@ -109,6 +110,7 @@ protected function user($user) 'preferences' => Preference::all(), 'permissions' => $user->permissions()->all(), 'theme' => $user->preferredTheme(), + 'is_impersonating' => session()->has('statamic_impersonated_by'), ])->toArray(); } From 80fd413d0034c474d0bbd854ea09e3fa47a3b4f3 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 14:19:08 -0400 Subject: [PATCH 09/59] Move command palette to inertia --- resources/js/bootstrap/App.vue | 2 -- resources/js/pages/layout/CommandPalette.vue | 19 +++++++++++++++++-- .../components/command-palette.blade.php | 15 --------------- 3 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 resources/views/components/command-palette.blade.php diff --git a/resources/js/bootstrap/App.vue b/resources/js/bootstrap/App.vue index 5685eb62772..b01e49bc596 100644 --- a/resources/js/bootstrap/App.vue +++ b/resources/js/bootstrap/App.vue @@ -29,7 +29,6 @@ import NavigationView from '../components/navigation/View.vue'; import TaxonomyBlueprintListing from '../components/taxonomies/BlueprintListing.vue'; import Updater from '../components/updater/Updater.vue'; import SitesEditForm from '../components/sites/EditForm.vue'; -import CommandPalette from '../components/command-palette/CommandPalette.vue'; import ItemActions from '../components/actions/ItemActions.vue'; import BulkActions from '../components/actions/BulkActions.vue'; import LicensingAlert from '../components/LicensingAlert.vue'; @@ -38,7 +37,6 @@ import { defineAsyncComponent } from 'vue'; export default { components: { - CommandPalette, GlobalSiteSelector, Login, TwoFactorChallenge, diff --git a/resources/js/pages/layout/CommandPalette.vue b/resources/js/pages/layout/CommandPalette.vue index c6412004a28..f38a204b35f 100644 --- a/resources/js/pages/layout/CommandPalette.vue +++ b/resources/js/pages/layout/CommandPalette.vue @@ -1,7 +1,22 @@ diff --git a/resources/views/components/command-palette.blade.php b/resources/views/components/command-palette.blade.php deleted file mode 100644 index c43ea612eaa..00000000000 --- a/resources/views/components/command-palette.blade.php +++ /dev/null @@ -1,15 +0,0 @@ - -
- @cp_svg('icons/magnifying-glass', 'size-5 flex-none text-white/50 group-hover:text-white/70') - {{ __('Search') }} - -
-
From 1d1ec7b8180420d7af697256eefe52ae4ed7e146 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 14:21:26 -0400 Subject: [PATCH 10/59] Move global site selector to inertia --- resources/js/bootstrap/App.vue | 2 -- resources/js/pages/layout/Header.vue | 2 +- resources/js/pages/layout/SiteSelector.vue | 13 --------- .../components/global-site-selector.blade.php | 27 ------------------- 4 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 resources/js/pages/layout/SiteSelector.vue delete mode 100644 resources/views/components/global-site-selector.blade.php diff --git a/resources/js/bootstrap/App.vue b/resources/js/bootstrap/App.vue index b01e49bc596..1e226dc8b58 100644 --- a/resources/js/bootstrap/App.vue +++ b/resources/js/bootstrap/App.vue @@ -1,5 +1,4 @@ - - diff --git a/resources/views/components/global-site-selector.blade.php b/resources/views/components/global-site-selector.blade.php deleted file mode 100644 index 6000ee20a62..00000000000 --- a/resources/views/components/global-site-selector.blade.php +++ /dev/null @@ -1,27 +0,0 @@ - - -
-
- -
-
- @cp_svg('icons/chevron-down', 'size-4 shrink-0 text-gray-400 dark:text-white/40 text-gray-400 dark:text-white/40') -
-
From 082e857c2fc564f0f4d368677132bdc719afaf0b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 15:16:45 -0400 Subject: [PATCH 11/59] logo --- resources/js/pages/layout/Logo.vue | 65 ++++++++++++------- resources/js/pages/layout/ProBadge.vue | 6 ++ .../View/Composers/CustomLogoComposer.php | 58 ----------------- .../View/Composers/JavascriptComposer.php | 29 +++++++++ src/Providers/CpServiceProvider.php | 2 - 5 files changed, 75 insertions(+), 85 deletions(-) create mode 100644 resources/js/pages/layout/ProBadge.vue delete mode 100644 src/Http/View/Composers/CustomLogoComposer.php diff --git a/resources/js/pages/layout/Logo.vue b/resources/js/pages/layout/Logo.vue index 69a4b5b2d3e..e4c6e1eb3fa 100644 --- a/resources/js/pages/layout/Logo.vue +++ b/resources/js/pages/layout/Logo.vue @@ -1,30 +1,45 @@ diff --git a/resources/js/pages/layout/ProBadge.vue b/resources/js/pages/layout/ProBadge.vue new file mode 100644 index 00000000000..7517271e13f --- /dev/null +++ b/resources/js/pages/layout/ProBadge.vue @@ -0,0 +1,6 @@ + + + diff --git a/src/Http/View/Composers/CustomLogoComposer.php b/src/Http/View/Composers/CustomLogoComposer.php deleted file mode 100644 index 54e75669cd9..00000000000 --- a/src/Http/View/Composers/CustomLogoComposer.php +++ /dev/null @@ -1,58 +0,0 @@ -with('customLogo', $this->customLogo($view)); - $view->with('customDarkLogo', $this->customLogo($view, dark: true)); - $view->with('customLogoText', $this->customLogo($view, text: true)); - } - - protected function customLogo($view, bool $dark = false, bool $text = false) - { - if (! Statamic::pro()) { - return false; - } - - $config = config('statamic.cp.custom_logo_url'); - if ($dark && config('statamic.cp.custom_dark_logo_url')) { - $config = config('statamic.cp.custom_dark_logo_url'); - } - if ($text && config('statamic.cp.custom_logo_text')) { - $config = config('statamic.cp.custom_logo_text'); - } - - switch ($view->name()) { - case 'statamic::partials.outside-logo': - $type = 'outside'; - break; - case 'statamic::partials.global-header': - $type = 'nav'; - break; - default: - $type = 'other'; - } - - if ($logo = Arr::get($config, $type)) { - return $logo; - } - - if (! is_array($config)) { - return $config; - } - - return false; - } -} diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index 7186a72e669..b4026157b66 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -49,6 +49,7 @@ private function commonVariables() 'asciiReplaceExtraSymbols' => $replaceSymbols = config('statamic.system.ascii_replace_extra_symbols'), 'charmap' => ASCII::charsArray($replaceSymbols), 'cmsName' => __(Statamic::pro() ? config('statamic.cp.custom_cms_name', 'Statamic') : 'Statamic'), + 'logos' => $this->logos(), ]; } @@ -59,6 +60,7 @@ private function protectedVariables() return [ 'version' => Statamic::version(), + 'isPro' => Statamic::pro(), 'laravelVersion' => app()->version(), 'locales' => config('statamic.system.locales'), 'ajaxTimeout' => config('statamic.system.ajax_timeout'), @@ -128,4 +130,31 @@ private function icons() $set->name() => $set->contents(), ]); } + + private function logos() + { + if (! Statamic::pro()) { + return false; + } + + if (is_string($light = config('statamic.cp.custom_logo_url'))) { + $light = ['nav' => $light, 'outside' => $light]; + } + + if (is_string($dark = config('statamic.cp.custom_dark_logo_url'))) { + $dark = ['nav' => $dark, 'outside' => $dark]; + } + + return [ + 'text' => config('statamic.cp.custom_logo_text') ?? config('app.name'), + 'light' => [ + 'nav' => $light['nav'] ?? null, + 'outside' => $light['outside'] ?? null, + ], + 'dark' => [ + 'nav' => $dark['nav'] ?? null, + 'outside' => $dark['outside'] ?? null, + ], + ]; + } } diff --git a/src/Providers/CpServiceProvider.php b/src/Providers/CpServiceProvider.php index 7806cb0781a..cb893877365 100644 --- a/src/Providers/CpServiceProvider.php +++ b/src/Providers/CpServiceProvider.php @@ -15,7 +15,6 @@ use Statamic\Extensions\Translation\Translator; use Statamic\Facades\User; use Statamic\Http\Middleware\CP\StartSession; -use Statamic\Http\View\Composers\CustomLogoComposer; use Statamic\Http\View\Composers\FieldComposer; use Statamic\Http\View\Composers\JavascriptComposer; use Statamic\Http\View\Composers\NavComposer; @@ -37,7 +36,6 @@ public function boot() View::composer(SessionExpiryComposer::VIEWS, SessionExpiryComposer::class); View::composer(JavascriptComposer::VIEWS, JavascriptComposer::class); View::composer(NavComposer::VIEWS, NavComposer::class); - View::composer(CustomLogoComposer::VIEWS, CustomLogoComposer::class); Blade::component('statamic::outside-logo', OutsideLogo::class); From 439400d682d835d019c3658a27238edc863ee72a Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 15:31:35 -0400 Subject: [PATCH 12/59] Badges with hrefs use inertia --- packages/ui/src/Badge.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/Badge.vue b/packages/ui/src/Badge.vue index 8c426f9a80d..098e63af1b0 100644 --- a/packages/ui/src/Badge.vue +++ b/packages/ui/src/Badge.vue @@ -3,6 +3,7 @@ import { computed, useSlots } from 'vue'; import { cva } from 'cva'; import { twMerge } from 'tailwind-merge'; import Icon from './Icon/Icon.vue'; +import { Link } from '@inertiajs/vue3'; const props = defineProps({ append: { type: [String, Number, Boolean, null], default: null }, @@ -21,7 +22,7 @@ const props = defineProps({ const slots = useSlots(); const hasDefaultSlot = !!slots.default; -const tag = computed(() => (props.href ? 'a' : props.as)); +const tag = computed(() => (props.href ? Link : props.as)); const badgeClasses = computed(() => { const classes = cva({ From a7a9d59f6db4914c5fd3f3b8f8daf82daafbaa00 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 15:31:49 -0400 Subject: [PATCH 13/59] Pro badge to inertia --- resources/js/pages/layout/ProBadge.vue | 27 +++++++++++- .../components/pro-badge/index.blade.php | 41 ------------------- .../pro-badge/problematic.blade.php | 13 ------ .../View/Composers/JavascriptComposer.php | 15 ++++++- 4 files changed, 40 insertions(+), 56 deletions(-) delete mode 100644 resources/views/components/pro-badge/index.blade.php delete mode 100644 resources/views/components/pro-badge/problematic.blade.php diff --git a/resources/js/pages/layout/ProBadge.vue b/resources/js/pages/layout/ProBadge.vue index 7517271e13f..d1663e5e57b 100644 --- a/resources/js/pages/layout/ProBadge.vue +++ b/resources/js/pages/layout/ProBadge.vue @@ -1,6 +1,31 @@ diff --git a/resources/views/components/pro-badge/index.blade.php b/resources/views/components/pro-badge/index.blade.php deleted file mode 100644 index cef976b90a2..00000000000 --- a/resources/views/components/pro-badge/index.blade.php +++ /dev/null @@ -1,41 +0,0 @@ -@inject('licenses', 'Statamic\Licensing\LicenseManager') - -@php -$requestFailed = $licenses->requestFailed(); -$onPublicDomain = $licenses->isOnPublicDomain(); - -$base = 'relative inline-flex items-center justify-center gap-1 font-normal antialiased whitespace-nowrap no-underline not-prose [button]:cursor-pointer group [&_svg]:opacity-60 [&_svg]:group-hover:opacity-80 dark:[&_svg]:group-hover:opacity-70'; -$sizeDefault = 'text-xs leading-5.5 px-2 rounded-sm [&_svg]:size-3.5 gap-2'; -$sizeSm = 'text-2xs leading-normal px-1.25 rounded-[0.1875rem] [&_svg]:size-2.5'; -$flat = 'border-0 shadow-none!'; -$yellow = 'bg-yellow-100 border-yellow-400 text-yellow-700 dark:bg-yellow-300/6 dark:text-yellow-300 [a]:hover:bg-yellow-200/80 [button]:hover:bg-yellow-200/80 dark:[a]:hover:bg-yellow-300/15'; -$red = 'bg-red-100/80 border-red-400/80 text-red-700 dark:bg-red-300/6 dark:text-red-300 [a]:hover:bg-red-200/60 [button]:hover:bg-red-200/60 dark:[a]:hover:bg-red-300/15'; -$green = 'bg-green-100/80 border-green-400 text-green-700 dark:bg-green-300/6 dark:text-green-300 [a]:hover:bg-green-200/60 [button]:hover:bg-green-200/60 dark:[a]:hover:bg-green-300/15'; -@endphp - -@if ($licenses->valid()) -
- {{ __('Pro') }} -
-@else - - {{-- - Rendered twice: - - Once without being wrapped in a tooltip to prevent vue pop-in. - - Again surrounded by the tooltip so we can... have the tooltip. - --}} -
- -
- - requestFailureMessage()."'" : 'null' }}"> - - - -@endif diff --git a/resources/views/components/pro-badge/problematic.blade.php b/resources/views/components/pro-badge/problematic.blade.php deleted file mode 100644 index 12fb222e480..00000000000 --- a/resources/views/components/pro-badge/problematic.blade.php +++ /dev/null @@ -1,13 +0,0 @@ - - {{ __('Pro') }} – {{ $onPublicDomain ? __('statamic::messages.licensing_error_unlicensed') : __('Trial Mode') }} - diff --git a/src/Http/View/Composers/JavascriptComposer.php b/src/Http/View/Composers/JavascriptComposer.php index b4026157b66..9ae6c415295 100644 --- a/src/Http/View/Composers/JavascriptComposer.php +++ b/src/Http/View/Composers/JavascriptComposer.php @@ -13,6 +13,7 @@ use Statamic\Facades\Site; use Statamic\Facades\User; use Statamic\Icons\IconSet; +use Statamic\Licensing\LicenseManager; use Statamic\Statamic; use Statamic\Support\Str; use voku\helper\ASCII; @@ -56,7 +57,6 @@ private function commonVariables() private function protectedVariables() { $user = User::current(); - $licenses = app('Statamic\Licensing\LicenseManager'); return [ 'version' => Statamic::version(), @@ -81,6 +81,7 @@ private function protectedVariables() 'customSvgIcons' => $this->icons(), 'commandPaletteCategories' => Category::order(), 'commandPalettePreloadedItems' => CommandPalette::getPreloadedItems(), + 'licensing' => $this->licensing(), ]; } @@ -157,4 +158,16 @@ private function logos() ], ]; } + + private function licensing() + { + $licenses = app(LicenseManager::class); + + return [ + 'valid' => $licenses->valid(), + 'requestFailed' => $licenses->requestFailed(), + 'requestFailureMessage' => $licenses->requestFailureMessage(), + 'isOnPublicDomain' => $licenses->isOnPublicDomain(), + ]; + } } From cc4f587ae4bd1a3ed3e775e572603e0535f68407 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Fri, 26 Sep 2025 17:07:33 -0400 Subject: [PATCH 14/59] Badges with target blank will use regular links --- packages/ui/src/Badge.vue | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/Badge.vue b/packages/ui/src/Badge.vue index 098e63af1b0..83c853f4783 100644 --- a/packages/ui/src/Badge.vue +++ b/packages/ui/src/Badge.vue @@ -10,6 +10,7 @@ const props = defineProps({ as: { type: String, default: 'div' }, color: { type: String, default: 'default' }, href: { type: String, default: null }, + target: { type: String, default: null }, icon: { type: String, default: null }, iconAppend: { type: String, default: null }, pill: { type: Boolean, default: false }, @@ -22,7 +23,12 @@ const props = defineProps({ const slots = useSlots(); const hasDefaultSlot = !!slots.default; -const tag = computed(() => (props.href ? Link : props.as)); +const tag = computed(() => { + if (props.href) { + return props.target === '_blank' ? 'a' : Link; + } + return props.as; +}); const badgeClasses = computed(() => { const classes = cva({ @@ -72,7 +78,7 @@ const badgeClasses = computed(() => {