@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,265 @@
1
+ import { ref, computed, getCurrentInstance, onBeforeUnmount } from 'vue';
2
+ import { useForm, validate as veeValidate } from 'vee-validate';
3
+ import { notifySuccess, notifyError } from '../utils/notify.js';
4
+ // Use the globally configured axios instance (with CSRF token and X-Requested-With header)
5
+ const axios = window.axios;
6
+ import { formatDate, formatDatetime, formatTime } from '../utils/dateFormatters.js';
7
+ import { getDateFnsLocale } from '../utils/dateFnsLocale.js';
8
+
9
+ export function useBaseForm(props, options = {}) {
10
+ const instance = getCurrentInstance();
11
+ const form = ref(props.data && Object.keys(props.data).length > 0 ? { ...props.data } : {});
12
+ const wysiwygMedia = ref([]);
13
+ const mediaCollections = ref([]);
14
+ const isFormLocalized = ref(false);
15
+ const currentLocale = ref('');
16
+ const submitting = ref(false);
17
+ const onSmallScreen = ref(window.innerWidth < (props.responsiveBreakpoint || 850));
18
+
19
+ const { setErrors, errors } = useForm();
20
+
21
+ // Date picker configs
22
+ const datePickerConfig = ref({
23
+ enableTimePicker: false,
24
+ enableSeconds: false,
25
+ modelType: 'yyyy-MM-dd HH:mm:ss',
26
+ format: 'dd.MM.yyyy',
27
+ locale: getDateFnsLocale(),
28
+ });
29
+
30
+ const timePickerConfig = ref({
31
+ timePicker: true,
32
+ enableTimePicker: true,
33
+ enableSeconds: false,
34
+ modelType: 'HH:mm:ss',
35
+ format: 'HH:mm',
36
+ locale: getDateFnsLocale(),
37
+ });
38
+
39
+ const datetimePickerConfig = ref({
40
+ enableTimePicker: true,
41
+ enableSeconds: false,
42
+ modelType: 'yyyy-MM-dd HH:mm:ss',
43
+ format: 'dd.MM.yyyy HH:mm',
44
+ locale: getDateFnsLocale(),
45
+ });
46
+
47
+ // Localization
48
+ const locales = computed(() => props.locales || []);
49
+ const defaultLocale = computed(() => props.defaultLocale || (locales.value.length > 0 ? locales.value[0] : ''));
50
+
51
+ const otherLocales = computed(() => {
52
+ return locales.value.filter((x) => x !== defaultLocale.value);
53
+ });
54
+
55
+ const showLocalizedValidationError = computed(() => {
56
+ return otherLocales.value.some((lang) => {
57
+ const errorKeys = Object.keys(errors.value);
58
+ return errorKeys.some((field) => {
59
+ return field.endsWith('_' + lang) || field.startsWith(lang + '_');
60
+ });
61
+ });
62
+ });
63
+
64
+ // Init localization
65
+ if (locales.value.length > 0) {
66
+ currentLocale.value = defaultLocale.value;
67
+ }
68
+
69
+ // Resize handler
70
+ function onResize() {
71
+ onSmallScreen.value = window.innerWidth < (props.responsiveBreakpoint || 850);
72
+ }
73
+
74
+ window.addEventListener('resize', onResize);
75
+ onBeforeUnmount(() => {
76
+ window.removeEventListener('resize', onResize);
77
+ });
78
+
79
+ // Methods
80
+ function getPostData() {
81
+ const refs = instance?.proxy?.$refs || {};
82
+ if (mediaCollections.value) {
83
+ mediaCollections.value.forEach((collection) => {
84
+ if (form.value[collection]) {
85
+ console.warn(
86
+ "MediaUploader warning: Media input must have a unique name, '" +
87
+ collection +
88
+ "' is already defined in regular inputs.",
89
+ );
90
+ }
91
+
92
+ const uploaderRef = refs[collection + '_uploader'];
93
+ if (uploaderRef) {
94
+ const uploader = Array.isArray(uploaderRef) ? uploaderRef[0] : uploaderRef;
95
+ if (uploader && typeof uploader.getFiles === 'function') {
96
+ form.value[collection] = uploader.getFiles();
97
+ }
98
+ }
99
+ });
100
+ }
101
+ form.value['wysiwygMedia'] = wysiwygMedia.value;
102
+
103
+ if (typeof options.transformData === 'function') {
104
+ return options.transformData({ ...form.value });
105
+ }
106
+
107
+ return form.value;
108
+ }
109
+
110
+ async function onSubmit() {
111
+ if (options.validationSchema) {
112
+ const fieldErrors = {};
113
+ for (const [field, rules] of Object.entries(options.validationSchema)) {
114
+ const result = await veeValidate(form.value[field], rules, {
115
+ name: field,
116
+ values: form.value,
117
+ });
118
+ if (!result.valid) {
119
+ fieldErrors[field] = result.errors[0];
120
+ }
121
+ }
122
+
123
+ // Clear all schema field errors, then set only invalid ones
124
+ const errorState = {};
125
+ for (const field of Object.keys(options.validationSchema)) {
126
+ errorState[field] = fieldErrors[field] || undefined;
127
+ }
128
+ setErrors(errorState);
129
+
130
+ if (Object.keys(fieldErrors).length > 0) {
131
+ notifyError('The form contains invalid fields.');
132
+ return false;
133
+ }
134
+ }
135
+
136
+ submitting.value = true;
137
+
138
+ let postData = getPostData();
139
+ if (!props.sendEmptyLocales && props.sendEmptyLocales !== undefined) {
140
+ const filtered = {};
141
+ for (const [key, value] of Object.entries(postData)) {
142
+ if (!locales.value.includes(key) || (value && Object.keys(value).length > 0)) {
143
+ filtered[key] = value;
144
+ }
145
+ }
146
+ postData = filtered;
147
+ }
148
+
149
+ axios
150
+ .post(props.action, postData)
151
+ .then((response) => {
152
+ onSuccess(response.data);
153
+ })
154
+ .catch((err) => {
155
+ onFail(err.response?.data || {});
156
+ });
157
+ }
158
+
159
+ function onSuccess(data) {
160
+ submitting.value = false;
161
+ if (data.message) {
162
+ notifySuccess(data.message);
163
+ }
164
+ if (data.redirect) {
165
+ if (data.message) {
166
+ setTimeout(() => window.location.replace(data.redirect), 2000);
167
+ } else {
168
+ window.location.replace(data.redirect);
169
+ }
170
+ }
171
+ }
172
+
173
+ function onFail(data) {
174
+ submitting.value = false;
175
+ if (data.errors !== undefined) {
176
+ const fieldErrors = {};
177
+ Object.keys(data.errors).forEach((key) => {
178
+ const splitted = key.split('.', 2);
179
+ if (splitted.length > 1) {
180
+ fieldErrors[splitted[0] + '_' + splitted[1]] = data.errors[key][0];
181
+ } else {
182
+ fieldErrors[key] = data.errors[key][0];
183
+ }
184
+ });
185
+ setErrors(fieldErrors);
186
+ if (data.message === undefined) {
187
+ notifyError('The form contains invalid fields.');
188
+ }
189
+ }
190
+ if (data.message !== undefined) {
191
+ notifyError(data.message);
192
+ }
193
+ }
194
+
195
+ function getLocalizedFormDefaults() {
196
+ const object = {};
197
+ locales.value.forEach((locale) => {
198
+ object[locale] = null;
199
+ });
200
+ return object;
201
+ }
202
+
203
+ function showLocalization() {
204
+ isFormLocalized.value = true;
205
+ currentLocale.value = otherLocales.value[0];
206
+ const container = document.querySelector('.container-xl');
207
+ if (container) container.classList.add('width-auto');
208
+ }
209
+
210
+ function hideLocalization() {
211
+ isFormLocalized.value = false;
212
+ const container = document.querySelector('.container-xl');
213
+ if (container) container.classList.remove('width-auto');
214
+ }
215
+
216
+ function shouldShowLangGroup(locale) {
217
+ if (!onSmallScreen.value) {
218
+ if (defaultLocale.value === locale) return true;
219
+ return isFormLocalized.value && currentLocale.value === locale;
220
+ } else {
221
+ return currentLocale.value === locale;
222
+ }
223
+ }
224
+
225
+ return {
226
+ // State
227
+ form,
228
+ wysiwygMedia,
229
+ mediaCollections,
230
+ isFormLocalized,
231
+ currentLocale,
232
+ submitting,
233
+ onSmallScreen,
234
+ errors,
235
+
236
+ // Configs
237
+ datePickerConfig,
238
+ timePickerConfig,
239
+ datetimePickerConfig,
240
+
241
+ // Computed
242
+ locales,
243
+ defaultLocale,
244
+ otherLocales,
245
+ showLocalizedValidationError,
246
+
247
+ // Methods
248
+ getPostData,
249
+ onSubmit,
250
+ onSuccess,
251
+ onFail,
252
+ getLocalizedFormDefaults,
253
+ showLocalization,
254
+ hideLocalization,
255
+ shouldShowLangGroup,
256
+
257
+ // Vee-validate
258
+ setErrors,
259
+
260
+ // Date formatters (convenience re-exports)
261
+ formatDate,
262
+ formatDatetime,
263
+ formatTime,
264
+ };
265
+ }
@@ -0,0 +1,316 @@
1
+ import { ref, reactive, computed, onBeforeUnmount } from 'vue';
2
+ import { useCookies } from 'vue3-cookies';
3
+ const axios = window.axios;
4
+ import { formatDate, formatDatetime, formatTime, nowInTimezone } from '../utils/dateFormatters.js';
5
+ import { notifySuccess, notifyError } from '../utils/notify.js';
6
+
7
+ export function useBaseListing(props) {
8
+ const { cookies } = useCookies();
9
+
10
+ const pagination = reactive({
11
+ state: {
12
+ per_page: cookies.get('per_page') || 10,
13
+ current_page: 1,
14
+ last_page: 1,
15
+ from: 1,
16
+ to: 10,
17
+ },
18
+ options: {
19
+ alwaysShowPrevNext: true,
20
+ },
21
+ });
22
+
23
+ const orderBy = reactive({
24
+ column: 'id',
25
+ direction: 'asc',
26
+ });
27
+
28
+ const filters = reactive({});
29
+ const search = ref('');
30
+ const collection = ref(null);
31
+ const now = ref(nowInTimezone(props.timezone || 'UTC'));
32
+ const bulkItems = reactive({});
33
+ const bulkCheckingAllLoader = ref(false);
34
+
35
+ const confirmModal = reactive({
36
+ show: false,
37
+ onConfirm: null,
38
+ translations: {
39
+ confirm_title: 'Warning!',
40
+ confirm_text: 'Are you sure?',
41
+ confirm_btn: 'Yes, confirm',
42
+ cancel_btn: 'Cancel',
43
+ },
44
+ });
45
+
46
+ // Update "now" every second
47
+ const nowInterval = setInterval(() => {
48
+ now.value = nowInTimezone(props.timezone || 'UTC');
49
+ }, 1000);
50
+
51
+ onBeforeUnmount(() => {
52
+ clearInterval(nowInterval);
53
+ });
54
+
55
+ // Computed
56
+ const clickedBulkItemsCount = computed(() => {
57
+ return Object.values(bulkItems).filter((item) => item === true).length;
58
+ });
59
+
60
+ const isClickedAll = computed({
61
+ get() {
62
+ return (
63
+ clickedBulkItemsCount.value >= pagination.state.to - pagination.state.from + 1 &&
64
+ clickedBulkItemsCount.value > 0 &&
65
+ allClickedItemsAreSame()
66
+ );
67
+ },
68
+ set() {},
69
+ });
70
+
71
+ // Init
72
+ if (props.data != null) {
73
+ populateCurrentStateAndData(props.data);
74
+ } else {
75
+ loadData();
76
+ }
77
+
78
+ // Methods
79
+ function allClickedItemsAreSame() {
80
+ if (!collection.value) return false;
81
+ const itemsInPaginationIds = Object.values(collection.value).map(({ id }) => id);
82
+
83
+ for (let i = 0; i < itemsInPaginationIds.length; i++) {
84
+ const itemInPaginationId = itemsInPaginationIds[i];
85
+ if (bulkItems[itemInPaginationId] === undefined || bulkItems[itemInPaginationId] === false) {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ function onBulkItemClicked(id) {
94
+ if (bulkItems[id] === undefined) {
95
+ bulkItems[id] = true;
96
+ } else {
97
+ bulkItems[id] = !bulkItems[id];
98
+ }
99
+ }
100
+
101
+ function onBulkItemsClickedAll(url) {
102
+ const options = {
103
+ params: {
104
+ bulk: true,
105
+ },
106
+ };
107
+
108
+ bulkCheckingAllLoader.value = true;
109
+ Object.assign(options.params, filters);
110
+
111
+ axios
112
+ .get(url, options)
113
+ .then((response) => {
114
+ checkAllItems(response.data.bulkItems);
115
+ })
116
+ .catch((error) => {
117
+ notifyError(error.response?.data?.message);
118
+ })
119
+ .finally(() => {
120
+ bulkCheckingAllLoader.value = false;
121
+ });
122
+ }
123
+
124
+ function onBulkItemsClickedAllWithPagination() {
125
+ if (!collection.value) return;
126
+ const itemsInPagination = Object.values(collection.value).map(({ id }) => id);
127
+ if (!isClickedAll.value) {
128
+ bulkCheckingAllLoader.value = true;
129
+ checkAllItems(itemsInPagination);
130
+ bulkCheckingAllLoader.value = false;
131
+ } else {
132
+ onBulkItemsClickedAllUncheck(itemsInPagination);
133
+ }
134
+ }
135
+
136
+ function checkAllItems(itemsToCheck) {
137
+ itemsToCheck.forEach((itemId) => {
138
+ bulkItems[itemId] = true;
139
+ });
140
+ }
141
+
142
+ function onBulkItemsClickedAllUncheck(bulkItemsToUncheck = null) {
143
+ if (bulkItemsToUncheck === null) {
144
+ Object.keys(bulkItems).forEach((key) => delete bulkItems[key]);
145
+ } else {
146
+ if (!collection.value) return;
147
+ Object.values(collection.value)
148
+ .map(({ id }) => id)
149
+ .forEach((itemId) => {
150
+ bulkItems[itemId] = false;
151
+ });
152
+ }
153
+ }
154
+
155
+ function bulkDelete(url, trans = {}) {
156
+ const itemsToDelete = Object.keys(bulkItems).filter((key) => bulkItems[key]);
157
+
158
+ confirmModal.translations = {
159
+ ...confirmModal.translations,
160
+ ...trans,
161
+ confirm_text: (trans.confirm_text || 'Do you really want to delete :number selected items?').replace(
162
+ ':number',
163
+ clickedBulkItemsCount.value,
164
+ ),
165
+ };
166
+ confirmModal.onConfirm = () => {
167
+ confirmModal.show = false;
168
+ axios
169
+ .post(url, {
170
+ ids: itemsToDelete,
171
+ })
172
+ .then((response) => {
173
+ Object.keys(bulkItems).forEach((key) => delete bulkItems[key]);
174
+ loadData();
175
+ notifySuccess(response.data.message);
176
+ })
177
+ .catch((error) => {
178
+ notifyError(error.response?.data?.message);
179
+ });
180
+ };
181
+ confirmModal.show = true;
182
+ }
183
+
184
+ function loadData(resetCurrentPage) {
185
+ const options = {
186
+ params: {
187
+ per_page: pagination.state.per_page,
188
+ page: pagination.state.current_page,
189
+ orderBy: orderBy.column,
190
+ orderDirection: orderBy.direction,
191
+ },
192
+ };
193
+
194
+ if (resetCurrentPage === true) {
195
+ options.params.page = 1;
196
+ }
197
+
198
+ Object.assign(options.params, filters);
199
+
200
+ axios
201
+ .get(props.url, options)
202
+ .then((response) => {
203
+ populateCurrentStateAndData(response.data.data);
204
+ })
205
+ .catch((error) => {
206
+ notifyError(error.response?.data?.message);
207
+ });
208
+ }
209
+
210
+ function filter(column, value) {
211
+ if (value === '') {
212
+ delete filters[column];
213
+ } else {
214
+ filters[column] = value;
215
+ }
216
+ loadData(true);
217
+ }
218
+
219
+ function populateCurrentStateAndData(object) {
220
+ if (object.current_page > object.last_page && object.total > 0) {
221
+ pagination.state.current_page = object.last_page;
222
+ loadData();
223
+ return;
224
+ }
225
+
226
+ collection.value = object.data;
227
+ pagination.state.current_page = object.current_page;
228
+ pagination.state.last_page = object.last_page;
229
+ pagination.state.total = object.total;
230
+ pagination.state.per_page = object.per_page;
231
+ pagination.state.to = object.to;
232
+ pagination.state.from = object.from;
233
+ }
234
+
235
+ function getAction(url) {
236
+ return axios
237
+ .get(url)
238
+ .then((response) => {
239
+ if (response.data.message) {
240
+ notifySuccess(response.data.message);
241
+ } else if (response.data.redirect) {
242
+ window.location.replace(response.data.redirect);
243
+ } else if (response.data.data?.path) {
244
+ window.location.replace(response.data.data.path);
245
+ }
246
+ })
247
+ .catch((error) => {
248
+ notifyError(error.response?.data?.message);
249
+ });
250
+ }
251
+
252
+ function resolveUrl(template, item) {
253
+ return template.replace(':id', item.id);
254
+ }
255
+
256
+ function onSort({ column, direction }) {
257
+ orderBy.column = column;
258
+ orderBy.direction = direction;
259
+ loadData();
260
+ }
261
+
262
+ function onPageChange(page) {
263
+ pagination.state.current_page = page;
264
+ loadData();
265
+ }
266
+
267
+ function onPerPageChange() {
268
+ pagination.state.current_page = 1;
269
+ loadData(true);
270
+ }
271
+
272
+ function onUpdateItem(updatedItem) {
273
+ if (!collection.value) return;
274
+ const index = collection.value.findIndex((item) => item.id === updatedItem.id);
275
+ if (index !== -1) {
276
+ collection.value[index] = updatedItem;
277
+ }
278
+ }
279
+
280
+ return {
281
+ // State
282
+ pagination,
283
+ orderBy,
284
+ filters,
285
+ search,
286
+ collection,
287
+ now,
288
+ bulkItems,
289
+ bulkCheckingAllLoader,
290
+ confirmModal,
291
+
292
+ // Computed
293
+ isClickedAll,
294
+ clickedBulkItemsCount,
295
+
296
+ // Methods
297
+ onBulkItemClicked,
298
+ onBulkItemsClickedAll,
299
+ onBulkItemsClickedAllWithPagination,
300
+ onBulkItemsClickedAllUncheck,
301
+ bulkDelete,
302
+ loadData,
303
+ filter,
304
+ resolveUrl,
305
+ getAction,
306
+ onSort,
307
+ onPageChange,
308
+ onPerPageChange,
309
+ onUpdateItem,
310
+
311
+ // Date formatters (convenience re-exports)
312
+ formatDate,
313
+ formatDatetime,
314
+ formatTime,
315
+ };
316
+ }
@@ -0,0 +1,62 @@
1
+ import { ref, watch, onBeforeUnmount } from 'vue';
2
+
3
+ const defaultBreakpoints = [
4
+ { minWidth: 1800, maxPriority: 10 },
5
+ { minWidth: 1600, maxPriority: 8 },
6
+ { minWidth: 1400, maxPriority: 6 },
7
+ { minWidth: 1200, maxPriority: 4 },
8
+ { minWidth: 1000, maxPriority: 3 },
9
+ { minWidth: 800, maxPriority: 2 },
10
+ { minWidth: 0, maxPriority: 0 },
11
+ ];
12
+
13
+ export function useResponsiveColumns(containerRef, options = {}) {
14
+ const breakpoints = options.breakpoints || defaultBreakpoints;
15
+ const currentMaxPriority = ref(10);
16
+ let observer = null;
17
+
18
+ function updatePriority(width) {
19
+ for (const bp of breakpoints) {
20
+ if (width >= bp.minWidth) {
21
+ currentMaxPriority.value = bp.maxPriority;
22
+ return;
23
+ }
24
+ }
25
+ currentMaxPriority.value = 0;
26
+ }
27
+
28
+ function setupObserver(el) {
29
+ if (observer) {
30
+ observer.disconnect();
31
+ }
32
+ if (!el) return;
33
+
34
+ observer = new ResizeObserver((entries) => {
35
+ for (const entry of entries) {
36
+ updatePriority(entry.contentRect.width);
37
+ }
38
+ });
39
+ observer.observe(el);
40
+ }
41
+
42
+ watch(
43
+ containerRef,
44
+ (el) => {
45
+ setupObserver(el);
46
+ },
47
+ { immediate: true },
48
+ );
49
+
50
+ onBeforeUnmount(() => {
51
+ if (observer) {
52
+ observer.disconnect();
53
+ observer = null;
54
+ }
55
+ });
56
+
57
+ function isColumnVisible(priority) {
58
+ return priority <= currentMaxPriority.value;
59
+ }
60
+
61
+ return { isColumnVisible };
62
+ }
package/src/index.js ADDED
@@ -0,0 +1,62 @@
1
+ // Composables
2
+ export { useBaseForm } from './composables/useBaseForm.js';
3
+ export { useBaseAuth } from './composables/useBaseAuth.js';
4
+ export { useBaseListing } from './composables/useBaseListing.js';
5
+ export { useAdmin } from './composables/useAdmin.js';
6
+ export { useResponsiveColumns } from './composables/useResponsiveColumns.js';
7
+
8
+ // Components
9
+ export { default as MediaUpload } from './components/form/MediaUpload.vue';
10
+ export { default as TiptapEditor } from './components/form/TiptapEditor.vue';
11
+ export { default as CkeditorEditor } from './components/form/CkeditorEditor.vue';
12
+ export { default as UserDetailTooltip } from './components/UserDetailTooltip.vue';
13
+
14
+ // Listing components
15
+ export { default as Pagination } from './components/listing/Pagination.vue';
16
+ export { default as Sortable } from './components/listing/Sortable.vue';
17
+ export { default as Search } from './components/listing/Search.vue';
18
+ export { default as PerPage } from './components/listing/PerPage.vue';
19
+ export { default as EmptyState } from './components/listing/EmptyState.vue';
20
+ export { default as BulkCheckboxHeader } from './components/listing/BulkCheckboxHeader.vue';
21
+ export { default as BulkCheckboxRow } from './components/listing/BulkCheckboxRow.vue';
22
+ export { default as BulkOperationsBar } from './components/listing/BulkOperationsBar.vue';
23
+ export { default as PublishedAtColumn } from './components/listing/PublishedAtColumn.vue';
24
+ export { default as ListingHeader } from './components/listing/ListingHeader.vue';
25
+ export { default as EditButton } from './components/listing/EditButton.vue';
26
+ export { default as DeleteButton } from './components/listing/DeleteButton.vue';
27
+ export { default as ConfirmModal } from './components/ConfirmModal.vue';
28
+ export { default as ToggleSwitch } from './components/listing/ToggleSwitch.vue';
29
+
30
+ // Form components
31
+ export { default as FormInput } from './components/form/FormInput.vue';
32
+ export { default as FormEmail } from './components/form/FormEmail.vue';
33
+ export { default as FormTextarea } from './components/form/FormTextarea.vue';
34
+ export { default as FormCheckbox } from './components/form/FormCheckbox.vue';
35
+ export { default as FormDatePicker } from './components/form/FormDatePicker.vue';
36
+ export { default as FormWysiwyg } from './components/form/FormWysiwyg.vue';
37
+ export { default as FormMultiSelect } from './components/form/FormMultiSelect.vue';
38
+ export { default as FormLocalizedInput } from './components/form/FormLocalizedInput.vue';
39
+ export { default as FormLocalizedWysiwyg } from './components/form/FormLocalizedWysiwyg.vue';
40
+ export { default as FormWysiwygSimple } from './components/form/FormWysiwygSimple.vue';
41
+ export { default as FormLocalizedWysiwygSimple } from './components/form/FormLocalizedWysiwygSimple.vue';
42
+ export { default as FormSelect } from './components/form/FormSelect.vue';
43
+ export { default as FormPasswordConfirm } from './components/form/FormPasswordConfirm.vue';
44
+ export { default as FormSubmit } from './components/form/FormSubmit.vue';
45
+ export { default as LocalizationBar } from './components/form/LocalizationBar.vue';
46
+
47
+ // Auth components
48
+ export { default as LoginForm } from './auth/LoginForm.vue';
49
+ export { default as ForgotPasswordForm } from './auth/ForgotPasswordForm.vue';
50
+ export { default as ResetPasswordForm } from './auth/ResetPasswordForm.vue';
51
+ export { default as ActivationForm } from './auth/ActivationForm.vue';
52
+ export { default as ActivationError } from './auth/ActivationError.vue';
53
+
54
+ // Page components
55
+ export { default as TranslationListing } from './translation/TranslationListing.vue';
56
+
57
+ // Utilities
58
+ export { formatDate, formatDatetime, formatTime, nowInTimezone, dayjs } from './utils/dateFormatters.js';
59
+ export { mediaCollectionProp, mediaCollectionDefaults } from './utils/mediaProps.js';
60
+ export { notifySuccess, notifyError } from './utils/notify.js';
61
+ export { getDateFnsLocale, initDateFnsLocale } from './utils/dateFnsLocale.js';
62
+ export { initUI } from './ui/index.js';