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
2 changes: 1 addition & 1 deletion dist/css/field.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/js/field.js

Large diffs are not rendered by default.

414 changes: 259 additions & 155 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions resources/js/components/DetailField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export default {
this.field,
key,
collapsed,
layout.attributes,
layout,
resolved_title,
);
Expand Down
5 changes: 4 additions & 1 deletion resources/js/components/DetailGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
><!--
--><span class="text-80">{{ index + 1 }}</span>
</span>
<span class="font-bold">{{ group.title }}</span>
<span class="font-bold"><span v-html="title"></span></span>
</div>
<component
v-for="(item, index) in group.fields"
Expand Down Expand Up @@ -42,6 +42,9 @@ export default {
titleStyle() {
return ["pb-4", "border-b", "border-gray-100", "dark:border-gray-700"];
},
title() {
return this.group.attributes?.popover || this.group.title;
},
},
};
</script>
39 changes: 29 additions & 10 deletions resources/js/components/FormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
@move-up="moveUp(group.key)"
@move-down="moveDown(group.key)"
@remove="remove(group.key)"
@set-internal-title="setInternalTitle(group.key, group.internalTitle)"
/>
</div>

Expand Down Expand Up @@ -145,6 +146,7 @@ export default {
this.value.push({
layout: group.layout,
key: group.key,
internalTitle: group.internalTitle,
attributes: group.attributes,
});

Expand Down Expand Up @@ -209,6 +211,8 @@ export default {
this.value[i].attributes,
this.value[i].key,
this.currentField.collapsed,
this.value[i].internalTitle,
this.value[i].popover,
);
}
},
Expand All @@ -224,7 +228,7 @@ export default {
/**
* Append the given layout to flexible content's list
*/
addGroup(layout, attributes, key, collapsed) {
addGroup(layout, attributes, key, collapsed, internalTitle, popover) {
if (!layout) return;

collapsed = collapsed || false;
Expand All @@ -233,10 +237,13 @@ export default {
group = new Group(
layout.name,
layout.title,
internalTitle,
popover,
fields,
this.currentField,
key,
collapsed,
layout.attributes,
);

this.groups[group.key] = group;
Expand Down Expand Up @@ -277,6 +284,10 @@ export default {
delete this.groups[key];
},

setInternalTitle(key, title) {
this.groups[key]['internalTitle'] = title;
},

initSortable() {
const containerRef = this.$refs["flexibleFieldContainer"];

Expand All @@ -293,16 +304,24 @@ export default {
scrollSpeed: 5,
animation: 500,
onEnd: (evt) => {
const item = evt.item;
const key = item.id;
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;

if (newIndex < oldIndex) {
this.moveUp(key);
} else if (newIndex > oldIndex) {
this.moveDown(key);
const draggedId = evt.item.id;
const oldPosition = this.order.indexOf(draggedId);
this.order.splice(oldPosition, 1);

let newPosition;
if (evt.oldIndex < evt.newIndex) {
const next = evt.item.nextElementSibling;
newPosition = next?.id
? this.order.indexOf(next.id)
: this.order.length;
} else {
const prev = evt.item.previousElementSibling;
newPosition = prev?.id
? this.order.indexOf(prev.id) + 1
: 0;
}

this.order.splice(newPosition, 0, draggedId);
},
});
},
Expand Down
135 changes: 131 additions & 4 deletions resources/js/components/FormGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,24 @@

<p class="text-80 grow px-4">
<span class="mr-3 font-semibold">#{{ index + 1 }}</span>
{{ group.title }}
<span v-if="group.popover">
<button type="button"
tabindex="0"
:popovertarget="group.name + '-popover-' + group.key"
popovertargetaction="toggle"
style="cursor: pointer;"
data-popover-toggle
:data-image-url="field.popover"
:data-slug="group.name"
data-image-check="pending">
<span v-text="title"></span>
</button>

<div popover :id="group.name + '-popover-' + group.key" class="bg-white p-1 rounded-lg shadow w-24 ring ring-inset ring-black/10">
<img :src="group.popover" class="w-full" :alt="title">
</div>
</span>
<span v-else>{{ title }}</span>
</p>

<div class="flex" v-if="!readonly">
Expand Down Expand Up @@ -76,6 +93,18 @@
class="align-top"
type="micro" />
</button>
<button
dusk="title-group"
type="button"
class="group-control btn border-l border-gray-200 dark:border-gray-700 w-8 h-8 flex justify-center items-center"
:title="__('Title')"
@click.prevent="setInternalTitle"
>
<Icon
name="document-text"
class="align-top"
type="mini" />
</button>
<button
dusk="delete-group"
type="button"
Expand All @@ -96,6 +125,58 @@
:yes="field.confirmRemoveYes"
:no="field.confirmRemoveNo"
/>

<!-- Internal Title Modal -->
<Modal
:show="showTitleModal"
@close-via-escape="cancelInternalTitle"
role="dialog"
size="md"
>
<form @submit.prevent="saveInternalTitle" class="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
<ModalHeader class="flex items-center">
<h2 class="text-xl text-gray-800 dark:text-gray-200">
{{ __('Set Internal Title') }}
</h2>
</ModalHeader>

<ModalContent class="px-8 py-6">
<div class="mb-6">
<label class="inline-block text-gray-800 dark:text-gray-200 mb-2">
{{ __('Internal Title') }}
</label>
<input
ref="titleInput"
v-model="tempInternalTitle"
type="text"
class="w-full form-control form-input form-control-bordered"
:placeholder="__('Enter internal title...')"
@keyup.enter="saveInternalTitle"
@keyup.escape="cancelInternalTitle"
/>
</div>
</ModalContent>

<ModalFooter>
<div class="ml-auto">
<Button
type="button"
@click="cancelInternalTitle"
class="mr-3"
>
{{ __('Cancel') }}
</Button>

<Button
type="submit"
:loading="false"
>
{{ __('Save') }}
</Button>
</div>
</ModalFooter>
</form>
</Modal>
</div>
</div>
</div>
Expand All @@ -119,10 +200,10 @@

<script>
import { mapProps } from "laravel-nova";
import { Icon } from "laravel-nova-ui";
import { Icon, Button } from "laravel-nova-ui";

export default {
components: { Icon },
components: { Icon, Button },
props: {
errors: {},
group: {},
Expand All @@ -131,13 +212,16 @@ export default {
...mapProps(["resourceName", "resourceId", "mode"]),
},

emits: ["move-up", "move-down", "remove"],
emits: ["move-up", "move-down", "remove", "set-internal-title"],

data() {
return {
removeMessage: false,
collapsed: this.group.collapsed,
readonly: this.group.readonly,
showTitleModal: false,
tempInternalTitle: '',
internalTitle: this.group.internalTitle || '',
};
},

Expand Down Expand Up @@ -180,6 +264,14 @@ export default {

return classes;
},

title() {
if (this.internalTitle) {
return `${this.internalTitle} [${this.group.title}]`;
} else {
return this.group.title;
}
}
},

methods: {
Expand Down Expand Up @@ -228,6 +320,41 @@ export default {
collapse() {
this.collapsed = true;
},

/**
* Set internal title for this group
*/
setInternalTitle() {
this.tempInternalTitle = this.internalTitle;
this.showTitleModal = true;

// Focus the input field after the modal is shown
this.$nextTick(() => {
if (this.$refs.titleInput) {
this.$refs.titleInput.focus();
}
});
},

/**
* Save the internal title
*/
saveInternalTitle() {
this.internalTitle = this.tempInternalTitle;
this.showTitleModal = false;

// Update the group object to persist the internal title (Vue 3 compatible)
this.group.internalTitle = this.internalTitle;
this.$emit("set-internal-title", this.internalTitle);
},

/**
* Cancel internal title editing
*/
cancelInternalTitle() {
this.tempInternalTitle = '';
this.showTitleModal = false;
},
},
};
</script>
Expand Down
6 changes: 5 additions & 1 deletion resources/js/group.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export default class Group {
constructor(name, title, fields, field, key, collapsed = true) {
constructor(name, title, internalTitle, popover, fields, field, key, collapsed = true, attributes = {}) {
this.name = name;
this.title = title;
this.internalTitle = internalTitle || '';
this.popover = popover;
this.fields = fields;
this.key = key || this.getTemporaryUniqueKey(field.attribute);
this.collapsed = collapsed;
this.readonly = field.readonly;
this.attributes = attributes;

this.renameFields();
}
Expand All @@ -29,6 +32,7 @@ export default class Group {
serialize() {
let data = {
layout: this.name,
internalTitle: this.internalTitle,
key: this.key,
attributes: {},
files: {},
Expand Down
29 changes: 29 additions & 0 deletions resources/sass/field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,32 @@ $multiselect-placeholder-color: #7c858e;
.dark .nova-flexible-content-sortable-drag {
background-color: rgba(var(--colors-gray-900),var(--tw-bg-opacity));
}

[popover] {
margin: auto;
width: 24rem;
text-align: center;
opacity: 0;
transform: translateY(-2rem);
transition-property: opacity transform display;
transition-behavior: allow-discrete;
transition-duration: 0.2s;
}

[popover]::backdrop {
background-color: black;
opacity: .65;
}

[popover]:popover-open {
opacity: 1;
transform: translateY(0);
@starting-style {
opacity: 0;
transform: translateY(-2rem);
}
}

[data-popover-toggle] {
anchor-name: --poppy
}
Loading