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.
@@ -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 ms-lg-3 animation-slide-up d-inline-flex" data-wm-bind="@show auth.user" hidden>
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 ms-lg-3">
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 ms-lg-3 {% if action.class %}{{ action.class }}{% endif %}{% endcapture %}
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
- <div class="container py-5">
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
- <div class="container py-5">
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
- <div class="container py-5">
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
- <div class="container py-5">
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
- <div class="container py-5">
17
- <h1 class="mb-4">FormManager Test Page</h1>
18
- <p class="text-muted mb-5">Testing different configurations of the FormManager library.</p>
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
- <div class="row g-4">
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
- </div>
306
+ </section>
@@ -13,7 +13,8 @@ meta:
13
13
  index: false
14
14
  ---
15
15
 
16
- <div class="container py-5">
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>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultimate-jekyll-manager",
3
- "version": "0.0.221",
3
+ "version": "0.0.223",
4
4
  "description": "Ultimate Jekyll dependency manager",
5
5
  "main": "dist/index.js",
6
6
  "exports": {