1const MediaBrowser = { 2 delimiters: ['{[', ']}'], 3 components: { CategoryBranch }, 4 props: { 5 modelValue: { 6 type: String, 7 default: () => '' 8 }, 9 standalone: { 10 type: Boolean, 11 default: true 12 }, 13 showdelete: { 14 type: Boolean, 15 default: true 16 } 17 }, 18 emits: ['update:modelValue', 'image-selected'], 19 template: ` 20 <!-- Buttons nur anzeigen, wenn im standalone-Modus --> 21 <template v-if="standalone"> 22 <button type="button" class="btn btn-primary" @click="openBrowserInternally">Medien Bild ausw\u00E4hlen</button> 23 <button type="button" class="btn btn-default" @click="removeImage" v-if="selectedImagePath !== '' && showdelete">Entfernen</button> 24 </template> 25 <div class="modal" role="dialog" :style="{ display: isEditing ? 'block' : 'none' }"> 26 <div class="modal-dialog modal-xl" role="document"> 27 <div class="modal-content"> 28 <div class="modal-header"> 29 <h1 class="modal-title fs-4">Bilder-Auswahl</h1> 30 </div> 31 <div class="modal-body"> 32 <div class="row"> 33 <div class="col-3 mediaMenu"> 34 <category-branch v-for="category in displayedCategories" :key="category.categoryId" :category="category" @select-category="handleCategorySelection" :highlightselected="selectedFolder" /> 35 </div> 36 <div class="col-9 mediaManager"> 37 <div id="mediaMenuFileList" class="row"> 38 <div v-for="image in folderImages" class="col-3"> 39 <div class="image-box" @click="selectImage(image.absoluteOrRelativeWebFile)"> 40 <div class="image-fluid-box"> 41 <img :src="'/' + image.absoluteOrRelativeWebFile" class="img-fluid" > 42 </div> 43 <p class="img-title">{[ image.fileNameWithExtension ]}</p> 44 </div> 45 </div> 46 <div v-if="folderImages.length === 0">keine Bilder gefunden</div> 47 </div> 48 </div> 49 </div> 50 </div> 51 <div class="modal-footer"> 52 <button type="button" class="btn btn-secondary" @click="closeBrowser">Abbrechen</button> 53 </div> 54 </div> 55 </div> 56 </div> 57 `, 58 data() { 59 return { 60 displayedCategories: {}, 61 selectedImagePath: '', 62 selectedFolder: '', 63 folders: {}, 64 folderImages: [], 65 isEditing: false, 66 showAsWidget: false, // Diese Property wird nicht direkt verwendet, aber ich lasse sie mal drin. 67 callingEditorId: null // NEU: Speichert die ID des aufrufenden Editors 68 }; 69 }, 70 watch: { 71 selectedImagePath(newVal, oldVal) { 72 this.$emit('update:modelValue', newVal); 73 } 74 }, 75 created() { 76 this.selectedImagePath = this.modelValue; 77 if (!this.standalone) { 78 this.handleOpenMediaBrowserEvent = (event) => { 79 if (event.detail && event.detail.editorId) { 80 this.callingEditorId = event.detail.editorId; 81 } else { 82 this.callingEditorId = null; 83 console.warn('MediaBrowser (non-standalone) opened without an editorId.'); 84 } 85 this.isEditing = true; 86 }; 87 if (window.mediaBrowserEvents) { 88 window.mediaBrowserEvents.addEventListener('open-media-browser', this.handleOpenMediaBrowserEvent); 89 } else { 90 console.warn('window.mediaBrowserEvents not found in MediaBrowser (non-standalone). Exter
90nal opening might fail.'); 91 } 92 } 93 fetch(`/servlets/ajax?todo=getmediafolders`, { method: 'GET', headers: new Headers({ 'content-type': 'application/json' }) }) 94 .then(response => response.json()) 95 .then(folders => { 96 let newFolders = Object.fromEntries( 97 Object.entries(folders).map(([fullPath, value]) => { 98 const newKey = fullPath.split('media')[1]; 99 return [newKey, value]; 100 }) 101 ); 102 this.selectedFolder = Object.keys(newFolders)[0]; 103 this.loadImages(this.selectedFolder); 104 this.folders = newFolders; 105 let test = this.buildTreeFromOrderedFolders(this.folders); 106 this.displayedCategories = test; 107 }); 108 }, 109 beforeUnmount() { // NEU: Event Listener aufräumen 110 if (window.mediaBrowserEvents && this.handleOpenMediaBrowserEvent) { 111 window.mediaBrowserEvents.removeEventListener('open-media-browser', this.handleOpenMediaBrowserEvent); 112 } 113 }, 114 methods: { 115 loadImages(folder) { 116 if (typeof folder === 'undefined') { 117 return; 118 } 119 folder = folder.replaceAll('\\', '/'); 120 fetch(`/servlets/ajax?todo=getmediaimages&dir=${folder}`, { method: 'GET', headers: new Headers({ 'content-type': 'application/json' }) }) 121 .then(response => response.json()) 122 .then(images => { 123 this.folderImages = images; 124 }); 125 }, 126 selectImage(path) { 127 this.selectedImagePath = '/' + path; 128 const event = new CustomEvent('image-selected', { 129 detail: { 130 path: this.selectedImagePath, 131 targetEditorId: this.callingEditorId 132 } 133 }); 134 if (window.mediaBrowserEvents) { 135 window.mediaBrowserEvents.dispatchEvent(event); 136 } else { 137 console.warn('window.mediaBrowserEvents not found. Image selection event might not be globally dispatched.'); 138 } 139 this.$emit('image-selected', this.selectedImagePath); // Für standalone v-model und event 140 this.isEditing = false; 141 // this.callingEditorId = null; // Kann hier zurückgesetzt werden, oder in closeBrowser/openBrowserInternally 142 }, 143 removeImage() { 144 this.selectedImagePath = ''; 145 this.$emit('update:modelValue', ''); 146 }, 147 handleCategorySelection(id) { 148 this.loadImages(id); 149 }, 150 buildTreeFromOrderedFolders(folders) { 151 const categories = []; 152 const nodeMap = {};
153 for (let path of Object.keys(folders)) { 154 path = path.replaceAll('/', '\\'); 155 const lastSlashIndex = path.lastIndexOf("\\"); 156 let parentPath = null; 157 if (lastSlashIndex !== -1) { 158 parentPath = path.substring(0, lastSlashIndex); 159 } 160 const node = { 161 categoryId: path, 162 identifier: path.substring(lastSlashIndex).substring(1), 163 children: [] 164 }; 165 nodeMap[path] = node; 166 if (parentPath) { 167 const parentNode = nodeMap[parentPath]; 168 if (parentNode) { 169 parentNode.children.push(node); 170 } else { 171 categories.push(node); 172 } 173 } else { 174 categories.push(node); 175 } 176 } 177 return categories; 178 }, 179 openBrowserInternally() { // Umbenannt von openBrowser, um Verwechslung mit externem Event zu vermeiden 180 // Wenn der Browser "standalone" geöffnet wird, gibt es keine callingEditorId von auÃen 181 this.callingEditorId = null; // Sicherstellen, dass keine alte ID verwendet wird 182 this.isEditing = true; 183 }, 184 closeBrowser() { 185 this.isEditing = false; 186 this.callingEditorId = null; // NEU: ID zurücksetzen, wenn abgebrochen wird 187 }, 188 } 189}; 190window.designerwidgets = window.designerwidgets || {}; 191window.designerwidgets.mediabrowser = MediaBrowser;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.