plugin.js 40 KB

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