| 22 | 1 <!doctype html> | 
|  | 2 <html> | 
|  | 3 	<head> | 
|  | 4 		<meta name="viewport" content="width=device-width, initial-scale=1"> | 
| 40 | 5 		<script src="upload.js"></script> | 
| 24 | 6 		<script src="http://tinymce.luan.software/tinymce.min.js" xreferrerpolicy="origin"></script> | 
| 22 | 7 		<style> | 
| 35 | 8 			input[type="file"] { | 
|  | 9 				display: none; | 
|  | 10 			} | 
| 22 | 11 		</style> | 
|  | 12 		<script> | 
| 28 | 13 			function videoIframe(url) { | 
|  | 14 				return '<iframe data-video="'+url+'" width="560" height="315" frameborder="0" allowfullscreen src="'+url+'"></iframe>'; | 
|  | 15 			} | 
|  | 16 | 
| 30 | 17 			// fucking moronic javascript doesn't have \Q \E in regex | 
| 28 | 18 			var videoHandlers = {}; | 
|  | 19 			{ | 
| 30 | 20 				let ptn1 = /^https:\/\/youtu\.be\/([a-zA-Z0-9_-]+)(?:\?t=([0-9]+))?/; | 
|  | 21 				let ptn2 = /^https:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)(?:&t=([0-9]+)s)?/; | 
| 28 | 22 				videoHandlers.youtube = function(url) { | 
|  | 23 					let result = url.match(ptn1) || url.match(ptn2); | 
|  | 24 					if( result ) { | 
|  | 25 						url = 'https://www.youtube.com/embed/' + result[1]; | 
|  | 26 						if( result[2] ) | 
|  | 27 							url += '?start=' + result[2]; | 
|  | 28 						return videoIframe(url); | 
|  | 29 					} | 
|  | 30 				} | 
|  | 31 			} | 
|  | 32 			{ | 
| 30 | 33 				let ptn = /^https:\/\/rumble\.com\/embed\/[a-z0-9]+\/\?pub=[a-z0-9]+/; | 
| 28 | 34 				videoHandlers.rumble = function(url) { | 
|  | 35 					if( url.match(ptn) ) { | 
|  | 36 						return videoIframe(url); | 
|  | 37 					} | 
|  | 38 				} | 
|  | 39 			} | 
|  | 40 			{ | 
| 30 | 41 				let ptn = /^https:\/\/www\.bitchute\.com\/video\/([a-zA-Z0-9]+)\//; | 
| 28 | 42 				videoHandlers.bitchute = function(url) { | 
|  | 43 					let result = url.match(ptn); | 
|  | 44 					if( result ) { | 
|  | 45 						url = 'https://www.bitchute.com/embed/' + result[1]; | 
|  | 46 						return videoIframe(url); | 
|  | 47 					} | 
|  | 48 				} | 
|  | 49 			} | 
|  | 50 			{ | 
| 30 | 51 				let ptn = /^https:\/\/vimeo\.com\/([0-9]+)/; | 
| 28 | 52 				videoHandlers.vimeo = function(url) { | 
|  | 53 					let result = url.match(ptn); | 
|  | 54 					if( result ) { | 
|  | 55 						url = 'https://player.vimeo.com/video/' + result[1]; | 
|  | 56 						return videoIframe(url); | 
|  | 57 					} | 
|  | 58 				} | 
|  | 59 			} | 
|  | 60 			{ | 
| 30 | 61 				let ptn = /^https:\/\/dai\.ly\/([a-z0-9]+)/; | 
| 28 | 62 				videoHandlers.dailymotion = function(url) { | 
|  | 63 					let result = url.match(ptn); | 
|  | 64 					if( result ) { | 
|  | 65 						url = 'https://www.dailymotion.com/embed/video/' + result[1]; | 
|  | 66 						return videoIframe(url); | 
|  | 67 					} | 
|  | 68 				} | 
|  | 69 			} | 
|  | 70 			{ | 
| 30 | 71 				let ptn = /^https:\/\/www\.tiktok\.com\/[^/]+\/video\/([0-9]+)/; | 
| 28 | 72 				videoHandlers.tiktok = function(url) { | 
|  | 73 					let result = url.match(ptn); | 
|  | 74 					if( result ) { | 
| 31 | 75 						//let html = '<blockquote class="tiktok-embed" data-video="'+result[1]+'" style="max-width: 560px; margin-left: 0;"><section></section></blockquote>'; | 
| 28 | 76 						//html += '<script async src="https://www.tiktok.com/embed.js"></'+'script>'; | 
| 31 | 77 						let html = '<img data-video="'+result[1]+'" src="whataever">'; | 
| 28 | 78 						return html; | 
|  | 79 					} | 
|  | 80 				} | 
|  | 81 			} | 
|  | 82 			{ | 
| 30 | 83 				let ptn = /\.[a-zA-Z0-9]+$/; | 
| 28 | 84 				videoHandlers.file = function(url) { | 
|  | 85 					if( url.match(ptn) ) { | 
|  | 86 						return '<video controls width="560" height><source src="'+url+'"></video>'; | 
|  | 87 					} | 
|  | 88 				} | 
|  | 89 			} | 
|  | 90 | 
|  | 91 			function videoUrlToHtml(url) { | 
|  | 92 				for (let key in videoHandlers) { | 
|  | 93 					let handle = videoHandlers[key]; | 
|  | 94 					let html = handle(url); | 
|  | 95 					if(html) return html; | 
|  | 96 				} | 
|  | 97 				return '<a data-video="'+url+'" href="'+url+'">'+url+'</a>'; | 
|  | 98 			} | 
| 22 | 99 | 
| 35 | 100 			function uploaded(input,url,filename) { | 
|  | 101 				input.parentNode.parentNode.querySelector('input[type="url"]').value = url; | 
|  | 102 			} | 
|  | 103 | 
|  | 104 			let imageFileHtml = '<input type=file accept="image/*" onchange="upload(this,uploaded)">'; | 
|  | 105 			imageFileHtml += '<button class="tox-button tox-button--secondary" onclick="fileButtonClick(this)">Upload Image</button>'; | 
|  | 106 | 
|  | 107 			let videoFileHtml = '<input type=file accept="video/*" onchange="upload(this,uploaded)">'; | 
|  | 108 			videoFileHtml += '<button class="tox-button tox-button--secondary" onclick="fileButtonClick(this)">Upload Video</button>'; | 
| 24 | 109 | 
| 23 | 110 			function tinymceSetup(editor) { | 
| 24 | 111 | 
| 35 | 112 				editor.ui.registry.addButton('insertLink', { | 
|  | 113 					icon: 'link', | 
|  | 114 					tooltip: 'Insert link', | 
|  | 115 					onAction: function(api) { | 
|  | 116 						editor.windowManager.open({ | 
|  | 117 							title: 'Insert Link', | 
|  | 118 							body: { | 
|  | 119 								type: 'panel', | 
|  | 120 								items: [ | 
|  | 121 									{ | 
|  | 122 										type: 'urlinput', | 
|  | 123 										name: 'src', | 
|  | 124 										label: 'URL' | 
|  | 125 									}, | 
|  | 126 									{ | 
|  | 127 										type: 'input', | 
|  | 128 										name: 'text', | 
|  | 129 										label: 'Text to display' | 
|  | 130 									}, | 
|  | 131 								] | 
|  | 132 							}, | 
|  | 133 							buttons: [ | 
|  | 134 								{ | 
|  | 135 									type: 'cancel', | 
|  | 136 									text: 'Cancel' | 
|  | 137 								}, | 
|  | 138 								{ | 
|  | 139 									type: 'submit', | 
|  | 140 									text: 'Save', | 
|  | 141 									buttonType: 'primary' | 
|  | 142 								} | 
|  | 143 							], | 
|  | 144 							onSubmit: function(dialogApi) { | 
|  | 145 								let data = dialogApi.getData(); | 
|  | 146 								let src = data.src.value; | 
|  | 147 								if(!src) return; | 
|  | 148 								src = tinymce.DOM.encode(src); | 
|  | 149 								let text = data.text; | 
|  | 150 								let html = '<a href="' + src + '">' + text + '</a>'; | 
|  | 151 								dialogApi.close(); | 
|  | 152 								editor.insertContent(html); | 
|  | 153 							} | 
|  | 154 						}); | 
|  | 155 					}, | 
|  | 156 				}); | 
|  | 157 | 
| 31 | 158 				editor.ui.registry.addButton('insertImage', { | 
|  | 159 					icon: 'image', | 
|  | 160 					tooltip: 'Insert image', | 
|  | 161 					onAction: function(api) { | 
|  | 162 						editor.windowManager.open({ | 
|  | 163 							title: 'Insert Image', | 
|  | 164 							body: { | 
|  | 165 								type: 'panel', | 
|  | 166 								items: [ | 
|  | 167 									{ | 
|  | 168 										type: 'urlinput', | 
|  | 169 										name: 'src', | 
|  | 170 										filetype: 'image', | 
|  | 171 										label: 'Source URL' | 
|  | 172 									}, | 
| 35 | 173 									{ | 
|  | 174 										type: 'htmlpanel', | 
|  | 175 										html: imageFileHtml | 
|  | 176 									}, | 
| 31 | 177 								] | 
|  | 178 							}, | 
|  | 179 							buttons: [ | 
|  | 180 								{ | 
|  | 181 									type: 'cancel', | 
|  | 182 									text: 'Cancel' | 
|  | 183 								}, | 
|  | 184 								{ | 
|  | 185 									type: 'submit', | 
|  | 186 									text: 'Save', | 
|  | 187 									buttonType: 'primary' | 
|  | 188 								} | 
|  | 189 							], | 
|  | 190 							onSubmit: function(dialogApi) { | 
| 35 | 191 								let src = dialogApi.getData().src.value; | 
| 31 | 192 								if(!src) return; | 
|  | 193 								src = tinymce.DOM.encode(src); | 
|  | 194 								let html = '<img src="' + src + '">'; | 
|  | 195 								dialogApi.close(); | 
|  | 196 								editor.insertContent(html); | 
|  | 197 							} | 
|  | 198 						}); | 
|  | 199 					}, | 
|  | 200 				}); | 
|  | 201 | 
|  | 202 				editor.ui.registry.addButton('insertVideo', { | 
|  | 203 					icon: 'embed', | 
|  | 204 					tooltip: 'Insert video', | 
|  | 205 					onAction: function(api) { | 
|  | 206 						editor.windowManager.open({ | 
|  | 207 							title: 'Insert Video', | 
|  | 208 							body: { | 
|  | 209 								type: 'panel', | 
|  | 210 								items: [ | 
|  | 211 									{ | 
|  | 212 										type: 'urlinput', | 
|  | 213 										name: 'src', | 
| 35 | 214 										filetype: 'media', | 
| 31 | 215 										label: 'Source URL' | 
|  | 216 									}, | 
| 35 | 217 									{ | 
|  | 218 										type: 'htmlpanel', | 
|  | 219 										html: videoFileHtml | 
|  | 220 									}, | 
| 31 | 221 								] | 
|  | 222 							}, | 
|  | 223 							buttons: [ | 
|  | 224 								{ | 
|  | 225 									type: 'cancel', | 
|  | 226 									text: 'Cancel' | 
|  | 227 								}, | 
|  | 228 								{ | 
|  | 229 									type: 'submit', | 
|  | 230 									text: 'Save', | 
|  | 231 									buttonType: 'primary' | 
|  | 232 								} | 
|  | 233 							], | 
|  | 234 							onSubmit: function(dialogApi) { | 
| 35 | 235 								let src = dialogApi.getData().src.value; | 
| 31 | 236 								if(!src) return; | 
|  | 237 								let html = videoUrlToHtml(src); | 
|  | 238 								//alert(html); | 
|  | 239 								dialogApi.close(); | 
|  | 240 								editor.insertContent(html); | 
|  | 241 							} | 
|  | 242 						}); | 
|  | 243 					}, | 
|  | 244 				}); | 
|  | 245 | 
| 24 | 246 				editor.ui.registry.addToggleButton('styleCode', { | 
| 32 | 247 					icon: 'code-sample', | 
| 24 | 248 					tooltip: 'Code', | 
|  | 249 					onAction: function(api) { | 
| 31 | 250 						editor.execCommand('mceToggleFormat', false, 'code'); | 
| 24 | 251 					}, | 
|  | 252 					onSetup: function(api) { | 
|  | 253 						api.setActive(editor.formatter.match('code')); | 
|  | 254 						let changed = editor.formatter.formatChanged('code', api.setActive); | 
|  | 255 						return function() { changed.unbind(); }; | 
|  | 256 					} | 
|  | 257 				}); | 
|  | 258 | 
|  | 259 				editor.ui.registry.addMenuButton('styleText', { | 
|  | 260 					icon: 'format', | 
|  | 261 					tooltip: 'Text', | 
|  | 262 					fetch: function(callback) { | 
|  | 263 						callback([ | 
|  | 264 							'fontsize', | 
|  | 265 							'forecolor', | 
|  | 266 						]) | 
|  | 267 					} | 
| 23 | 268 				}); | 
| 26 | 269 | 
| 31 | 270 				editor.on( 'init', function(e) { | 
|  | 271 					editor.focus(); | 
|  | 272 				} ); | 
| 23 | 273 			} | 
|  | 274 | 
| 22 | 275 			tinymce.init({ | 
|  | 276 				selector: 'textarea', | 
| 23 | 277 				setup: tinymceSetup, | 
| 27 | 278 				//menubar: false, | 
| 26 | 279 				statusbar: false, | 
| 32 | 280 				plugins: ['link', 'image', 'media', 'lists', 'code', 'autoresize'], | 
| 35 | 281 				toolbar: 'link insertLink insertImage insertVideo | styleCode bold italic underline strikethrough superscript styleText | blockquote numlist bullist', | 
| 26 | 282 				autoresize_bottom_margin: 0, | 
| 22 | 283 				link_target_list: false, | 
|  | 284 				link_title: false, | 
|  | 285 				object_resizing: false, | 
|  | 286 				contextmenu: false, | 
|  | 287 				text_patterns: false, | 
| 26 | 288 				content_style: 'img {max-width: 500px;} p {margin: 0}', | 
| 22 | 289 				extended_valid_elements: 'b,i', | 
| 39 | 290 				resize: 'both', | 
| 22 | 291 				formats: { | 
|  | 292 					bold: { inline: 'b' }, | 
|  | 293 					italic: {inline: 'i'}, | 
|  | 294 					underline: {inline: 'u'}, | 
|  | 295 				}, | 
|  | 296 			}); | 
|  | 297 | 
|  | 298 			function log() { | 
|  | 299 				console.log(tinymce.activeEditor.getContent()); | 
|  | 300 			} | 
|  | 301 		</script> | 
|  | 302 	</head> | 
|  | 303 	<body> | 
| 35 | 304 		<p> | 
|  | 305 			<a href="https://www.tiny.cloud/">TinyMCE</a> | 
|  | 306 			<a href="https://github.com/tinymce/tinymce">source</a> | 
|  | 307 		</p> | 
| 22 | 308 		<textarea></textarea> | 
|  | 309 		<p><button onclick="log()">log</button></p> | 
|  | 310 	</body> | 
|  | 311 </html> |