2
0

render.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. "use strict";
  2. // https://css-tricks.com/how-to-animate-the-details-element/
  3. class Accordion {
  4. constructor(el) {
  5. this.el = el;
  6. this.summary = el.querySelector('summary');
  7. this.content = el.querySelector('.opennamu_folding');
  8. this.animation = null;
  9. this.isClosing = false;
  10. this.isExpanding = false;
  11. this.summary.addEventListener('click', (e) => this.onClick(e));
  12. }
  13. onClick(e) {
  14. e.preventDefault();
  15. this.el.style.overflow = 'hidden';
  16. if(this.isClosing || !this.el.open) {
  17. this.open();
  18. } else if(this.isExpanding || this.el.open) {
  19. this.shrink();
  20. }
  21. }
  22. shrink() {
  23. this.isClosing = true;
  24. const startHeight = `${this.el.offsetHeight}px`;
  25. const endHeight = `${this.summary.offsetHeight}px`;
  26. if(this.animation) {
  27. this.animation.cancel();
  28. }
  29. this.animation = this.el.animate({
  30. height: [startHeight, endHeight]
  31. }, {
  32. duration: 200,
  33. easing: 'ease-out'
  34. });
  35. this.animation.onfinish = () => this.onAnimationFinish(false);
  36. this.animation.oncancel = () => this.isClosing = false;
  37. }
  38. open() {
  39. this.el.style.height = `${this.el.offsetHeight}px`;
  40. this.el.open = true;
  41. window.requestAnimationFrame(() => this.expand());
  42. }
  43. expand() {
  44. this.isExpanding = true;
  45. const startHeight = `${this.el.offsetHeight}px`;
  46. const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
  47. if(this.animation) {
  48. this.animation.cancel();
  49. }
  50. this.animation = this.el.animate({
  51. height: [startHeight, endHeight]
  52. }, {
  53. duration: 200,
  54. easing: 'ease-out'
  55. });
  56. this.animation.onfinish = () => this.onAnimationFinish(true);
  57. this.animation.oncancel = () => this.isExpanding = false;
  58. }
  59. onAnimationFinish(open) {
  60. this.el.open = open;
  61. this.animation = null;
  62. this.isClosing = false;
  63. this.isExpanding = false;
  64. this.el.style.height = this.el.style.overflow = '';
  65. }
  66. }
  67. function opennamu_heading_folding(data, element = '') {
  68. let fol = document.getElementById(data);
  69. if(fol.style.display === '' || fol.style.display === 'inline-block' || fol.style.display === 'block') {
  70. document.getElementById(data).style.display = 'none';
  71. document.getElementById(data + '_sub').style.opacity = '0.5';
  72. } else {
  73. document.getElementById(data).style.display = 'block';
  74. document.getElementById(data + '_sub').style.opacity = '1';
  75. }
  76. if(element !== '') {
  77. if(element.innerHTML !== '⊖') {
  78. element.innerHTML = '⊖';
  79. } else {
  80. element.innerHTML = '⊕';
  81. }
  82. }
  83. }
  84. function opennamu_do_render_html(name = '') {
  85. if(document.getElementById(name)) {
  86. let data = document.getElementById(name).innerHTML;
  87. let src_list = ['www.youtube.com', 'www.google.com', 'play-tv.kakao.com'];
  88. let t_data = [
  89. 'b', 'i', 's', 'del', 'strong', 'bold', 'em', 'sub', 'sup',
  90. 'div', 'span',
  91. 'a',
  92. 'iframe'
  93. ];
  94. for(let key in t_data) {
  95. let patt = new RegExp(
  96. '<' + t_data[key] + '( (?:(?:(?!>).)+))?>((?:(?!<\/' + t_data[key] + '>).)*)(<\/' + t_data[key] + '>|$)',
  97. 'ig'
  98. );
  99. data = data.replace(patt, function(full, in_data, in_data_2) {
  100. if(['b', 'i', 's', 'del', 'strong', 'bold', 'em', 'sub', 'sup'].includes(t_data[key])) {
  101. return '<' + t_data[key] + '>' + in_data_2 + '</' + t_data[key] + '>'
  102. } else if(t_data[key] === 'div' || t_data[key] === 'span') {
  103. let style_data = in_data?.match(/ style=['"]([^'"]*)['"]/);
  104. if(style_data) {
  105. style_data = style_data[1];
  106. const find_regex = [
  107. / *box-shadow *: *(([^,;]*)(,|;|$)){10,}/i,
  108. / *url\([^()]*\)/i,
  109. / *linear-gradient\((([^(),]+)(,|\))){10,}/i,
  110. / *position *: */i
  111. ];
  112. for(let regex of find_regex) {
  113. if(regex.test(style_data)) {
  114. style_data = "";
  115. break;
  116. }
  117. }
  118. } else {
  119. style_data = '';
  120. }
  121. return '<' + t_data[key] + ' style="' + style_data + '">' + in_data_2 + '</' + t_data[key] + '>';
  122. } else if(t_data[key] === 'a') {
  123. let link_data = in_data?.match(/ href=['"]([^'"]*)['"]/);
  124. if(link_data) {
  125. link_data = link_data[1].replace(/^javascript:/ig, '');
  126. } else {
  127. link_data = '';
  128. }
  129. return '<' + t_data[key] + ' class="opennamu_link_out" href="' + link_data + '">' + in_data_2 + '</' + t_data[key] + '>';
  130. } else if(t_data[key] === 'iframe') {
  131. let src_data = in_data?.match(/ src=['"]([^'"]*)['"]/);
  132. if(src_data) {
  133. src_data = src_data[1];
  134. let src_check = src_data.match(/^http(?:s)?:\/\/([^/]+)/);
  135. if(src_check) {
  136. if(!src_list.includes(src_check[1])) {
  137. src_data = '';
  138. }
  139. } else {
  140. src_data = '';
  141. }
  142. } else {
  143. src_data = '';
  144. }
  145. let width_data = in_data?.match(/ width=['"]([^'"]*)['"]/);
  146. if(width_data) {
  147. width_data = width_data[1];
  148. } else {
  149. width_data = '';
  150. }
  151. let height_data = in_data?.match(/ height=['"]([^'"]*)['"]/);
  152. if(height_data) {
  153. height_data = height_data[1];
  154. } else {
  155. height_data = '';
  156. }
  157. return '<' + t_data[key] + ' src="' + src_data + '" width="' + width_data + '" height="' + height_data + '" allowfullscreen frameborder="0">' + in_data_2 + '</' + t_data[key] + '>';
  158. } else {
  159. let src_data = in_data?.match(/ src=['"]([^'"]*)['"]/);
  160. if(src_data) {
  161. src_data = src_data[1];
  162. } else {
  163. src_data = '';
  164. }
  165. let width_data = in_data?.match(/ width=['"]([^'"]*)['"]/);
  166. if(width_data) {
  167. width_data = width_data[1];
  168. } else {
  169. width_data = '';
  170. }
  171. let height_data = in_data?.match(/ height=['"]([^'"]*)['"]/);
  172. if(height_data) {
  173. height_data = height_data[1];
  174. } else {
  175. height_data = '';
  176. }
  177. return '<' + t_data[key] + ' controls src="' + src_data + '" width="' + width_data + '" height="' + height_data + '">' + in_data_2 + '</' + t_data[key] + '>';
  178. }
  179. });
  180. }
  181. document.getElementById(name).innerHTML = data;
  182. }
  183. }
  184. function opennamu_do_footnote_spread(set_name, load_name) {
  185. if(document.getElementById(set_name + '_load').style.display === 'none') {
  186. document.getElementById(set_name).title = '';
  187. document.getElementById(set_name + '_load').innerHTML = '<a href="#' + load_name + '">(Go)</a> ' + document.getElementById(load_name + '_title').innerHTML;
  188. document.getElementById(set_name + '_load').style.display = "inline-block";
  189. } else {
  190. document.getElementById(set_name + '_load').style.display = "none";
  191. }
  192. }
  193. function opennamu_do_footnote_popover(set_name, load_name, sub_obj = undefined, do_type = 'open') {
  194. if(document.getElementById(set_name + '_load')) {
  195. if(do_type === 'open') {
  196. if(sub_obj !== undefined) {
  197. document.getElementById(set_name + '_load').innerHTML = document.getElementById(sub_obj).innerHTML;
  198. } else {
  199. document.getElementById(set_name).title = '';
  200. document.getElementById(set_name + '_load').innerHTML = '<a href="#' + load_name + '">(Go)</a> ' + document.getElementById(load_name + '_title').innerHTML;
  201. }
  202. document.getElementById(set_name + '_load').style.display = "inline-block";
  203. document.getElementById(set_name + '_load').count = 0;
  204. let width = document.getElementById(set_name + '_load').clientWidth;
  205. let screen_width = window.innerWidth;
  206. let left = document.getElementById(set_name).getBoundingClientRect().left;
  207. let left_org = document.getElementById(set_name + '_load').getBoundingClientRect().left;
  208. let top = window.pageYOffset + document.getElementById(set_name).getBoundingClientRect().top;
  209. document.getElementById(set_name + '_load').style.top = String(top) + "px";
  210. if(screen_width - (left + width) < 50) {
  211. if(left > 350) {
  212. document.getElementById(set_name + '_load').style.left = String(left - 300) + "px";
  213. } else {
  214. document.getElementById(set_name + '_load').style.left = "0px";
  215. }
  216. left = document.getElementById(set_name + '_load').getBoundingClientRect().left;
  217. width = document.getElementById(set_name + '_load').clientWidth;
  218. if(300 > width) {
  219. document.getElementById(set_name + '_load').style.left = String(left + (300 - width)) + "px";
  220. } else {
  221. document.getElementById(set_name + '_load').style.marginTop = "20px";
  222. }
  223. }
  224. } else {
  225. if(document.getElementById(set_name + '_load').count === 1) {
  226. document.getElementById(set_name + '_load').style.display = "none";
  227. } else {
  228. document.getElementById(set_name + '_load').count = 1;
  229. }
  230. }
  231. }
  232. }
  233. function opennamu_do_category_spread() {
  234. if(document.getElementsByClassName('opennamu_render_complete')) {
  235. document.getElementsByClassName('opennamu_render_complete')[0].innerHTML = '' +
  236. '<style>.opennamu_main .opennamu_category_button { display: none; } .opennamu_main .opennamu_category { white-space: pre-wrap; overflow-x: unset; text-overflow: unset; }</style>' +
  237. '' + document.getElementsByClassName('opennamu_render_complete')[0].innerHTML;
  238. }
  239. }
  240. function opennamu_do_toc() {
  241. let data = document.getElementById('opennamu_render_complete');
  242. let h_tag = data.querySelectorAll("h1, h2, h3, h4, h5, h6");
  243. let toc_count = [0, 0, 0, 0, 0, 0];
  244. let toc_html = '';
  245. for(let for_a = 0; for_a < h_tag.length; for_a++) {
  246. let tag = h_tag[for_a].tagName.toLowerCase();
  247. tag = tag.replace('h', '');
  248. tag = Number(tag) - 1;
  249. for(let for_b = tag + 1; for_b < 6; for_b++) {
  250. toc_count[for_b] = 0;
  251. }
  252. toc_count[tag] += 1;
  253. let toc_string = '';
  254. let add_on = false;
  255. for(let for_b = 5; for_b >= 0; for_b--) {
  256. if(add_on === false && toc_count[for_b] != 0) {
  257. add_on = true;
  258. }
  259. if(add_on === true) {
  260. toc_string = String(toc_count[for_b]) + '.' + toc_string;
  261. }
  262. }
  263. toc_string = toc_string.replace(/^(0\.)+/, '');
  264. let toc_string_sub = toc_string.replace(/\.$/, '');
  265. let toc_margin = '<span style="margin-left: 10px;"></span>'.repeat(toc_string_sub.split('.').length - 1);
  266. toc_html += toc_margin + '<a href="#s-' + toc_string_sub + '">' + toc_string + '</a> ' + h_tag[for_a].innerHTML + '<br>';
  267. h_tag[for_a].innerHTML = '<a id="s-' + toc_string_sub + '" href="#toc">' + toc_string + '</a> ' + h_tag[for_a].innerHTML;
  268. }
  269. data.innerHTML = data.innerHTML.replace(/(<h[1-6]>)/, '<div class="opennamu_toc"></div>$1');
  270. data.innerHTML = data.innerHTML.replace(/<div class="opennamu_toc"><\/div>/g, function(match) {
  271. return '<div class="opennamu_TOC" id="toc"><div class="opennamu_TOC_title">TOC</div><br>' + toc_html + '</div>';
  272. });
  273. }
  274. function opennamu_do_render(to_obj, data, name = '', do_type = '', option = '', callback = undefined) {
  275. let url;
  276. if (do_type === '') {
  277. url = "/api/render";
  278. } else {
  279. url = "/api/render/" + do_type;
  280. }
  281. fetch(url, {
  282. method: 'POST',
  283. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  284. body: new URLSearchParams({
  285. 'name': name,
  286. 'data': data,
  287. 'option': option
  288. })
  289. }).then(response => {
  290. if(!response.ok) {
  291. throw new Error(`API 호출 실패: ${response.status}`);
  292. }
  293. return response.json();
  294. }).then(text => {
  295. if(document.getElementById(to_obj)) {
  296. if(text["data"]) {
  297. document.getElementById(to_obj).innerHTML = text["data"];
  298. eval(text["js_data"]);
  299. } else {
  300. document.getElementById(to_obj).innerHTML = '';
  301. }
  302. if(callback) {
  303. callback();
  304. }
  305. }
  306. }).catch(err => {
  307. console.error('렌더링 호출 중 오류 발생:', err);
  308. if(document.getElementById(to_obj)) {
  309. document.getElementById(to_obj).innerHTML = '렌더링에 실패했습니다. 잠시 후 다시 시도해 주세요.';
  310. }
  311. });
  312. }
  313. function opennamu_do_render_with_dom(to_obj, from_obj, name = '', do_type = '', option = '', callback = undefined) {
  314. let url;
  315. if (do_type === '') {
  316. url = "/api/render";
  317. } else {
  318. url = "/api/render/" + do_type;
  319. }
  320. fetch(url, {
  321. method: 'POST',
  322. headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  323. body: new URLSearchParams({
  324. 'name': name,
  325. 'data': opennamu_xss_filter_decode(document.getElementById(from_obj).innerHTML),
  326. 'option': option
  327. })
  328. }).then(response => {
  329. if(!response.ok) {
  330. throw new Error(`API 호출 실패: ${response.status}`);
  331. }
  332. return response.json();
  333. }).then(text => {
  334. if(document.getElementById(to_obj)) {
  335. if(text["data"]) {
  336. document.getElementById(to_obj).innerHTML = text["data"];
  337. eval(text["js_data"]);
  338. } else {
  339. document.getElementById(to_obj).innerHTML = '';
  340. }
  341. if(callback) {
  342. callback();
  343. }
  344. }
  345. }).catch(err => {
  346. console.error('렌더링 호출 중 오류 발생:', err);
  347. if(document.getElementById(to_obj)) {
  348. document.getElementById(to_obj).innerHTML = '렌더링에 실패했습니다. 잠시 후 다시 시도해 주세요.';
  349. }
  350. });
  351. }