plugin.js 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151
  1. /**
  2. * TinyMCE version 6.0.3 (2022-05-25)
  3. */
  4. (function () {
  5. 'use strict';
  6. var global$6 = tinymce.util.Tools.resolve('tinymce.PluginManager');
  7. const hasProto = (v, constructor, predicate) => {
  8. var _a;
  9. if (predicate(v, constructor.prototype)) {
  10. return true;
  11. } else {
  12. return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
  13. }
  14. };
  15. const typeOf = x => {
  16. const t = typeof x;
  17. if (x === null) {
  18. return 'null';
  19. } else if (t === 'object' && Array.isArray(x)) {
  20. return 'array';
  21. } else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
  22. return 'string';
  23. } else {
  24. return t;
  25. }
  26. };
  27. const isType = type => value => typeOf(value) === type;
  28. const isString = isType('string');
  29. const isObject = isType('object');
  30. const isArray = isType('array');
  31. const isNullable = a => a === null || a === undefined;
  32. const isNonNullable = a => !isNullable(a);
  33. class Optional {
  34. constructor(tag, value) {
  35. this.tag = tag;
  36. this.value = value;
  37. }
  38. static some(value) {
  39. return new Optional(true, value);
  40. }
  41. static none() {
  42. return Optional.singletonNone;
  43. }
  44. fold(onNone, onSome) {
  45. if (this.tag) {
  46. return onSome(this.value);
  47. } else {
  48. return onNone();
  49. }
  50. }
  51. isSome() {
  52. return this.tag;
  53. }
  54. isNone() {
  55. return !this.tag;
  56. }
  57. map(mapper) {
  58. if (this.tag) {
  59. return Optional.some(mapper(this.value));
  60. } else {
  61. return Optional.none();
  62. }
  63. }
  64. bind(binder) {
  65. if (this.tag) {
  66. return binder(this.value);
  67. } else {
  68. return Optional.none();
  69. }
  70. }
  71. exists(predicate) {
  72. return this.tag && predicate(this.value);
  73. }
  74. forall(predicate) {
  75. return !this.tag || predicate(this.value);
  76. }
  77. filter(predicate) {
  78. if (!this.tag || predicate(this.value)) {
  79. return this;
  80. } else {
  81. return Optional.none();
  82. }
  83. }
  84. getOr(replacement) {
  85. return this.tag ? this.value : replacement;
  86. }
  87. or(replacement) {
  88. return this.tag ? this : replacement;
  89. }
  90. getOrThunk(thunk) {
  91. return this.tag ? this.value : thunk();
  92. }
  93. orThunk(thunk) {
  94. return this.tag ? this : thunk();
  95. }
  96. getOrDie(message) {
  97. if (!this.tag) {
  98. throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
  99. } else {
  100. return this.value;
  101. }
  102. }
  103. static from(value) {
  104. return isNonNullable(value) ? Optional.some(value) : Optional.none();
  105. }
  106. getOrNull() {
  107. return this.tag ? this.value : null;
  108. }
  109. getOrUndefined() {
  110. return this.value;
  111. }
  112. each(worker) {
  113. if (this.tag) {
  114. worker(this.value);
  115. }
  116. }
  117. toArray() {
  118. return this.tag ? [this.value] : [];
  119. }
  120. toString() {
  121. return this.tag ? `some(${ this.value })` : 'none()';
  122. }
  123. }
  124. Optional.singletonNone = new Optional(false);
  125. const nativePush = Array.prototype.push;
  126. const each$1 = (xs, f) => {
  127. for (let i = 0, len = xs.length; i < len; i++) {
  128. const x = xs[i];
  129. f(x, i);
  130. }
  131. };
  132. const flatten = xs => {
  133. const r = [];
  134. for (let i = 0, len = xs.length; i < len; ++i) {
  135. if (!isArray(xs[i])) {
  136. throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
  137. }
  138. nativePush.apply(r, xs[i]);
  139. }
  140. return r;
  141. };
  142. const Cell = initial => {
  143. let value = initial;
  144. const get = () => {
  145. return value;
  146. };
  147. const set = v => {
  148. value = v;
  149. };
  150. return {
  151. get,
  152. set
  153. };
  154. };
  155. const keys = Object.keys;
  156. const hasOwnProperty = Object.hasOwnProperty;
  157. const each = (obj, f) => {
  158. const props = keys(obj);
  159. for (let k = 0, len = props.length; k < len; k++) {
  160. const i = props[k];
  161. const x = obj[i];
  162. f(x, i);
  163. }
  164. };
  165. const get$1 = (obj, key) => {
  166. return has(obj, key) ? Optional.from(obj[key]) : Optional.none();
  167. };
  168. const has = (obj, key) => hasOwnProperty.call(obj, key);
  169. const option = name => editor => editor.options.get(name);
  170. const register$2 = editor => {
  171. const registerOption = editor.options.register;
  172. registerOption('audio_template_callback', { processor: 'function' });
  173. registerOption('video_template_callback', { processor: 'function' });
  174. registerOption('media_live_embeds', {
  175. processor: 'boolean',
  176. default: true
  177. });
  178. registerOption('media_filter_html', {
  179. processor: 'boolean',
  180. default: true
  181. });
  182. registerOption('media_url_resolver', { processor: 'function' });
  183. registerOption('media_alt_source', {
  184. processor: 'boolean',
  185. default: true
  186. });
  187. registerOption('media_poster', {
  188. processor: 'boolean',
  189. default: true
  190. });
  191. registerOption('media_dimensions', {
  192. processor: 'boolean',
  193. default: true
  194. });
  195. };
  196. const getAudioTemplateCallback = option('audio_template_callback');
  197. const getVideoTemplateCallback = option('video_template_callback');
  198. const hasLiveEmbeds = option('media_live_embeds');
  199. const shouldFilterHtml = option('media_filter_html');
  200. const getUrlResolver = option('media_url_resolver');
  201. const hasAltSource = option('media_alt_source');
  202. const hasPoster = option('media_poster');
  203. const hasDimensions = option('media_dimensions');
  204. var global$5 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  205. var global$4 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils');
  206. var global$3 = tinymce.util.Tools.resolve('tinymce.html.DomParser');
  207. const DOM$1 = global$4.DOM;
  208. const trimPx = value => value.replace(/px$/, '');
  209. const getEphoxEmbedData = node => {
  210. const style = node.attr('style');
  211. const styles = style ? DOM$1.parseStyle(style) : {};
  212. return {
  213. type: 'ephox-embed-iri',
  214. source: node.attr('data-ephox-embed-iri'),
  215. altsource: '',
  216. poster: '',
  217. width: get$1(styles, 'max-width').map(trimPx).getOr(''),
  218. height: get$1(styles, 'max-height').map(trimPx).getOr('')
  219. };
  220. };
  221. const htmlToData = (html, schema) => {
  222. let data = {};
  223. const parser = global$3({
  224. validate: false,
  225. forced_root_block: false
  226. }, schema);
  227. const rootNode = parser.parse(html);
  228. for (let node = rootNode; node; node = node.walk()) {
  229. if (node.type === 1) {
  230. const name = node.name;
  231. if (node.attr('data-ephox-embed-iri')) {
  232. data = getEphoxEmbedData(node);
  233. break;
  234. } else {
  235. if (!data.source && name === 'param') {
  236. data.source = node.attr('movie');
  237. }
  238. if (name === 'iframe' || name === 'object' || name === 'embed' || name === 'video' || name === 'audio') {
  239. if (!data.type) {
  240. data.type = name;
  241. }
  242. data = global$5.extend(node.attributes.map, data);
  243. }
  244. if (name === 'script') {
  245. data = {
  246. type: 'script',
  247. source: node.attr('src')
  248. };
  249. }
  250. if (name === 'source') {
  251. if (!data.source) {
  252. data.source = node.attr('src');
  253. } else if (!data.altsource) {
  254. data.altsource = node.attr('src');
  255. }
  256. }
  257. if (name === 'img' && !data.poster) {
  258. data.poster = node.attr('src');
  259. }
  260. }
  261. }
  262. }
  263. data.source = data.source || data.src || data.data;
  264. data.altsource = data.altsource || '';
  265. data.poster = data.poster || '';
  266. return data;
  267. };
  268. const guess = url => {
  269. const mimes = {
  270. mp3: 'audio/mpeg',
  271. m4a: 'audio/x-m4a',
  272. wav: 'audio/wav',
  273. mp4: 'video/mp4',
  274. webm: 'video/webm',
  275. ogg: 'video/ogg',
  276. swf: 'application/x-shockwave-flash'
  277. };
  278. const fileEnd = url.toLowerCase().split('.').pop();
  279. const mime = mimes[fileEnd];
  280. return mime ? mime : '';
  281. };
  282. var global$2 = tinymce.util.Tools.resolve('tinymce.html.Node');
  283. var global$1 = tinymce.util.Tools.resolve('tinymce.html.Serializer');
  284. const Parser = (schema, settings = {}) => global$3({
  285. forced_root_block: false,
  286. validate: false,
  287. allow_conditional_comments: true,
  288. ...settings
  289. }, schema);
  290. const DOM = global$4.DOM;
  291. const addPx = value => /^[0-9.]+$/.test(value) ? value + 'px' : value;
  292. const updateEphoxEmbed = (data, node) => {
  293. const style = node.attr('style');
  294. const styleMap = style ? DOM.parseStyle(style) : {};
  295. styleMap['max-width'] = addPx(data.width);
  296. styleMap['max-height'] = addPx(data.height);
  297. node.attr('style', DOM.serializeStyle(styleMap));
  298. };
  299. const sources = [
  300. 'source',
  301. 'altsource'
  302. ];
  303. const updateHtml = (html, data, updateAll, schema) => {
  304. let numSources = 0;
  305. let sourceCount = 0;
  306. const parser = Parser(schema);
  307. parser.addNodeFilter('source', nodes => numSources = nodes.length);
  308. const rootNode = parser.parse(html);
  309. for (let node = rootNode; node; node = node.walk()) {
  310. if (node.type === 1) {
  311. const name = node.name;
  312. if (node.attr('data-ephox-embed-iri')) {
  313. updateEphoxEmbed(data, node);
  314. break;
  315. } else {
  316. switch (name) {
  317. case 'video':
  318. case 'object':
  319. case 'embed':
  320. case 'img':
  321. case 'iframe':
  322. if (data.height !== undefined && data.width !== undefined) {
  323. node.attr('width', data.width);
  324. node.attr('height', data.height);
  325. }
  326. break;
  327. }
  328. if (updateAll) {
  329. switch (name) {
  330. case 'video':
  331. node.attr('poster', data.poster);
  332. node.attr('src', null);
  333. for (let index = numSources; index < 2; index++) {
  334. if (data[sources[index]]) {
  335. const source = new global$2('source', 1);
  336. source.attr('src', data[sources[index]]);
  337. source.attr('type', data[sources[index] + 'mime'] || null);
  338. node.append(source);
  339. }
  340. }
  341. break;
  342. case 'iframe':
  343. node.attr('src', data.source);
  344. break;
  345. case 'object':
  346. const hasImage = node.getAll('img').length > 0;
  347. if (data.poster && !hasImage) {
  348. node.attr('src', data.poster);
  349. const img = new global$2('img', 1);
  350. img.attr('src', data.poster);
  351. img.attr('width', data.width);
  352. img.attr('height', data.height);
  353. node.append(img);
  354. }
  355. break;
  356. case 'source':
  357. if (sourceCount < 2) {
  358. node.attr('src', data[sources[sourceCount]]);
  359. node.attr('type', data[sources[sourceCount] + 'mime'] || null);
  360. if (!data[sources[sourceCount]]) {
  361. node.remove();
  362. continue;
  363. }
  364. }
  365. sourceCount++;
  366. break;
  367. case 'img':
  368. if (!data.poster) {
  369. node.remove();
  370. }
  371. break;
  372. }
  373. }
  374. }
  375. }
  376. }
  377. return global$1({}, schema).serialize(rootNode);
  378. };
  379. const urlPatterns = [
  380. {
  381. regex: /youtu\.be\/([\w\-_\?&=.]+)/i,
  382. type: 'iframe',
  383. w: 560,
  384. h: 314,
  385. url: 'www.youtube.com/embed/$1',
  386. allowFullscreen: true
  387. },
  388. {
  389. regex: /youtube\.com(.+)v=([^&]+)(&([a-z0-9&=\-_]+))?/i,
  390. type: 'iframe',
  391. w: 560,
  392. h: 314,
  393. url: 'www.youtube.com/embed/$2?$4',
  394. allowFullscreen: true
  395. },
  396. {
  397. regex: /youtube.com\/embed\/([a-z0-9\?&=\-_]+)/i,
  398. type: 'iframe',
  399. w: 560,
  400. h: 314,
  401. url: 'www.youtube.com/embed/$1',
  402. allowFullscreen: true
  403. },
  404. {
  405. regex: /vimeo\.com\/([0-9]+)/,
  406. type: 'iframe',
  407. w: 425,
  408. h: 350,
  409. url: 'player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc',
  410. allowFullscreen: true
  411. },
  412. {
  413. regex: /vimeo\.com\/(.*)\/([0-9]+)/,
  414. type: 'iframe',
  415. w: 425,
  416. h: 350,
  417. url: 'player.vimeo.com/video/$2?title=0&amp;byline=0',
  418. allowFullscreen: true
  419. },
  420. {
  421. regex: /maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,
  422. type: 'iframe',
  423. w: 425,
  424. h: 350,
  425. url: 'maps.google.com/maps/ms?msid=$2&output=embed"',
  426. allowFullscreen: false
  427. },
  428. {
  429. regex: /dailymotion\.com\/video\/([^_]+)/,
  430. type: 'iframe',
  431. w: 480,
  432. h: 270,
  433. url: 'www.dailymotion.com/embed/video/$1',
  434. allowFullscreen: true
  435. },
  436. {
  437. regex: /dai\.ly\/([^_]+)/,
  438. type: 'iframe',
  439. w: 480,
  440. h: 270,
  441. url: 'www.dailymotion.com/embed/video/$1',
  442. allowFullscreen: true
  443. }
  444. ];
  445. const getProtocol = url => {
  446. const protocolMatches = url.match(/^(https?:\/\/|www\.)(.+)$/i);
  447. if (protocolMatches && protocolMatches.length > 1) {
  448. return protocolMatches[1] === 'www.' ? 'https://' : protocolMatches[1];
  449. } else {
  450. return 'https://';
  451. }
  452. };
  453. const getUrl = (pattern, url) => {
  454. const protocol = getProtocol(url);
  455. const match = pattern.regex.exec(url);
  456. let newUrl = protocol + pattern.url;
  457. for (let i = 0; i < match.length; i++) {
  458. newUrl = newUrl.replace('$' + i, () => match[i] ? match[i] : '');
  459. }
  460. return newUrl.replace(/\?$/, '');
  461. };
  462. const matchPattern = url => {
  463. const patterns = urlPatterns.filter(pattern => pattern.regex.test(url));
  464. if (patterns.length > 0) {
  465. return global$5.extend({}, patterns[0], { url: getUrl(patterns[0], url) });
  466. } else {
  467. return null;
  468. }
  469. };
  470. const getIframeHtml = data => {
  471. const allowFullscreen = data.allowfullscreen ? ' allowFullscreen="1"' : '';
  472. return '<iframe src="' + data.source + '" width="' + data.width + '" height="' + data.height + '"' + allowFullscreen + '></iframe>';
  473. };
  474. const getFlashHtml = data => {
  475. let html = '<object data="' + data.source + '" width="' + data.width + '" height="' + data.height + '" type="application/x-shockwave-flash">';
  476. if (data.poster) {
  477. html += '<img src="' + data.poster + '" width="' + data.width + '" height="' + data.height + '" />';
  478. }
  479. html += '</object>';
  480. return html;
  481. };
  482. const getAudioHtml = (data, audioTemplateCallback) => {
  483. if (audioTemplateCallback) {
  484. return audioTemplateCallback(data);
  485. } else {
  486. return '<audio controls="controls" src="' + data.source + '">' + (data.altsource ? '\n<source src="' + data.altsource + '"' + (data.altsourcemime ? ' type="' + data.altsourcemime + '"' : '') + ' />\n' : '') + '</audio>';
  487. }
  488. };
  489. const getVideoHtml = (data, videoTemplateCallback) => {
  490. if (videoTemplateCallback) {
  491. return videoTemplateCallback(data);
  492. } else {
  493. return '<video width="' + data.width + '" height="' + data.height + '"' + (data.poster ? ' poster="' + data.poster + '"' : '') + ' controls="controls">\n' + '<source src="' + data.source + '"' + (data.sourcemime ? ' type="' + data.sourcemime + '"' : '') + ' />\n' + (data.altsource ? '<source src="' + data.altsource + '"' + (data.altsourcemime ? ' type="' + data.altsourcemime + '"' : '') + ' />\n' : '') + '</video>';
  494. }
  495. };
  496. const getScriptHtml = data => {
  497. return '<script src="' + data.source + '"></script>';
  498. };
  499. const dataToHtml = (editor, dataIn) => {
  500. const data = global$5.extend({}, dataIn);
  501. if (!data.source) {
  502. global$5.extend(data, htmlToData(data.embed, editor.schema));
  503. if (!data.source) {
  504. return '';
  505. }
  506. }
  507. if (!data.altsource) {
  508. data.altsource = '';
  509. }
  510. if (!data.poster) {
  511. data.poster = '';
  512. }
  513. data.source = editor.convertURL(data.source, 'source');
  514. data.altsource = editor.convertURL(data.altsource, 'source');
  515. data.sourcemime = guess(data.source);
  516. data.altsourcemime = guess(data.altsource);
  517. data.poster = editor.convertURL(data.poster, 'poster');
  518. const pattern = matchPattern(data.source);
  519. if (pattern) {
  520. data.source = pattern.url;
  521. data.type = pattern.type;
  522. data.allowfullscreen = pattern.allowFullscreen;
  523. data.width = data.width || String(pattern.w);
  524. data.height = data.height || String(pattern.h);
  525. }
  526. if (data.embed) {
  527. return updateHtml(data.embed, data, true, editor.schema);
  528. } else {
  529. const audioTemplateCallback = getAudioTemplateCallback(editor);
  530. const videoTemplateCallback = getVideoTemplateCallback(editor);
  531. data.width = data.width || '300';
  532. data.height = data.height || '150';
  533. global$5.each(data, (value, key) => {
  534. data[key] = editor.dom.encode('' + value);
  535. });
  536. if (data.type === 'iframe') {
  537. return getIframeHtml(data);
  538. } else if (data.sourcemime === 'application/x-shockwave-flash') {
  539. return getFlashHtml(data);
  540. } else if (data.sourcemime.indexOf('audio') !== -1) {
  541. return getAudioHtml(data, audioTemplateCallback);
  542. } else if (data.type === 'script') {
  543. return getScriptHtml(data);
  544. } else {
  545. return getVideoHtml(data, videoTemplateCallback);
  546. }
  547. }
  548. };
  549. const isMediaElement = element => element.hasAttribute('data-mce-object') || element.hasAttribute('data-ephox-embed-iri');
  550. const setup$2 = editor => {
  551. editor.on('click keyup touchend', () => {
  552. const selectedNode = editor.selection.getNode();
  553. if (selectedNode && editor.dom.hasClass(selectedNode, 'mce-preview-object')) {
  554. if (editor.dom.getAttrib(selectedNode, 'data-mce-selected')) {
  555. selectedNode.setAttribute('data-mce-selected', '2');
  556. }
  557. }
  558. });
  559. editor.on('ObjectSelected', e => {
  560. const objectType = e.target.getAttribute('data-mce-object');
  561. if (objectType === 'script') {
  562. e.preventDefault();
  563. }
  564. });
  565. editor.on('ObjectResized', e => {
  566. const target = e.target;
  567. if (target.getAttribute('data-mce-object')) {
  568. let html = target.getAttribute('data-mce-html');
  569. if (html) {
  570. html = unescape(html);
  571. target.setAttribute('data-mce-html', escape(updateHtml(html, {
  572. width: String(e.width),
  573. height: String(e.height)
  574. }, false, editor.schema)));
  575. }
  576. }
  577. });
  578. };
  579. const cache = {};
  580. const embedPromise = (data, dataToHtml, handler) => {
  581. return new Promise((res, rej) => {
  582. const wrappedResolve = response => {
  583. if (response.html) {
  584. cache[data.source] = response;
  585. }
  586. return res({
  587. url: data.source,
  588. html: response.html ? response.html : dataToHtml(data)
  589. });
  590. };
  591. if (cache[data.source]) {
  592. wrappedResolve(cache[data.source]);
  593. } else {
  594. handler({ url: data.source }, wrappedResolve, rej);
  595. }
  596. });
  597. };
  598. const defaultPromise = (data, dataToHtml) => Promise.resolve({
  599. html: dataToHtml(data),
  600. url: data.source
  601. });
  602. const loadedData = editor => data => dataToHtml(editor, data);
  603. const getEmbedHtml = (editor, data) => {
  604. const embedHandler = getUrlResolver(editor);
  605. return embedHandler ? embedPromise(data, loadedData(editor), embedHandler) : defaultPromise(data, loadedData(editor));
  606. };
  607. const isCached = url => has(cache, url);
  608. const extractMeta = (sourceInput, data) => get$1(data, sourceInput).bind(mainData => get$1(mainData, 'meta'));
  609. const getValue = (data, metaData, sourceInput) => prop => {
  610. const getFromData = () => get$1(data, prop);
  611. const getFromMetaData = () => get$1(metaData, prop);
  612. const getNonEmptyValue = c => get$1(c, 'value').bind(v => v.length > 0 ? Optional.some(v) : Optional.none());
  613. const getFromValueFirst = () => getFromData().bind(child => isObject(child) ? getNonEmptyValue(child).orThunk(getFromMetaData) : getFromMetaData().orThunk(() => Optional.from(child)));
  614. const getFromMetaFirst = () => getFromMetaData().orThunk(() => getFromData().bind(child => isObject(child) ? getNonEmptyValue(child) : Optional.from(child)));
  615. return { [prop]: (prop === sourceInput ? getFromValueFirst() : getFromMetaFirst()).getOr('') };
  616. };
  617. const getDimensions = (data, metaData) => {
  618. const dimensions = {};
  619. get$1(data, 'dimensions').each(dims => {
  620. each$1([
  621. 'width',
  622. 'height'
  623. ], prop => {
  624. get$1(metaData, prop).orThunk(() => get$1(dims, prop)).each(value => dimensions[prop] = value);
  625. });
  626. });
  627. return dimensions;
  628. };
  629. const unwrap = (data, sourceInput) => {
  630. const metaData = sourceInput ? extractMeta(sourceInput, data).getOr({}) : {};
  631. const get = getValue(data, metaData, sourceInput);
  632. return {
  633. ...get('source'),
  634. ...get('altsource'),
  635. ...get('poster'),
  636. ...get('embed'),
  637. ...getDimensions(data, metaData)
  638. };
  639. };
  640. const wrap = data => {
  641. const wrapped = {
  642. ...data,
  643. source: { value: get$1(data, 'source').getOr('') },
  644. altsource: { value: get$1(data, 'altsource').getOr('') },
  645. poster: { value: get$1(data, 'poster').getOr('') }
  646. };
  647. each$1([
  648. 'width',
  649. 'height'
  650. ], prop => {
  651. get$1(data, prop).each(value => {
  652. const dimensions = wrapped.dimensions || {};
  653. dimensions[prop] = value;
  654. wrapped.dimensions = dimensions;
  655. });
  656. });
  657. return wrapped;
  658. };
  659. const handleError = editor => error => {
  660. const errorMessage = error && error.msg ? 'Media embed handler error: ' + error.msg : 'Media embed handler threw unknown error.';
  661. editor.notificationManager.open({
  662. type: 'error',
  663. text: errorMessage
  664. });
  665. };
  666. const getEditorData = editor => {
  667. const element = editor.selection.getNode();
  668. const snippet = isMediaElement(element) ? editor.serializer.serialize(element, { selection: true }) : '';
  669. return {
  670. embed: snippet,
  671. ...htmlToData(snippet, editor.schema)
  672. };
  673. };
  674. const addEmbedHtml = (api, editor) => response => {
  675. if (isString(response.url) && response.url.trim().length > 0) {
  676. const html = response.html;
  677. const snippetData = htmlToData(html, editor.schema);
  678. const nuData = {
  679. ...snippetData,
  680. source: response.url,
  681. embed: html
  682. };
  683. api.setData(wrap(nuData));
  684. }
  685. };
  686. const selectPlaceholder = (editor, beforeObjects) => {
  687. const afterObjects = editor.dom.select('*[data-mce-object]');
  688. for (let i = 0; i < beforeObjects.length; i++) {
  689. for (let y = afterObjects.length - 1; y >= 0; y--) {
  690. if (beforeObjects[i] === afterObjects[y]) {
  691. afterObjects.splice(y, 1);
  692. }
  693. }
  694. }
  695. editor.selection.select(afterObjects[0]);
  696. };
  697. const handleInsert = (editor, html) => {
  698. const beforeObjects = editor.dom.select('*[data-mce-object]');
  699. editor.insertContent(html);
  700. selectPlaceholder(editor, beforeObjects);
  701. editor.nodeChanged();
  702. };
  703. const submitForm = (prevData, newData, editor) => {
  704. newData.embed = updateHtml(newData.embed, newData, false, editor.schema);
  705. if (newData.embed && (prevData.source === newData.source || isCached(newData.source))) {
  706. handleInsert(editor, newData.embed);
  707. } else {
  708. getEmbedHtml(editor, newData).then(response => {
  709. handleInsert(editor, response.html);
  710. }).catch(handleError(editor));
  711. }
  712. };
  713. const showDialog = editor => {
  714. const editorData = getEditorData(editor);
  715. const currentData = Cell(editorData);
  716. const initialData = wrap(editorData);
  717. const handleSource = (prevData, api) => {
  718. const serviceData = unwrap(api.getData(), 'source');
  719. if (prevData.source !== serviceData.source) {
  720. addEmbedHtml(win, editor)({
  721. url: serviceData.source,
  722. html: ''
  723. });
  724. getEmbedHtml(editor, serviceData).then(addEmbedHtml(win, editor)).catch(handleError(editor));
  725. }
  726. };
  727. const handleEmbed = api => {
  728. const data = unwrap(api.getData());
  729. const dataFromEmbed = htmlToData(data.embed, editor.schema);
  730. api.setData(wrap(dataFromEmbed));
  731. };
  732. const handleUpdate = (api, sourceInput) => {
  733. const data = unwrap(api.getData(), sourceInput);
  734. const embed = dataToHtml(editor, data);
  735. api.setData(wrap({
  736. ...data,
  737. embed
  738. }));
  739. };
  740. const mediaInput = [{
  741. name: 'source',
  742. type: 'urlinput',
  743. filetype: 'media',
  744. label: 'Source'
  745. }];
  746. const sizeInput = !hasDimensions(editor) ? [] : [{
  747. type: 'sizeinput',
  748. name: 'dimensions',
  749. label: 'Constrain proportions',
  750. constrain: true
  751. }];
  752. const generalTab = {
  753. title: 'General',
  754. name: 'general',
  755. items: flatten([
  756. mediaInput,
  757. sizeInput
  758. ])
  759. };
  760. const embedTextarea = {
  761. type: 'textarea',
  762. name: 'embed',
  763. label: 'Paste your embed code below:'
  764. };
  765. const embedTab = {
  766. title: 'Embed',
  767. items: [embedTextarea]
  768. };
  769. const advancedFormItems = [];
  770. if (hasAltSource(editor)) {
  771. advancedFormItems.push({
  772. name: 'altsource',
  773. type: 'urlinput',
  774. filetype: 'media',
  775. label: 'Alternative source URL'
  776. });
  777. }
  778. if (hasPoster(editor)) {
  779. advancedFormItems.push({
  780. name: 'poster',
  781. type: 'urlinput',
  782. filetype: 'image',
  783. label: 'Media poster (Image URL)'
  784. });
  785. }
  786. const advancedTab = {
  787. title: 'Advanced',
  788. name: 'advanced',
  789. items: advancedFormItems
  790. };
  791. const tabs = [
  792. generalTab,
  793. embedTab
  794. ];
  795. if (advancedFormItems.length > 0) {
  796. tabs.push(advancedTab);
  797. }
  798. const body = {
  799. type: 'tabpanel',
  800. tabs
  801. };
  802. const win = editor.windowManager.open({
  803. title: 'Insert/Edit Media',
  804. size: 'normal',
  805. body,
  806. buttons: [
  807. {
  808. type: 'cancel',
  809. name: 'cancel',
  810. text: 'Cancel'
  811. },
  812. {
  813. type: 'submit',
  814. name: 'save',
  815. text: 'Save',
  816. primary: true
  817. }
  818. ],
  819. onSubmit: api => {
  820. const serviceData = unwrap(api.getData());
  821. submitForm(currentData.get(), serviceData, editor);
  822. api.close();
  823. },
  824. onChange: (api, detail) => {
  825. switch (detail.name) {
  826. case 'source':
  827. handleSource(currentData.get(), api);
  828. break;
  829. case 'embed':
  830. handleEmbed(api);
  831. break;
  832. case 'dimensions':
  833. case 'altsource':
  834. case 'poster':
  835. handleUpdate(api, detail.name);
  836. break;
  837. }
  838. currentData.set(unwrap(api.getData()));
  839. },
  840. initialData
  841. });
  842. };
  843. const get = editor => {
  844. const showDialog$1 = () => {
  845. showDialog(editor);
  846. };
  847. return { showDialog: showDialog$1 };
  848. };
  849. const register$1 = editor => {
  850. const showDialog$1 = () => {
  851. showDialog(editor);
  852. };
  853. editor.addCommand('mceMedia', showDialog$1);
  854. };
  855. const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
  856. const startsWith = (str, prefix) => {
  857. return checkRange(str, prefix, 0);
  858. };
  859. var global = tinymce.util.Tools.resolve('tinymce.Env');
  860. const isLiveEmbedNode = node => {
  861. const name = node.name;
  862. return name === 'iframe' || name === 'video' || name === 'audio';
  863. };
  864. const getDimension = (node, styles, dimension, defaultValue = null) => {
  865. const value = node.attr(dimension);
  866. if (isNonNullable(value)) {
  867. return value;
  868. } else if (!has(styles, dimension)) {
  869. return defaultValue;
  870. } else {
  871. return null;
  872. }
  873. };
  874. const setDimensions = (node, previewNode, styles) => {
  875. const useDefaults = previewNode.name === 'img' || node.name === 'video';
  876. const defaultWidth = useDefaults ? '300' : null;
  877. const fallbackHeight = node.name === 'audio' ? '30' : '150';
  878. const defaultHeight = useDefaults ? fallbackHeight : null;
  879. previewNode.attr({
  880. width: getDimension(node, styles, 'width', defaultWidth),
  881. height: getDimension(node, styles, 'height', defaultHeight)
  882. });
  883. };
  884. const appendNodeContent = (editor, nodeName, previewNode, html) => {
  885. const newNode = Parser(editor.schema).parse(html, { context: nodeName });
  886. while (newNode.firstChild) {
  887. previewNode.append(newNode.firstChild);
  888. }
  889. };
  890. const createPlaceholderNode = (editor, node) => {
  891. const name = node.name;
  892. const placeHolder = new global$2('img', 1);
  893. retainAttributesAndInnerHtml(editor, node, placeHolder);
  894. setDimensions(node, placeHolder, {});
  895. placeHolder.attr({
  896. 'style': node.attr('style'),
  897. 'src': global.transparentSrc,
  898. 'data-mce-object': name,
  899. 'class': 'mce-object mce-object-' + name
  900. });
  901. return placeHolder;
  902. };
  903. const createPreviewNode = (editor, node) => {
  904. const name = node.name;
  905. const previewWrapper = new global$2('span', 1);
  906. previewWrapper.attr({
  907. 'contentEditable': 'false',
  908. 'style': node.attr('style'),
  909. 'data-mce-object': name,
  910. 'class': 'mce-preview-object mce-object-' + name
  911. });
  912. retainAttributesAndInnerHtml(editor, node, previewWrapper);
  913. const styles = editor.dom.parseStyle(node.attr('style'));
  914. const previewNode = new global$2(name, 1);
  915. setDimensions(node, previewNode, styles);
  916. previewNode.attr({
  917. src: node.attr('src'),
  918. style: node.attr('style'),
  919. class: node.attr('class')
  920. });
  921. if (name === 'iframe') {
  922. previewNode.attr({
  923. allowfullscreen: node.attr('allowfullscreen'),
  924. frameborder: '0'
  925. });
  926. } else {
  927. const attrs = [
  928. 'controls',
  929. 'crossorigin',
  930. 'currentTime',
  931. 'loop',
  932. 'muted',
  933. 'poster',
  934. 'preload'
  935. ];
  936. each$1(attrs, attrName => {
  937. previewNode.attr(attrName, node.attr(attrName));
  938. });
  939. const sanitizedHtml = previewWrapper.attr('data-mce-html');
  940. if (isNonNullable(sanitizedHtml)) {
  941. appendNodeContent(editor, name, previewNode, unescape(sanitizedHtml));
  942. }
  943. }
  944. const shimNode = new global$2('span', 1);
  945. shimNode.attr('class', 'mce-shim');
  946. previewWrapper.append(previewNode);
  947. previewWrapper.append(shimNode);
  948. return previewWrapper;
  949. };
  950. const retainAttributesAndInnerHtml = (editor, sourceNode, targetNode) => {
  951. const attribs = sourceNode.attributes;
  952. let ai = attribs.length;
  953. while (ai--) {
  954. const attrName = attribs[ai].name;
  955. let attrValue = attribs[ai].value;
  956. if (attrName !== 'width' && attrName !== 'height' && attrName !== 'style' && !startsWith(attrName, 'data-mce-')) {
  957. if (attrName === 'data' || attrName === 'src') {
  958. attrValue = editor.convertURL(attrValue, attrName);
  959. }
  960. targetNode.attr('data-mce-p-' + attrName, attrValue);
  961. }
  962. }
  963. const serializer = global$1({ inner: true }, editor.schema);
  964. const tempNode = new global$2('div', 1);
  965. each$1(sourceNode.children(), child => tempNode.append(child));
  966. const innerHtml = serializer.serialize(tempNode);
  967. if (innerHtml) {
  968. targetNode.attr('data-mce-html', escape(innerHtml));
  969. targetNode.empty();
  970. }
  971. };
  972. const isPageEmbedWrapper = node => {
  973. const nodeClass = node.attr('class');
  974. return nodeClass && /\btiny-pageembed\b/.test(nodeClass);
  975. };
  976. const isWithinEmbedWrapper = node => {
  977. while (node = node.parent) {
  978. if (node.attr('data-ephox-embed-iri') || isPageEmbedWrapper(node)) {
  979. return true;
  980. }
  981. }
  982. return false;
  983. };
  984. const placeHolderConverter = editor => nodes => {
  985. let i = nodes.length;
  986. let node;
  987. while (i--) {
  988. node = nodes[i];
  989. if (!node.parent) {
  990. continue;
  991. }
  992. if (node.parent.attr('data-mce-object')) {
  993. continue;
  994. }
  995. if (isLiveEmbedNode(node) && hasLiveEmbeds(editor)) {
  996. if (!isWithinEmbedWrapper(node)) {
  997. node.replace(createPreviewNode(editor, node));
  998. }
  999. } else {
  1000. if (!isWithinEmbedWrapper(node)) {
  1001. node.replace(createPlaceholderNode(editor, node));
  1002. }
  1003. }
  1004. }
  1005. };
  1006. const parseAndSanitize = (editor, context, html) => {
  1007. const validate = shouldFilterHtml(editor);
  1008. return Parser(editor.schema, { validate }).parse(html, { context });
  1009. };
  1010. const setup$1 = editor => {
  1011. editor.on('PreInit', () => {
  1012. const {schema, serializer, parser} = editor;
  1013. const boolAttrs = schema.getBoolAttrs();
  1014. each$1('webkitallowfullscreen mozallowfullscreen'.split(' '), name => {
  1015. boolAttrs[name] = {};
  1016. });
  1017. each({ embed: ['wmode'] }, (attrs, name) => {
  1018. const rule = schema.getElementRule(name);
  1019. each$1(attrs, attr => {
  1020. rule.attributes[attr] = {};
  1021. rule.attributesOrder.push(attr);
  1022. });
  1023. });
  1024. parser.addNodeFilter('iframe,video,audio,object,embed,script', placeHolderConverter(editor));
  1025. serializer.addAttributeFilter('data-mce-object', (nodes, name) => {
  1026. let i = nodes.length;
  1027. while (i--) {
  1028. const node = nodes[i];
  1029. if (!node.parent) {
  1030. continue;
  1031. }
  1032. const realElmName = node.attr(name);
  1033. const realElm = new global$2(realElmName, 1);
  1034. if (realElmName !== 'audio' && realElmName !== 'script') {
  1035. const className = node.attr('class');
  1036. if (className && className.indexOf('mce-preview-object') !== -1) {
  1037. realElm.attr({
  1038. width: node.firstChild.attr('width'),
  1039. height: node.firstChild.attr('height')
  1040. });
  1041. } else {
  1042. realElm.attr({
  1043. width: node.attr('width'),
  1044. height: node.attr('height')
  1045. });
  1046. }
  1047. }
  1048. realElm.attr({ style: node.attr('style') });
  1049. const attribs = node.attributes;
  1050. let ai = attribs.length;
  1051. while (ai--) {
  1052. const attrName = attribs[ai].name;
  1053. if (attrName.indexOf('data-mce-p-') === 0) {
  1054. realElm.attr(attrName.substr(11), attribs[ai].value);
  1055. }
  1056. }
  1057. if (realElmName === 'script') {
  1058. realElm.attr('type', 'text/javascript');
  1059. }
  1060. const innerHtml = node.attr('data-mce-html');
  1061. if (innerHtml) {
  1062. const fragment = parseAndSanitize(editor, realElmName, unescape(innerHtml));
  1063. each$1(fragment.children(), child => realElm.append(child));
  1064. }
  1065. node.replace(realElm);
  1066. }
  1067. });
  1068. });
  1069. editor.on('SetContent', () => {
  1070. const dom = editor.dom;
  1071. each$1(dom.select('span.mce-preview-object'), elm => {
  1072. if (dom.select('span.mce-shim', elm).length === 0) {
  1073. dom.add(elm, 'span', { class: 'mce-shim' });
  1074. }
  1075. });
  1076. });
  1077. };
  1078. const setup = editor => {
  1079. editor.on('ResolveName', e => {
  1080. let name;
  1081. if (e.target.nodeType === 1 && (name = e.target.getAttribute('data-mce-object'))) {
  1082. e.name = name;
  1083. }
  1084. });
  1085. };
  1086. const register = editor => {
  1087. const onAction = () => editor.execCommand('mceMedia');
  1088. editor.ui.registry.addToggleButton('media', {
  1089. tooltip: 'Insert/edit media',
  1090. icon: 'embed',
  1091. onAction,
  1092. onSetup: buttonApi => {
  1093. const selection = editor.selection;
  1094. buttonApi.setActive(isMediaElement(selection.getNode()));
  1095. return selection.selectorChangedWithUnbind('img[data-mce-object],span[data-mce-object],div[data-ephox-embed-iri]', buttonApi.setActive).unbind;
  1096. }
  1097. });
  1098. editor.ui.registry.addMenuItem('media', {
  1099. icon: 'embed',
  1100. text: 'Media...',
  1101. onAction
  1102. });
  1103. };
  1104. var Plugin = () => {
  1105. global$6.add('media', editor => {
  1106. register$2(editor);
  1107. register$1(editor);
  1108. register(editor);
  1109. setup(editor);
  1110. setup$1(editor);
  1111. setup$2(editor);
  1112. return get(editor);
  1113. });
  1114. };
  1115. Plugin();
  1116. })();