0 Votes

Wiki source code of Hatred

Last modified by Ryan C on 2025/09/11 22:41

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