ultimate-jekyll-manager 0.0.221 → 0.0.223
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/dist/assets/js/libs/form-manager.js +277 -0
- package/dist/assets/js/pages/test/libraries/form-manager/index.js +23 -0
- package/dist/assets/themes/classy/css/components/_forms.scss +24 -0
- package/dist/assets/themes/classy/css/layout/_navigation.scss +6 -0
- package/dist/defaults/dist/_includes/themes/classy/frontend/sections/nav.html +20 -20
- package/dist/defaults/dist/pages/test/libraries/ads.html +3 -1
- package/dist/defaults/dist/pages/test/libraries/appearance.html +3 -1
- package/dist/defaults/dist/pages/test/libraries/bootstrap.html +3 -1
- package/dist/defaults/dist/pages/test/libraries/error.html +3 -1
- package/dist/defaults/dist/pages/test/libraries/form-manager.html +43 -5
- package/dist/defaults/dist/pages/test/libraries/lazy-loading.html +3 -1
- package/package.json +1 -1
|
@@ -95,6 +95,9 @@ export class FormManager {
|
|
|
95
95
|
// Handle page restored from bfcache (e.g., back button after OAuth redirect)
|
|
96
96
|
window.addEventListener('pageshow', (e) => this._handlePageShow(e));
|
|
97
97
|
|
|
98
|
+
// Initialize file drop zones
|
|
99
|
+
this._initFileDropZones();
|
|
100
|
+
|
|
98
101
|
// Auto-transition to initialState when DOM is ready
|
|
99
102
|
if (this.config.autoReady) {
|
|
100
103
|
domReady().then(() => this._setInitialState());
|
|
@@ -281,6 +284,12 @@ export class FormManager {
|
|
|
281
284
|
if (this._fieldErrors[e.target.name]) {
|
|
282
285
|
this._clearFieldError(e.target.name);
|
|
283
286
|
}
|
|
287
|
+
|
|
288
|
+
// Clear file drop error when the file input inside a drop zone changes
|
|
289
|
+
const $zone = e.target.closest('[data-file-drop]');
|
|
290
|
+
if ($zone) {
|
|
291
|
+
$zone.classList.remove('file-drop-error');
|
|
292
|
+
}
|
|
284
293
|
}
|
|
285
294
|
|
|
286
295
|
/**
|
|
@@ -537,6 +546,11 @@ export class FormManager {
|
|
|
537
546
|
this._clearFieldError(fieldName);
|
|
538
547
|
}
|
|
539
548
|
this._fieldErrors = {};
|
|
549
|
+
|
|
550
|
+
// Clear file drop error states
|
|
551
|
+
this.$form.querySelectorAll('[data-file-drop].file-drop-error').forEach(($zone) => {
|
|
552
|
+
$zone.classList.remove('file-drop-error');
|
|
553
|
+
});
|
|
540
554
|
}
|
|
541
555
|
|
|
542
556
|
/**
|
|
@@ -964,6 +978,269 @@ export class FormManager {
|
|
|
964
978
|
return allowedGroups.includes(fieldGroup.toLowerCase());
|
|
965
979
|
}
|
|
966
980
|
|
|
981
|
+
/**
|
|
982
|
+
* Initialize file drop zones within the form
|
|
983
|
+
* Scans for [data-file-drop] containers and attaches drag-and-drop behavior
|
|
984
|
+
*/
|
|
985
|
+
_initFileDropZones() {
|
|
986
|
+
const $zones = this.$form.querySelectorAll('[data-file-drop]');
|
|
987
|
+
|
|
988
|
+
/* @dev-only:start */
|
|
989
|
+
{
|
|
990
|
+
console.log('[Form-manager] _initFileDropZones found', $zones.length, 'zones');
|
|
991
|
+
}
|
|
992
|
+
/* @dev-only:end */
|
|
993
|
+
|
|
994
|
+
$zones.forEach(($zone) => {
|
|
995
|
+
this._setupFileDropZone($zone);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Set up a single file drop zone
|
|
1001
|
+
* @param {HTMLElement} $zone - The container with data-file-drop attribute
|
|
1002
|
+
*/
|
|
1003
|
+
_setupFileDropZone($zone) {
|
|
1004
|
+
const $input = $zone.querySelector('input[type="file"]');
|
|
1005
|
+
if (!$input) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const mode = ($zone.getAttribute('data-file-drop') || '').toLowerCase();
|
|
1010
|
+
const isPageMode = mode === 'page';
|
|
1011
|
+
|
|
1012
|
+
/* @dev-only:start */
|
|
1013
|
+
{
|
|
1014
|
+
console.log('[Form-manager] Setting up file drop zone', {
|
|
1015
|
+
mode: isPageMode ? 'page' : 'local',
|
|
1016
|
+
input: $input.name || $input.id,
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
/* @dev-only:end */
|
|
1020
|
+
|
|
1021
|
+
// Track drag enter/leave depth for reliable active state
|
|
1022
|
+
let dragDepth = 0;
|
|
1023
|
+
|
|
1024
|
+
// Determine the drop target (zone or entire page)
|
|
1025
|
+
const $dropTarget = isPageMode ? document.body : $zone;
|
|
1026
|
+
|
|
1027
|
+
// Helper: check if event landed on a different local drop zone (page mode only)
|
|
1028
|
+
// If so, let that zone handle it instead
|
|
1029
|
+
const isOverOtherZone = (e) => {
|
|
1030
|
+
if (!isPageMode) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const $closest = e.target.closest('[data-file-drop]');
|
|
1035
|
+
return $closest && $closest !== $zone;
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// Prevent default on dragover to allow drop
|
|
1039
|
+
$dropTarget.addEventListener('dragover', (e) => {
|
|
1040
|
+
if (isOverOtherZone(e)) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
e.preventDefault();
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// Track drag enter for active state
|
|
1048
|
+
$dropTarget.addEventListener('dragenter', (e) => {
|
|
1049
|
+
if (isOverOtherZone(e)) {
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
e.preventDefault();
|
|
1054
|
+
dragDepth++;
|
|
1055
|
+
|
|
1056
|
+
if (dragDepth === 1) {
|
|
1057
|
+
$zone.classList.add('file-drop-active');
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
// Track drag leave for active state
|
|
1062
|
+
$dropTarget.addEventListener('dragleave', (e) => {
|
|
1063
|
+
if (isOverOtherZone(e)) {
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
e.preventDefault();
|
|
1068
|
+
dragDepth--;
|
|
1069
|
+
|
|
1070
|
+
if (dragDepth === 0) {
|
|
1071
|
+
$zone.classList.remove('file-drop-active');
|
|
1072
|
+
}
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
// Handle drop
|
|
1076
|
+
$dropTarget.addEventListener('drop', (e) => {
|
|
1077
|
+
if (isOverOtherZone(e)) {
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
e.preventDefault();
|
|
1082
|
+
dragDepth = 0;
|
|
1083
|
+
$zone.classList.remove('file-drop-active');
|
|
1084
|
+
|
|
1085
|
+
this._handleFileDrop(e, $input, $zone);
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
// Click-to-browse: click anywhere on the zone opens the file picker
|
|
1089
|
+
$zone.addEventListener('click', (e) => {
|
|
1090
|
+
// Skip if clicking the input itself (avoid double-open)
|
|
1091
|
+
if (e.target === $input) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
$input.click();
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// Update file name display when file is selected via browse dialog
|
|
1099
|
+
$input.addEventListener('change', () => {
|
|
1100
|
+
this._updateFileDropName($input, $zone);
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Handle a file drop event
|
|
1106
|
+
* @param {DragEvent} e - The drop event
|
|
1107
|
+
* @param {HTMLInputElement} $input - The file input to assign files to
|
|
1108
|
+
* @param {HTMLElement} $zone - The drop zone container
|
|
1109
|
+
*/
|
|
1110
|
+
_handleFileDrop(e, $input, $zone) {
|
|
1111
|
+
const files = e.dataTransfer?.files;
|
|
1112
|
+
if (!files || files.length === 0) {
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Assign files to the input using DataTransfer
|
|
1117
|
+
const dt = new DataTransfer();
|
|
1118
|
+
const acceptAttr = $input.getAttribute('accept');
|
|
1119
|
+
const isMultiple = $input.hasAttribute('multiple');
|
|
1120
|
+
|
|
1121
|
+
for (const file of files) {
|
|
1122
|
+
// Filter by accept attribute if present
|
|
1123
|
+
if (acceptAttr && !this._fileMatchesAccept(file, acceptAttr)) {
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
dt.items.add(file);
|
|
1128
|
+
|
|
1129
|
+
// Only take the first file if input is not multiple
|
|
1130
|
+
if (!isMultiple) {
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Show error if no valid files after filtering
|
|
1136
|
+
if (dt.files.length === 0) {
|
|
1137
|
+
$zone.classList.add('file-drop-error');
|
|
1138
|
+
this.showError(`File type not accepted. Accepted: ${acceptAttr}`);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
$input.files = dt.files;
|
|
1143
|
+
|
|
1144
|
+
// Dispatch change event so existing handlers pick it up
|
|
1145
|
+
$input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
1146
|
+
|
|
1147
|
+
// Update file name display
|
|
1148
|
+
this._updateFileDropName($input, $zone);
|
|
1149
|
+
|
|
1150
|
+
/* @dev-only:start */
|
|
1151
|
+
{
|
|
1152
|
+
console.log('[Form-manager] File dropped', {
|
|
1153
|
+
files: Array.from(dt.files).map((f) => f.name),
|
|
1154
|
+
input: $input.name || $input.id,
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
/* @dev-only:end */
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Update the file name display element in a drop zone
|
|
1162
|
+
* @param {HTMLInputElement} $input - The file input
|
|
1163
|
+
* @param {HTMLElement} $zone - The drop zone container
|
|
1164
|
+
*/
|
|
1165
|
+
_updateFileDropName($input, $zone) {
|
|
1166
|
+
const $name = $zone.querySelector('[data-file-drop-name]');
|
|
1167
|
+
if (!$name) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const files = $input.files;
|
|
1172
|
+
if (!files || files.length === 0) {
|
|
1173
|
+
$name.textContent = 'No file selected';
|
|
1174
|
+
$zone.classList.remove('file-drop-has-file');
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
if (files.length === 1) {
|
|
1179
|
+
$name.textContent = files[0].name;
|
|
1180
|
+
} else {
|
|
1181
|
+
$name.textContent = `${files.length} files selected`;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
$zone.classList.add('file-drop-has-file');
|
|
1185
|
+
$zone.classList.remove('file-drop-error');
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Check if a file matches an accept attribute value
|
|
1190
|
+
* @param {File} file - The file to check
|
|
1191
|
+
* @param {string} accept - The accept attribute value (e.g., "image/*,.pdf")
|
|
1192
|
+
* @returns {boolean}
|
|
1193
|
+
*/
|
|
1194
|
+
_fileMatchesAccept(file, accept) {
|
|
1195
|
+
const types = accept.split(',').map((t) => t.trim().toLowerCase());
|
|
1196
|
+
const fileName = file.name.toLowerCase();
|
|
1197
|
+
const fileType = (file.type || '').toLowerCase();
|
|
1198
|
+
|
|
1199
|
+
// Common extension-to-MIME-category map for when browser doesn't provide file.type
|
|
1200
|
+
const extToCategory = {
|
|
1201
|
+
'.jpg': 'image/', '.jpeg': 'image/', '.png': 'image/', '.gif': 'image/',
|
|
1202
|
+
'.webp': 'image/', '.svg': 'image/', '.bmp': 'image/', '.ico': 'image/',
|
|
1203
|
+
'.pdf': 'application/pdf',
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
for (const type of types) {
|
|
1207
|
+
// Extension match (e.g., ".pdf")
|
|
1208
|
+
if (type.startsWith('.')) {
|
|
1209
|
+
if (fileName.endsWith(type)) {
|
|
1210
|
+
return true;
|
|
1211
|
+
}
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Wildcard MIME match (e.g., "image/*")
|
|
1216
|
+
if (type.endsWith('/*')) {
|
|
1217
|
+
const prefix = type.slice(0, -2) + '/';
|
|
1218
|
+
|
|
1219
|
+
// Check actual MIME type
|
|
1220
|
+
if (fileType && fileType.startsWith(prefix)) {
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// Fallback: check extension when browser doesn't provide MIME type
|
|
1225
|
+
if (!fileType) {
|
|
1226
|
+
const ext = '.' + fileName.split('.').pop();
|
|
1227
|
+
const guessedCategory = extToCategory[ext] || '';
|
|
1228
|
+
if (guessedCategory.startsWith(prefix)) {
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// Exact MIME match (e.g., "application/pdf")
|
|
1236
|
+
if (fileType === type) {
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
967
1244
|
/**
|
|
968
1245
|
* Set form data from a nested object (supports dot notation field names)
|
|
969
1246
|
*/
|
|
@@ -22,6 +22,7 @@ export default (Manager) => {
|
|
|
22
22
|
initTestFormContact();
|
|
23
23
|
initTestFormManual();
|
|
24
24
|
initTestFormGroups();
|
|
25
|
+
initTestFormFileDrop();
|
|
25
26
|
|
|
26
27
|
// Resolve after initialization
|
|
27
28
|
return resolve();
|
|
@@ -195,6 +196,8 @@ function initTestFormManual() {
|
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
// Test 5: Input Groups
|
|
199
|
+
// Test 6: File Drop (defined below initTestFormGroups)
|
|
200
|
+
|
|
198
201
|
function initTestFormGroups() {
|
|
199
202
|
const formManager = new FormManager('#test-form-groups');
|
|
200
203
|
const $status = document.getElementById('groups-status');
|
|
@@ -245,3 +248,23 @@ function initTestFormGroups() {
|
|
|
245
248
|
formManager.showSuccess('getData() returned ' + Object.keys(data).length + ' top-level keys');
|
|
246
249
|
});
|
|
247
250
|
}
|
|
251
|
+
|
|
252
|
+
// Test 6: File Drop
|
|
253
|
+
function initTestFormFileDrop() {
|
|
254
|
+
const formManager = new FormManager('#test-form-file-drop');
|
|
255
|
+
const $status = document.getElementById('file-drop-status');
|
|
256
|
+
|
|
257
|
+
formManager.on('statechange', ({ state }) => {
|
|
258
|
+
$status.textContent = `Status: ${state}`;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
formManager.on('change', ({ name, value }) => {
|
|
262
|
+
console.log('[Test 6] Change:', name, '=', value);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
formManager.on('submit', async ({ data }) => {
|
|
266
|
+
console.log('[Test 6] Submitting:', data);
|
|
267
|
+
await simulateApi(500);
|
|
268
|
+
formManager.showSuccess('File(s) submitted!');
|
|
269
|
+
});
|
|
270
|
+
}
|
|
@@ -124,3 +124,27 @@
|
|
|
124
124
|
box-shadow: 0 0 0 0.25rem rgba($primary, 0.25);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
+
|
|
128
|
+
// ============================================
|
|
129
|
+
// File Drop Zones
|
|
130
|
+
// ============================================
|
|
131
|
+
[data-file-drop] {
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
border: 2px dashed var(--bs-border-color) !important;
|
|
134
|
+
border-radius: $classy-radius-lg;
|
|
135
|
+
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
136
|
+
|
|
137
|
+
&.file-drop-active {
|
|
138
|
+
border-color: $primary !important;
|
|
139
|
+
background-color: rgba($primary, 0.05);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
&.file-drop-has-file {
|
|
143
|
+
border-color: $success !important;
|
|
144
|
+
border-style: solid !important;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
&.file-drop-error {
|
|
148
|
+
border-color: $danger !important;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
font-weight: 700;
|
|
83
83
|
font-size: 1.25rem;
|
|
84
84
|
letter-spacing: -0.02em;
|
|
85
|
+
margin-right: 0;
|
|
85
86
|
|
|
86
87
|
// Logo with text
|
|
87
88
|
.brand-logo {
|
|
@@ -121,6 +122,11 @@
|
|
|
121
122
|
// Navigation Links
|
|
122
123
|
// ============================================
|
|
123
124
|
.navbar-nav {
|
|
125
|
+
// Add top spacing on mobile so first item doesn't touch the toggler bar
|
|
126
|
+
@media (max-width: 991.98px) {
|
|
127
|
+
margin-top: 0.5rem;
|
|
128
|
+
}
|
|
129
|
+
|
|
124
130
|
.nav-link {
|
|
125
131
|
font-weight: 500;
|
|
126
132
|
font-size: 0.95rem;
|
|
@@ -88,12 +88,12 @@
|
|
|
88
88
|
<li>
|
|
89
89
|
{% iftruthy child.href %}
|
|
90
90
|
<a class="{{ child_class }}" href="{{ child.href }}" {{ child_attributes }}>
|
|
91
|
-
{{ child_content }}
|
|
91
|
+
{{ child_content | strip }}
|
|
92
92
|
</a>
|
|
93
93
|
{% endiftruthy %}
|
|
94
94
|
{% iffalsy child.href %}
|
|
95
95
|
<button class="{{ child_class }}" type="button" {{ child_attributes }}>
|
|
96
|
-
{{ child_content }}
|
|
96
|
+
{{ child_content | strip }}
|
|
97
97
|
</button>
|
|
98
98
|
{% endiffalsy %}
|
|
99
99
|
</li>
|
|
@@ -143,12 +143,12 @@
|
|
|
143
143
|
<li>
|
|
144
144
|
{% iftruthy child.href %}
|
|
145
145
|
<a class="{{ child_class }}" href="{{ child.href }}" {{ child_attributes }}>
|
|
146
|
-
{{ child_content }}
|
|
146
|
+
{{ child_content | strip }}
|
|
147
147
|
</a>
|
|
148
148
|
{% endiftruthy %}
|
|
149
149
|
{% iffalsy child.href %}
|
|
150
150
|
<button class="{{ child_class }}" type="button" {{ child_attributes }}>
|
|
151
|
-
{{ child_content }}
|
|
151
|
+
{{ child_content | strip }}
|
|
152
152
|
</button>
|
|
153
153
|
{% endiffalsy %}
|
|
154
154
|
</li>
|
|
@@ -171,12 +171,12 @@
|
|
|
171
171
|
<li class="nav-item">
|
|
172
172
|
{% iftruthy link.href %}
|
|
173
173
|
<a class="{{ link_class }}" href="{{ link.href }}" {{ link_attributes }}>
|
|
174
|
-
{{ link_content }}
|
|
174
|
+
{{ link_content | strip }}
|
|
175
175
|
</a>
|
|
176
176
|
{% endiftruthy %}
|
|
177
177
|
{% iffalsy link.href %}
|
|
178
178
|
<button class="{{ link_class }}" type="button" {{ link_attributes }}>
|
|
179
|
-
{{ link_content }}
|
|
179
|
+
{{ link_content | strip }}
|
|
180
180
|
</button>
|
|
181
181
|
{% endiffalsy %}
|
|
182
182
|
</li>
|
|
@@ -185,13 +185,13 @@
|
|
|
185
185
|
</ul>
|
|
186
186
|
|
|
187
187
|
<!-- Right-aligned Actions (desktop only) -->
|
|
188
|
-
<div class="d-none d-lg-inline-flex align-items-center">
|
|
188
|
+
<div class="d-none d-lg-inline-flex align-items-center gap-3">
|
|
189
189
|
{% for action in data.actions %}
|
|
190
190
|
{% if action.dropdown %}
|
|
191
191
|
{% if action.type == 'account' %}
|
|
192
192
|
<!-- Account dropdown with profile picture (desktop) -->
|
|
193
193
|
{% capture action_attributes %}{% if action.attributes %}{% for attr in action.attributes %} {{ attr[0] }}="{{ attr[1] }}"{% endfor %}{% endif %}{% endcapture %}
|
|
194
|
-
<div class="dropdown
|
|
194
|
+
<div class="dropdown animation-slide-up d-inline-flex" data-wm-bind="@show auth.user" hidden>
|
|
195
195
|
<button class="p-0 border-0 bg-transparent" type="button" data-bs-toggle="dropdown" {{ action_attributes }} aria-expanded="false">
|
|
196
196
|
<span class="d-flex align-items-center">
|
|
197
197
|
<span class="position-relative d-inline-flex">
|
|
@@ -244,12 +244,12 @@
|
|
|
244
244
|
<li>
|
|
245
245
|
{% iftruthy child.href %}
|
|
246
246
|
<a class="{{ child_class }}" href="{{ child.href }}" {{ child_attributes }}>
|
|
247
|
-
{{ child_content }}
|
|
247
|
+
{{ child_content | strip }}
|
|
248
248
|
</a>
|
|
249
249
|
{% endiftruthy %}
|
|
250
250
|
{% iffalsy child.href %}
|
|
251
251
|
<button class="{{ child_class }}" type="button" {{ child_attributes }}>
|
|
252
|
-
{{ child_content }}
|
|
252
|
+
{{ child_content | strip }}
|
|
253
253
|
</button>
|
|
254
254
|
{% endiffalsy %}
|
|
255
255
|
</li>
|
|
@@ -261,7 +261,7 @@
|
|
|
261
261
|
<!-- Regular button dropdown -->
|
|
262
262
|
{% capture action_class %}btn dropdown-toggle {% if action.class %}{{ action.class }}{% endif %}{% endcapture %}
|
|
263
263
|
{% capture action_attributes %}{% if action.attributes %}{% for attr in action.attributes %} {{ attr[0] }}="{{ attr[1] }}"{% endfor %}{% endif %}{% endcapture %}
|
|
264
|
-
<div class="dropdown
|
|
264
|
+
<div class="dropdown">
|
|
265
265
|
<button class="{{ action_class }}" type="button" data-bs-toggle="dropdown" {{ action_attributes }}>
|
|
266
266
|
<span class="d-inline-flex align-items-center">
|
|
267
267
|
{% if action.icon %}
|
|
@@ -290,12 +290,12 @@
|
|
|
290
290
|
<li>
|
|
291
291
|
{% iftruthy child.href %}
|
|
292
292
|
<a class="{{ child_class }}" href="{{ child.href }}" {{ child_attributes }}>
|
|
293
|
-
{{ child_content }}
|
|
293
|
+
{{ child_content | strip }}
|
|
294
294
|
</a>
|
|
295
295
|
{% endiftruthy %}
|
|
296
296
|
{% iffalsy child.href %}
|
|
297
297
|
<button class="{{ child_class }}" type="button" {{ child_attributes }}>
|
|
298
|
-
{{ child_content }}
|
|
298
|
+
{{ child_content | strip }}
|
|
299
299
|
</button>
|
|
300
300
|
{% endiffalsy %}
|
|
301
301
|
</li>
|
|
@@ -305,7 +305,7 @@
|
|
|
305
305
|
</div>
|
|
306
306
|
{% endif %}
|
|
307
307
|
{% else %}
|
|
308
|
-
{% capture action_class %}btn
|
|
308
|
+
{% capture action_class %}btn {% if action.class %}{{ action.class }}{% endif %}{% endcapture %}
|
|
309
309
|
{% capture action_attributes %}{% if action.attributes %}{% for attr in action.attributes %} {{ attr[0] }}="{{ attr[1] }}"{% endfor %}{% endif %}{% endcapture %}
|
|
310
310
|
{% capture action_content %}
|
|
311
311
|
<span class="d-inline-flex align-items-center">
|
|
@@ -317,12 +317,12 @@
|
|
|
317
317
|
{% endcapture %}
|
|
318
318
|
{% iftruthy action.href %}
|
|
319
319
|
<a class="{{ action_class }}" href="{{ action.href }}" role="button" {{ action_attributes }}>
|
|
320
|
-
{{ action_content }}
|
|
320
|
+
{{ action_content | strip }}
|
|
321
321
|
</a>
|
|
322
322
|
{% endiftruthy %}
|
|
323
323
|
{% iffalsy action.href %}
|
|
324
324
|
<button class="{{ action_class }}" type="button" {{ action_attributes }}>
|
|
325
|
-
{{ action_content }}
|
|
325
|
+
{{ action_content | strip }}
|
|
326
326
|
</button>
|
|
327
327
|
{% endiffalsy %}
|
|
328
328
|
{% endif %}
|
|
@@ -366,12 +366,12 @@
|
|
|
366
366
|
<li>
|
|
367
367
|
{% iftruthy child.href %}
|
|
368
368
|
<a class="{{ child_class }}" href="{{ child.href }}" {{ child_attributes }}>
|
|
369
|
-
{{ child_content }}
|
|
369
|
+
{{ child_content | strip }}
|
|
370
370
|
</a>
|
|
371
371
|
{% endiftruthy %}
|
|
372
372
|
{% iffalsy child.href %}
|
|
373
373
|
<button class="{{ child_class }}" type="button" {{ child_attributes }}>
|
|
374
|
-
{{ child_content }}
|
|
374
|
+
{{ child_content | strip }}
|
|
375
375
|
</button>
|
|
376
376
|
{% endiffalsy %}
|
|
377
377
|
</li>
|
|
@@ -392,12 +392,12 @@
|
|
|
392
392
|
{% endcapture %}
|
|
393
393
|
{% iftruthy action.href %}
|
|
394
394
|
<a class="{{ action_class }}" href="{{ action.href }}" role="button" {{ action_attributes }}>
|
|
395
|
-
{{ action_content }}
|
|
395
|
+
{{ action_content | strip }}
|
|
396
396
|
</a>
|
|
397
397
|
{% endiftruthy %}
|
|
398
398
|
{% iffalsy action.href %}
|
|
399
399
|
<button class="{{ action_class }}" type="button" {{ action_attributes }}>
|
|
400
|
-
{{ action_content }}
|
|
400
|
+
{{ action_content | strip }}
|
|
401
401
|
</button>
|
|
402
402
|
{% endiffalsy %}
|
|
403
403
|
{% endif %}
|
|
@@ -13,7 +13,8 @@ meta:
|
|
|
13
13
|
index: false
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<section>
|
|
17
|
+
<div class="container">
|
|
17
18
|
<div class="row">
|
|
18
19
|
<div class="col-lg-8 mx-auto">
|
|
19
20
|
<h1 class="h2 mb-4">AdSense Test Page</h1>
|
|
@@ -69,3 +70,4 @@ meta:
|
|
|
69
70
|
</div>
|
|
70
71
|
</div>
|
|
71
72
|
</div>
|
|
73
|
+
</section>
|
|
@@ -14,7 +14,8 @@ meta:
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
<
|
|
17
|
+
<section>
|
|
18
|
+
<div class="container">
|
|
18
19
|
<h1 class="mb-4">Appearance Test Page</h1>
|
|
19
20
|
<p class="text-muted mb-5">Testing the appearance switching system with detailed debugging information.</p>
|
|
20
21
|
|
|
@@ -269,3 +270,4 @@ meta:
|
|
|
269
270
|
|
|
270
271
|
</div>
|
|
271
272
|
</div>
|
|
273
|
+
</section>
|
|
@@ -13,7 +13,8 @@ meta:
|
|
|
13
13
|
index: false
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<section>
|
|
17
|
+
<div class="container">
|
|
17
18
|
<h1 class="display-1 mb-5">Bootstrap Components Test Page</h1>
|
|
18
19
|
|
|
19
20
|
<!-- Core CSS Utilities -->
|
|
@@ -1522,3 +1523,4 @@ meta:
|
|
|
1522
1523
|
</section>
|
|
1523
1524
|
|
|
1524
1525
|
</div>
|
|
1526
|
+
</section>
|
|
@@ -13,7 +13,8 @@ meta:
|
|
|
13
13
|
index: false
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<section>
|
|
17
|
+
<div class="container">
|
|
17
18
|
<div class="row">
|
|
18
19
|
<div class="col-lg-8 mx-auto">
|
|
19
20
|
<h1 class="h2 mb-4">Error Test Page</h1>
|
|
@@ -30,3 +31,4 @@ meta:
|
|
|
30
31
|
</div>
|
|
31
32
|
</div>
|
|
32
33
|
</div>
|
|
34
|
+
</section>
|
|
@@ -13,11 +13,12 @@ meta:
|
|
|
13
13
|
index: false
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
16
|
+
<section>
|
|
17
|
+
<div class="container">
|
|
18
|
+
<h1 class="mb-4">FormManager Test Page</h1>
|
|
19
|
+
<p class="text-muted mb-5">Testing different configurations of the FormManager library.</p>
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
<div class="row g-4">
|
|
21
22
|
|
|
22
23
|
<!-- Test 1: Full Test (success/fail, nested, change events) -->
|
|
23
24
|
<div class="col-lg-8">
|
|
@@ -264,5 +265,42 @@ meta:
|
|
|
264
265
|
</div>
|
|
265
266
|
</div>
|
|
266
267
|
|
|
268
|
+
<!-- Test 6: File Drop -->
|
|
269
|
+
<div class="col-lg-4">
|
|
270
|
+
<div class="card">
|
|
271
|
+
<div class="card-header">
|
|
272
|
+
<h5 class="mb-0">Test 6: File Drop</h5>
|
|
273
|
+
<small class="text-muted">data-file-drop (local) and data-file-drop="page"</small>
|
|
274
|
+
</div>
|
|
275
|
+
<div class="card-body">
|
|
276
|
+
<form id="test-form-file-drop">
|
|
277
|
+
<h6 class="text-muted mb-2">Local mode <span class="badge bg-secondary">default</span></h6>
|
|
278
|
+
<div class="p-4 text-center mb-3" data-file-drop>
|
|
279
|
+
<p class="mb-2">Drag & drop or click to browse</p>
|
|
280
|
+
<input type="file" class="form-control mx-auto" style="max-width: 300px;" name="localFile">
|
|
281
|
+
<p data-file-drop-name class="small mt-2 mb-0 text-muted">No file selected</p>
|
|
282
|
+
</div>
|
|
283
|
+
<h6 class="text-muted mb-2">Local mode + accept <span class="badge bg-warning text-dark">accept=".pdf"</span></h6>
|
|
284
|
+
<div class="p-4 text-center mb-3" data-file-drop>
|
|
285
|
+
<p class="mb-2">Drag & drop or click to browse (PDF only)</p>
|
|
286
|
+
<input type="file" class="form-control mx-auto" style="max-width: 300px;" name="pdfFile" accept=".pdf">
|
|
287
|
+
<p data-file-drop-name class="small mt-2 mb-0 text-muted">No file selected</p>
|
|
288
|
+
</div>
|
|
289
|
+
<h6 class="text-muted mb-2">Page mode <span class="badge bg-info">data-file-drop="page"</span></h6>
|
|
290
|
+
<div class="p-4 text-center mb-3" data-file-drop="page">
|
|
291
|
+
<p class="mb-2">Drop anywhere on the page</p>
|
|
292
|
+
<input type="file" class="form-control mx-auto" style="max-width: 300px;" name="pageFile">
|
|
293
|
+
<p data-file-drop-name class="small mt-2 mb-0 text-muted">No file selected</p>
|
|
294
|
+
</div>
|
|
295
|
+
<button type="submit" class="btn btn-primary" disabled>Submit</button>
|
|
296
|
+
</form>
|
|
297
|
+
<div class="mt-2">
|
|
298
|
+
<small class="text-muted" id="file-drop-status">Status: ready</small>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
</div>
|
|
267
305
|
</div>
|
|
268
|
-
</
|
|
306
|
+
</section>
|
|
@@ -13,7 +13,8 @@ meta:
|
|
|
13
13
|
index: false
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
<
|
|
16
|
+
<section>
|
|
17
|
+
<div class="container">
|
|
17
18
|
<div class="row">
|
|
18
19
|
<div class="col-lg-8 mx-auto">
|
|
19
20
|
<h1 class="h2 mb-4">Lazy Loading Test Page</h1>
|
|
@@ -384,6 +385,7 @@ meta:
|
|
|
384
385
|
</div>
|
|
385
386
|
</div>
|
|
386
387
|
</div>
|
|
388
|
+
</section>
|
|
387
389
|
|
|
388
390
|
<!-- Script for dynamic content test -->
|
|
389
391
|
<script>
|