@dejwcake/craftable 0.1.10 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +117 -3
  2. package/dist/buildMatchPatternFn-BV4I8r8j.js +63 -0
  3. package/dist/buildMatchPatternFn-T5KjFTBQ.cjs +1 -0
  4. package/dist/craftable.css +2 -0
  5. package/dist/cs-B5QMxFrl.js +682 -0
  6. package/dist/cs-MAoetkhe.cjs +1 -0
  7. package/dist/de-BlQ2ZKmc.js +526 -0
  8. package/dist/de-BoVLAn6U.cjs +1 -0
  9. package/dist/en-US-BLvEnhs4.js +443 -0
  10. package/dist/en-US-Dz8vkDZ_.cjs +1 -0
  11. package/dist/en-US-X9gX759i.cjs +1 -0
  12. package/dist/es-DBHDeXHI.cjs +1 -0
  13. package/dist/es-YEqfkBY2.js +437 -0
  14. package/dist/fr-B7MHC2AN.js +407 -0
  15. package/dist/fr-DLs3QoNB.cjs +1 -0
  16. package/dist/hu-BKZPI9jH.cjs +1 -0
  17. package/dist/hu-reoJgNxD.js +425 -0
  18. package/dist/index.cjs +1 -0
  19. package/dist/index.mjs +3866 -0
  20. package/dist/isSameWeek-CC6GX_zp.js +40 -0
  21. package/dist/isSameWeek-Mctj6Tdj.cjs +1 -0
  22. package/dist/pl-B5A0Rjp7.js +628 -0
  23. package/dist/pl-BMyrOO-Q.cjs +1 -0
  24. package/dist/sk-B7JF2MoV.cjs +1 -0
  25. package/dist/sk-Btro3W4S.js +602 -0
  26. package/package.json +68 -40
  27. package/scripts/publish-components.js +118 -0
  28. package/scss/_images.scss +1 -1
  29. package/scss/_variables.scss +13 -6
  30. package/scss/app.scss +4 -4
  31. package/scss/overrides/_alerts.scss +0 -2
  32. package/scss/overrides/_badges.scss +1 -17
  33. package/scss/overrides/_buttons.scss +64 -7
  34. package/scss/overrides/_cards.scss +4 -51
  35. package/scss/overrides/_checkboxes.scss +54 -59
  36. package/scss/overrides/_forms.scss +270 -176
  37. package/scss/overrides/_index.scss +18 -18
  38. package/scss/overrides/_keyframes.scss +6 -4
  39. package/scss/overrides/_layout.scss +24 -30
  40. package/scss/overrides/_media-wysiwyg.scss +9 -111
  41. package/scss/overrides/_modal.scss +0 -18
  42. package/scss/overrides/_multiselect.scss +63 -7
  43. package/scss/overrides/_nav.scss +55 -33
  44. package/scss/overrides/_notifications.scss +6 -1
  45. package/scss/overrides/_pagination.scss +3 -3
  46. package/scss/overrides/_sidebar.scss +84 -0
  47. package/scss/overrides/_tables.scss +52 -37
  48. package/scss/overrides/_text.scss +1 -3
  49. package/scss/overrides/_wysiwyg.scss +151 -48
  50. package/scss/styles/_auth.scss +32 -37
  51. package/scss/styles/_custom.scss +44 -1
  52. package/scss/styles/_index.scss +4 -4
  53. package/scss/styles/_legacy.scss +5 -81
  54. package/scss/styles/_others.scss +232 -337
  55. package/scss/vendor/_index.scss +5 -4
  56. package/src/auth/ActivationError.vue +23 -0
  57. package/src/auth/ActivationForm.vue +69 -0
  58. package/src/auth/ForgotPasswordForm.vue +74 -0
  59. package/src/auth/LoginForm.vue +107 -0
  60. package/src/auth/ResetPasswordForm.vue +122 -0
  61. package/src/components/ConfirmModal.vue +65 -0
  62. package/src/components/UserDetailTooltip.vue +88 -0
  63. package/src/components/form/CkeditorEditor.vue +205 -0
  64. package/src/components/form/FormCheckbox.vue +29 -0
  65. package/src/components/form/FormDatePicker.vue +52 -0
  66. package/src/components/form/FormEmail.vue +17 -0
  67. package/src/components/form/FormInput.vue +31 -0
  68. package/src/components/form/FormLocalizedInput.vue +47 -0
  69. package/src/components/form/FormLocalizedWysiwyg.vue +55 -0
  70. package/src/components/form/FormLocalizedWysiwygSimple.vue +55 -0
  71. package/src/components/form/FormMultiSelect.vue +36 -0
  72. package/src/components/form/FormPasswordConfirm.vue +49 -0
  73. package/src/components/form/FormSelect.vue +35 -0
  74. package/src/components/form/FormSubmit.vue +13 -0
  75. package/src/components/form/FormTextarea.vue +31 -0
  76. package/src/components/form/FormWysiwyg.vue +31 -0
  77. package/src/components/form/FormWysiwygSimple.vue +31 -0
  78. package/src/components/form/LocalizationBar.vue +90 -0
  79. package/src/components/form/MediaUpload.vue +317 -0
  80. package/src/components/form/TiptapEditor.vue +238 -0
  81. package/src/components/listing/BulkCheckboxHeader.vue +18 -0
  82. package/src/components/listing/BulkCheckboxRow.vue +21 -0
  83. package/src/components/listing/BulkOperationsBar.vue +41 -0
  84. package/src/components/listing/DeleteButton.vue +46 -0
  85. package/src/components/listing/EditButton.vue +17 -0
  86. package/src/components/listing/EmptyState.vue +25 -0
  87. package/src/components/listing/ListingHeader.vue +24 -0
  88. package/src/components/listing/Pagination.vue +158 -0
  89. package/src/components/listing/PerPage.vue +15 -0
  90. package/src/components/listing/PublishedAtColumn.vue +226 -0
  91. package/src/components/listing/Search.vue +81 -0
  92. package/src/components/listing/Sortable.vue +28 -0
  93. package/src/components/listing/ToggleSwitch.vue +47 -0
  94. package/src/composables/useAdmin.js +39 -0
  95. package/src/composables/useBaseAuth.js +81 -0
  96. package/src/composables/useBaseForm.js +265 -0
  97. package/src/composables/useBaseListing.js +316 -0
  98. package/src/composables/useResponsiveColumns.js +62 -0
  99. package/src/index.js +62 -0
  100. package/src/translation/TranslationListing.vue +230 -0
  101. package/src/translation/modals/TranslationEditModal.vue +86 -0
  102. package/src/translation/modals/TranslationExportModal.vue +74 -0
  103. package/src/translation/modals/TranslationImportModal.vue +255 -0
  104. package/src/ui/index.js +99 -0
  105. package/src/utils/ckeditorHelpers.js +36 -0
  106. package/src/utils/dateFnsLocale.js +24 -0
  107. package/src/utils/dateFormatters.js +30 -0
  108. package/src/utils/mediaProps.js +14 -0
  109. package/src/utils/notify.js +22 -0
  110. package/dist/admin/Admin.js +0 -42
  111. package/dist/auth/Form.js +0 -15
  112. package/dist/base-components/Form/BaseForm.js +0 -395
  113. package/dist/base-components/Form/BaseUpload.js +0 -188
  114. package/dist/base-components/Form/bootstrap.js +0 -21
  115. package/dist/base-components/Listing/BaseListing.js +0 -454
  116. package/dist/base-components/Listing/components/Pagination.js +0 -105
  117. package/dist/base-components/Listing/components/Sortable.js +0 -48
  118. package/dist/base-components/Listing/components/UserDetailTooltip.js +0 -55
  119. package/dist/index.js +0 -70
  120. package/dist/overrides/trumbowyg.edit-embed-template.js +0 -72
  121. package/dist/overrides/trumbowyg.reupload.js +0 -146
  122. package/dist/overrides/trumbowyg.template.js +0 -67
  123. package/dist/translation/Form.js +0 -15
  124. package/dist/translation/Listing.js +0 -283
  125. package/dist/ui/index.js +0 -33
  126. package/scss/overrides/_switches.scss +0 -25
@@ -0,0 +1,238 @@
1
+ <template>
2
+ <div class="tiptap-editor">
3
+ <div v-if="editor" class="tiptap-toolbar">
4
+ <select class="tiptap-heading-select" @change="setHeading($event)">
5
+ <option value="0" :selected="!editor.isActive('heading')">Paragraph</option>
6
+ <option
7
+ v-for="level in [1, 2, 3, 4, 5, 6]"
8
+ :key="level"
9
+ :value="level"
10
+ :selected="editor.isActive('heading', { level })"
11
+ >
12
+ H{{ level }}
13
+ </option>
14
+ </select>
15
+
16
+ <button
17
+ type="button"
18
+ :class="{ 'is-active': editor.isActive('bold') }"
19
+ title="Bold"
20
+ @click="editor.chain().focus().toggleBold().run()"
21
+ >
22
+ <strong>B</strong>
23
+ </button>
24
+ <button
25
+ type="button"
26
+ :class="{ 'is-active': editor.isActive('italic') }"
27
+ title="Italic"
28
+ @click="editor.chain().focus().toggleItalic().run()"
29
+ >
30
+ <em>I</em>
31
+ </button>
32
+ <button
33
+ type="button"
34
+ :class="{ 'is-active': editor.isActive('strike') }"
35
+ title="Strikethrough"
36
+ @click="editor.chain().focus().toggleStrike().run()"
37
+ >
38
+ <s>S</s>
39
+ </button>
40
+
41
+ <span class="tiptap-toolbar-divider"></span>
42
+
43
+ <button
44
+ type="button"
45
+ :class="{ 'is-active': editor.isActive('bulletList') }"
46
+ title="Bullet List"
47
+ @click="editor.chain().focus().toggleBulletList().run()"
48
+ >
49
+ &#8226;
50
+ </button>
51
+ <button
52
+ type="button"
53
+ :class="{ 'is-active': editor.isActive('orderedList') }"
54
+ title="Ordered List"
55
+ @click="editor.chain().focus().toggleOrderedList().run()"
56
+ >
57
+ 1.
58
+ </button>
59
+
60
+ <span class="tiptap-toolbar-divider"></span>
61
+
62
+ <button type="button" title="Link" @click="setLink">&#128279;</button>
63
+ <button type="button" title="Upload Image" @click="uploadImage">&#128228;</button>
64
+ <button type="button" title="Image URL" @click="addImageByUrl">&#128247;</button>
65
+ <input ref="fileInput" type="file" accept="image/*" style="display: none" @change="onFileSelected" />
66
+
67
+ <span v-if="showTableControls" class="tiptap-toolbar-divider"></span>
68
+
69
+ <button
70
+ v-if="showTableControls"
71
+ type="button"
72
+ title="Insert Table"
73
+ @click="editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()"
74
+ >
75
+ &#9638;
76
+ </button>
77
+
78
+ <span class="tiptap-toolbar-divider"></span>
79
+
80
+ <button
81
+ type="button"
82
+ :disabled="!editor.can().undo()"
83
+ title="Undo"
84
+ @click="editor.chain().focus().undo().run()"
85
+ >
86
+ &#8617;
87
+ </button>
88
+ <button
89
+ type="button"
90
+ :disabled="!editor.can().redo()"
91
+ title="Redo"
92
+ @click="editor.chain().focus().redo().run()"
93
+ >
94
+ &#8618;
95
+ </button>
96
+ </div>
97
+
98
+ <editor-content :editor="editor" class="tiptap-content" />
99
+ </div>
100
+ </template>
101
+
102
+ <script setup>
103
+ import { ref, onBeforeUnmount, watch } from 'vue';
104
+ const axios = window.axios;
105
+ import { notifyError } from '../../utils/notify.js';
106
+ import { useEditor, EditorContent } from '@tiptap/vue-3';
107
+ import StarterKit from '@tiptap/starter-kit';
108
+ import Image from '@tiptap/extension-image';
109
+ import { Table, TableRow, TableCell, TableHeader } from '@tiptap/extension-table';
110
+
111
+ const props = defineProps({
112
+ modelValue: {
113
+ type: String,
114
+ default: '',
115
+ },
116
+ config: {
117
+ type: Object,
118
+ default: () => ({}),
119
+ },
120
+ showTableControls: {
121
+ type: Boolean,
122
+ default: true,
123
+ },
124
+ uploadUrl: {
125
+ type: String,
126
+ default: '/admin/wysiwyg-media',
127
+ },
128
+ });
129
+
130
+ const emit = defineEmits(['update:modelValue', 'media-uploaded']);
131
+
132
+ const extensions = [
133
+ StarterKit.configure({
134
+ link: { openOnClick: false },
135
+ }),
136
+ Image.configure({
137
+ HTMLAttributes: {
138
+ class: 'tiptap-image',
139
+ },
140
+ }),
141
+ Table.configure({
142
+ resizable: true,
143
+ }),
144
+ TableRow,
145
+ TableCell,
146
+ TableHeader,
147
+ ...(props.config.extensions || []),
148
+ ];
149
+
150
+ const editor = useEditor({
151
+ content: props.modelValue,
152
+ extensions,
153
+ onUpdate: ({ editor: ed }) => {
154
+ emit('update:modelValue', ed.getHTML());
155
+ },
156
+ });
157
+
158
+ watch(
159
+ () => props.modelValue,
160
+ (value) => {
161
+ if (editor.value && editor.value.getHTML() !== value) {
162
+ editor.value.commands.setContent(value, { emitUpdate: false });
163
+ }
164
+ },
165
+ );
166
+
167
+ onBeforeUnmount(() => {
168
+ if (editor.value) {
169
+ editor.value.destroy();
170
+ }
171
+ });
172
+
173
+ function setHeading(event) {
174
+ const level = parseInt(event.target.value);
175
+ if (level === 0) {
176
+ editor.value.chain().focus().setParagraph().run();
177
+ } else {
178
+ editor.value.chain().focus().toggleHeading({ level }).run();
179
+ }
180
+ }
181
+
182
+ function setLink() {
183
+ const previousUrl = editor.value.getAttributes('link').href;
184
+ const url = window.prompt('URL', previousUrl);
185
+
186
+ if (url === null) return;
187
+
188
+ if (url === '') {
189
+ editor.value.chain().focus().extendMarkRange('link').unsetLink().run();
190
+ return;
191
+ }
192
+
193
+ editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
194
+ }
195
+
196
+ const fileInput = ref(null);
197
+
198
+ function addImageByUrl() {
199
+ const url = window.prompt('Image URL');
200
+ if (url) {
201
+ editor.value.chain().focus().setImage({ src: url }).run();
202
+ }
203
+ }
204
+
205
+ function uploadImage() {
206
+ fileInput.value.click();
207
+ }
208
+
209
+ function onFileSelected(event) {
210
+ const file = event.target.files[0];
211
+ if (!file) return;
212
+
213
+ const formData = new FormData();
214
+ formData.append('fileToUpload', file);
215
+
216
+ const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
217
+
218
+ axios
219
+ .post(props.uploadUrl, formData, {
220
+ headers: {
221
+ 'Content-Type': 'multipart/form-data',
222
+ ...(token ? { 'X-CSRF-TOKEN': token } : {}),
223
+ },
224
+ })
225
+ .then(
226
+ (response) => {
227
+ editor.value.chain().focus().setImage({ src: response.data.file }).run();
228
+ emit('media-uploaded', response.data);
229
+ },
230
+ (error) => {
231
+ notifyError(error.response?.data?.error);
232
+ },
233
+ )
234
+ .finally(() => {
235
+ fileInput.value.value = '';
236
+ });
237
+ }
238
+ </script>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <input
3
+ id="enabled"
4
+ class="form-check-input"
5
+ type="checkbox"
6
+ :checked="isClickedAll"
7
+ name="enabled_fake_element"
8
+ @click="onToggleAll()"
9
+ />
10
+ <label class="form-check-label" for="enabled"> # </label>
11
+ </template>
12
+
13
+ <script setup>
14
+ defineProps({
15
+ isClickedAll: { type: Boolean, required: true },
16
+ onToggleAll: { type: Function, required: true },
17
+ });
18
+ </script>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <input
3
+ :id="'enabled' + itemId"
4
+ class="form-check-input"
5
+ type="checkbox"
6
+ :checked="checked"
7
+ :name="'enabled' + itemId + '_fake_element'"
8
+ :disabled="disabled"
9
+ @click="onToggle(itemId)"
10
+ />
11
+ <label class="form-check-label" :for="'enabled' + itemId"> </label>
12
+ </template>
13
+
14
+ <script setup>
15
+ defineProps({
16
+ itemId: { type: [Number, String], required: true },
17
+ checked: { type: Boolean, default: false },
18
+ disabled: { type: Boolean, default: false },
19
+ onToggle: { type: Function, required: true },
20
+ });
21
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <span class="align-middle fw-light text-dark">
3
+ {{ translations.selected_items }} {{ selectedCount }}.
4
+ <a v-if="selectedCount < totalCount" href="javascript:void(0)" class="text-primary" @click="onCheckAll()">
5
+ <i v-if="loading" class="fa fa-spinner"></i>
6
+ {{ translations.check_all_items }} {{ totalCount }}
7
+ </a>
8
+ <span v-if="selectedCount < totalCount" class="text-primary"> | </span>
9
+ <a href="javascript:void(0)" class="text-primary" @click="onUncheckAll()">
10
+ {{ translations.uncheck_all_items }}
11
+ </a>
12
+ </span>
13
+
14
+ <span class="float-end pe-2">
15
+ <slot>
16
+ <button class="btn btn-sm btn-danger px-4" @click="onBulkDelete()">
17
+ {{ translations.delete_btn }}
18
+ </button>
19
+ </slot>
20
+ </span>
21
+ </template>
22
+
23
+ <script setup>
24
+ defineProps({
25
+ selectedCount: { type: Number, required: true },
26
+ totalCount: { type: Number, required: true },
27
+ loading: { type: Boolean, default: false },
28
+ onCheckAll: { type: Function, required: true },
29
+ onUncheckAll: { type: Function, required: true },
30
+ onBulkDelete: { type: Function, required: true },
31
+ translations: {
32
+ type: Object,
33
+ default: () => ({
34
+ selected_items: 'Selected items',
35
+ check_all_items: 'Check all items',
36
+ uncheck_all_items: 'Uncheck all items',
37
+ delete_btn: 'Delete',
38
+ }),
39
+ },
40
+ });
41
+ </script>
@@ -0,0 +1,46 @@
1
+ <template>
2
+ <button type="button" class="btn btn-sm btn-danger" :title="translations.delete_btn" @click="showConfirm = true">
3
+ <i class="fa fa-trash"></i>
4
+ </button>
5
+
6
+ <ConfirmModal :show="showConfirm" :translations="translations" @confirm="onConfirm" @cancel="showConfirm = false" />
7
+ </template>
8
+
9
+ <script setup>
10
+ import { ref } from 'vue';
11
+ const axios = window.axios;
12
+ import { notifySuccess, notifyError } from '../../utils/notify.js';
13
+ import ConfirmModal from '../ConfirmModal.vue';
14
+
15
+ const props = defineProps({
16
+ url: { type: String, required: true },
17
+ translations: {
18
+ type: Object,
19
+ default: () => ({
20
+ delete_btn: 'Delete',
21
+ confirm_title: 'Warning!',
22
+ confirm_text: 'Do you really want to delete this item?',
23
+ confirm_btn: 'Delete',
24
+ cancel_btn: 'Cancel',
25
+ }),
26
+ },
27
+ });
28
+
29
+ const emit = defineEmits(['deleted']);
30
+
31
+ const showConfirm = ref(false);
32
+
33
+ function onConfirm() {
34
+ showConfirm.value = false;
35
+
36
+ axios.delete(props.url).then(
37
+ (response) => {
38
+ notifySuccess(response.data.message);
39
+ emit('deleted');
40
+ },
41
+ (error) => {
42
+ notifyError(error.response?.data?.message);
43
+ },
44
+ );
45
+ }
46
+ </script>
@@ -0,0 +1,17 @@
1
+ <template>
2
+ <a class="btn btn-sm btn-spinner btn-info" :href="url" :title="translations.edit_btn" role="button">
3
+ <i class="fa fa-edit"></i>
4
+ </a>
5
+ </template>
6
+
7
+ <script setup>
8
+ defineProps({
9
+ url: { type: String, required: true },
10
+ translations: {
11
+ type: Object,
12
+ default: () => ({
13
+ edit_btn: 'Edit',
14
+ }),
15
+ },
16
+ });
17
+ </script>
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div v-if="show" class="no-items-found">
3
+ <i class="fa fa-magnifying-glass"></i>
4
+ <h3>{{ translations.no_items }}</h3>
5
+ <p>{{ translations.try_changing_items }}</p>
6
+ <a v-if="createUrl" class="btn btn-primary btn-spinner" :href="createUrl" role="button">
7
+ <i class="fa fa-plus"></i>&nbsp; {{ translations.create_btn }}
8
+ </a>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup>
13
+ defineProps({
14
+ show: { type: Boolean, default: true },
15
+ createUrl: { type: String, default: '' },
16
+ translations: {
17
+ type: Object,
18
+ default: () => ({
19
+ no_items: 'Could not find any items',
20
+ try_changing_items: 'Try changing the filters or add a new one',
21
+ create_btn: 'New',
22
+ }),
23
+ },
24
+ });
25
+ </script>
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <i class="fa fa-align-justify"></i> {{ translations.listing_title }}
3
+ <a v-if="exportUrl" class="btn btn-primary btn-sm float-end mb-0 ms-2" :href="exportUrl" role="button">
4
+ <i class="fa fa-file-excel"></i>&nbsp; {{ translations.export_btn }}
5
+ </a>
6
+ <a v-if="createUrl" class="btn btn-primary btn-spinner btn-sm float-end mb-0" :href="createUrl" role="button">
7
+ <i class="fa fa-plus"></i>&nbsp; {{ translations.create_btn }}
8
+ </a>
9
+ </template>
10
+
11
+ <script setup>
12
+ defineProps({
13
+ createUrl: { type: String, default: '' },
14
+ exportUrl: { type: String, default: '' },
15
+ translations: {
16
+ type: Object,
17
+ default: () => ({
18
+ listing_title: 'Listing',
19
+ create_btn: 'New',
20
+ export_btn: 'Export',
21
+ }),
22
+ },
23
+ });
24
+ </script>
@@ -0,0 +1,158 @@
1
+ <template>
2
+ <div v-if="pagination.total > 0" class="row">
3
+ <div class="col-sm">
4
+ <span class="pagination-caption">{{ overview }}</span>
5
+ </div>
6
+ <div class="col-sm-auto">
7
+ <nav>
8
+ <ul v-if="pagination.last_page > 1" :class="['pagination', sizeClass]">
9
+ <li v-if="showPrevious()" :class="{ disabled: pagination.current_page <= 1, 'page-item': true }">
10
+ <span v-if="pagination.current_page <= 1" class="page-link">
11
+ <span aria-hidden="true">{{ translations.previous }}</span>
12
+ </span>
13
+
14
+ <a
15
+ v-if="pagination.current_page > 1"
16
+ href="#"
17
+ :aria-label="translations.previous"
18
+ class="page-link"
19
+ @click.prevent="changePage(pagination.current_page - 1)"
20
+ >
21
+ <span aria-hidden="true">{{ translations.previous }}</span>
22
+ </a>
23
+ </li>
24
+
25
+ <li
26
+ v-for="num in array"
27
+ :key="num"
28
+ :class="{ active: num === pagination.current_page, 'page-item': true }"
29
+ >
30
+ <a href="#" class="page-link" @click.prevent="changePage(num)">{{ num }}</a>
31
+ </li>
32
+
33
+ <li
34
+ v-if="showNext()"
35
+ :class="{
36
+ disabled: pagination.current_page === pagination.last_page || pagination.last_page === 0,
37
+ 'page-item': true,
38
+ }"
39
+ >
40
+ <span
41
+ v-if="pagination.current_page === pagination.last_page || pagination.last_page === 0"
42
+ class="page-link"
43
+ >
44
+ <span aria-hidden="true">{{ translations.next }}</span>
45
+ </span>
46
+
47
+ <a
48
+ v-if="pagination.current_page < pagination.last_page"
49
+ href="#"
50
+ :aria-label="translations.next"
51
+ class="page-link"
52
+ @click.prevent="changePage(pagination.current_page + 1)"
53
+ >
54
+ <span aria-hidden="true">{{ translations.next }}</span>
55
+ </a>
56
+ </li>
57
+ </ul>
58
+ </nav>
59
+ </div>
60
+ </div>
61
+ </template>
62
+
63
+ <script setup>
64
+ import { computed, watch } from 'vue';
65
+ import { useCookies } from 'vue3-cookies';
66
+
67
+ const props = defineProps({
68
+ pagination: { type: Object, required: true },
69
+ options: { type: Object, default: () => ({}) },
70
+ size: { type: String, default: '' },
71
+ translations: {
72
+ type: Object,
73
+ default: () => ({
74
+ previous: 'Previous',
75
+ next: 'Next',
76
+ overview: 'Displaying items from :from to :to of total :total items.',
77
+ }),
78
+ },
79
+ });
80
+
81
+ const emit = defineEmits(['page-change', 'per-page-change']);
82
+ const { cookies } = useCookies();
83
+
84
+ const overview = computed(() => {
85
+ return (props.translations.overview || '')
86
+ .replace(':from', props.pagination.from)
87
+ .replace(':to', props.pagination.to)
88
+ .replace(':total', props.pagination.total);
89
+ });
90
+
91
+ const config = computed(() => {
92
+ return Object.assign(
93
+ {
94
+ offset: 3,
95
+ alwaysShowPrevNext: false,
96
+ },
97
+ props.options,
98
+ );
99
+ });
100
+
101
+ const array = computed(() => {
102
+ if (props.pagination.last_page <= 0) {
103
+ return [];
104
+ }
105
+
106
+ let from = props.pagination.current_page - config.value.offset;
107
+ if (from < 1) {
108
+ from = 1;
109
+ }
110
+
111
+ let to = from + config.value.offset * 2;
112
+ if (to >= props.pagination.last_page) {
113
+ to = props.pagination.last_page;
114
+ }
115
+
116
+ const arr = [];
117
+ while (from <= to) {
118
+ arr.push(from);
119
+ from++;
120
+ }
121
+
122
+ return arr;
123
+ });
124
+
125
+ const sizeClass = computed(() => {
126
+ if (props.size === 'large') {
127
+ return 'pagination-lg';
128
+ } else if (props.size === 'small') {
129
+ return 'pagination-sm';
130
+ }
131
+ return '';
132
+ });
133
+
134
+ watch(
135
+ () => props.pagination.per_page,
136
+ (newVal, oldVal) => {
137
+ if (+newVal !== +oldVal) {
138
+ cookies.set('per_page', newVal);
139
+ emit('per-page-change');
140
+ }
141
+ },
142
+ );
143
+
144
+ function showPrevious() {
145
+ return config.value.alwaysShowPrevNext || props.pagination.current_page > 1;
146
+ }
147
+
148
+ function showNext() {
149
+ return config.value.alwaysShowPrevNext || props.pagination.current_page < props.pagination.last_page;
150
+ }
151
+
152
+ function changePage(page) {
153
+ if (props.pagination.current_page === page) {
154
+ return;
155
+ }
156
+ emit('page-change', page);
157
+ }
158
+ </script>
@@ -0,0 +1,15 @@
1
+ <template>
2
+ <select class="form-select" :value="modelValue" @change="$emit('update:modelValue', +$event.target.value)">
3
+ <option value="10">10</option>
4
+ <option value="25">25</option>
5
+ <option value="100">100</option>
6
+ </select>
7
+ </template>
8
+
9
+ <script setup>
10
+ defineProps({
11
+ modelValue: { type: [Number, String], default: 10 },
12
+ });
13
+
14
+ defineEmits(['update:modelValue']);
15
+ </script>