plugin.js 39 KB

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