@fy-/fws-vue 2.3.76 → 2.3.79

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.
@@ -75,10 +75,10 @@ onUnmounted(() => {
75
75
  <DefaultModal
76
76
  id="confirm"
77
77
  ref="modalRef"
78
- m-size="!max-w-3xl w-full !z-[99999]"
78
+ m-size="!max-w-3xl w-full"
79
79
  >
80
80
  <div
81
- class="bg-gradient-to-br from-gray-900/70 to-gray-800/50 rounded-lg border border-gray-700/30 overflow-hidden !z-[99999]"
81
+ class="bg-gradient-to-br from-gray-900/70 to-gray-800/50 rounded-lg border border-gray-700/30 overflow-hidden"
82
82
  :aria-labelledby="title ? 'confirm-modal-title' : undefined"
83
83
  :aria-describedby="desc ? 'confirm-modal-desc' : undefined"
84
84
  aria-modal="true"
@@ -85,6 +85,18 @@ const props = withDefaults(
85
85
  imageComponent?: Component | string
86
86
  isVideo?: Function
87
87
  ranking?: boolean
88
+ // Edit mode support
89
+ editEnabled?: boolean
90
+ editMode?: boolean
91
+ selectedItems?: Set<string>
92
+ onBulkAction?: Function
93
+ getItemId?: Function
94
+ editButtonText?: string
95
+ cancelButtonText?: string
96
+ bulkActionText?: string
97
+ selectAllText?: string
98
+ clearSelectionText?: string
99
+ selectedCountText?: string
88
100
  }>(),
89
101
  {
90
102
  modelValue: 0,
@@ -99,11 +111,23 @@ const props = withDefaults(
99
111
  paging: undefined,
100
112
  borderColor: undefined,
101
113
  ranking: false,
114
+ // Edit mode defaults
115
+ editEnabled: false,
116
+ editMode: false,
117
+ selectedItems: () => new Set(),
118
+ onBulkAction: undefined,
119
+ getItemId: (item: any, index: number) => item.id || item.UUID || index.toString(),
120
+ editButtonText: 'Edit',
121
+ cancelButtonText: 'Cancel',
122
+ bulkActionText: 'Delete Selected',
123
+ selectAllText: 'Select All',
124
+ clearSelectionText: 'Clear',
125
+ selectedCountText: 'selected',
102
126
  },
103
127
  )
104
128
 
105
129
  // Emits
106
- const emit = defineEmits(['update:modelValue'])
130
+ const emit = defineEmits(['update:modelValue', 'update:editMode', 'update:selectedItems'])
107
131
 
108
132
  // Two-way binding for model value
109
133
  const modelValue = computed({
@@ -113,6 +137,74 @@ const modelValue = computed({
113
137
  },
114
138
  })
115
139
 
140
+ // Edit mode state management
141
+ const localEditMode = computed({
142
+ get: () => props.editMode,
143
+ set: value => emit('update:editMode', value),
144
+ })
145
+
146
+ const localSelectedItems = computed({
147
+ get: () => props.selectedItems,
148
+ set: value => emit('update:selectedItems', value),
149
+ })
150
+
151
+ // Edit mode functions
152
+ function toggleEditMode() {
153
+ localEditMode.value = !localEditMode.value
154
+ if (!localEditMode.value) {
155
+ // Clear selections when exiting edit mode
156
+ localSelectedItems.value = new Set()
157
+ }
158
+ }
159
+
160
+ function toggleItemSelection(item: any, index: number) {
161
+ const itemId = props.getItemId(item, index)
162
+ const newSelection = new Set(localSelectedItems.value)
163
+
164
+ if (newSelection.has(itemId)) {
165
+ newSelection.delete(itemId)
166
+ }
167
+ else {
168
+ newSelection.add(itemId)
169
+ }
170
+
171
+ localSelectedItems.value = newSelection
172
+ }
173
+
174
+ function selectAll() {
175
+ const newSelection = new Set<string>()
176
+ props.images.forEach((item, index) => {
177
+ newSelection.add(props.getItemId(item, index))
178
+ })
179
+ localSelectedItems.value = newSelection
180
+ }
181
+
182
+ function clearSelection() {
183
+ localSelectedItems.value = new Set()
184
+ }
185
+
186
+ function handleBulkAction() {
187
+ if (props.onBulkAction && localSelectedItems.value.size > 0) {
188
+ props.onBulkAction(Array.from(localSelectedItems.value))
189
+ }
190
+ }
191
+
192
+ // Check if an item is selected
193
+ function isItemSelected(item: any, index: number): boolean {
194
+ const itemId = props.getItemId(item, index)
195
+ return localSelectedItems.value.has(itemId)
196
+ }
197
+
198
+ // Handle thumbnail click - either select or open gallery
199
+ function handleThumbnailClick(item: any, index: number) {
200
+ if (props.editMode) {
201
+ toggleItemSelection(item, index)
202
+ }
203
+ else {
204
+ eventBus.emit(`${props.id}GalleryImage`, index)
205
+ }
206
+ }
207
+
116
208
  // Computed values
117
209
  const modelValueSrc = computed(() => {
118
210
  if (props.images.length === 0) return false
@@ -800,6 +892,52 @@ onUnmounted(() => {
800
892
  </div>
801
893
  </transition>
802
894
 
895
+ <!-- Edit Mode Controls (only if editEnabled is true) -->
896
+ <div v-if="editEnabled && images.length > 0 && (mode === 'grid' || mode === 'mason' || mode === 'custom')">
897
+ <!-- Edit Mode Toggle Button -->
898
+ <div class="flex justify-end mb-3">
899
+ <button
900
+ class="px-4 py-2 rounded-lg font-medium text-sm transition-colors"
901
+ :class="localEditMode
902
+ ? 'bg-red-600 hover:bg-red-700 text-white'
903
+ : 'bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200'"
904
+ @click="toggleEditMode"
905
+ >
906
+ {{ localEditMode ? cancelButtonText : editButtonText }}
907
+ </button>
908
+ </div>
909
+
910
+ <!-- Bulk Actions Bar (shown when items are selected) -->
911
+ <div
912
+ v-if="localEditMode && localSelectedItems.size > 0"
913
+ class="flex flex-wrap items-center justify-between gap-2 p-3 mb-3 bg-gray-100 dark:bg-gray-800 rounded-lg border border-gray-300 dark:border-gray-700"
914
+ >
915
+ <div class="flex items-center gap-3">
916
+ <span class="text-sm font-medium">
917
+ {{ localSelectedItems.size }} {{ selectedCountText }}
918
+ </span>
919
+ <button
920
+ class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
921
+ @click="selectAll"
922
+ >
923
+ {{ selectAllText }}
924
+ </button>
925
+ <button
926
+ class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
927
+ @click="clearSelection"
928
+ >
929
+ {{ clearSelectionText }}
930
+ </button>
931
+ </div>
932
+ <button
933
+ class="px-4 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors font-medium"
934
+ @click="handleBulkAction"
935
+ >
936
+ {{ bulkActionText }}
937
+ </button>
938
+ </div>
939
+ </div>
940
+
803
941
  <!-- Thumbnail Grid/Masonry/Custom Layouts if gallery is not open -->
804
942
  <div v-if="mode === 'grid' || mode === 'mason' || mode === 'custom'" class="gallery-grid">
805
943
  <div
@@ -823,13 +961,44 @@ onUnmounted(() => {
823
961
  {{ i }}
824
962
  </div>
825
963
  <template v-for="j in gridHeight" :key="`gi_${id}_${i + j}`">
826
- <div class="masonry-item">
964
+ <div
965
+ v-if="i + j - 2 < images.length"
966
+ class="masonry-item relative"
967
+ :class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i + j - 2], i + j - 2) }"
968
+ >
969
+ <!-- Checkbox overlay for edit mode -->
970
+ <div
971
+ v-if="localEditMode"
972
+ class="absolute top-2 left-2 z-20 pointer-events-none"
973
+ >
974
+ <div
975
+ class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
976
+ :class="isItemSelected(images[i + j - 2], i + j - 2) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
977
+ >
978
+ <svg
979
+ v-if="isItemSelected(images[i + j - 2], i + j - 2)"
980
+ class="w-4 h-4 text-white"
981
+ fill="none"
982
+ stroke="currentColor"
983
+ viewBox="0 0 24 24"
984
+ >
985
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
986
+ </svg>
987
+ </div>
988
+ </div>
989
+
990
+ <!-- Selection overlay -->
991
+ <div
992
+ v-if="localEditMode && isItemSelected(images[i + j - 2], i + j - 2)"
993
+ class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10 rounded-lg"
994
+ />
995
+
827
996
  <img
828
- v-if="i + j - 2 < images.length && imageComponent === 'img'"
997
+ v-if="imageComponent === 'img'"
829
998
  class="h-auto max-w-full w-full rounded-lg cursor-pointer shadow-md hover:shadow-xl transition-all duration-300 hover:brightness-110 hover:scale-[1.02]"
830
999
  :src="getThumbnailUrl(images[i + j - 2])"
831
1000
  :alt="`Gallery image ${i + j - 1}`"
832
- @click="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
1001
+ @click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
833
1002
  >
834
1003
  <component
835
1004
  :is="imageComponent"
@@ -844,7 +1013,7 @@ onUnmounted(() => {
844
1013
  :show-likes="getThumbnailUrl(images[i + j - 2]).showLikes"
845
1014
  :is-author="getThumbnailUrl(images[i + j - 2]).isAuthor"
846
1015
  :user-uuid="getThumbnailUrl(images[i + j - 2]).userUUID"
847
- @click="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
1016
+ @click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
848
1017
  />
849
1018
  </div>
850
1019
  </template>
@@ -854,13 +1023,43 @@ onUnmounted(() => {
854
1023
  <div v-if="ranking" class="img-gallery-ranking">
855
1024
  {{ i }}
856
1025
  </div>
857
- <div class="overflow-hidden rounded-lg">
1026
+ <div
1027
+ class="overflow-hidden rounded-lg relative"
1028
+ :class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i - 1], i - 1) }"
1029
+ >
1030
+ <!-- Checkbox overlay for edit mode -->
1031
+ <div
1032
+ v-if="localEditMode"
1033
+ class="absolute top-2 left-2 z-20 pointer-events-none"
1034
+ >
1035
+ <div
1036
+ class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
1037
+ :class="isItemSelected(images[i - 1], i - 1) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
1038
+ >
1039
+ <svg
1040
+ v-if="isItemSelected(images[i - 1], i - 1)"
1041
+ class="w-4 h-4 text-white"
1042
+ fill="none"
1043
+ stroke="currentColor"
1044
+ viewBox="0 0 24 24"
1045
+ >
1046
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
1047
+ </svg>
1048
+ </div>
1049
+ </div>
1050
+
1051
+ <!-- Selection overlay -->
1052
+ <div
1053
+ v-if="localEditMode && isItemSelected(images[i - 1], i - 1)"
1054
+ class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10"
1055
+ />
1056
+
858
1057
  <img
859
1058
  v-if="imageComponent === 'img'"
860
1059
  class="h-auto max-w-full w-full rounded-lg cursor-pointer shadow-md transition-all duration-300 group-hover:brightness-110 group-hover:scale-[1.03]"
861
1060
  :src="getThumbnailUrl(images[i - 1])"
862
1061
  :alt="`Gallery image ${i}`"
863
- @click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
1062
+ @click="handleThumbnailClick(images[i - 1], i - 1)"
864
1063
  >
865
1064
  <component
866
1065
  :is="imageComponent"
@@ -875,7 +1074,7 @@ onUnmounted(() => {
875
1074
  :show-likes="getThumbnailUrl(images[i - 1]).showLikes"
876
1075
  :is-author="getThumbnailUrl(images[i - 1]).isAuthor"
877
1076
  :user-uuid="getThumbnailUrl(images[i - 1]).userUUID"
878
- @click="$eventBus.emit(`${id}GalleryImage`, i - 1)"
1077
+ @click="handleThumbnailClick(images[i - 1], i - 1)"
879
1078
  />
880
1079
  </div>
881
1080
  </div>
@@ -183,7 +183,7 @@ const paginationLinks = computed(() => {
183
183
  try {
184
184
  // Parse the canonical URL once
185
185
  const canonicalUrl = new URL(url.Canonical)
186
- const baseUrl = `${url.Scheme}://${url.Host}${url.Path}`
186
+ const baseUrl = `https://${url.Host}${url.Path}`
187
187
 
188
188
  // Build query params object
189
189
  const currentQuery: Record<string, string> = {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.3.76",
3
+ "version": "2.3.79",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",