diff --git a/config/stache.php b/config/stache.php index 6cca12baba0..47ea8110840 100644 --- a/config/stache.php +++ b/config/stache.php @@ -76,6 +76,11 @@ 'directory' => base_path('content/trees/navigation'), ], + 'taxonomy-trees' => [ + 'class' => Stores\TaxonomyTreeStore::class, + 'directory' => base_path('content/trees/taxonomies'), + ], + 'globals' => [ 'class' => Stores\GlobalsStore::class, 'directory' => base_path('content/globals'), diff --git a/lang/en/fieldtypes.php b/lang/en/fieldtypes.php index fa5ec422d0d..a8a43a268f3 100644 --- a/lang/en/fieldtypes.php +++ b/lang/en/fieldtypes.php @@ -121,6 +121,7 @@ 'integer.title' => 'Integer', 'link.config.collections' => 'Entries from these collections will be available. Leaving this empty will make entries from routable collections available.', 'link.config.container' => 'Choose which asset container to use for this field.', + 'link.config.taxonomies' => 'Terms from these taxonomies will be available. Leaving this empty will make terms from routable taxonomies available.', 'link.config.default_option' => 'The default Link type option, if available for the fieldtype\'s configuration.', 'link.title' => 'Link', 'list.config.add_row' => 'Customize the label of the "Add Item" button.', diff --git a/lang/en/messages.php b/lang/en/messages.php index f6339ba62df..8437d32d95a 100644 --- a/lang/en/messages.php +++ b/lang/en/messages.php @@ -254,8 +254,12 @@ 'sync_term_field_confirmation_text' => 'Are you sure? This field\'s value will be replaced by the value in the originating term.', 'taxonomies_blueprints_instructions' => 'Terms in this taxonomy may use any of these blueprints.', 'taxonomies_collections_instructions' => 'The collections that use this taxonomy.', + 'taxonomies_max_depth_instructions' => 'Set the max term nesting level. A depth of 1 allows reordering without nesting, keeping URLs flat.', + 'taxonomies_orderable_instructions' => 'Enable manual ordering and hierarchy (parent/child terms) via drag & drop. Nested terms get nested URLs.', 'taxonomies_preview_target_refresh_instructions' => 'Automatically refresh the preview while editing. Disabling this will use postMessage.', 'taxonomies_preview_targets_instructions' => 'The URLs to be viewable within Live Preview. Learn more in the [documentation](https://statamic.dev/live-preview#preview-targets).', + 'taxonomies_routes_instructions' => 'The route controls the terms URL pattern. Learn more in the [documentation](https://statamic.dev/taxonomies#routing).', + 'taxonomies_route_instructions' => 'The URL pattern for term pages. Include `{slug}` (and `{parent_uri}` when nested).', 'taxonomy_configure_handle_instructions' => 'Used to reference this taxonomy on the frontend. This cannot be easily changed later.', 'taxonomy_configure_intro' => 'A taxonomy is a system of classifying data around a set of unique characteristics, such as categories, tags, or colors.', 'taxonomy_configure_layout_instructions' => 'Set this taxonomy\'s default layout. Terms can override this setting with a `layout` field.', @@ -265,6 +269,8 @@ 'taxonomy_next_steps_blueprints_description' => 'Manage the blueprints and fields available for this taxonomy.', 'taxonomy_next_steps_configure_description' => 'Configure names, associate collections, define blueprints, and more.', 'taxonomy_next_steps_create_term_description' => 'Create the first term or stub out a handful of placeholder terms—it\'s up to you.', + 'taxonomy_next_steps_scaffold_description' => 'Generate index and detail views from the taxonomy name at the click of a button.', + 'taxonomy_scaffold_instructions' => 'Select and configure views to generate. Any existing files will be preserved.', 'theme_share_instructions' => 'Publish this theme through your statamic.com account to make it available to others.', 'try_again_in_seconds' => '{0,1}Try again now.|Try again in :count seconds.', 'try_again_in_minutes' => 'Try again in a minute.|Try again in :count minutes.', diff --git a/lang/en/validation.php b/lang/en/validation.php index 9e46026ccab..c24a2124122 100644 --- a/lang/en/validation.php +++ b/lang/en/validation.php @@ -177,9 +177,12 @@ 'options_require_keys' => 'All options must have keys.', 'origin_cannot_be_disabled' => 'Cannot select a disabled origin.', 'parent_cannot_be_itself' => 'Cannot be its own parent.', + 'parent_cannot_be_descendant' => 'Cannot be nested under one of its descendants.', 'parent_causes_root_children' => 'This would cause the root page to have children.', 'parent_exceeds_max_depth' => 'This would exceed the maximum depth.', 'reserved' => 'This is a reserved word.', + 'term_path_exceeds_max_depth' => 'The path :path exceeds the maximum depth of :max.', + 'taxonomy_route_requires_slug' => 'The route must include {slug}.', 'reserved_field_handle' => 'Field with a handle of :handle is a reserved word.', 'unique_entry_value' => 'This value has already been taken.', 'unique_form_handle' => 'This value has already been taken.', diff --git a/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue b/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue index 843eb55c4ac..ee1367318c7 100644 --- a/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue +++ b/resources/js/components/fieldtypes/relationship/RelationshipFieldtype.vue @@ -3,7 +3,7 @@ ref="input" :name="name" :value="value" - :mode="config.mode" + :mode="mode" :can-edit="canEdit" :config="config" :can-create="canCreate" @@ -48,6 +48,10 @@ export default { }, computed: { + mode() { + return this.meta?.mode || this.config.mode; + }, + maxItems() { return this.config.max_items || Infinity; }, diff --git a/resources/js/components/inputs/relationship/Item.vue b/resources/js/components/inputs/relationship/Item.vue index 707a589808a..468df69e403 100644 --- a/resources/js/components/inputs/relationship/Item.vue +++ b/resources/js/components/inputs/relationship/Item.vue @@ -2,27 +2,63 @@
- -
- + + +
-
- +
+ + + + + + +
@@ -103,6 +131,24 @@ export default { }; }, + computed: { + hintTaxonomy() { + if (!this.item.hint?.includes(' • ') || !this.item.hint.includes(' » ')) return null; + + return this.item.hint.split(' • ')[0]; + }, + + hintPathSegments() { + if (!this.item.hint?.includes(' » ')) return []; + + const path = this.item.hint.includes(' • ') + ? this.item.hint.split(' • ').slice(1).join(' • ') + : this.item.hint; + + return path.split(' » '); + }, + }, + methods: { edit() { if (!this.editable) return; diff --git a/resources/js/components/inputs/relationship/RelationshipInput.vue b/resources/js/components/inputs/relationship/RelationshipInput.vue index a6bb3dd7d9c..0c428f6ae22 100644 --- a/resources/js/components/inputs/relationship/RelationshipInput.vue +++ b/resources/js/components/inputs/relationship/RelationshipInput.vue @@ -11,6 +11,7 @@ :read-only="readOnly" :url="selectionsUrl" :site="site" + :tree="tree" @input="selectFieldSelected" @focus="$emit('focus')" @blur="$emit('blur')" diff --git a/resources/js/components/inputs/relationship/SelectField.vue b/resources/js/components/inputs/relationship/SelectField.vue index 628d716ae12..1630dcb6c3f 100644 --- a/resources/js/components/inputs/relationship/SelectField.vue +++ b/resources/js/components/inputs/relationship/SelectField.vue @@ -8,7 +8,7 @@ :model-value="items.map((item) => item.id)" :multiple :options="comboboxOptions" - :placeholder="__(config.placeholder) || __('Choose...')" + :placeholder="fieldPlaceholder" :read-only="readOnly" :taggable="isTaggable" :close-on-select="isTaggable" @@ -18,11 +18,38 @@ @update:modelValue="itemsSelected" @search="search" > -