config.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. // jQuery not really required, it's here to overcome an inability to pass configuration options to the fiddle remotely
  2. // Custom example logic
  3. /**
  4. * TinyMCE Load
  5. */
  6. if (typeof (tinyMCE) == "undefined") {
  7. console.log('Requires TinyMCE.');
  8. }
  9. let isMobile = !tinymce.Env.desktop; // (tinymce.Env.phone || tinymce.Env.tablet || false);
  10. let pluploadHandler,
  11. menubar,
  12. plugins,
  13. toolbar,
  14. cssText;
  15. cssText = "div.emoticon-group {margin:0; padding:0;}";
  16. cssText += "div.emoticon-group > A.emoticon-group-item {margin:0;padding:0 9px 0 11px; float:left;width:60px;height:60px; list-style:none;cursor:pointer;}";
  17. cssText += "div.emoticon-group > A.emoticon-group-item > IMG.emoticon-group-item-icon {max-width:100%;max-height:100%;}";
  18. cssText += "div.mce-path > .mce-path-item {display:none !important;}";
  19. cssText += "@media all and (max-width: 480px;) {";
  20. cssText += "ul.emoticon-group > li.emoticon-group-item {width:32px;}";
  21. cssText += "}";
  22. if(isMobile) {
  23. cssText += "@media all and (max-width: 480px;){";
  24. cssText += ".tinycme-full .mce-edit-area {display:flex;flex-flow:column;}";
  25. cssText += ".tinycme-full .mce-edit-area iframe {flex:1 1 auto;}";
  26. cssText += ".tinycme-full {height:100%;}";
  27. cssText += ".tinycme-full .mce-tinymce.mce-container {width:100%;height:100%;/*border:0;*/}";
  28. cssText += ".tinycme-full .mce-panel{/*border:0;*/}";
  29. cssText += ".tinycme-full .mce-container-body.mce-stack-layout {display: flex; flex-flow: column;height: 100%;}";
  30. cssText += ".tinycme-full .mce-stack-layout-item{ flex: 0 0 auto;}";
  31. cssText += ".tinycme-full .mce-edit-area{flex:1 1 auto;}";
  32. cssText += ".tinycme-full .mce-window .mce-btn.mce-colorbutton {display:none}";
  33. cssText += ".tinycme-full .mce-window-head .mce-dragh {cursor:default !important;} }";
  34. }
  35. // Viewport Add HTML5기반 (IE 10, Chrome 9, Opera 12.1, Safari 5.1, Firefox 6.0
  36. if (!$('head > meta[name="viewport"]:last').length) {
  37. $('<meta name="viewport" content="width=device-width, initial-scale=1.0" />').append('head:last');
  38. }
  39. $('<style>').prop("type", "text/css").attr({'id': 'tinymce-css-responsive'}).html(cssText).appendTo('head:last');
  40. $.ajaxSetup({'async': false});
  41. // 허용 iframe 주소 기본값
  42. if(typeof whiteIframe == "undefined" || whiteIframe == null || whiteIframe == "") {
  43. var whiteIframe = ['www.youtube.com', 'www.youtube-nocookie.com', 'maps.google.co.kr',
  44. 'maps.google.com', 'flvs.daum.net', 'player.vimeo.com', 'sbsplayer.sbs.co.kr',
  45. 'serviceapi.rmcnmv.naver.com', 'serviceapi.nmv.naver.com', 'www.mgoon.com',
  46. 'videofarm.daum.net', 'player.sbs.co.kr', 'sbsplayer.sbs.co.kr', 'www.tagstory.com',
  47. 'play.tagstory.com', 'flvr.pandora.tv', 'youtu.be', 'vimeo.com', 'dailymotion.com', 'tvpot.daum.net'];
  48. }
  49. // 이모티콘 사용여부
  50. if(typeof useEmoticon == "undefined" || useEmoticon == null || useEmoticon === "") {
  51. var useEmoticon = 'N';
  52. }
  53. // 관리자와 사용자 구분하여 기능 구분
  54. if(window.location.href.indexOf('admin') <= -1) // 사용자
  55. {
  56. // autoresize라는 플러그인도 있음.
  57. menubar = false;
  58. plugins = 'advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker searchreplace visualblocks visualchars code fullscreen insertdatetime media nonbreaking save table contextmenu directionality emoticons template paste textcolor codesample';
  59. toolbar = 'fontselect | fontStyle | fontsizeselect | bold underline strikethrough forecolor backcolor alignleft aligncenter alignright alignjustify | link imageUpload media | code | codesample';
  60. } else { // 관리자
  61. // wordcount 제외시킴
  62. menubar = true;
  63. plugins = 'print preview searchreplace autolink directionality visualblocks visualchars fullscreen link media template codesample table charmap hr pagebreak nonbreaking anchor toc insertdatetime advlist lists textcolor imagetools contextmenu colorpicker textpattern help code codesample';
  64. toolbar = 'formatselect | bold italic strikethrough forecolor backcolor | alignleft aligncenter alignright alignjustify | numlist bullist outdent indent | removeformat | link imageUpload media | code | codesample';
  65. }
  66. // Placeholder 기능 추가
  67. let label = {
  68. init: function(e) {
  69. let placeholderText = (e.getElement().getAttribute("placeholoer") || e.settings.placeholder);
  70. let placeholderAttrs = (e.settings.placeholderAttrs || {
  71. style: {
  72. position: 'absolute',
  73. top: '2px',
  74. left: 0,
  75. color: '#959595',
  76. padding: '.25%',
  77. margin: '5px',
  78. width: 'auto',
  79. 'font-size': '17px !important;',
  80. overflow: 'hidden',
  81. 'line-height': '24px',
  82. 'white-space': 'pre-wrap'
  83. }
  84. });
  85. let contentAreaContainer = e.getContentAreaContainer();
  86. tinymce.DOM.setStyle(contentAreaContainer, 'position', 'relative');
  87. this.el = tinymce.DOM.add(contentAreaContainer, "label", placeholderAttrs, placeholderText);
  88. },
  89. focus: function(e) {
  90. if (!e.settings.readonly === true) {
  91. this.hide();
  92. }
  93. e.execCommand('mceFocus', false);
  94. },
  95. blur: function(e) {
  96. if (e.getContent() == '') {
  97. this.show();
  98. } else {
  99. this.hide();
  100. }
  101. },
  102. hide: function() {
  103. tinymce.DOM.setStyle(this.el, 'display', 'none');
  104. },
  105. show: function() {
  106. tinymce.DOM.setStyle(this.el, 'display', '');
  107. }
  108. };
  109. let setting = {
  110. selector: '.tinymce-editor',
  111. language: 'ko_KR',
  112. menubar: menubar,
  113. theme: 'modern',
  114. skin: 'default',
  115. plugins: plugins,
  116. width: 'auto',
  117. inline: false, // <br data-mce-bogus="1"> 삭제
  118. forced_root_block: "", // p tag 비 활성화
  119. min_height: 400, // 에디터 세로크기
  120. // autoresize_min_height: 400, // 에디터 최소 세로크기
  121. // autoresize_bottom_margin: 0,
  122. // autoresize_on_init: true,
  123. content_style:
  124. "body {font-family: Nanum-Square-Neo-Regular;}",
  125. font_formats: "Default='나눔스퀘어'" +
  126. ";나눔스퀘어=Nanum-Square-Neo-Regular, 나눔스퀘어" +
  127. ";돋움=dotum, 돋움" +
  128. ";굴림=gulim, 굴림" +
  129. ";바탕=batang, 바탕" +
  130. ";궁서=gungsuh, 궁서" +
  131. ";맑은 고딕=malgun gothic, 맑은 고딕" +
  132. ";Arial=arial,helvetica, verdana, sans-serif" +
  133. ";Andale Mono=andale mono, monospace" +
  134. ";Tahoma=tahoma,arial, helvetica, sans-serif" +
  135. ";Verdana=verdana, geneva, sans-serif",
  136. codesample_languages: [
  137. {text: "HTML/XML", value: "markup"},
  138. {text: "JavaScript", value: "javascript"},
  139. {text: "CSS", value: "css"},
  140. {text: "PHP", value: "php"},
  141. {text: "Ruby", value: "ruby"},
  142. {text: "Python", value: "python"},
  143. {text: "Java", value: "java"},
  144. {text: "C", value: "c"},
  145. {text: "C#", value: "csharp"},
  146. {text: "C++", value: "cpp"},
  147. {text: "scss", value: "scss"},
  148. {text: "SQL", value: "sql"},
  149. {text: "YAML", value: "yaml"},
  150. {text: "nginx", value: "nginx"},
  151. {text: "JSON", value: "json"},
  152. {text: "go", value: "go"},
  153. {text: "Bash", value: "bash"},
  154. {text: "Shell", value: "shell-session"}
  155. ],
  156. branding: false, // 아래 `POWERED BY TINYMCE` 표시 삭제
  157. toolbar: toolbar,
  158. fontsize_formats: '8pt 10pt 12pt 14pt 18pt 24pt 36pt',
  159. statusbar: true, // 하단 단어 개수, p 태그 지정 상태창 숨김
  160. directionality: 'ltr',
  161. visualblocks_default_state: false, // 선택 영역 지정
  162. // 링크 설정
  163. link_title: false,
  164. target_list: false,
  165. default_link_target: "_blank",
  166. // 이미지 설정
  167. image_dimensions: false, // 이미지 크기
  168. file_browser_url: "/editor/uploader", // 라우터에 의한 이동
  169. image_advtab: true,
  170. paste_data_images: true,
  171. paste_postprocess: (editor, args) => {
  172. let images = args.node.getElementsByTagName("img");
  173. if(images.length > 0) {
  174. for(let img of images) {
  175. img.dataset.target = "thumb";
  176. }
  177. }
  178. },
  179. // 이미지 크기 조정
  180. elementpath: false,
  181. relative_urls: true,
  182. remove_script_host: true,
  183. // 비디오 설정
  184. media_poster: false, // 포스터 파일
  185. media_alt_source: false, // 대체 소스
  186. media_dimensions: false,
  187. media_filter_html: true,
  188. media_allow_host: whiteIframe
  189. , 'media_url_patterns' : [
  190. {regex : /videofarm\.daum\.net\/controller\/video\/viewer\/Video\.html\?vid=([^&]+)/, type: 'iframe', w: 640, h: 360, url: '//videofarm.daum.net/controller/video/viewer/Video.html?vid=$1&play_loc=undefined&alert=true', allowFullscreen: true}
  191. ]
  192. , 'media_pattern_types' : [
  193. {'regex' : /^(http[s]?:)?\/\/videofarm\.daum\.net/, 'type' : 'iframe' , 'allowFullscreen' : true, 'width': 640, 'height': 360}
  194. ],
  195. extended_valid_elements : "iframe[src|title|width|height|allowfullscreen|frameborder]",
  196. init_instance_callback: function (editor) {
  197. label.init(editor);
  198. label.blur(editor);
  199. tinymce.DOM.bind(label.el, 'click', label.focus(editor));
  200. },
  201. setup: function (editor) {
  202. editor.on('submit', function (e) {
  203. console.log("Editor submited !!");
  204. });
  205. editor.addButton('imageUpload', {
  206. type: 'button',
  207. title: 'Insert/edit image',
  208. icon: 'image',
  209. id: 'imageUpload',
  210. stateSelector: 'img:not([data-mce-object],[data-mce-placeholder],[data-smileys]),figure.image',
  211. onclick: function () {
  212. let dialog = editor.windowManager.open({
  213. title: "사진 첨부하기",
  214. width: 800,
  215. height: 415,
  216. scrollbars: false,
  217. labelby: "imageUpload",
  218. resizable: true,
  219. file: editor.settings.file_browser_url,
  220. close_previous: true,
  221. maximizable: true,
  222. inline: true,
  223. buttons: [{
  224. text: "확인",
  225. subtype: "primary",
  226. onclick: function (e) {
  227. // pluploadHandler.startUploader(this);
  228. pluploadHandler.insertContent();
  229. }
  230. }, {
  231. text: "닫기",
  232. onclick: function (e) {
  233. pluploadHandler.stopUploader();
  234. }
  235. }]
  236. });
  237. pluploadSetting(dialog);
  238. }
  239. });
  240. // 초기 설정
  241. editor.on('init', function(e) {
  242. if(useEmoticon === 'Y') {
  243. let div = $('<div class="mce-container mce-panel mce-stack-layout-item">');
  244. $(e.getContentAreaContainer()).after(div); // 에디터 하단에 새로운 영역 생성
  245. setEmoticons(e, div);
  246. selfAutoResize();
  247. (typeof (selfAutoResize) == 'function' && e.on("nodechange setcontent keyup FullscreenStateChanged", selfAutoResize));
  248. console.log("editor emoticon loaded");
  249. }
  250. });
  251. // 팝업 창 오픈
  252. editor.on("OpenWindow", function(arg) {
  253. let hWin = arg.win;
  254. hWin.settings.maxWidth = (hWin.settings.maxWidth || hWin.settings.width || hWin._layoutRect.w);
  255. hWin.settings.maxHeight = (hWin.settings.maxHeight || hWin.settings.height || hWin._layoutRect.h);
  256. $(window).off('resize', windowDialogResize).on('resize', windowDialogResize);
  257. setTimeout(windowDialogResize, 100);
  258. });
  259. // 팝업창 닫기
  260. editor.on("CloseWindow", function() {
  261. $(".wrap-loading").hide();
  262. $(window).off('resize', windowDialogResize);
  263. });
  264. // 에디터 자동저장
  265. editor.on('change', function(e) {
  266. e.target.save();
  267. label.blur(editor);
  268. });
  269. editor.on('focus', function(e) {
  270. label.focus(editor);
  271. });
  272. editor.on('blur', function(e) {
  273. label.blur(editor);
  274. });
  275. editor.on('setContent', function(e) {
  276. // iframe에 입력되는 주소는 hidden으로 저장한다.
  277. let iframeRows = e.target.contentDocument.body.getElementsByTagName("iframe");
  278. if(iframeRows > 0) {
  279. $(e.content).filter("iframe").attr("src", ""); // iframe src 주소
  280. }
  281. label.blur(editor);
  282. });
  283. }
  284. };
  285. tinymce.init(setting);
  286. // 파일 업로드 환경설정
  287. function pluploadSetting(dialog)
  288. {
  289. let iframe = $(dialog.$el).find(".mce-window-body iframe").get(0);
  290. $(iframe).on("load", function () {
  291. let window = this.contentWindow || this;
  292. let document = this.contentDocument || win.document;
  293. pluploadHandler = window;
  294. // 전체삭제 버튼 클릭
  295. $(document).on("click", "div#remove-all", function () {
  296. pluploadHandler.removeThumb();
  297. });
  298. // 개별 삭제 버튼 클릭
  299. $(document).on("click", ".picker-image-remove", function () {
  300. pluploadHandler.removeThumb($(this));
  301. });
  302. // 제대로 로딩이 안되면 팝업 닫기
  303. if(document.firstElementChild.querySelectorAll(".picker-container").length <= 0) {
  304. parent.tinyMCE.activeEditor.windowManager.close(window);
  305. }
  306. console.log('Upload View Loaded.');
  307. });
  308. }
  309. // 에디터 팝업창 크기 자동조절
  310. function windowDialogResize()
  311. {
  312. let editor = tinyMCE.activeEditor;
  313. let hWin = editor.windowManager.getWindows()[0];
  314. if(typeof hWin == 'undefined') {
  315. return;
  316. }
  317. let dialog = $(hWin.$el);
  318. let currentWidth = dialog.width();
  319. let innerWidth = $(window).innerWidth();
  320. let innerHeight = $(window).innerHeight();
  321. let maxWidth = hWin.settings.maxWidth;
  322. let maxHeight = hWin.settings.maxHeight;
  323. let resizeWidth = Math.max(Math.min(innerWidth, maxWidth), 240);
  324. let resizeHeight = Math.max(Math.min(innerHeight, maxHeight), 200);
  325. hWin.resizeTo(resizeWidth, resizeHeight);
  326. let posX = Math.floor((innerWidth / 2) - (dialog.width() / 2)) - 1;
  327. let posY = Math.floor((innerHeight / 2) - (dialog.height() / 2));
  328. if(isMobile) {
  329. posY = innerHeight - (dialog.height() + 2);
  330. }
  331. hWin.moveTo(posX, posY);
  332. setTimeout(function () {
  333. var dw = (currentWidth - 40) - (dialog.width() - 40);
  334. dialog.find('.' + hWin.bodyClasses.prefix + 'textbox').each(function () {
  335. var el = $(this);
  336. el.width(el.width() - dw);
  337. });
  338. }, 0);
  339. }
  340. // 에디터 알림 메시지
  341. function message(message, severity, args)
  342. {
  343. let setting = $.extend({'timeout': 3000, 'closeButton': true, 'icon': (severity || 'notice').replace('error', 'notice')}, args, {'text': message, 'type': severity || 'info'});
  344. tinymce.activeEditor.notificationManager.open(setting);
  345. }
  346. // 팝업 알림 메시지
  347. function popMessage(message)
  348. {
  349. tinymce.activeEditor.windowManager.alert(message);
  350. }
  351. let selfResizeObj;
  352. let selfIframe;
  353. let wrapper = $("div#wrapper");
  354. let oldHeight = wrapper.height();
  355. // 크기 자동조정 기능정의
  356. function resizeDone(wait)
  357. {
  358. clearTimeout(selfResizeObj);
  359. selfResizeObj = null;
  360. if (!selfIframe) {
  361. $(parent.document).find("iframe").each(function () {
  362. return ((this.contentWindow == window) ? (selfIframe = $(this), false) : void 0);
  363. });
  364. if (!selfIframe) {
  365. return true;
  366. }
  367. }
  368. try {
  369. let curHeight;
  370. (curHeight = wrapper.height()) != oldHeight && (selfIframe.height(curHeight), oldHeight = curHeight);
  371. } catch (err) {
  372. alert(err);
  373. }
  374. if (wait > 0) {
  375. selfResizeObj = setTimeout(function () {
  376. resizeDone(--wait);
  377. }, 10);
  378. }
  379. }
  380. // 현재 에디터 크기 자동조정
  381. function selfAutoResize(wait)
  382. {
  383. if(!window.parent) {
  384. return true;
  385. }
  386. // tinymce.activeEditor.execCommand('mceAutoResize', false);
  387. wait = (wait || 1);
  388. selfResizeObj = setTimeout(function() {
  389. resizeDone(--wait);
  390. }, 10);
  391. }
  392. // 이모티콘 설정
  393. function setEmoticons(editor, container)
  394. {
  395. // 현재 창이 관리자 모드 라면 취소
  396. let adminUrl = document.location.href;
  397. if(adminUrl.indexOf("admin") != -1) {
  398. return false;
  399. }
  400. // 이모티콘 로드
  401. let emoticons= new Array();
  402. $.get(tinyMCE.baseURL + '/emoticon.xml', function(data) {
  403. if(typeof data == 'string'){
  404. data = $.parseXML(data);
  405. }
  406. emoticons = $.map(data.documentElement.getElementsByTagName('emoticon'), function(emoticon) {
  407. let s = {};
  408. $.each(emoticon.attributes, function() {
  409. if($.trim(this.nodeValue))
  410. s[this.nodeName] = this.nodeValue;
  411. });
  412. return s;
  413. });
  414. });
  415. container.css({'background-color': '#FFFFFF', 'border-width': '1px 0 0 0'});
  416. let group = $('<DIV />').addClass('emoticon-group').appendTo(container);
  417. $.each(emoticons, function(index) {
  418. $('<img />').attr(this).addClass('emoticon-group-item-icon').appendTo($('<a href="#" onclick="return false;" class="emoticon-group-item"></a>').appendTo(group)).on('click', function(event) {
  419. event.preventDefault();
  420. editor.insertContent('<img src="' + $(this).attr('src') + '" align="middle" data-mce-resize="false" alt="이모티콘_' + index + ' "/>');
  421. });
  422. });
  423. group.append($('<div />').css({'display': 'block', 'width': '100%', 'height': '1px', 'clear': 'both', 'float': 'none'}));
  424. }
  425. // 업로드 이미지 번호 저장
  426. function setImageID(imageID) {
  427. let editor = tinyMCE.activeEditor;
  428. if (typeof imageID == "undefined" || imageID == null) {
  429. message("해당 사진을 올릴 수 없습니다.");
  430. return false;
  431. } else {
  432. let div = $('<input type="hidden" name="imageID" value="' + imageID + '"/>');
  433. $(editor.getContentAreaContainer()).after(div); // 에디터 하단에 새로운 영역 생성
  434. }
  435. }