plugin.js 38 KB

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