plugin.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /**
  2. * Copyright (c) Tiny Technologies, Inc. All rights reserved.
  3. * Licensed under the LGPL or a commercial license.
  4. * For LGPL see License.txt in the project root for license information.
  5. * For commercial licenses see https://www.tiny.cloud/
  6. *
  7. * Version: 5.1.0 (2019-10-17)
  8. */
  9. (function (domGlobals) {
  10. 'use strict';
  11. var Cell = function (initial) {
  12. var value = initial;
  13. var get = function () {
  14. return value;
  15. };
  16. var set = function (v) {
  17. value = v;
  18. };
  19. var clone = function () {
  20. return Cell(get());
  21. };
  22. return {
  23. get: get,
  24. set: set,
  25. clone: clone
  26. };
  27. };
  28. var global = tinymce.util.Tools.resolve('tinymce.PluginManager');
  29. var global$1 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  30. var global$2 = tinymce.util.Tools.resolve('tinymce.html.DomParser');
  31. var global$3 = tinymce.util.Tools.resolve('tinymce.html.Node');
  32. var global$4 = tinymce.util.Tools.resolve('tinymce.html.Serializer');
  33. var shouldHideInSourceView = function (editor) {
  34. return editor.getParam('fullpage_hide_in_source_view');
  35. };
  36. var getDefaultXmlPi = function (editor) {
  37. return editor.getParam('fullpage_default_xml_pi');
  38. };
  39. var getDefaultEncoding = function (editor) {
  40. return editor.getParam('fullpage_default_encoding');
  41. };
  42. var getDefaultFontFamily = function (editor) {
  43. return editor.getParam('fullpage_default_font_family');
  44. };
  45. var getDefaultFontSize = function (editor) {
  46. return editor.getParam('fullpage_default_font_size');
  47. };
  48. var getDefaultTextColor = function (editor) {
  49. return editor.getParam('fullpage_default_text_color');
  50. };
  51. var getDefaultTitle = function (editor) {
  52. return editor.getParam('fullpage_default_title');
  53. };
  54. var getDefaultDocType = function (editor) {
  55. return editor.getParam('fullpage_default_doctype', '<!DOCTYPE html>');
  56. };
  57. var Settings = {
  58. shouldHideInSourceView: shouldHideInSourceView,
  59. getDefaultXmlPi: getDefaultXmlPi,
  60. getDefaultEncoding: getDefaultEncoding,
  61. getDefaultFontFamily: getDefaultFontFamily,
  62. getDefaultFontSize: getDefaultFontSize,
  63. getDefaultTextColor: getDefaultTextColor,
  64. getDefaultTitle: getDefaultTitle,
  65. getDefaultDocType: getDefaultDocType
  66. };
  67. var parseHeader = function (head) {
  68. return global$2({
  69. validate: false,
  70. root_name: '#document'
  71. }).parse(head);
  72. };
  73. var htmlToData = function (editor, head) {
  74. var headerFragment = parseHeader(head);
  75. var data = {};
  76. var elm, matches;
  77. function getAttr(elm, name) {
  78. var value = elm.attr(name);
  79. return value || '';
  80. }
  81. data.fontface = Settings.getDefaultFontFamily(editor);
  82. data.fontsize = Settings.getDefaultFontSize(editor);
  83. elm = headerFragment.firstChild;
  84. if (elm.type === 7) {
  85. data.xml_pi = true;
  86. matches = /encoding="([^"]+)"/.exec(elm.value);
  87. if (matches) {
  88. data.docencoding = matches[1];
  89. }
  90. }
  91. elm = headerFragment.getAll('#doctype')[0];
  92. if (elm) {
  93. data.doctype = '<!DOCTYPE' + elm.value + '>';
  94. }
  95. elm = headerFragment.getAll('title')[0];
  96. if (elm && elm.firstChild) {
  97. data.title = elm.firstChild.value;
  98. }
  99. global$1.each(headerFragment.getAll('meta'), function (meta) {
  100. var name = meta.attr('name');
  101. var httpEquiv = meta.attr('http-equiv');
  102. var matches;
  103. if (name) {
  104. data[name.toLowerCase()] = meta.attr('content');
  105. } else if (httpEquiv === 'Content-Type') {
  106. matches = /charset\s*=\s*(.*)\s*/gi.exec(meta.attr('content'));
  107. if (matches) {
  108. data.docencoding = matches[1];
  109. }
  110. }
  111. });
  112. elm = headerFragment.getAll('html')[0];
  113. if (elm) {
  114. data.langcode = getAttr(elm, 'lang') || getAttr(elm, 'xml:lang');
  115. }
  116. data.stylesheets = [];
  117. global$1.each(headerFragment.getAll('link'), function (link) {
  118. if (link.attr('rel') === 'stylesheet') {
  119. data.stylesheets.push(link.attr('href'));
  120. }
  121. });
  122. elm = headerFragment.getAll('body')[0];
  123. if (elm) {
  124. data.langdir = getAttr(elm, 'dir');
  125. data.style = getAttr(elm, 'style');
  126. data.visited_color = getAttr(elm, 'vlink');
  127. data.link_color = getAttr(elm, 'link');
  128. data.active_color = getAttr(elm, 'alink');
  129. }
  130. return data;
  131. };
  132. var dataToHtml = function (editor, data, head) {
  133. var headerFragment, headElement, html, elm, value;
  134. var dom = editor.dom;
  135. function setAttr(elm, name, value) {
  136. elm.attr(name, value ? value : undefined);
  137. }
  138. function addHeadNode(node) {
  139. if (headElement.firstChild) {
  140. headElement.insert(node, headElement.firstChild);
  141. } else {
  142. headElement.append(node);
  143. }
  144. }
  145. headerFragment = parseHeader(head);
  146. headElement = headerFragment.getAll('head')[0];
  147. if (!headElement) {
  148. elm = headerFragment.getAll('html')[0];
  149. headElement = new global$3('head', 1);
  150. if (elm.firstChild) {
  151. elm.insert(headElement, elm.firstChild, true);
  152. } else {
  153. elm.append(headElement);
  154. }
  155. }
  156. elm = headerFragment.firstChild;
  157. if (data.xml_pi) {
  158. value = 'version="1.0"';
  159. if (data.docencoding) {
  160. value += ' encoding="' + data.docencoding + '"';
  161. }
  162. if (elm.type !== 7) {
  163. elm = new global$3('xml', 7);
  164. headerFragment.insert(elm, headerFragment.firstChild, true);
  165. }
  166. elm.value = value;
  167. } else if (elm && elm.type === 7) {
  168. elm.remove();
  169. }
  170. elm = headerFragment.getAll('#doctype')[0];
  171. if (data.doctype) {
  172. if (!elm) {
  173. elm = new global$3('#doctype', 10);
  174. if (data.xml_pi) {
  175. headerFragment.insert(elm, headerFragment.firstChild);
  176. } else {
  177. addHeadNode(elm);
  178. }
  179. }
  180. elm.value = data.doctype.substring(9, data.doctype.length - 1);
  181. } else if (elm) {
  182. elm.remove();
  183. }
  184. elm = null;
  185. global$1.each(headerFragment.getAll('meta'), function (meta) {
  186. if (meta.attr('http-equiv') === 'Content-Type') {
  187. elm = meta;
  188. }
  189. });
  190. if (data.docencoding) {
  191. if (!elm) {
  192. elm = new global$3('meta', 1);
  193. elm.attr('http-equiv', 'Content-Type');
  194. elm.shortEnded = true;
  195. addHeadNode(elm);
  196. }
  197. elm.attr('content', 'text/html; charset=' + data.docencoding);
  198. } else if (elm) {
  199. elm.remove();
  200. }
  201. elm = headerFragment.getAll('title')[0];
  202. if (data.title) {
  203. if (!elm) {
  204. elm = new global$3('title', 1);
  205. addHeadNode(elm);
  206. } else {
  207. elm.empty();
  208. }
  209. elm.append(new global$3('#text', 3)).value = data.title;
  210. } else if (elm) {
  211. elm.remove();
  212. }
  213. global$1.each('keywords,description,author,copyright,robots'.split(','), function (name) {
  214. var nodes = headerFragment.getAll('meta');
  215. var i, meta;
  216. var value = data[name];
  217. for (i = 0; i < nodes.length; i++) {
  218. meta = nodes[i];
  219. if (meta.attr('name') === name) {
  220. if (value) {
  221. meta.attr('content', value);
  222. } else {
  223. meta.remove();
  224. }
  225. return;
  226. }
  227. }
  228. if (value) {
  229. elm = new global$3('meta', 1);
  230. elm.attr('name', name);
  231. elm.attr('content', value);
  232. elm.shortEnded = true;
  233. addHeadNode(elm);
  234. }
  235. });
  236. var currentStyleSheetsMap = {};
  237. global$1.each(headerFragment.getAll('link'), function (stylesheet) {
  238. if (stylesheet.attr('rel') === 'stylesheet') {
  239. currentStyleSheetsMap[stylesheet.attr('href')] = stylesheet;
  240. }
  241. });
  242. global$1.each(data.stylesheets, function (stylesheet) {
  243. if (!currentStyleSheetsMap[stylesheet]) {
  244. elm = new global$3('link', 1);
  245. elm.attr({
  246. rel: 'stylesheet',
  247. text: 'text/css',
  248. href: stylesheet
  249. });
  250. elm.shortEnded = true;
  251. addHeadNode(elm);
  252. }
  253. delete currentStyleSheetsMap[stylesheet];
  254. });
  255. global$1.each(currentStyleSheetsMap, function (stylesheet) {
  256. stylesheet.remove();
  257. });
  258. elm = headerFragment.getAll('body')[0];
  259. if (elm) {
  260. setAttr(elm, 'dir', data.langdir);
  261. setAttr(elm, 'style', data.style);
  262. setAttr(elm, 'vlink', data.visited_color);
  263. setAttr(elm, 'link', data.link_color);
  264. setAttr(elm, 'alink', data.active_color);
  265. dom.setAttribs(editor.getBody(), {
  266. style: data.style,
  267. dir: data.dir,
  268. vLink: data.visited_color,
  269. link: data.link_color,
  270. aLink: data.active_color
  271. });
  272. }
  273. elm = headerFragment.getAll('html')[0];
  274. if (elm) {
  275. setAttr(elm, 'lang', data.langcode);
  276. setAttr(elm, 'xml:lang', data.langcode);
  277. }
  278. if (!headElement.firstChild) {
  279. headElement.remove();
  280. }
  281. html = global$4({
  282. validate: false,
  283. indent: true,
  284. indent_before: 'head,html,body,meta,title,script,link,style',
  285. indent_after: 'head,html,body,meta,title,script,link,style'
  286. }).serialize(headerFragment);
  287. return html.substring(0, html.indexOf('</body>'));
  288. };
  289. var Parser = {
  290. parseHeader: parseHeader,
  291. htmlToData: htmlToData,
  292. dataToHtml: dataToHtml
  293. };
  294. var hasOwnProperty = Object.prototype.hasOwnProperty;
  295. var shallow = function (old, nu) {
  296. return nu;
  297. };
  298. var baseMerge = function (merger) {
  299. return function () {
  300. var objects = new Array(arguments.length);
  301. for (var i = 0; i < objects.length; i++) {
  302. objects[i] = arguments[i];
  303. }
  304. if (objects.length === 0) {
  305. throw new Error('Can\'t merge zero objects');
  306. }
  307. var ret = {};
  308. for (var j = 0; j < objects.length; j++) {
  309. var curObject = objects[j];
  310. for (var key in curObject) {
  311. if (hasOwnProperty.call(curObject, key)) {
  312. ret[key] = merger(ret[key], curObject[key]);
  313. }
  314. }
  315. }
  316. return ret;
  317. };
  318. };
  319. var merge = baseMerge(shallow);
  320. var open = function (editor, headState) {
  321. var data = Parser.htmlToData(editor, headState.get());
  322. var defaultData = {
  323. title: '',
  324. keywords: '',
  325. description: '',
  326. robots: '',
  327. author: '',
  328. docencoding: ''
  329. };
  330. var initialData = merge(defaultData, data);
  331. editor.windowManager.open({
  332. title: 'Metadata and Document Properties',
  333. size: 'normal',
  334. body: {
  335. type: 'panel',
  336. items: [
  337. {
  338. name: 'title',
  339. type: 'input',
  340. label: 'Title'
  341. },
  342. {
  343. name: 'keywords',
  344. type: 'input',
  345. label: 'Keywords'
  346. },
  347. {
  348. name: 'description',
  349. type: 'input',
  350. label: 'Description'
  351. },
  352. {
  353. name: 'robots',
  354. type: 'input',
  355. label: 'Robots'
  356. },
  357. {
  358. name: 'author',
  359. type: 'input',
  360. label: 'Author'
  361. },
  362. {
  363. name: 'docencoding',
  364. type: 'input',
  365. label: 'Encoding'
  366. }
  367. ]
  368. },
  369. buttons: [
  370. {
  371. type: 'cancel',
  372. name: 'cancel',
  373. text: 'Cancel'
  374. },
  375. {
  376. type: 'submit',
  377. name: 'save',
  378. text: 'Save',
  379. primary: true
  380. }
  381. ],
  382. initialData: initialData,
  383. onSubmit: function (api) {
  384. var nuData = api.getData();
  385. var headHtml = Parser.dataToHtml(editor, global$1.extend(data, nuData), headState.get());
  386. headState.set(headHtml);
  387. api.close();
  388. }
  389. });
  390. };
  391. var Dialog = { open: open };
  392. var register = function (editor, headState) {
  393. editor.addCommand('mceFullPageProperties', function () {
  394. Dialog.open(editor, headState);
  395. });
  396. };
  397. var Commands = { register: register };
  398. var protectHtml = function (protect, html) {
  399. global$1.each(protect, function (pattern) {
  400. html = html.replace(pattern, function (str) {
  401. return '<!--mce:protected ' + escape(str) + '-->';
  402. });
  403. });
  404. return html;
  405. };
  406. var unprotectHtml = function (html) {
  407. return html.replace(/<!--mce:protected ([\s\S]*?)-->/g, function (a, m) {
  408. return unescape(m);
  409. });
  410. };
  411. var Protect = {
  412. protectHtml: protectHtml,
  413. unprotectHtml: unprotectHtml
  414. };
  415. var each = global$1.each;
  416. var low = function (s) {
  417. return s.replace(/<\/?[A-Z]+/g, function (a) {
  418. return a.toLowerCase();
  419. });
  420. };
  421. var handleSetContent = function (editor, headState, footState, evt) {
  422. var startPos, endPos, content, headerFragment, styles = '';
  423. var dom = editor.dom;
  424. if (evt.selection) {
  425. return;
  426. }
  427. content = Protect.protectHtml(editor.settings.protect, evt.content);
  428. if (evt.format === 'raw' && headState.get()) {
  429. return;
  430. }
  431. if (evt.source_view && Settings.shouldHideInSourceView(editor)) {
  432. return;
  433. }
  434. if (content.length === 0 && !evt.source_view) {
  435. content = global$1.trim(headState.get()) + '\n' + global$1.trim(content) + '\n' + global$1.trim(footState.get());
  436. }
  437. content = content.replace(/<(\/?)BODY/gi, '<$1body');
  438. startPos = content.indexOf('<body');
  439. if (startPos !== -1) {
  440. startPos = content.indexOf('>', startPos);
  441. headState.set(low(content.substring(0, startPos + 1)));
  442. endPos = content.indexOf('</body', startPos);
  443. if (endPos === -1) {
  444. endPos = content.length;
  445. }
  446. evt.content = global$1.trim(content.substring(startPos + 1, endPos));
  447. footState.set(low(content.substring(endPos)));
  448. } else {
  449. headState.set(getDefaultHeader(editor));
  450. footState.set('\n</body>\n</html>');
  451. }
  452. headerFragment = Parser.parseHeader(headState.get());
  453. each(headerFragment.getAll('style'), function (node) {
  454. if (node.firstChild) {
  455. styles += node.firstChild.value;
  456. }
  457. });
  458. var bodyElm = headerFragment.getAll('body')[0];
  459. if (bodyElm) {
  460. dom.setAttribs(editor.getBody(), {
  461. style: bodyElm.attr('style') || '',
  462. dir: bodyElm.attr('dir') || '',
  463. vLink: bodyElm.attr('vlink') || '',
  464. link: bodyElm.attr('link') || '',
  465. aLink: bodyElm.attr('alink') || ''
  466. });
  467. }
  468. dom.remove('fullpage_styles');
  469. var headElm = editor.getDoc().getElementsByTagName('head')[0];
  470. if (styles) {
  471. var styleElm = dom.add(headElm, 'style', { id: 'fullpage_styles' });
  472. styleElm.appendChild(domGlobals.document.createTextNode(styles));
  473. }
  474. var currentStyleSheetsMap = {};
  475. global$1.each(headElm.getElementsByTagName('link'), function (stylesheet) {
  476. if (stylesheet.rel === 'stylesheet' && stylesheet.getAttribute('data-mce-fullpage')) {
  477. currentStyleSheetsMap[stylesheet.href] = stylesheet;
  478. }
  479. });
  480. global$1.each(headerFragment.getAll('link'), function (stylesheet) {
  481. var href = stylesheet.attr('href');
  482. if (!href) {
  483. return true;
  484. }
  485. if (!currentStyleSheetsMap[href] && stylesheet.attr('rel') === 'stylesheet') {
  486. dom.add(headElm, 'link', {
  487. 'rel': 'stylesheet',
  488. 'text': 'text/css',
  489. 'href': href,
  490. 'data-mce-fullpage': '1'
  491. });
  492. }
  493. delete currentStyleSheetsMap[href];
  494. });
  495. global$1.each(currentStyleSheetsMap, function (stylesheet) {
  496. stylesheet.parentNode.removeChild(stylesheet);
  497. });
  498. };
  499. var getDefaultHeader = function (editor) {
  500. var header = '', value, styles = '';
  501. if (Settings.getDefaultXmlPi(editor)) {
  502. var piEncoding = Settings.getDefaultEncoding(editor);
  503. header += '<?xml version="1.0" encoding="' + (piEncoding ? piEncoding : 'ISO-8859-1') + '" ?>\n';
  504. }
  505. header += Settings.getDefaultDocType(editor);
  506. header += '\n<html>\n<head>\n';
  507. if (value = Settings.getDefaultTitle(editor)) {
  508. header += '<title>' + value + '</title>\n';
  509. }
  510. if (value = Settings.getDefaultEncoding(editor)) {
  511. header += '<meta http-equiv="Content-Type" content="text/html; charset=' + value + '" />\n';
  512. }
  513. if (value = Settings.getDefaultFontFamily(editor)) {
  514. styles += 'font-family: ' + value + ';';
  515. }
  516. if (value = Settings.getDefaultFontSize(editor)) {
  517. styles += 'font-size: ' + value + ';';
  518. }
  519. if (value = Settings.getDefaultTextColor(editor)) {
  520. styles += 'color: ' + value + ';';
  521. }
  522. header += '</head>\n<body' + (styles ? ' style="' + styles + '"' : '') + '>\n';
  523. return header;
  524. };
  525. var handleGetContent = function (editor, head, foot, evt) {
  526. if (!evt.selection && (!evt.source_view || !Settings.shouldHideInSourceView(editor))) {
  527. evt.content = Protect.unprotectHtml(global$1.trim(head) + '\n' + global$1.trim(evt.content) + '\n' + global$1.trim(foot));
  528. }
  529. };
  530. var setup = function (editor, headState, footState) {
  531. editor.on('BeforeSetContent', function (evt) {
  532. handleSetContent(editor, headState, footState, evt);
  533. });
  534. editor.on('GetContent', function (evt) {
  535. handleGetContent(editor, headState.get(), footState.get(), evt);
  536. });
  537. };
  538. var FilterContent = { setup: setup };
  539. var register$1 = function (editor) {
  540. editor.ui.registry.addButton('fullpage', {
  541. tooltip: 'Metadata and document properties',
  542. icon: 'document-properties',
  543. onAction: function () {
  544. editor.execCommand('mceFullPageProperties');
  545. }
  546. });
  547. editor.ui.registry.addMenuItem('fullpage', {
  548. text: 'Metadata and document properties',
  549. icon: 'document-properties',
  550. onAction: function () {
  551. editor.execCommand('mceFullPageProperties');
  552. }
  553. });
  554. };
  555. var Buttons = { register: register$1 };
  556. function Plugin () {
  557. global.add('fullpage', function (editor) {
  558. var headState = Cell(''), footState = Cell('');
  559. Commands.register(editor, headState);
  560. Buttons.register(editor);
  561. FilterContent.setup(editor, headState, footState);
  562. });
  563. }
  564. Plugin();
  565. }(window));