@dejwcake/craftable 0.1.9 → 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.
- package/README.md +117 -3
- package/dist/buildMatchPatternFn-BV4I8r8j.js +63 -0
- package/dist/buildMatchPatternFn-T5KjFTBQ.cjs +1 -0
- package/dist/craftable.css +2 -0
- package/dist/cs-B5QMxFrl.js +682 -0
- package/dist/cs-MAoetkhe.cjs +1 -0
- package/dist/de-BlQ2ZKmc.js +526 -0
- package/dist/de-BoVLAn6U.cjs +1 -0
- package/dist/en-US-BLvEnhs4.js +443 -0
- package/dist/en-US-Dz8vkDZ_.cjs +1 -0
- package/dist/en-US-X9gX759i.cjs +1 -0
- package/dist/es-DBHDeXHI.cjs +1 -0
- package/dist/es-YEqfkBY2.js +437 -0
- package/dist/fr-B7MHC2AN.js +407 -0
- package/dist/fr-DLs3QoNB.cjs +1 -0
- package/dist/hu-BKZPI9jH.cjs +1 -0
- package/dist/hu-reoJgNxD.js +425 -0
- package/dist/index.cjs +1 -0
- package/dist/index.mjs +3866 -0
- package/dist/isSameWeek-CC6GX_zp.js +40 -0
- package/dist/isSameWeek-Mctj6Tdj.cjs +1 -0
- package/dist/pl-B5A0Rjp7.js +628 -0
- package/dist/pl-BMyrOO-Q.cjs +1 -0
- package/dist/sk-B7JF2MoV.cjs +1 -0
- package/dist/sk-Btro3W4S.js +602 -0
- package/package.json +68 -40
- package/scripts/publish-components.js +118 -0
- package/scss/_images.scss +1 -1
- package/scss/_variables.scss +13 -6
- package/scss/app.scss +4 -4
- package/scss/overrides/_alerts.scss +0 -2
- package/scss/overrides/_badges.scss +1 -17
- package/scss/overrides/_buttons.scss +64 -7
- package/scss/overrides/_cards.scss +4 -51
- package/scss/overrides/_checkboxes.scss +54 -59
- package/scss/overrides/_forms.scss +270 -176
- package/scss/overrides/_index.scss +18 -18
- package/scss/overrides/_keyframes.scss +6 -4
- package/scss/overrides/_layout.scss +24 -30
- package/scss/overrides/_media-wysiwyg.scss +9 -111
- package/scss/overrides/_modal.scss +0 -18
- package/scss/overrides/_multiselect.scss +63 -7
- package/scss/overrides/_nav.scss +55 -33
- package/scss/overrides/_notifications.scss +6 -1
- package/scss/overrides/_pagination.scss +3 -3
- package/scss/overrides/_sidebar.scss +84 -0
- package/scss/overrides/_tables.scss +52 -37
- package/scss/overrides/_text.scss +1 -3
- package/scss/overrides/_wysiwyg.scss +151 -48
- package/scss/styles/_auth.scss +32 -37
- package/scss/styles/_custom.scss +44 -1
- package/scss/styles/_index.scss +4 -4
- package/scss/styles/_legacy.scss +5 -81
- package/scss/styles/_others.scss +232 -337
- package/scss/vendor/_index.scss +5 -4
- package/src/auth/ActivationError.vue +23 -0
- package/src/auth/ActivationForm.vue +69 -0
- package/src/auth/ForgotPasswordForm.vue +74 -0
- package/src/auth/LoginForm.vue +107 -0
- package/src/auth/ResetPasswordForm.vue +122 -0
- package/src/components/ConfirmModal.vue +65 -0
- package/src/components/UserDetailTooltip.vue +88 -0
- package/src/components/form/CkeditorEditor.vue +205 -0
- package/src/components/form/FormCheckbox.vue +29 -0
- package/src/components/form/FormDatePicker.vue +52 -0
- package/src/components/form/FormEmail.vue +17 -0
- package/src/components/form/FormInput.vue +31 -0
- package/src/components/form/FormLocalizedInput.vue +47 -0
- package/src/components/form/FormLocalizedWysiwyg.vue +55 -0
- package/src/components/form/FormLocalizedWysiwygSimple.vue +55 -0
- package/src/components/form/FormMultiSelect.vue +36 -0
- package/src/components/form/FormPasswordConfirm.vue +49 -0
- package/src/components/form/FormSelect.vue +35 -0
- package/src/components/form/FormSubmit.vue +13 -0
- package/src/components/form/FormTextarea.vue +31 -0
- package/src/components/form/FormWysiwyg.vue +31 -0
- package/src/components/form/FormWysiwygSimple.vue +31 -0
- package/src/components/form/LocalizationBar.vue +90 -0
- package/src/components/form/MediaUpload.vue +317 -0
- package/src/components/form/TiptapEditor.vue +238 -0
- package/src/components/listing/BulkCheckboxHeader.vue +18 -0
- package/src/components/listing/BulkCheckboxRow.vue +21 -0
- package/src/components/listing/BulkOperationsBar.vue +41 -0
- package/src/components/listing/DeleteButton.vue +46 -0
- package/src/components/listing/EditButton.vue +17 -0
- package/src/components/listing/EmptyState.vue +25 -0
- package/src/components/listing/ListingHeader.vue +24 -0
- package/src/components/listing/Pagination.vue +158 -0
- package/src/components/listing/PerPage.vue +15 -0
- package/src/components/listing/PublishedAtColumn.vue +226 -0
- package/src/components/listing/Search.vue +81 -0
- package/src/components/listing/Sortable.vue +28 -0
- package/src/components/listing/ToggleSwitch.vue +47 -0
- package/src/composables/useAdmin.js +39 -0
- package/src/composables/useBaseAuth.js +81 -0
- package/src/composables/useBaseForm.js +265 -0
- package/src/composables/useBaseListing.js +316 -0
- package/src/composables/useResponsiveColumns.js +62 -0
- package/src/index.js +62 -0
- package/src/translation/TranslationListing.vue +230 -0
- package/src/translation/modals/TranslationEditModal.vue +86 -0
- package/src/translation/modals/TranslationExportModal.vue +74 -0
- package/src/translation/modals/TranslationImportModal.vue +255 -0
- package/src/ui/index.js +99 -0
- package/src/utils/ckeditorHelpers.js +36 -0
- package/src/utils/dateFnsLocale.js +24 -0
- package/src/utils/dateFormatters.js +30 -0
- package/src/utils/mediaProps.js +14 -0
- package/src/utils/notify.js +22 -0
- package/dist/admin/Admin.js +0 -42
- package/dist/auth/Form.js +0 -15
- package/dist/base-components/Form/BaseForm.js +0 -392
- package/dist/base-components/Form/BaseUpload.js +0 -188
- package/dist/base-components/Form/bootstrap.js +0 -21
- package/dist/base-components/Listing/BaseListing.js +0 -454
- package/dist/base-components/Listing/components/Pagination.js +0 -105
- package/dist/base-components/Listing/components/Sortable.js +0 -48
- package/dist/base-components/Listing/components/UserDetailTooltip.js +0 -55
- package/dist/index.js +0 -70
- package/dist/overrides/trumbowyg.edit-embed-template.js +0 -72
- package/dist/overrides/trumbowyg.reupload.js +0 -146
- package/dist/overrides/trumbowyg.template.js +0 -67
- package/dist/translation/Form.js +0 -15
- package/dist/translation/Listing.js +0 -283
- package/dist/ui/index.js +0 -33
- 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';
|