0 Votes

Changes for page Conditioning

Last modified by Ryan C on 2025/09/15 10:19

From version 62.1
edited by Ryan C
on 2025/09/15 10:19
Change comment: There is no comment for this version
To version 59.3
edited by Ryan C
on 2025/09/14 22:14
Change comment: There is no comment for this version

Summary

Details

Page properties
Content
... ... @@ -1,461 +1,12 @@
1 -{{velocity}}
2 -#pagePicker_import
1 +{{video attachment="Propaganda.mp4"/}}
3 3  
4 -## 1) Collect video attachments on this page
5 -#set($videoExts = ['mp4','webm','ogg','avi','mov','wmv','flv','m4v'])
6 -#set($videos = [])
7 -#foreach($att in $doc.getAttachmentList())
8 - #set($n = $att.getFilename())
9 - #set($ln = $n.toLowerCase())
10 - #foreach($e in $videoExts)
11 - #if($ln.endsWith("." + $e))
12 - #set($discard = $videos.add($att))
13 - #break
14 - #end
15 - #end
16 -#end
3 +{{video attachment="2025-02-26_05-18-58_0.mp4"/}}
17 17  
18 -## 2) Cache HTML (auto-bust on version)
19 -{{cache id="vid-list-$doc.fullName-$doc.version" timeToLive="21600"}}
20 -{{html wiki="false" clean="false"}}
21 -<div id="xwiki-video-manager" style="margin:20px 0;">
22 - <h2>📹 Videos on: ${escapetool.xml($doc.fullName)}</h2>
5 +{{videopicker video="1776General__20240817__1824613900312080533_1_18246138792021319680.mp4"/}}
23 23  
24 - #if($videos.size() == 0)
25 - <div style="text-align:center;padding:40px;background:#f8f9fa;border-radius:8px;">
26 - <h3>No Videos Found</h3>
27 - <p>Attach video files to this page to see them here.</p>
28 - </div>
29 - #else
7 +{{jwplayer attachment="MEGYNK~~1.MP4"/}}
30 30  
31 - <!-- Bulk toolbar -->
32 - <div id="bulk-bar" class="bulk-bar">
33 - <label style="margin-right:8px;">
34 - <input type="checkbox" id="pick-all"> Select visible
35 - </label>
36 - <span class="sep"></span>
37 - <input type="text" class="bulk-dest suggest-pages" placeholder="Find a page…"
38 - data-search-scope="wiki:${escapetool.xml($xcontext.database)}">
39 - <button type="button" class="btn btn-sm btn-warning" id="bulk-move">Move selected</button>
40 - <button type="button" class="btn btn-sm btn-danger" id="bulk-delete">Delete selected</button>
41 - <span id="bulk-status" class="bulk-status"></span>
42 - </div>
43 43  
44 - <script>
45 - window.VID_CHUNK_SIZE = 48;
46 - window.VID_LAZY_MARGIN = '600px';
47 - window.XWIKI_WIKI = ${jsontool.serialize($xcontext.database)};
48 - window.SOURCE_SPACE = ${jsontool.serialize($doc.space)};
49 - window.SOURCE_PAGE = ${jsontool.serialize($doc.name)};
50 - </script>
10 +{{jwplayer attachment="Propaganda.mp4"/}}
51 51  
52 - <div id="video-chunks">
53 - #set($i = 0)
54 - #set($chunkIndex = 0)
55 - #foreach($att in $videos)
56 - #set($i = $i + 1)
57 - #set($filename = $att.getFilename())
58 - #set($lname = $filename.toLowerCase())
59 - #set($url = $doc.getAttachmentURL($filename))
60 -
61 - ## MIME guess
62 - #set($videoType = "video/mp4")
63 - #if($lname.endsWith(".webm"))
64 - #set($videoType = "video/webm")
65 - #elseif($lname.endsWith(".ogg"))
66 - #set($videoType = "video/ogg")
67 - #elseif($lname.endsWith(".avi"))
68 - #set($videoType = "video/x-msvideo")
69 - #elseif($lname.endsWith(".mov"))
70 - #set($videoType = "video/quicktime")
71 - #elseif($lname.endsWith(".wmv"))
72 - #set($videoType = "video/x-ms-wmv")
73 - #elseif($lname.endsWith(".flv"))
74 - #set($videoType = "video/x-flv")
75 - #elseif($lname.endsWith(".m4v"))
76 - #set($videoType = "video/mp4")
77 - #end
78 -
79 - #if($i == 1 || ($i - 1) % 48 == 0)
80 - #set($chunkIndex = $chunkIndex + 1)
81 - <div class="vid-chunk" data-chunk="$chunkIndex" style="display: #if($chunkIndex == 1) block #else none #end;">
82 - <div class="video-display-grid">
83 - #end
84 -
85 - <div class="video-container">
86 - <div class="video-header">
87 - <label class="pick"><input type="checkbox" class="vid-pick" data-filename="${escapetool.xml($filename)}"></label>
88 - <h4 class="video-title">${escapetool.xml($filename)}</h4>
89 - </div>
90 -
91 - <div class="video-frame"
92 - data-src="${url}"
93 - data-type="${videoType}"
94 - data-name="${escapetool.xml($filename)}">
95 - <canvas class="vid-canvas" width="320" height="180"></canvas>
96 - <button class="btn btn-sm btn-primary" type="button">Load &amp; Play</button>
97 - </div>
98 -
99 - <div class="video-controls">
100 - <a href="${url}" download="${escapetool.xml($filename)}" class="btn btn-sm btn-success">📥 Download</a>
101 - <button class="btn btn-sm btn-danger del-one" data-filename="${escapetool.xml($filename)}">Delete</button>
102 - <span class="vid-duration">Duration: —</span>
103 - </div>
104 -
105 - <!-- Move-to-page -->
106 - <div class="move-box">
107 - <label>Move to page:</label>
108 - <div class="move-row">
109 - <input type="text"
110 - class="move-input suggest-pages"
111 - placeholder="Find a page…"
112 - data-search-scope="wiki:${escapetool.xml($xcontext.database)}"
113 - data-filename="${escapetool.xml($filename)}">
114 - <button type="button"
115 - class="btn btn-sm btn-warning move-go"
116 - data-filename="${escapetool.xml($filename)}">Move</button>
117 - </div>
118 - <small class="hint">Pick a page (e.g., <code>Main.SomePage</code>). Click <b>Move</b> to relocate this file.</small>
119 - </div>
120 - </div> <!-- .video-container -->
121 -
122 - #if(($i % 48 == 0) || $foreach.last)
123 - </div> <!-- .video-display-grid -->
124 - #if(!$foreach.last)
125 - <div class="loadmore-wrap">
126 - <button class="btn btn-secondary load-more" data-next="$mathtool.add($chunkIndex,1)">Load more</button>
127 - </div>
128 - #end
129 - </div> <!-- .vid-chunk -->
130 - #end
131 - #end
132 - </div>
133 - #end
134 -</div>
135 -
136 -<script>
137 -(function(){
138 - /* ===== constants & helpers ===== */
139 - var WIKI = window.XWIKI_WIKI;
140 - var CURRENT_SPACE = document.documentElement.getAttribute('data-xwiki-space') || (window.SOURCE_SPACE || 'Main');
141 - var ROOT_SPACE = CURRENT_SPACE.split('.')[0];
142 -
143 - function spacesPath(dotPath){
144 - if(!dotPath) return '';
145 - return dotPath.split('.').map(function(s){ return 'spaces/' + encodeURIComponent(s); }).join('/');
146 - }
147 - function parseFullName(full){
148 - full = String(full||'').replace(/^[^:]+:/,'');
149 - var parts = full.split('.');
150 - var page = parts.pop();
151 - return {spacePath: parts.join('.'), page: page};
152 - }
153 - function getFormToken(){
154 - return document.documentElement.getAttribute('data-xwiki-form-token') || '';
155 - }
156 -
157 - /* ===== existence check (prevents DocumentDoesNotExist) ===== */
158 - async function docExists(fullRef){
159 - var p = parseFullName(fullRef);
160 - var url = '/rest/wikis/' + encodeURIComponent(WIKI) + '/' +
161 - spacesPath(p.spacePath) + '/pages/' + encodeURIComponent(p.page) + '?media=json';
162 - var r = await fetch(url, {credentials:'same-origin'});
163 - return r.status === 200;
164 - }
165 -
166 - /* ===== robust resolver for Page Picker / typed titles ===== */
167 - async function resolveReference(inp){
168 - var ref = inp.getAttribute('data-reference') || (inp.dataset && inp.dataset.reference) || '';
169 - var raw = (inp.value || '').trim();
170 -
171 - if (ref){
172 - if (/\.WebHome$/i.test(ref)) { if (await docExists(ref)) return ref; }
173 - else {
174 - if (await docExists(ref)) return ref;
175 - var rh = ref + '.WebHome'; if (await docExists(rh)) return rh;
176 - }
177 - }
178 - var base = raw.indexOf('.') === -1 ? (ROOT_SPACE + '.' + raw) : raw;
179 - if (await docExists(base)) return base;
180 - var home2 = /\.WebHome$/i.test(base) ? base : (base + '.WebHome');
181 - if (await docExists(home2)) return home2;
182 -
183 - throw new Error('Target page not found: "' + raw + '". Choose a suggestion or type a full reference like "Main Categories.SomePage".');
184 - }
185 -
186 - /* ===== posters & video mount ===== */
187 - async function makePoster(frame){
188 - if(frame.getAttribute('data-poster-ready')==='1') return;
189 - var src = frame.getAttribute('data-src');
190 - try{
191 - var v = document.createElement('video');
192 - v.preload = 'metadata'; v.muted = true; v.playsInline = true; v.src = src;
193 - function once(t,e){return new Promise(function(res){t.addEventListener(e,res,{once:true});});}
194 - await once(v,'loadedmetadata');
195 - if (typeof v.requestVideoFrameCallback === 'function'){
196 - await new Promise(function(res){ v.requestVideoFrameCallback(function(){ res(); }); });
197 - } else {
198 - try { v.currentTime = 0.25; } catch(e){}
199 - await once(v,'seeked').catch(function(){});
200 - await once(v,'loadeddata').catch(function(){});
201 - }
202 - var canvas = frame.querySelector('.vid-canvas');
203 - if(canvas){
204 - var w = 320, h = Math.round(320 * (v.videoHeight||9) / (v.videoWidth||16));
205 - canvas.width = 320; canvas.height = h>0?h:180;
206 - var ctx = canvas.getContext('2d', {willReadFrequently:true});
207 - ctx.drawImage(v, 0, 0, canvas.width, canvas.height);
208 - try { frame.setAttribute('data-poster', canvas.toDataURL('image/webp', 0.85)); } catch(e){}
209 - }
210 - frame.setAttribute('data-poster-ready','1');
211 - }catch(e){}
212 - }
213 -
214 - function mountVideo(frame){
215 - if(frame.getAttribute('data-mounted')==='1') return;
216 - var src = frame.getAttribute('data-src');
217 - var type = frame.getAttribute('data-type') || 'video/mp4';
218 - var poster = frame.getAttribute('data-poster');
219 -
220 - var v = document.createElement('video');
221 - v.setAttribute('controls',''); v.setAttribute('preload','none');
222 - if(poster) v.setAttribute('poster', poster);
223 - v.style.width='100%'; v.style.maxWidth='100%'; v.style.borderRadius='4px';
224 -
225 - var s = document.createElement('source'); s.src = src; s.type = type; v.appendChild(s);
226 - v.addEventListener('loadedmetadata', function(){
227 - var d = Math.round(v.duration||0), mm = Math.floor(d/60), ss = String(d%60).padStart(2,'0');
228 - var dur = frame.parentElement.querySelector('.vid-duration'); if(dur) dur.textContent = 'Duration: '+mm+':'+ss;
229 - });
230 -
231 - frame.replaceChildren(v);
232 - frame.setAttribute('data-mounted','1');
233 - }
234 -
235 - /* ===== observers & UI wiring ===== */
236 - if('IntersectionObserver' in window){
237 - var io = new IntersectionObserver(function(entries){
238 - entries.forEach(function(e){
239 - if(e.isIntersecting){ makePoster(e.target); io.unobserve(e.target); }
240 - });
241 - }, { rootMargin: (window.VID_LAZY_MARGIN||'600px') });
242 - document.querySelectorAll('.video-frame').forEach(function(el){ io.observe(el); });
243 - }
244 -
245 - document.addEventListener('click', function(ev){
246 - var frame = ev.target.closest('.video-frame');
247 - if(frame){
248 - mountVideo(frame);
249 - var v = frame.querySelector('video'); if(v) v.play().catch(function(){});
250 - }
251 - });
252 -
253 - document.addEventListener('click', function(ev){
254 - var b = ev.target.closest('.load-more'); if(!b) return;
255 - var next = b.getAttribute('data-next');
256 - var nxt = document.querySelector('.vid-chunk[data-chunk="'+ next +'"]');
257 - if(nxt){ nxt.style.display='block'; b.parentElement.style.display='none'; }
258 - });
259 -
260 - /* ===== MOVE & DELETE core ===== */
261 - async function moveAttachment(opts){
262 - var srcSpace = opts.srcSpace, srcPage = opts.srcPage, filename = opts.filename, dstFull = opts.dstFull;
263 - var pf = parseFullName(dstFull);
264 - var srcSpacesPath = spacesPath(srcSpace);
265 - var dstSpacesPath = spacesPath(pf.spacePath);
266 -
267 - var sel = '.video-container input.move-input[data-filename="' + CSS.escape(filename) + '"]';
268 - var inp = document.querySelector(sel);
269 - var card = inp ? inp.closest('.video-container') : null;
270 - var frame = card ? card.querySelector('.video-frame') : null;
271 - var srcURL = frame ? frame.getAttribute('data-src') : null;
272 - if(!srcURL) throw new Error('Missing source URL');
273 -
274 - var downloading = await fetch(srcURL, {credentials:'same-origin'});
275 - if(!downloading.ok) throw new Error('Download failed: ' + downloading.status);
276 - var blob = await downloading.blob();
277 -
278 - var token = getFormToken();
279 -
280 - var putURL = '/rest/wikis/' + encodeURIComponent(WIKI) + '/' + dstSpacesPath +
281 - '/pages/' + encodeURIComponent(pf.page) +
282 - '/attachments/' + encodeURIComponent(filename) + '?media=json';
283 - var uploading = await fetch(putURL, {
284 - method: 'PUT',
285 - body: blob,
286 - headers: {'Content-Type':'application/octet-stream','XWiki-Form-Token': token},
287 - credentials:'same-origin'
288 - });
289 - if(!(uploading.status===201 || uploading.status===202)){
290 - var txt = await uploading.text().catch(function(){ return String(uploading.status); });
291 - throw new Error('Upload failed: ' + txt);
292 - }
293 -
294 - var delURL = '/rest/wikis/' + encodeURIComponent(WIKI) + '/' + srcSpacesPath +
295 - '/pages/' + encodeURIComponent(srcPage) +
296 - '/attachments/' + encodeURIComponent(filename);
297 - var deleting = await fetch(delURL, {method:'DELETE', headers:{'XWiki-Form-Token': token}, credentials:'same-origin'});
298 - if(!(deleting.status===204)){ console.warn('Delete original failed', deleting.status); }
299 - return true;
300 - }
301 -
302 - async function deleteAttachment(filename){
303 - var token = getFormToken();
304 - var srcSpacesPath = spacesPath(window.SOURCE_SPACE);
305 - var url = '/rest/wikis/' + encodeURIComponent(WIKI) + '/' + srcSpacesPath +
306 - '/pages/' + encodeURIComponent(window.SOURCE_PAGE) +
307 - '/attachments/' + encodeURIComponent(filename);
308 - var r = await fetch(url, {
309 - method: 'DELETE',
310 - headers: {'XWiki-Form-Token': token},
311 - credentials: 'same-origin'
312 - });
313 - if (r.status !== 204) {
314 - var t = await r.text().catch(()=>String(r.status));
315 - throw new Error('Delete failed: ' + t);
316 - }
317 - return true;
318 - }
319 -
320 - function removeCardByFilename(filename){
321 - var card = document.querySelector('.video-container input.vid-pick[data-filename="'+CSS.escape(filename)+'"]');
322 - card = card ? card.closest('.video-container') : null;
323 - if (card) card.remove();
324 - }
325 - function selectedFilenames(){
326 - return Array.from(document.querySelectorAll('.vid-pick:checked'))
327 - .map(ch => ch.getAttribute('data-filename'));
328 - }
329 -
330 - /* ===== Move (per-card) ===== */
331 - document.addEventListener('click', function(e){
332 - var btn = e.target.closest('.move-go'); if(!btn) return;
333 - var box = btn.closest('.move-box');
334 - var inp = box.querySelector('.move-input');
335 - (async function(){
336 - var notice = box.querySelector('.move-notice');
337 - if(!notice){ notice = document.createElement('div'); notice.className = 'move-notice'; box.appendChild(notice); }
338 - try{
339 - var ref = await resolveReference(inp);
340 - var filename = btn.getAttribute('data-filename');
341 - notice.style.color = '#666';
342 - notice.textContent = 'Moving “' + filename + '” to ' + ref + ' …';
343 -
344 - await moveAttachment({ srcSpace: window.SOURCE_SPACE, srcPage: window.SOURCE_PAGE, filename: filename, dstFull: ref });
345 -
346 - notice.textContent = 'Moved ✔ — reloading…';
347 - setTimeout(function(){ location.reload(); }, 600);
348 - }catch(err){
349 - notice.style.color = '#b00020';
350 - notice.textContent = 'Move failed: ' + (err && err.message ? err.message : err);
351 - }
352 - })();
353 - });
354 -
355 - /* ===== Delete (per-card) ===== */
356 - document.addEventListener('click', function(e){
357 - var btn = e.target.closest('.del-one'); if(!btn) return;
358 - var filename = btn.getAttribute('data-filename');
359 - if(!confirm('Delete this file permanently?\n\n' + filename)) return;
360 - var notice = document.createElement('div');
361 - notice.className = 'move-notice'; notice.textContent = 'Deleting…';
362 - btn.parentElement.appendChild(notice);
363 - deleteAttachment(filename)
364 - .then(()=>{ notice.textContent = 'Deleted ✔'; removeCardByFilename(filename); })
365 - .catch(err=>{ notice.style.color='#b00020'; notice.textContent = (err && err.message)||String(err); });
366 - });
367 -
368 - /* ===== Bulk: select visible ===== */
369 - document.addEventListener('change', function(e){
370 - if (e.target && e.target.id === 'pick-all'){
371 - var on = e.target.checked;
372 - document.querySelectorAll('.vid-pick').forEach(ch => { ch.checked = on; });
373 - }
374 - });
375 -
376 - /* ===== Bulk: Move ===== */
377 - document.getElementById('bulk-move')?.addEventListener('click', async function(){
378 - var files = selectedFilenames();
379 - if (!files.length) return alert('No videos selected.');
380 - var destInput = document.querySelector('.bulk-dest');
381 - var status = document.getElementById('bulk-status');
382 - try{
383 - var ref = await resolveReference(destInput);
384 - if(!confirm('Move '+files.length+' file(s) to:\n\n'+ref+' ?')) return;
385 - status.style.color=''; status.textContent = 'Moving 0/' + files.length + '…';
386 - let done = 0;
387 - for (const f of files){
388 - await moveAttachment({ srcSpace: window.SOURCE_SPACE, srcPage: window.SOURCE_PAGE, filename: f, dstFull: ref });
389 - done++; status.textContent = 'Moving ' + done + '/' + files.length + '…';
390 - removeCardByFilename(f);
391 - }
392 - status.textContent = 'Move complete ✔';
393 - }catch(err){
394 - status.style.color = '#b00020';
395 - status.textContent = 'Move failed: ' + ((err && err.message) || String(err));
396 - }
397 - });
398 -
399 - /* ===== Bulk: Delete ===== */
400 - document.getElementById('bulk-delete')?.addEventListener('click', async function(){
401 - var files = selectedFilenames();
402 - if (!files.length) return alert('No videos selected.');
403 - if(!confirm('DELETE '+files.length+' file(s)? This cannot be undone.')) return;
404 - var status = document.getElementById('bulk-status');
405 - try{
406 - status.style.color=''; status.textContent = 'Deleting 0/' + files.length + '…';
407 - let done = 0;
408 - for (const f of files){
409 - await deleteAttachment(f);
410 - done++; status.textContent = 'Deleting ' + done + '/' + files.length + '…';
411 - removeCardByFilename(f);
412 - }
413 - status.textContent = 'Delete complete ✔';
414 - }catch(err){
415 - status.style.color = '#b00020';
416 - status.textContent = 'Delete failed: ' + ((err && err.message) || String(err));
417 - }
418 - });
419 -
420 -})();
421 -</script>
422 -
423 -<style>
424 - .video-display-grid{
425 - display:grid;
426 - grid-template-columns:repeat(auto-fit,minmax(320px,1fr));
427 - gap:20px; align-items:start;
428 - }
429 - .video-container{border:1px solid #ddd; border-radius:8px; padding:12px; background:#fff;}
430 - .video-header{display:flex;gap:8px;align-items:center;margin-bottom:8px;}
431 - .video-title{margin:0;flex:1;min-width:0;word-break:break-word;}
432 - .pick{margin-right:2px}
433 - .video-frame{
434 - position:relative;width:100%;aspect-ratio:16/9;background:#111;border-radius:4px;
435 - overflow:hidden;display:flex;align-items:center;justify-content:center;cursor:pointer;
436 - }
437 - .vid-canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
438 - .video-controls{margin-top:8px;display:flex;flex-wrap:wrap;gap:6px;align-items:center;}
439 - .move-box{margin-top:10px;}
440 - .move-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap;}
441 - .move-input{flex:1;min-width:220px;padding:4px;border:1px solid #ccc;border-radius:4px;}
442 - .hint{color:#888;}
443 - .loadmore-wrap{text-align:center;margin:12px 0 28px;}
444 - .btn{padding:4px 8px;border:1px solid #ddd;background:#f8f9fa;border-radius:4px;cursor:pointer;text-decoration:none;display:inline-block;}
445 - .btn:hover{background:#e9ecef;}
446 - .btn-primary{background:#007bff;color:#fff;border-color:#007bff;}
447 - .btn-secondary{background:#6c757d;color:#fff;border-color:#6c757d;}
448 - .btn-success{background:#28a745;color:#fff;border-color:#28a745;}
449 - .btn-danger{background:#dc3545;color:#fff;border-color:#dc3545;}
450 - .btn-danger:hover{background:#c82333}
451 - .btn-sm{font-size:12px;padding:2px 6px;}
452 - .move-notice{font-size:12px;color:#666;margin-top:6px;max-height:6.5em;overflow:auto;white-space:normal;overflow-wrap:anywhere;}
453 - .bulk-bar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0 16px;}
454 - .bulk-bar .sep{width:1px;height:18px;background:#ddd;margin:0 4px;}
455 - .bulk-status{font-size:12px;color:#666;margin-left:6px;white-space:normal;overflow-wrap:anywhere;}
456 - @media (max-width:768px){.video-display-grid{grid-template-columns:1fr}}
457 -</style>
458 -{{/html}}
459 -{{/cache}}
460 -{{/velocity}}
461 -
12 +
MEGYNK~1.MP4
Author
... ... @@ -1,1 +1,0 @@
1 -XWiki.AdminAngriff
Size
... ... @@ -1,1 +1,0 @@
1 -147.7 MB
Content