widgets.nosql-backup 469 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. 2019-05-23 08:30 | Administrator | {"id":"19052100440001ofa1","name":"Ui-gallery-carousel-slide","reference":"ui/gallery/carousel/ui-gallery-carousel-slide.html","body":"<style>\n #CMS .ui-gallery-carousel-slide { visibility: visible; height: auto; margin-bottom: .5rem; }\n #CMS .ui-gallery-carousel-slide .slide-image { max-height: 500px; }\n #CMS .CMS_selected > .ui-gallery-carousel-slide { border: .5rem solid #1A88E0; }\n</style>\n\n<script editor>\n option('slideImage', 'Slide Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('.slide-image', $el).css('background-image', `url(${options.slideImage})`);\n };\n</script>\n<script>\n COMPONENT('ui-gallery-carousel-slide', function(component){\n component.make = function(){};\n });\n</script>\n\n<div class=\"js-slide ui-gallery-carousel-slide\" data-jc=\"ui-gallery-carousel-slide\">\n <div class=\"g-flex-centered g-height-100vh g-min-height-500--md g-bg-cover g-bg-pos-center g-bg-img-hero g-bg-black-opacity-0_5--after slide-image\" style=\"background-image: url(//via.placeholder.com/1920x1280/eeeeee);\">\n <div class=\"container text-center g-z-index-1\">\n <h2 class=\"text-uppercase g-font-weight-700 g-font-size-22 g-font-size-36--md g-color-white g-mb-20 CMS_edit\">여기를\n <span class=\"g-color-primary\">편집 </span> 해주세요.\n </h2>\n <p class=\"g-hidden-xs-down g-max-width-645 g-color-white-opacity-0_9 mx-auto g-mb-35 CMS_edit\">\n 여기를 편집해주세요.\n </p>\n <a class=\"btn btn-lg u-btn-primary g-font-weight-700 g-font-size-12 text-uppercase g-rounded-50 g-px-40 g-py-15 CMS_edit\" href=\"#!\">더 알아보기</a>\n </div>\n </div>\n</div>","datecreated":"2019-05-20T15:44:54.234Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-05-22T01:21:23.397Z"}
  2. 2019-05-23 08:30 | Administrator | {"id":"19042615300019wmg1","name":"Ui-gallery-carousel","reference":"ui/gallery/carousel/ui-gallery-carousel.html","body":"<style>\n #CMS .ui-gallery-carousel { min-height: 10rem; }\n #CMS .ui-gallery-carousel .js-carousel { background: unset; }\n #CMS .ui-gallery-carousel .js-carousel .js-slide { visibility: visible; height: 10rem; width: 10rem; display: inline-block; margin-right: 1rem; }\n #CMS .ui-gallery-carousel .js-carousel .js-slide:last-child { margin-right: 0; }\n #CMS .ui-gallery-carousel .js-carousel .js-slide, .js-thumb { height: auto; }\n #CMS .ui-gallery-carousel .js-carousel .js-slide img { width: 10rem; height: 10rem; }\n</style>\n\n<script>\n COMPONENT('ui-gallery-carousel', function(component){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSCarousel.init(component.$el);\n };\n }, [\n '/libs/slick-carousel/1.9.0/slick.min.css',\n '/libs/slick-carousel/1.9.0/slick.min.js',\n\n '/libs/ui/1.0.0/js/components/hs.carousel.js',\n ]);\n</script>\n\n<section class=\"js-carousel g-overflow-hidden g-max-height-100vh ui-gallery-carousel CMS_widgets\" data-autoplay=\"true\" data-infinite=\"true\" data-speed=\"15000\" data-pagi-classes=\"u-carousel-indicators-v29 container text-center text-uppercase g-absolute-centered--x g-bottom-50 g-line-height-1_2 g-font-size-12 g-color-white g-brd-white-opacity-0_2\" data-jc=\"ui-gallery-carousel\"></section>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-05-22T01:21:23.397Z"}
  3. 2019-06-25 01:00 | Administrator | {"id":"19061902000001ptb1","name":"Test-v1","reference":"test/test-v1.html","body":"<style>\n @media (min-width: 767.98px) {\n .test-v1 .test-paper {\n display: flex;\n }\n .test-v1 .test-paper.two-col {\n flex-direction: column;\n flex-wrap: wrap;\n align-content: flex-start;\n height: 60rem;\n }\n }\n .test-v1 .test-paper article.question-answer {\n padding: 0 1rem;\n padding-bottom: 3rem;\n }\n\n .test-v1 .test-paper article.question-answer ul.answer.multiple-choice {\n list-style-type: none;\n }\n</style>\n<script>\n COMPONENT('test-v1', 'paperCol:2', function(component, config){\n component.$el = component.element;\n\n let dummy = [\n 'break-page',\n {\n _id: '5d0908a2f8c31c0000aff135',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">1. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>어니스트 베델은 영국의 기자였다. 그는 1904년 러일 정쟁을 취재하기 위해 우리나라에 왔다. 그 당시 우리 국민들은 일본의 침략으로 하루하루 어렵게 살고 있었다. 일본은 우리 국민들을 철저히 감시하였기 때문에 우리나라 사람들이 발행하는 시문에는 일본의 칩략 행위에 대한 기사를 실을 수 없엇다. 그래서 일본의 만행이 제대로 알려지지 않았다. 이러한 실상을 알게 된 베델은 기자로서 깊은 고민에 빠졌다. 당시 영국은 일본과 동맹국이어서 일본에 우호적인 기사를 써야 했기 때문에 그는 더욱 괴로웠다.</p>\n <p>'아! 일본 때문에 한국인들이 고통을 받고 있구나. 일본이 한국을 위해 을사조약을 맺었다는 것은 사실이 아니야. 또 한국인들이 을사조약을 환영한다는 것도 거짓이었어. 그런데도 이런 사실을 감추고 거짓 기사를 쓸 수는 없어. 기자는 사실만을 써야 해. 이 진실을 세상 사람들에게 알릴 수 있는 방법이 없을까?'</p>\n <div class=\"d-flex mb-3\">\n <span class=\"my-auto px-3\">(가)</span>\n <div class=\"w-100 mx-0 border border-dark p-2\" style=\"height: 2.5rem\"></div>\n </div>\n <p>베델은 자신이 만든 대한매일신보에 우리나라에 대한 일본의 침략행위를 사실대로 실었다. 을사조약 문서에 우리나라의 국새가 찍혀있지 않다는 것과 일본이 우리나라의 문화재를 몰래 빼돌렸다는 사실을 기사로 썼다. 또 이완용의 행동을 비판하는 신문 사설을 처음으로 싣기도 하였다.</p>\n <p>베델이 신문을 통해 일본의 침략 행위를 낱낱이 ㉠ <u>밝히자</u> 일본은 베델을 압박하고 베델의 일을 방해하기 시작하였다. 일본은 영국 정부에 베델을 추방하는 데 협력해 줄 것을 요청하기도 하고, 그의 처벌을 요구하는 소송장을 내기도 하였다. 베델은 세 차례의 재판을 거치면서 건강이 악화되어 결국 서른일곱의 나이에 우리나라에서 눈을 감았다.</p>\n <p>\"나는 죽되 대한매일신보는 길이 살아 한국 동포를 구하기를 원하노라.\"</p>\n <p>마지막 순간까지도 언론인의 신념을 지키고자 했던 어니스트 베델. 언론인으로서의 참모습을 실현하고자 했던 그의 정신은 우리 마음속에 영원히 남아 있을 것이다.</p>\n </div>\n </div>\n <p>밑줄 친 말이 위 글의 ㉠과 같은 뜻으로 쓰인 것은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '전등을 켜서 방을 <u>밝혔다.</u>'\n },\n {\n content: '그 친구는 먹을 것을 너무 <u>밝혔다.</u>'\n },\n {\n content: '책을 읽다가 뜬눈으로 밤을 <u>밝혔다.</u>'\n },\n {\n content: '어둠 속에서 길을 찾기 위해 횃불을 <u>밝혔다.</u>'\n },\n {\n content: '탐정은 방송을 통하여 사건의 비밀을 <u>밝혔다.</u>'\n },\n ]\n }\n },\n {\n _id: '5d090a2f9e35540000d47fed',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">2. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>옛날 아이들은 윷놀이, 자치기, 팽이치기와 같은 전래 놀이를 하면서 친구들과 즐겁게 놀았다. <span class=\"border border-dark px-2\">그러나</span> 요즘 아이들은 주로 인터넷 게임을 하거나 텔레비전을 시청하며 여가를 보낸다. 전래 놀이는 여럿이 함께하는 놀이가 많아 친구를 사귈 기회를 더 많이 갖게 하고 ㉠ <u>건강한 몸을 유지하는 데 도움을 준다.</u> 전래 놀이를 하면 다음과 같은 좋은 점이 있다.</p>\n <p>(가) 첫째, 자연을 가까이 느낄 수 있다. 대부분의 전래 놀이는 바깥에서 한다. 그래서 풀, 바람, 나무, 흙, 돌, 햇빛 등 자연을 가까이 느낄 수 있다.</p>\n <p>(나) 둘째, 공동체 의식을 기를 수 있다. 또래 아이들과 함께 윷놀이나 비사치기를 하면서 의논하고 힘을 모으기도 한다. 이러한 과정을 거치면서 아이들은 서로 돕고 위하는 태도를 배우게 된다.</p>\n <p>((다)) 셋째, 체력이 향상될 수 있다. 전래 놀이는 뛰거나 빠른 걸음으로 걷는 등 움직임이 많아서 따로 운동을 하지 않아도 운동 효과를 볼 수 있기 때문이다.</p>\n <p>((라)) 넷째, 놀이를 통해 지식을 얻을 수 있다. 자치기에 필요한 막대기를 고르면서 나무의 특성을 자연스럽게 알고, 연날리기를 하면서 바람을 이용하는 방법을 터득하게 된다.</p>\n <p>((마)) 다섯째, 조상들의 지혜를 알 수 있다. 우리 조상들은 언제 어디서나 누구든지 즐길 수 있는 다양한 놀이 방법을 만들어 냈다. 아이들은 전래 놀이를 하면서 지혜를 터득하게 된다.</p>\n <p>이와 같이 전래 놀이를 하면 좋은 점이 많다. 우리 모두 전래 놀이에 관심을 가지고 즐겨 하자.</p>\n </div>\n </div>\n <p>밑줄 친 ㉠을 뒷받침하는 근거로 가장 적절한 것은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '(가)'\n },\n {\n content: '(나)'\n },\n {\n content: '(다)'\n },\n {\n content: '(라)'\n },\n {\n content: '(마)'\n },\n ]\n }\n },\n 'break-page',\n {\n _id: '5d09111a8fcc6500006d78d0',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">3. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>흙 속의 미생물에서 감기약 성분을 얻는다면 믿을 수 있을까? 놀랍게도 과학자들은 흙 속의 미생물인 방선균에서 그 성분을 얻고 있다. 방선균은 실처럼 생긴 가지가 서로 연결된 형태를 띤 세균의 한 종류이다. 방선균은 흙, 식물, 동물의 몸, 하천, 바닷물 등에 사는데 그 중에서도 흙 속에 가장 많이 산다.</p>\n <p>방선균은 우리 생활에 많은 도움을 준다. 먼저 방선균은 식물이 사는 데 꼭 필요한 질소를 공급해 준다. 그래서 농사에 도움이 된다. 또한 방선균은 유기물을 분해하기 때문에 퇴비를 만드는 데에 쓰인다. 화장실, 정화조 등의 악취를 없애고 가정의 하수 등을 정화하는 데에도 이용된다. 무엇보다도 방선균의 가장 큰 특징은 곰팡이나 병원균<sup>*</sup>을 파괴하는 항생 물질을 만들어 내는 것이다.</p>\n <div class=\"d-flex mb-2\">\n <span class=\"my-auto px-3\">(가)</span>\n <div class=\"w-100 mx-0 border border-dark p-2\">\n <p>방선균이 만들어 내는 항생 물질은 의약품을 만드는 데 널리 이용된다. 우리가 사용하는 의약품 중 약 70%가 방선균이 만들어 낸 항생 물질을 원료로 한다. 감기약이나 안약, 피부 질환에 바르는 연고에서부터 암이나 결핵을 치료하는 약에 이르기까지 방선균의 쓰임은 다양하다.</p>\n <p>과학자들은 계속해서 방선균 연구에 힘쓰고 있다. 최근에는 흙 속에 있는 방선균뿐 아니라 바다에 있는 방선균에 대한 연구가 새롭게 진행되고 있다. 새로운 방선균의 발견과 그것의 활용 방안에 대한 연구는 방선균의 활용 가치를 높이는 데 기여할 것이다.</p>\n </div>\n </div>\n <p>* 병원균: 병의 원인이 되는 균.</p>\n </div>\n </div>\n <p>위 글의 내용과 일치하지 않는 것은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '방선균은 물속에서 가장 많이 발견된다.'\n },\n {\n content: '방선균은 식물이 살아가는 데 도움을 준다.'\n },\n {\n content: '방선균은 가정의 하수를 정화하는 데 활용된다.'\n },\n {\n content: '최근에 바다에 있는 방선균에 대한 연구가 이루어지고 있다.'\n },\n {\n content: '방선균이 만들어 내는 항생 물질은 의약품을 만드는 데 이용된다.'\n },\n ]\n }\n },\n {\n _id: '5d0913ce65fa31000020f2c3',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">4. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>우리나라 사람에게 호랑이는 특별한 동물이다. 우리 조상들은 호랑이에게 잡귀와 나쁜 기운을 쫓아낼 수 있는 능력이 있다고 믿었다. 호랑이를 단순한 동물이 아닌 산신령이나 수호신으로 생각했던 것이다. <span class=\"border border-dark px-4\">㉠</span> 사람들은 호랑이 형상을 넣은 물건을 가까이두었다.</p>\n <p>우리 조상들이 평소 사용하던 생활 용품을 살펴보면 호랑이 형상이 들어 있는 물건이 많다. 어른들은 아이들이 나쁜 꿈을 꾸지 않고 편안하게 잠들기를 바라며 아이들의 베개에 호랑이 문양을 수놓았다. 남자들이 거처하는 사랑방에는 높은 벼슬에 오르기를 기원하며 호랑이 장식이 있는 가구를 놓았다. 부엌에서는 호랑이 다리를 본떠 만든 소반<sup>*</sup>이나 호랑이 무늬가 있는 그릇 등을 사용하였다.</p>\n <p>특별한 일이 있을 때에도 사람들은 호랑이 형상을 그리거나 본뜬 물건을 사용하였다. 혼례를 치를 때에는 신부가 안전하기를 바라는 마음에서 가마에 호랑이 문양을 그려 넣었다. 또, 한 해가 시작되는 정월이 되면 사람들은 집 안으로 들어오려는 잡귀를 막기 위해 대문에 호랑이 '호(虍)'자를 써 붙였다. 그리고 전쟁을 무사히 마치고 싶어 했던 군인들은 호랑이 그림이 있는 옷을 입고, 호랑이 수염을 꽂은 모자를 썼으며, 호랑이가 그려진 깃발을 흔들면서 행군하였다. ㉡ <u>사람들은 무덤에도 호랑이 형상을 세웠다.</u> 호랑이가 죽은 사람을 지켜 준다고 믿었기 때문이다.</p>\n\n <p>* 소반: 자그마한 밥상</p>\n </div>\n </div>\n </div>\n <p>위 글의 <span class=\"border border-dark px-4\">㉠</span>에 들어갈 알맞은 낱말은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '그러나'\n },\n {\n content: '그러면'\n },\n {\n content: '그래서'\n },\n {\n content: '하지만'\n },\n {\n content: '왜냐하면'\n },\n ]\n }\n },\n 'break-page',\n {\n _id: '5d0915ee82cc1800004f79f2',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">5. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>우리 생활에서 한글은 없어서는 안 될 존재이다. 한글은 1443년, 세종대왕과 집현전 학자들이 심혈을 기울여 만들어낸 노력의 ⓐ <u>결정</u>이라 할 수 있다. 자음 14자와 모음 10자로 구성되어 있고, 발음기관을 본떠서 만들어 과학적이며 배우기 쉬운 문자이다. 그런데 요즘은 우리의 소중한 한글이 많이 오염되고 훼손되고 있다는 생각이 자주 들게 된다. 그렇게 생각하는 이유는 다음과 같다.</p>\n <p>첫째, 심각한 통신언어가 사용되면서 한글이 훼손되고 있다. 예를 들어 '초등학생'을 '초딩', '어서오세요.'를 '어솨요.'라고 줄여 쓰는 통신언어들이 이제는 실생활에서까지 사용되면서, 어떤 것이 올바른 말인지 모르는 사람들이 많아진다. 통신언어는 말이 짧고 쓰기 쉬워서 경제성이 뛰어나다.</p>\n <p>둘째, 버젓이 고유의 한글이 있는데도 외국어를 사용한다. '색깔'을 '칼라'로, '악단'을 '밴드'로, 건물을 '빌딩'으로 사용하는 것은 자연스러운 일이 되었다. 많은 사람들이 외국어라는 거부감 없이 일상적으로 이런 말을 사용하고 있다. 이러다가는 미래에는 우리 고유의 한글이 기억 속에서 점점 잊혀갈지도 모른다.</p>\n </div>\n </div>\n </div>\n <p>위 글의 제목으로 알맞은 것은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '한글의 우수성'\n },\n {\n content: '통신언어의 특징'\n },\n {\n content: '한글 창제 원리'\n },\n {\n content: '한글의 오염과 훼손'\n },\n {\n content: '한글과 외국어의 차이점'\n },\n ]\n }\n },\n {\n _id: '5d0916b982cc1800004f79f3',\n questionContent: `\n<article class=\"question-answer\">\n <p class=\"question-title font-weight-bold\">6. 다음 글을 읽고 물음에 답하시오.</p>\n <div class=\"question-content mb-3\">\n <div class=\"question-quotation border border-dark p-2\">\n <p>(가) 우리는 가끔 하고 싶은 말을 다 하고 산다면 얼마나 속이 시원할까 하고 생각할 때가 있다. 하지만, 우리가 하고 싶은 말이 모두 옳고 고운 말일까? 우리가 하고자 하는 말 중에는 자기중심적인 말이 많다. 자기중심적인 말은 경우에 따라 상대방의 마음에 상처를 줄 수도 있다.</p>\n <p>(나) 우리는 때때로 보고도 보지 않은 것처럼 해야 할 때가 있다. 이것은 ㉠ <u>불의</u>를 보고 무조건 모르는 척하라는 것이 아니다. 그런 상황이 일어나게 된 까닭을 이해해 보라는 것이다. 만약, 친구가 무엇인가를 훔치는 것을 보게 된다면, 어떻게 할 것인가? 도둑이라고 막 떠벌릴 것인가, 아니면 친구의 사정을 고려하여 떠벌리지 말 것인가를 한번쯤 생각해 보라는 것이다.</p>\n <p>(다) 또, 듣고도 듣지 않은 것처럼 해야 할 때가 있다. 이것은 귀로 흘러들어온 말을 함부로 입 밖으로 내서는 안 도니다는 것이다. 직접 확인해 보지 않은 사실을 다른 사람에게 전해서 그 말이 눈덩이 처럼 커진다면, 그말에 책임을 질 수 있을까? 말을 전할 때에는 들은 말이 사실인지를 확인해야 한다.</p>\n <p>(라) 우리의 얼굴엔 눈과 귀가 두 개씩 있지만, 입은 하나밖에 없다. 이것은 많이 보고 많이 듣되, 말은 조금만 하라는 뜻이다. 생각 없이 이말저말을 함부로 하는 사람은 좋은 친구를 사귈 수 없을 것이며, 좋은 친구가 될 수도 없을 것이다. 왜냐하면, 그 사람의 입을 믿을 수가 없기 떄문이다.</p>\n </div>\n </div>\n <p>위 글의 주제로 가장 적절한 것은 어느 것입니까?</p>\n</article>`,\n type: 'multiple-choice',\n 'multiple-choice': {\n isMultipleSelection: false,\n options: [\n {\n content: '고운 말만 해야 한다.'\n },\n {\n content: '말은 신중하게 해야 한다.'\n },\n {\n content: '좋은 친구를 사귀어야 한다.'\n },\n {\n content: '남에게 들은 것은 전하지 말아야한다.'\n },\n {\n content: '다른 사람의 잘못을 지적해 주어야 한다.'\n },\n ]\n }\n },\n ];\n\n component.make = function(){\n let $testPaper;\n\n dummy.forEach(function(questionAnswer, idx){\n if ( questionAnswer === 'break-page' )\n return $testPaper = $(`<div class=\"test-paper ${config.paperCol === 2 ? 'two-col' : '' } ${ idx > 0 ? 'pt-3 border-top' : '' }\"></div>`).appendTo(component.$el);\n\n let $questionAnswer = $(questionAnswer.questionContent);\n\n $questionAnswer.addClass(config.paperCol === 2 ? 'g-width-50x--md' : '');\n\n if ( questionAnswer.type === 'multiple-choice' )\n {\n let $optionList = $('<ul class=\"answer multiple-choice pl-3\"/>');\n questionAnswer['multiple-choice'].options.forEach(function(option, idx){\n $optionList.append(`\n<li><div class=\"form-check form-check-inline\">\n <input class=\"form-check-input\" type=\"radio\" name=\"${questionAnswer._id}\" id=\"${questionAnswer._id}-${idx}\" value=\"option1\">\n <label class=\"form-check-label\" for=\"${questionAnswer._id}-${idx}\">${option.content}</label>\n</div></li>`\n );\n });\n\n $questionAnswer.append($optionList);\n }\n\n $testPaper.append($questionAnswer);\n });\n\n $(window).on('resize', _.debounce(component._resizeTestPaper, 150)).trigger('resize');\n };\n\n component._resizeTestPaper = function(){\n if( window.matchMedia( '(max-width: 768px)' ).matches )\n return $('.test-paper', component.$el).height('auto');\n\n $('.test-paper', component.$el).each(function(){\n let $testPaper = $('.test-paper', component.$el);\n let maxHeight = 0;\n\n $('article.question-answer', $testPaper).each(function(){\n if ( $(this).height() > maxHeight ) maxHeight = $(this).height();\n });\n\n $testPaper.height(maxHeight + 20);\n });\n };\n });\n</script>\n<div data-jc=\"test-v1\" class=\"test-v1\"></div>","datecreated":"2019-06-18T17:00:08.543Z","picture":"","icon":"","category":"Test","dateupdated":"2019-06-25T00:59:34.994Z"}
  4. 2019-06-25 01:00 | Administrator | {"id":"19052313340002iae0","name":"Ui-carousel-slide","reference":"ui/carousel/slide/ui-carousel-slide.html","body":"<style>\n #CMS .ui-carousel-slide { visibility: visible; height: auto; margin-bottom: .5rem; }\n #CMS .ui-carousel-slide .js-slide { visibility: visible; }\n #CMS .ui-carousel-slide .slide-image { max-height: 500px; }\n #CMS .CMS_selected > .ui-carousel-slide { border: .5rem solid #1A88E0; }\n</style>\n\n<script editor>\n const CONSTANTS = {\n TYPE: {\n GALLERY: 'GALLERY',\n STATIC: 'STATIC',\n },\n TEMPLATE: {\n GALLERY: `\n<div class=\"g-flex-centered g-height-100vh g-min-height-500--md g-bg-cover g-bg-pos-center g-bg-img-hero g-bg-black-opacity-0_5--after slide-image\" style=\"background-image: url(//via.placeholder.com/1920x1280/eeeeee);\">\n <div class=\"container text-center g-z-index-1\">\n <h2 class=\"text-uppercase g-font-weight-700 g-font-size-22 g-font-size-36--md g-color-white g-mb-20 CMS_edit\">여기를\n <span class=\"g-color-primary\">편집 </span> 해주세요.\n </h2>\n <p class=\"g-hidden-xs-down g-max-width-645 g-color-white-opacity-0_9 mx-auto g-mb-35 CMS_edit\">\n 여기를 편집해주세요.\n </p>\n <a class=\"btn btn-lg u-btn-primary g-font-weight-700 g-font-size-12 text-uppercase g-rounded-50 g-px-40 g-py-15 CMS_edit\" href=\"#!\">더 알아보기</a>\n </div>\n</div>`,\n STATIC: `\n<div class=\"text-center g-px-100--lg\">\n <i class=\"d-block g-color-primary g-font-size-60 g-line-height-0_7 g-pos-rel g-top-20\">“</i>\n <blockquote class=\"g-color-white g-font-size-25 g-py-40 CMS_edit\">여기를 편집해주세요.</blockquote>\n <h4 class=\"h6 g-color-white-opacity-0_7 text-uppercase g-mb-0 CMS_edit\">\n 여기를 <em class=\"g-font-style-normal g-color-primary\">편집해주세요.</em>\n </h4>\n</div>`\n }\n };\n\n const $slide = $('.ui-carousel-slide', $el);\n const type = getType($slide);\n\n // 부모에 따른 템플릿 설정 화면 의존성\n if ( type === CONSTANTS.TYPE.GALLERY )\n option('slideImage', 'Slide Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n let type = getType($slide);\n\n if ( type === CONSTANTS.TYPE.GALLERY )\n $('.slide-image', $el).css('background-image', `url(${options.slideImage})`);\n };\n\n exports.make = function($el){\n // 부모에 따른 템플릿 태그 분기\n let $template = $(CONSTANTS.TEMPLATE[type]);\n\n $slide.append($template);\n };\n\n function getType($slide){\n if ( $slide.closest('.ui-carousel-gallery').length > 0 ) return CONSTANTS.TYPE.GALLERY;\n if ( $slide.closest('.ui-carousel-static').length > 0 ) return CONSTANTS.TYPE.STATIC;\n return null;\n };\n</script>\n<script>\n COMPONENT('ui-carousel-slide', function(component){\n component.make = function(){};\n });\n</script>\n\n<div class=\"js-slide ui-carousel-slide\" data-jc=\"ui-carousel-slide\"></div>","datecreated":"2019-05-23T04:34:52.989Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  5. 2019-06-25 01:00 | Administrator | {"id":"19052313340001iae1","name":"Ui-carousel-gallery","reference":"ui/carousel/gallery/ui-carousel-gallery.html","body":"<style>\n #CMS .ui-carousel-gallery { min-height: 10rem; }\n</style>\n\n<script>\n COMPONENT('ui-carousel-gallery', function(component){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSCarousel.init(component.$el);\n };\n }, [\n '/libs/slick-carousel/1.9.0/slick.min.css',\n '/libs/slick-carousel/1.9.0/slick.min.js',\n\n '/libs/ui/1.0.0/js/components/hs.carousel.js',\n ]);\n</script>\n\n<section class=\"js-carousel g-overflow-hidden g-max-height-100vh ui-carousel-gallery CMS_widgets\" data-cms-importable=\"ui-carousel-slide\" data-autoplay=\"true\" data-infinite=\"true\" data-speed=\"15000\" data-pagi-classes=\"u-carousel-indicators-v29 container text-center text-uppercase g-absolute-centered--x g-bottom-50 g-line-height-1_2 g-font-size-12 g-color-white g-brd-white-opacity-0_2\" data-jc=\"ui-carousel-gallery\"></section>","datecreated":"2019-05-23T04:34:52.989Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  6. 2019-06-25 01:00 | Administrator | {"id":"19052313340003iae1","name":"Ui-carousel-static","reference":"ui/carousel/static/ui-carousel-static.html","body":"<style>\n #CMS .ui-carousel-static { min-height: 10rem; }\n</style>\n\n<script editor>\n option('bgImage', 'Background Image', '', 'file');\n\n exports.configure = function(options, $el, prevOptions){\n $('.divimage', $el).css('background-image', `url(${options.bgImage})`);\n };\n</script>\n\n<script>\n COMPONENT('ui-carousel-static', function(component, config){\n component.$el = component.element;\n component.$carousel = $('.js-carousel', component.$el);\n\n component.make = function(){\n $.HSCore.components.HSCarousel.init(component.$carousel);\n };\n }, [\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.css',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.css',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.css',\n\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.js',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.js',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.js',\n ]);\n</script>\n\n<div class=\"dzsparallaxer auto-init height-is-based-on-content use-loading g-bg-cover g-bg-black-opacity-0_7--after ui-carousel-static\" data-jc=\"ui-carousel-static\">\n <div class=\"divimage dzsparallaxer--target w-100\" style=\"height: 140%; background-image: url(//via.placeholder.com/1920x1280/eeeeee);\"></div>\n <div class=\"container g-bg-cover__inner g-py-120\">\n <div class=\"js-carousel g-pb-80 CMS_widgets\" data-cms-importable=\"ui-carousel-slide\" data-infinite=\"true\" data-arrows-classes=\"u-arrow-v1 g-width-40 g-height-40 g-brd-1 g-brd-style-solid g-brd-white-opacity-0_6 g-brd-primary--hover g-color-white-opacity-0_5 g-bg-primary--hover g-color-white--hover g-absolute-centered--x g-bottom-0\" data-arrow-left-classes=\"fa fa-angle-left g-ml-minus-25\" data-arrow-right-classes=\"fa fa-angle-right g-ml-25\">\n </div>\n </div>\n</div>","datecreated":"2019-05-23T04:34:52.989Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  7. 2019-06-25 01:00 | Administrator | {"id":"19052210190002dfi0","name":"Ui-go-to-naea-v1","reference":"ui/goto/naea/ui-go-to-naea-v1.html","body":"<style></style>\n<script>\n COMPONENT('ui-go-to-naea-v1', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSGoTo.init('.js-go-to', component.$el);\n };\n }, [\n '/libs/ui/1.0.0/js/components/hs.go-to.js',\n ]);\n</script>\n\n<div class=\"ui-go-to-naea-v1\" data-jc=\"ui-go-to-naea-v1\">\n <a class=\"js-go-to u-go-to-v2\" href=\"#!\" data-type=\"fixed\" data-position='{\"bottom\": 15,\"right\": 15}' data-offset-top=\"400\" data-compensation=\"#js-header\" data-show-effect=\"zoomIn\">\n <i class=\"fa fa-angle-up\"></i>\n </a>\n</div>\n","datecreated":"2019-05-22T01:19:10.841Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  8. 2019-06-25 01:00 | Administrator | {"id":"19052210190001dfi1","name":"Ui-info-block-v3","reference":"ui/info-block/ui-info-block-v3.html","body":"<style></style>\n\n<script editor>\n option('leftImage', 'Left Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('.left-image', $el).css('background-image', `url(${options.leftImage})`)\n .attr('data-bg-img-src', options.leftImage)\n .data('bg-img-src', options.leftImage);\n };\n</script>\n\n<script>\n COMPONENT('ui-info-block-v3', function(component, config){\n component.make = function(){};\n });\n</script>\n\n<section class=\"ui-info-block-v3\" data-jc=\"ui-info-block-v3\">\n <!-- Info Blocks -->\n <div class=\"row no-gutters\">\n <!-- Info Image -->\n <div class=\"col-lg-5 g-min-height-360 g-bg-size-cover g-bg-pos-center left-image\" data-bg-img-src=\"//via.placeholder.com/1920x1280\" style=\"background-image: url(//via.placeholder.com/1920x1280);\"></div>\n <!-- End Info Image -->\n <div class=\"col-lg-7 g-bg-gray-dark-v1 g-pt-100 g-pb-80 g-px-80\">\n <header class=\"text-uppercase g-mb-35\">\n <div class=\"g-mb-30\">\n <span class=\"d-block g-color-primary g-font-weight-700 g-font-size-default g-mb-15 CMS_edit\">이곳을 편집해주세요.</span>\n <h2 class=\"h2 g-color-white g-font-weight-700 mb-0 CMS_edit\">이곳을 편집해주세요.</h2>\n </div>\n <div class=\"g-width-70 g-brd-bottom g-brd-2 g-brd-primary\"></div>\n </header>\n <p class=\"g-color-white-opacity-0_7 g-mb-20 CMS_edit CMS_multiline\">이곳을 편집해주세요.</p>\n <p class=\"g-color-white-opacity-0_7 g-mb-45 CMS_edit CMS_multiline\">이곳을 편집해주세요.</p>\n\n <div class=\"row align-items-stretch CMS_repeat\">\n <div class=\"col-sm-6 g-mb-30 CMS_remove\">\n <!-- Article -->\n <article class=\"h-100 g-flex-middle g-brd-left g-brd-3 g-brd-primary g-brd-white--hover g-bg-black-opacity-0_8 g-transition-0_3 g-pa-20\">\n <div class=\"g-flex-middle-item\">\n <h4 class=\"h6 g-color-white g-font-weight-600 text-uppercase g-mb-10 CMS_edit\">이곳을 편집해주세요.</h4>\n <p class=\"g-color-white-opacity-0_7 mb-0 CMS_edit\">이곳을 편집해주세요.</p>\n </div>\n </article>\n <!-- End Article -->\n </div>\n <div class=\"col-sm-6 g-mb-30 CMS_remove\">\n <!-- Article -->\n <article class=\"h-100 g-flex-middle g-brd-left g-brd-3 g-brd-primary g-brd-white--hover g-bg-black-opacity-0_8 g-transition-0_3 g-pa-20\">\n <div class=\"g-flex-middle-item\">\n <h4 class=\"h6 g-color-white g-font-weight-600 text-uppercase g-mb-10 CMS_edit\">이곳을 편집해주세요.</h4>\n <p class=\"g-color-white-opacity-0_7 mb-0 CMS_edit\">이곳을 편집해주세요.</p>\n </div>\n </article>\n <!-- End Article -->\n </div>\n </div>\n\n </div>\n <!-- End Info Blocks -->\n</section>","datecreated":"2019-05-22T01:19:10.841Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  9. 2019-06-25 01:00 | Administrator | {"id":"19052210190003dfi1","name":"Ui-topic-naea-v1","reference":"ui/topic/naea/ui-topic-naea-v1.html","body":"<style></style>\n<script>\n COMPONENT('ui-topic-naea-v1', function(component, config){\n component.make = function(){};\n });\n</script>\n\n<section class=\"g-brd-top g-brd-bottom g-brd-gray-light-v4\">\n <div class=\"container text-center g-py-50--md g-py-20\">\n <h2 class=\"h2 text-uppercase g-font-weight-300 CMS_edit\">이곳을 편집하세요.</h2>\n <p class=\"lead g-px-100--md g-mb-20 CMS_edit CMS_multiline\">\n 이곳을 편집하세요.<br/>\n 이곳을 <strong class=\"g-color-primary\">편집</strong>하세요.\n </p>\n </div>\n</section>","datecreated":"2019-05-22T01:19:10.841Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  10. 2019-06-25 01:00 | Administrator | {"id":"19051916180001bld1","name":"Contrib-confirm","reference":"contrib/contrib-confirm.html","body":"<style>\n .ui-confirm-noscroll, .ui-confirm-noscroll body { overflow: hidden; }\n .ui-confirm { background-color: rgba(255,255,255,0.9); position: fixed; left: 0; right: 0; bottom: 0; top: 0; z-index: 100; width: 100%; display: table; height: 100%; transition: 0.5s all; opacity: 0; }\n .ui-confirm > div { display: table-cell; vertical-align: middle; text-align: center; color: white; padding: 15px; }\n .ui-confirm-body { padding: 30px; position: relative; display: inline-block; border-radius: 4px; max-width: 600px; text-align: left; transform: scale(0.5); transition: 0.3s all; color: gray; border: 8px solid #E0E0E0; background-color: white; box-shadow: 0 0 30px rgba(0,0,0,0.1); }\n .ui-confirm-message { border-bottom: 1px solid #F0F0F0; padding: 0 0 20px 0; margin-bottom: 20px; }\n .ui-confirm button { font-size: 12px; position: relative; display: inline-block; cursor: pointer; outline: 0; background-color: #E0E0E0; border: 0; border-radius: 4px; color: gray; padding: 0 20px; height: 30px; font-weight: normal; margin: 0 10px 0 0; }\n .ui-confirm button .fa { margin-right: 5px; }\n .ui-confirm button:hover { opacity: 0.9; }\n .ui-confirm button[data-index=\"0\"] { color: white; font-weight: bold; font-weight: bold; background-color: #EC2C28; }\n .ui-confirm-visible { opacity: 1; }\n .ui-confirm-visible .ui-confirm-body { -webkit-transform: scale(1); transform: scale(1); }\n .ui-confirm-click { transform: scale(1.07) !important; }\n\n .ui-dark .ui-confirm { background-color: rgba(0,0,0,0.9) }\n .ui-dark .ui-confirm-body { border: 10px solid #303030 }\n\n @media(max-width: 767px) {\n .ui-confirm button { display: block; margin: 10px 0 0; display: block; width: 100%; }\n }\n</style>\n\n<script>\n COMPONENT('contrib-confirm', function(self) {\n\n var is, visible = false;\n\n self.readonly();\n self.singleton();\n self.nocompile && self.nocompile();\n\n self.make = function() {\n\n self.aclass('ui-confirm d-none');\n\n self.event('click', 'button', function() {\n self.hide($(this).attrd('index').parseInt());\n });\n\n self.event('click', function(e) {\n var t = e.target.tagName;\n if (t !== 'DIV')\n return;\n var el = self.find('.ui-confirm-body');\n el.aclass('ui-confirm-click');\n setTimeout(function() {\n el.rclass('ui-confirm-click');\n }, 300);\n });\n\n $(window).on('keydown', function(e) {\n if (!visible)\n return;\n var index = e.which === 13 ? 0 : e.which === 27 ? 1 : null;\n if (index != null) {\n self.find('button[data-index=\"{0}\"]'.format(index)).trigger('click');\n e.preventDefault();\n e.stopPropagation();\n }\n });\n };\n\n self.show = self.confirm = function(message, buttons, fn) {\n self.callback = fn;\n\n var builder = [];\n\n for (var i = 0; i < buttons.length; i++) {\n var item = buttons[i];\n var icon = item.match(/\"[a-z0-9-]+\"/);\n if (icon) {\n item = item.replace(icon, '').trim();\n icon = '<i class=\"fa fa-{0}\"></i>'.format(icon.toString().replace(/\"/g, ''));\n } else\n icon = '';\n builder.push('<button data-index=\"{1}\">{2}{0}</button>'.format(item, i, icon));\n }\n\n self.content('ui-confirm-warning', '<div class=\"ui-confirm-message\">{0}</div>{1}'.format(message.replace(/\\n/g, '<br />'), builder.join('')));\n };\n\n self.hide = function(index) {\n self.callback && self.callback(index);\n self.rclass('ui-confirm-visible');\n visible = false;\n setTimeout2(self.id, function() {\n $('html').rclass('ui-confirm-noscroll');\n self.aclass('d-none');\n }, 1000);\n };\n\n self.content = function(cls, text) {\n $('html').aclass('ui-confirm-noscroll');\n !is && self.html('<div><div class=\"ui-confirm-body\"></div></div>');\n self.find('.ui-confirm-body').empty().append(text);\n self.rclass('d-none');\n visible = true;\n setTimeout2(self.id, function() {\n self.aclass('ui-confirm-visible');\n }, 5);\n };\n });\n</script>","datecreated":"2019-05-19T07:18:34.686Z","picture":"","icon":"","category":"Contrib","dateupdated":"2019-06-25T00:59:34.994Z"}
  11. 2019-06-25 01:00 | Administrator | {"id":"19051300390001apf1","name":"Ui-post-v1","reference":"ui/post/ui-post-v1.html","body":"<style>\n .ui-post-v1 .component-blocker { background: #eeeeee80; }\n .ui-post-v1 .component-blocker .progress { top: 50%; left: 50%; transform: translate(-50%, -50%); }\n .ui-post-v1 .component-blocker .blocker-message { top: 60%; left: 50%; transform: translate(-50%, -50%); }\n\n .ui-post-v1 .card-body.tab-content .tab-pane .post-editor { height: 500px; }\n .ui-post-v1 .tab-post-read {\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n max-width: 15rem;\n }\n\n .ui-post-v1 ul.recent-post-list { padding-left: 0; }\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item { cursor: pointer; min-height: 40px; padding: .25rem; }\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item:nth-child(even) { background-color:#f7f7f7; }\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item:hover { background-color:rgba(0,0,0,.075); }\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item a.post-title { max-width: 70%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item a.post-title:hover { text-decoration: none; }\n\n @media (min-width: 768px) {\n .ui-post-v1 ul.recent-post-list li.recent-post-list-item a.post-title { max-width: 80%; }\n }\n\n /* error message style */\n .ui-post-v1 .ui-post-v1-title.with-error {\n border-top-left-radius: .25rem;\n border-top-right-radius: .25rem;\n }\n\n .ui-post-v1 .ui-post-v1-title.with-error + .card-body {\n border-bottom-left-radius: .25rem;\n border-bottom-right-radius: .25rem;\n }\n\n /* ╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗ ╦ ╔═╗╔═╗ ╔═╗╔╦╗╦ ╦╦ ╦╔╗╔╔═╗ */\n /* ║║╠═╣ ║ ╠═╣ ║ ╠═╣╠╩╗║ ║╣ ╚═╗ ╚═╗ ║ ╚╦╝║ ║║║║║ ╦ */\n /* ═╩╝╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩╚═╝╩═╝╚═╝╚═╝ ╚═╝ ╩ ╩ ╩═╝╩╝╚╝╚═╝ */\n .ui-post-v1 .post-list-tab-content .dataTables_wrapper .dataTables_filter {\n width:100%;\n }\n\n .ui-post-v1 .post-list-tab-content table.dataTable thead tr th { white-space: nowrap; }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr { cursor: pointer; }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr.post-notice {\n border-bottom: 1px solid --var(primary);\n }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr:hover {\n background-color:rgba(0,0,0,.075);\n }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr td.post-title {\n white-space: normal;\n max-width: 100%;\n outline: 0;\n }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr td.post-title div.post-title-wrapper a.post-title,\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr td.post-writer a.post-writer {\n color: unset;\n }\n\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr td.post-title div.post-title-wrapper a.post-title:hover,\n .ui-post-v1 .post-list-tab-content table.dataTable tbody tr td.post-writer a.post-writer:hover {\n text-decoration: none;\n }\n\n .ui-post-v1 .post-list-tab-content .dataTables_wrapper .dataTables_filter {text-align: center!important;}\n .ui-post-v1 .post-list-tab-content .dataTables_paginate ul.pagination {justify-content: center!important;}\n\n /* ╦═╗╔═╗╔═╗╔═╗╔═╗╔╗╔╔═╗╦╦ ╦╔═╗ ╔═╗╔═╗╦═╗╔╦╗ ╔╦╗╔═╗╔╗ ╦ ╔═╗ */\n /* ╠╦╝║╣ ╚═╗╠═╝║ ║║║║╚═╗║╚╗╔╝║╣ ║ ╠═╣╠╦╝ ║║ ║ ╠═╣╠╩╗║ ║╣ */\n /* ╩╚═╚═╝╚═╝╩ ╚═╝╝╚╝╚═╝╩ ╚╝ ╚═╝ ╚═╝╩ ╩╩╚══╩╝ ╩ ╩ ╩╚═╝╩═╝╚═╝ */\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable thead { display: none; }\n\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr,\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody td { display: block; }\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody td { border: 0; }\n .ui-post-v1.cards-table .post-list-tab-content .dataTables_paginate ul.pagination li.paginate_button.page-item a.page-link {padding: .25rem .5rem;}\n\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr:first-child { margin-top: 1.5rem; }\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr {\n margin-bottom: 2.14286rem !important;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);\n border: solid 1px #0000 !important;\n border-color: #ccc !important;\n padding: 2.85714rem !important;\n /* border-right-width: 0!important; */\n /* border-left-width: 0!important; */\n background-color: #0000;\n }\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr:hover {\n background-color: #00000013;\n }\n\n/* .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-title a { text-decoration: none; } */\n/* .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-title { text-align: center; font-size: 1.5rem */; }\n/* .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-posted-at { text-align: right; } */\n\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-seq,\n .ui-post-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-view-count { display: none; }\n\n /* ╔═╗ ╦ ╦╦╦ ╦ ╦╔═╗ ╔═╗╔╦╗╦ ╦╦ ╔═╗ */\n /* ║═╬╗║ ║║║ ║ ║╚═╗ ╚═╗ ║ ╚╦╝║ ║╣ */\n /* ╚═╝╚╚═╝╩╩═╝╩═╝╚╝╚═╝ ╚═╝ ╩ ╩ ╩═╝╚═╝ */\n .ui-post-v1 .post-editor-tab-content .post-editor-title { margin-bottom: .5rem; }\n .ui-post-v1 .post-editor-tab-content .ql-toolbar {\n border-bottom: 0 !important;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-title,\n .ui-post-v1 .post-editor-tab-content .post-editor-toolbar,\n .ui-post-v1 .ql-container {\n border-left: 0 !important;\n border-right: 0 !important;\n border-radius: 0;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item {\n cursor: pointer;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item span.filename {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:nth-child(even) {\n background-color:#f7f7f7;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:not(:last-child) {\n border-bottom: 1px solid #ccc;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:hover {\n background-color: #3398dc2d;\n }\n\n .ui-post-v1 .post-read-content .post-viewer {\n border: 0;\n }\n\n /* 본문 첨부파일 객체 스타일 */\n .ui-post-v1 .ql-container .ql-editor div.attachment {\n display: flex;\n }\n\n .ui-post-v1 .ql-container .ql-editor div.attachment div.attachment-inner {\n min-width: 200px;\n max-width: 500px;\n padding: 1.5rem;\n margin: auto;\n cursor: pointer;\n text-align: center;\n border: 1px solid lightgray;\n display: flex;\n }\n\n .ui-post-v1 .ql-container .ql-editor div.attachment div.attachment-inner:hover {\n background-color: #3398dc2d;\n }\n\n .ui-post-v1 .ql-container .ql-editor div.attachment div.attachment-inner i.attachment-icon {\n margin: auto 0 auto auto;\n }\n .ui-post-v1 .ql-container .ql-editor div.attachment div.attachment-inner span.attachment-filename {\n margin: auto auto auto .75rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 100%;\n }\n\n /* 뷰어 하단 첨부파일 리스트 스타일 */\n .ui-post-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item:nth-child(odd),\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item:nth-child(odd) {\n background-color: #f7f7f7;\n }\n\n .ui-post-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item:hover,\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item:hover {\n background-color: #00000013;\n }\n\n .ui-post-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item a.post-viewer-attachment-download:hover,\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item a {\n text-decoration: none;\n }\n\n .ui-post-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item i.remove-attachment {\n cursor: pointer;\n }\n</style>\n\n<script total>\n const fs = require('fs');\n const CONSTANTS = {\n PERMISSIONS: {\n POST: {\n READABLE: 'post.read',\n WRITABLE: 'post.write',\n NOTICE_WRITABLE: 'post.notice.write',\n FILE: {\n UPLOADABLE: 'post.file.upload',\n DOWNLOADABLE: 'post.file.download',\n },\n COMMENT: {\n READABLE: 'post.comment.read',\n WRITABLE: 'post.comment.write',\n }\n },\n },\n };\n const Helpers = {};\n\n const readChunk = require('read-chunk');\n const fileType = require('file-type');\n\n const Post = DB('post');\n const PostGroup = DB('postgroup');\n\n const Attachment = DB('attachment', {});\n const AttachmentFile = DB('attachment.files');\n\n const ObjectID = require('mongodb').ObjectID;\n\n // ╔═╗╔═╗╔═╗╔╦╗ ╦ ╦╔╦╗╦╦ ╦╔╦╗╦╔═╗╔═╗\n // ╠═╝║ ║╚═╗ ║ ║ ║ ║ ║║ ║ ║ ║║╣ ╚═╗\n // ╩ ╚═╝╚═╝ ╩ ╚═╝ ╩ ╩╩═╝╩ ╩ ╩╚═╝╚═╝o\n // ────────────────────────────────────\n Helpers.cache = [];\n\n /**\n * 세션에 필요한 권한이 존재하는지 확인\n * @param {Object} session this.req.me 객체 (User 스키마)\n * @param {ObjectID} postGroupId postGroupId\n * @param {String} need 필요한 권한 ( CONSTANTS.PERMISSIONS )\n * @return {Boolean}\n */\n Helpers.hasPermission = async function(session, postGroupId, need){\n if ( session.isSysadmin ) return true;\n let postGroup = Helpers.cache[postGroupId.toString()];\n\n if ( !postGroup )\n postGroup = Helpers.cache[postGroupId.toString()] = await PostGroup.findOne({ _id: postGroupId });\n\n let sessionHas = _.map(session.parents, function(parent){ return parent._id; });\n\n let exchanged = _.chain(sessionHas).map(function(_id){\n return postGroup.permission[_id.toString()];\n }).flatten().uniq().value();\n\n if ( exchanged.indexOf(need) < 0 )\n return false;\n return true;\n };\n\n // ╦═╗╔═╗╔═╗╔╦╗╔═╗╦ ╦╦ ╔═╗╔═╗╦ ╦═╗╔═╗╦ ╦╔╦╗╔═╗\n // ╠╦╝║╣ ╚═╗ ║ ╠╣ ║ ║║ ╠═╣╠═╝║ ╠╦╝║ ║║ ║ ║ ║╣\n // ╩╚═╚═╝╚═╝ ╩ ╚ ╚═╝╩═╝ ╩ ╩╩ ╩ ╩╚═╚═╝╚═╝ ╩ ╚═╝o\n // ────────────────────────────────────────────────\n\n // 게시판 API\n ROUTE('POST /api/post-groups', ['*PostGroup --> @create-post-group' ]);\n ROUTE('DELETE /api/post-groups/{postgroupid}', ['*PostGroup --> @remove' ]);\n ROUTE('PUT /api/post-groups/{postgroupid}', ['*PostGroup --> @update' ]);\n ROUTE('GET /api/post-groups', ['*PostGroup --> @query' ]);\n ROUTE('GET /api/post-groups/{postgroupid}/include-notices', ['*PostGroup --> @get-with-notices' ]);\n ROUTE('GET /api/post-groups/{postgroupid}', ['*PostGroup --> @get' ]);\n\n // 파일 다운로드\n ROUTE('GET /api/posts/attachment/{fileid}', async function(fileid){\n let controller = this;\n\n fileid = ObjectID(fileid);\n let attachment = await AttachmentFile.findOne({ _id: fileid });\n\n if ( !(await Helpers.hasPermission(controller.req.me, attachment.metadata.postgroupid, CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) ) )\n return controller.throw403(TRANSLATE(controller.req.me.language, 'You Do Not Have Permission.'));\n\n controller.header('Content-Type', attachment.metadata.mime);\n controller.header('Content-Disposition', `attachment; filename=\"${attachment.filename}\"`);\n\n Attachment.openDownloadStream(fileid).pipe(controller.res);\n });\n\n // 파일 업로드\n ROUTE('POST /api/posts/{postgroupid}/{postid}/attachment', async function(postgroupid, postid){\n let controller = this;\n let uploadResult = [];\n let progressedCount = 0;\n\n let postGroupId = ObjectID(postgroupid);\n\n if ( !(await Helpers.hasPermission(controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE) ) )\n return controller.throw403(TRANSLATE(controller.req.me.language, 'You Do Not Have Permission.'));\n\n let postId = ObjectID(postid);\n\n try\n {\n controller.files.wait(function(file, next){\n // 바이너리로부터 파일 유형 확인\n let ft = fileType(readChunk.sync(file.path, 0, fileType.minimumBytes));\n\n // 알 수 없는 파일\n if ( !ft )\n ft = _.assign({}, { ext: '', mime: 'application/octet-stream' });\n\n // svg에 대해서는 예외처리\n if ( ft.ext === 'xml' && /\\.svg$/.test(file.filename) )\n _.assign(ft, { ext: 'svg', mime: 'image/svg+xml'});\n\n fs.createReadStream(file.path)\n .pipe(Attachment.openUploadStream(file.filename, {\n metadata: {\n postgroupid: postGroupId,\n postid: postId,\n mime: ft.mime,\n extension: ft.ext,\n size: file.length,\n }\n }))\n .on('finish', function(meta) {\n let result = _.assign({}, meta, {\n downloadURI: `/api/posts/attachment/${meta._id}`,\n fileId: _.isArray(controller.body['file-id']) ? controller.body['file-id'][progressedCount++] : controller.body['file-id'],\n });\n\n if ( file.width ) result.width = file.width;\n if ( file.height ) result.height = file.height;\n\n // HttpFile 객체에 속성 추가\n uploadResult.push(result);\n\n if ( controller.files.length === uploadResult.length ) controller.json(uploadResult);\n });\n\n setImmediate(next);\n });\n }\n catch(err)\n {\n U.log.error(err);\n controller.throw500(err);\n }\n }, ['upload'], 51200); // 50MB\n\n ROUTE('GET /api/posts/{postgroupid}', ['*Post --> @get-posts' ]);\n ROUTE('GET /api/posts/{postgroupid}/recent/{length}', ['*Post --> @get-recent-posts' ]);\n ROUTE('GET /api/posts/{postgroupid}/notices', ['*Post --> @get-notices' ]);\n ROUTE('GET /api/posts/{postgroupid}/{postid}', ['*Post --> @get-post' ]);\n ROUTE('PATCH /api/posts/{postgroupid}/{postid}', ['*Post --> @patch-post' ]);\n ROUTE('POST /api/posts/{postgroupid}', ['*Post --> @create-post' ]);\n ROUTE('DELETE /api/posts/{postgroupid}/{postid}', ['*Post --> @remove-post' ]);\n\n\n // 게시글 - 댓글 API\n ROUTE('POST /api/posts/{postgroupid}/{postid}/comments', ['*Post --> @create-comment' ]);\n ROUTE('PATCH /api/posts/{postgroupid}/{postid}/comments/{commentid}', ['*Post --> @patch-comment' ]);\n ROUTE('DELETE /api/posts/{postgroupid}/{postid}/comments/{commentid}', ['*Post --> @remove-comment' ]);\n\n\n // ╔╦╗╔═╗╔═╗╦╔╗╔╔═╗ ╔═╗╔═╗╦ ╦╔═╗╔╦╗╔═╗\n // ║║║╣ ╠╣ ║║║║║╣ ╚═╗║ ╠═╣║╣ ║║║╠═╣\n // ═╩╝╚═╝╚ ╩╝╚╝╚═╝ ╚═╝╚═╝╩ ╩╚═╝╩ ╩╩ ╩o\n // ─────────────────────────────────────\n let postSchema = GETSCHEMA('Post');\n let postGroupSchema = GETSCHEMA('PostGroup');\n let postCommentSchema = GETSCHEMA('PostComment');\n\n if ( !postSchema ) NEWSCHEMA('Post', makePostSchema);\n else makePostSchema(postSchema);\n\n if ( !postGroupSchema ) NEWSCHEMA('PostGroup', makePostGroupSchema);\n else makePostGroupSchema(postGroupSchema);\n\n if ( !postCommentSchema ) NEWSCHEMA('PostComment', makePostCommentSchema);\n else makePostCommentSchema(postCommentSchema);\n\n function makePostCommentSchema(schema){\n schema.define('_id', 'String(24)');\n\n schema.define('content', 'String(1000)');\n\n schema.define('writer', 'String(24)');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n };\n\n function makePostGroupSchema(schema){\n schema.define('_id', 'String(24)');\n schema.define('name', 'String(100)');\n schema.define('features', 'Array');\n schema.define('description', 'String(300)');\n schema.define('seqCursor', 'Number');\n schema.define('permission', 'Object');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n\n schema.setQuery(async function($){\n let postGroups = PostGroup.find({}).sort({ name: 1 }).collation({ locale: $.controller.req.me.language, numericOrdering: true });\n $.callback( await postGroups.toArray() );\n });\n\n schema.setGet(async function($){\n $.callback( await PostGroup.findOne({ _id: ObjectID($.params.postgroupid) }) );\n });\n\n schema.setRemove(async function($){\n if ( await Post.countDocuments({ postgroupid: ObjectID($.params.postgroupid) }) > 0 )\n return $.controller.throw404(TRANSLATE($.controller.req.me.language, 'There Are Still Exists Post.'));\n\n await PostGroup.removeOne({ _id: ObjectID($.params.postgroupid) });\n\n delete Helpers.cache[postGroupId.toString()];\n\n $.controller.status = 204;\n $.controller.plain();\n });\n\n schema.setUpdate(async function($){\n let model = $.model.$clean();\n let postGroupId = ObjectID($.params.postgroupid);\n\n await PostGroup.updateOne({ _id: postGroupId }, {\n $set: _.omit(model, '_id')\n });\n\n Helpers.cache[postGroupId.toString()] = await PostGroup.findOne({ _id: postGroupId });\n\n $.callback( Helpers.cache[postGroupId.toString()] );\n });\n\n schema.addWorkflow('create-post-group', async function($){\n let model = $.model.$clean();\n\n let created = (await PostGroup.insertOne(_.omit(model, '_id'))).ops[0]\n\n Helpers.cache[created._id.toString()] = created;\n\n $.callback( created );\n });\n\n // make와 관여하는 함수로 error status로 반환되면 make 함수 자체가 동작하지 않으므로 err 속성에 에러여부를 포함하여 200 상태로 반환\n schema.addWorkflow('get-with-notices', async function($){\n if ( !$.params.postgroupid || $.params.postgroupid === 'dummy' )\n return $.callback({ err: true, message: TRANSLATE($.controller.req.me.language, 'There is no Post Group.') });\n\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.callback({ err: true, message: TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.') })\n\n let postGroup = await PostGroup.findOne({ _id: postGroupId });\n\n let notices = await Post.aggregate([\n { $match: { postgroupid: postGroup._id, isnotice: true } },\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'content': 0, 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray();\n\n $.callback( _.assign(postGroup, { notices: notices }) );\n });\n };\n\n function escapeHtml (string) {\n let str = '' + string;\n let match = /[\"'&<>]/.exec(str);\n\n if (!match) return str;\n\n let escaped;\n let html = '';\n let index = 0;\n let lastIndex = 0;\n\n for ( index = match.index; index < str.length; index++ )\n {\n switch ( str.charCodeAt(index) )\n {\n case 34: // \"\n escape = '&quot;'; break;\n case 38: // &\n escape = '&amp;'; break;\n case 39: // '\n escape = '&#39;'; break;\n case 60: // <\n escape = '&lt;'; break;\n case 62: // >\n escape = '&gt;'; break;\n default: continue;\n }\n\n if (lastIndex !== index) html += str.substring(lastIndex, index);\n\n lastIndex = index + 1;\n html += escaped;\n }\n\n return lastIndex !== index ? html + str.substring(lastIndex, index) : html;\n }\n\n function makePostSchema(schema){\n schema.define('postgroupid', 'String(24)');\n schema.define('_id', 'String(24)');\n schema.define('id', 'UID');\n schema.define('seq', 'Number');\n schema.define('idcategory', 'String(50)');\n schema.define('template', 'String(30)');\n schema.define('type', ['html', 'markdown']);\n schema.define('name', 'String(100)');\n schema.define('author', 'String(24)');\n schema.define('description', 'String(300)');\n schema.define('summary', 'String(500)');\n schema.define('keywords', 'String(200)');\n schema.define('language', 'String'); // Only information\n schema.define('search', 'String(1000)');\n schema.define('pictures', '[String]'); // URL addresses for first 5 pictures\n schema.define('content', 'Object'); // quill editor delta object\n schema.define('bodywidgets', '[String(22)]'); // List of all used widgets\n schema.define('ispublished', 'Boolean');\n schema.define('isnotice', 'Boolean');\n schema.define('date', 'Date');\n schema.define('widgets', '[Object]'); // List of dynamic widgets, contains Array of ID widget\n schema.define('signals', '[String(30)]');\n schema.define('viewcount', 'Number');\n schema.define('attachments', '[String(24)]');\n schema.define('comments', '[Object]');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n\n schema.define('comment', 'String(1000)');\n\n schema.addWorkflow('create-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let post = $.model.$clean();\n let postGroup = await PostGroup.findOneAndUpdate({ _id: postGroupId }, { $inc: { seqCursor: 1 } });\n\n post.postgroupid = postGroup.value._id;\n post.seq = postGroup.value.seqCursor + 1;\n post.viewcount = 0;\n\n post.name = escapeHtml(post.name);\n post.attachments = _.map(post.attachments, function(attachment){ return ObjectID(attachment); });\n\n let created = (\n await Post.insertOne(\n _.assign(\n _.omit(post, '_id'),\n { author: $.controller.req.me._id }\n )\n )\n ).ops[0];\n\n await AttachmentFile.updateMany({ _id: { $in: post.attachments } }, {\n $set: {\n 'metadata.postgroupid': created.postgroupid,\n 'metadata.postid': created._id,\n }\n });\n\n $.callback(\n _.assign({}, created, {\n attachments: await AttachmentFile.find({ _id: { $in: post.attachments }}).toArray(),\n author: $.controller.req.me\n })\n );\n });\n\n schema.addWorkflow('get-recent-posts', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n posts = await Post.find({ postgroupid: postGroupId }).sort({ createdAt: -1 }).limit(Number($.params.length || 5)).toArray();\n $.callback(posts);\n });\n\n schema.addWorkflow('get-notices', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n $.callback(\n await Post.aggregate([\n { $match: { 'postgroupid': postGroupId, 'isnotice': true } },\n { $sort: { createdAt: -1 } },\n\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray()\n );\n });\n\n schema.addWorkflow('get-posts', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n $.controller.req.query = U.controller.req.parseQueryString($.controller.req.uri.query, { extended: true });\n\n // 정렬 기본값\n if ( !$.controller.req.query.order ) $.controller.req.query.order = [{ column: 0, dir: 'desc' }];\n $.controller.req.query.order = $.controller.req.query.order.map(function(item){ return _.assign(item, { column: Number(item.column) }); });\n\n let sortor = {};\n\n _.each($.query.order, function(order){\n sortor[$.query.columns[order.column].data] = order.dir.toLowerCase() === 'desc' ? -1 : 1;\n });\n\n let query = { 'postgroupid': postGroupId, 'isnotice': false };\n\n if ( $.query.search.value )\n {\n let searchKeyword = new RegExp($.query.search.value);\n query.$or = [ { name: searchKeyword } ];\n }\n\n // 페이징 기본값\n let pagination = { start: 0, length: 15 };\n\n if ( $.query.start ) pagination.start = Number($.query.start);\n if ( $.query.length ) pagination.length = Number($.query.length);\n\n if ( pagination.length > 200 )\n return $.controller.throw400(TRANSLATE($.controller.req.me.language, '\"length\" parameter must be least of 200'));\n\n let totalCount = await Post.countDocuments(query);\n\n let posts = await Post.aggregate([\n { $match: query },\n { $sort: sortor },\n\n { $skip: Number($.query.start) },\n { $limit: Number($.query.length) },\n\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray();\n\n $.callback({\n draw: Number($.query.draw),\n recordsTotal: totalCount,\n recordsFiltered: totalCount,\n data: posts,\n });\n });\n\n schema.addWorkflow('get-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n // 권한 체크\n if ( !await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n try\n {\n let post = await Post.aggregate([\n {\n $match: {\n $and: [ { postgroupid: postGroupId }, { _id: ObjectID($.params.postid) } ]\n }\n },\n { $lookup: { from: 'attachment.files', localField: 'attachments', foreignField: '_id', as: 'attachments' } },\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.secret': 0, 'author.salt': 0 } },\n\n { $unwind: { path: '$comments', preserveNullAndEmptyArrays: true } },\n { $lookup: { from: 'user', localField: 'comments.author', foreignField: '_id', as: 'comments.author' } },\n { $unwind: { path: '$comments.author', preserveNullAndEmptyArrays: true } },\n { $project: { 'comments.author.secret': 0, 'comments.author.salt': 0 } },\n\n { $group: { _id: '$_id', doc: { $first: '$$ROOT' }, comments: { $push: '$comments'} } },\n ]);\n\n if ( !await post.hasNext() ) return $.controller.throw404();\n\n post = await post.next();\n\n // 조회수 올림\n await Post.updateOne({\n _id: post._id,\n postgroupid: post.postgroupid,\n },\n {\n $inc: { viewcount: 1 }\n });\n\n $.callback( post );\n }\n catch(err)\n {\n U.log.error(err);\n controller.throw500(err);\n }\n });\n\n schema.addWorkflow('patch-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let post = $.model.$clean();\n\n post.name = escapeHtml(post.name);\n post.attachments = _.map(post.attachments, function(attachment){ return ObjectID(attachment); });\n\n let updated = await Post.updateOne({ postgroupid: postGroupId, _id: ObjectID($.params.postid), author: $.controller.req.me._id }, {\n $set: {\n name: post.name,\n content: post.content,\n isnotice: post.isnotice,\n attachments: post.attachments,\n }\n });\n\n $.controller.status = 200;\n $.controller.plain();\n });\n\n schema.addWorkflow('remove-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n await Post.removeOne({\n postgroupid: postGroupId,\n _id: ObjectID($.params.postid),\n author: $.controller.req.me._id\n });\n\n $.controller.status = 204;\n $.controller.plain();\n });\n\n schema.addWorkflow('create-comment', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let model = $.model.$clean();\n\n let now = new Date();\n let comment = {\n _id: ObjectID(),\n comment: model.comment,\n author: $.controller.req.me._id,\n createdAt: now,\n updatedAt: now,\n };\n\n await Post.updateOne({\n postgroupid: postGroupId,\n _id: ObjectID($.params.postid),\n },\n {\n $push: { comments: comment }\n });\n\n $.callback(comment);\n });\n\n schema.addWorkflow('patch-comment', async function($){\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let model = $.model.$clean();\n\n let postGroupId = ObjectID($.params.postgroupid);\n let postId = ObjectID($.params.postid);\n let commentId = ObjectID($.params.commentid);\n\n await Post.updateOne({ postgroupid: postGroupId, _id: postId },\n { $set: { 'comments.$[element].comment': model.conmment } },\n { arrayFilters: [ { 'element._id': commentId, author: $.controller.req.me._id } ] }\n );\n\n $.callback(\n await Post.findOne({ postgroupid: postGroupId, _id: postId })\n );\n });\n\n schema.addWorkflow('remove-comment', async function($){\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let postGroupId = ObjectID($.params.postgroupid);\n let postId = ObjectID($.params.postid);\n let commentId = ObjectID($.params.commentid);\n\n await Post.updateOne({ postgroupid: postGroupId, _id: postId },\n { $pull: { comments: { _id: commentId, author: $.controller.req.me._id } } },\n );\n\n $.controller.status = 200;\n $.controller.plain();\n });\n };\n</script>\n\n<script editor>\n // let postGroupId = ObjectID().toString();\n // option('post-group-name', '게시판 명', '자유게시판');\n // option('post-group-id', '게시판 ID', postGroupId);\n // option('post-group-id', '게시판 ID', '아이디는 자동생성됩니다.');\n // option('post-group-description', '게시판 설명', '');\n // option('theme', '테마', 'blue', [\n // { text: '파랑', value: 'blue' },\n // { text: '초록', value: 'green' },\n // { text: '분홍', value: 'pink' },\n // { text: '어두운 보라', value: 'darkpurple' },\n // { text: '물', value: 'teal' },\n // ]);\n\n option('showTitle', '게시판 명 숨기기', false);\n option('mode', '모드', 'post', [\n { text: '게시판', value: 'post' },\n { text: '최신글', value: 'recent' },\n ]);\n\n option.type = 'post';\n\n exports.configure = async function(options, $el, prevOptions){\n let $component = $('.ui-post-v1', $el);\n\n let themes = ['blue','green','pink','darkpurple','teal'];\n\n let postGroup = _.find(options._datasrc.postGroups, { _id: options.selectedPostGroup });\n\n // 설정값 data 속성 저장\n $component.attr({\n 'data-post-group-id': postGroup._id,\n 'data-post-mode': options['mode'],\n 'data-instant-id': $component.attr('data-instant-id') || helpers.getRandomId('short'),\n });\n\n // 테마 적용\n // $component.removeClass(themes.map(function(item){ return `g-brd-${item}`; }).join(' ')).addClass(`g-brd-${options['theme']}`);\n // $('.ui-post-v1-title', $component).removeClass(themes.map(function(item){ return `g-bg-${item}`; }).join(' ')).addClass(`g-bg-${options['theme']}`);\n\n // 컴포넌트 타이틀\n $('.post-group-name', $component).text(postGroup.name.replace(/\\s\\(\\d+\\)$/, ''));\n\n // 컴포넌트 타이틀 숨기기\n $('.ui-post-v1-title', $component).toggleClass('d-editor-block', option.showTitle);\n };\n</script>\n\n<script>\n COMPONENT('ui-post-v1', 'striped:true;bordered:false;totalCount:false;length:false;searchBar:false;hover:true;placeholder:내용을 입력해주세요...;titlePlaceHolder:제목을 입력해주세요...', function(component, config){\n const CONSTANTS = {\n $RECENT_POST_LIST: $(`<ul class=\"recent-post-list\"></ul>`),\n MODES: {\n RECENT: 'recent',\n POST: 'post',\n },\n POST_GROUP: {\n FEATURES: {\n NOTICE: 'notice',\n ATTACHMENT: 'attachment',\n COMMENT: 'comment',\n }\n },\n POST_GROUP_TYPES: {\n FILE_UPLOAD: 'file',\n NORMAL: 'normal',\n },\n TEMPLATES: {\n DATATABLE: {\n $PAGINATE: $(`<nav class=\"text-center\" aria-label=\"Page Navigation\"><ul class=\"list-inline\"></ul></nav>`)\n },\n VIEWER: {\n ATTACHMENT: {\n ITEM: $('<li class=\"d-flex\"></li>'),\n }\n },\n $BROWSE_FILE_INPUT: $('<input type=\"file\" multiple/>'),\n $TOOLBAR_BTN_FILE_UPLOAD: $('<button class=\"browse-file fas fa-upload\"></button>'),\n },\n PERMISSIONS: {\n POST: {\n READABLE: 'post.read',\n WRITABLE: 'post.write',\n NOTICE_WRITABLE: 'post.notice.write',\n FILE: {\n UPLOADABLE: 'post.file.upload',\n DOWNLOADABLE: 'post.file.download',\n },\n COMMENT: {\n READABLE: 'post.comment.read',\n WRITABLE: 'post.comment.write',\n }\n },\n },\n };\n\n component.postGroupSettings = {};\n component.allowedPermission = [];\n\n component.prevScrollTop = 0;\n component.sendFileList = [];\n\n component.$el = component.element;\n component.$blocker = $('.component-blocker', component.$el);\n component.$title = $('.ui-post-v1-title span', component.$el);\n component.$cardTitle = $('.ui-post-v1-title', component.$el);\n component.$cardBody = $('.card-body.tab-content', component.$el);\n component.$initSpinner = $('.init-spinner', component.$el);\n\n component.$progress = $('.progress', component.$blocker);\n component.$progressBar = $('.progress-bar', component.$progress);\n component.$blockerMessage = $('.blocker-message', component.$blocker);\n component.$blockerMessageTxt = $('.message', component.$blockerMessage);\n\n component.notices = [];\n\n component.instantID = component.$el.data('instant-id');\n if ( !component.instantID )\n {\n component.instantID = $.HSCore.helpers.getRandomId('short');\n component.$el.attr('data-instant-id', component.instantID).data('instant-id', component.instantID);\n }\n\n component.postGroupId = component.$el.data('post-group-id') || 'dummy';\n component.mode = component.$el.data('post-mode');\n\n component.$tabNavs = $('.nav.nav-tabs [data-toggle=\"tab\"]', component.$el);\n component.$tabContents = $('[role=\"tabpanel\"]', component.$el);\n\n component.$postListTab = component.$tabNavs.filter('.tab-post-list');\n component.$postViewerTab = component.$tabNavs.filter('.tab-post-read');\n component.$postEditorTab = component.$tabNavs.filter('.tab-post-editor');\n\n component.$postListTabContent = component.$tabContents.filter('.post-list-tab-content');\n component.$postViewerTabContent = component.$tabContents.filter('.post-read-content');\n component.$postEditorTabContent = component.$tabContents.filter('.post-editor-tab-content');\n\n component.$table = $('table.post-list', component.$postListTabContent);\n component.datatable = null; // datatables.api\n component.$originalPaginate = null;\n component.columnsDefines = [\n {\n data: 'seq', title: '번호', className: 'post-seq d-none d-md-block',\n render: function(data, type, row){\n return `<p class=\"m-0 px-3\">${data}</p>`;\n }\n },\n {\n data: 'name', title: '제목', className: 'post-title g-font-size-24 g-font-size-default--md',\n render: function(data, type, row){\n return `<div class=\"text-center text-md-left post-title-wrapper\">\n <a class=\"post-title\" href=\"#\">${ $.fn.dataTable.render.ellipsis(40)($.fn.dataTable.render.text().display(data), type, row) }</a>\n</div>`;\n }\n },\n // { data: 'seq', title: '글번호', width: 60, },\n {\n data: 'author.name', title: '작성자', className: 'post-writer g-font-size-14 g-font-size-default--md',\n render: function(data, type, row){\n return `\n<div class=\"d-flex\">\n <span class=\"ml-auto d-md-none\">작성자:</span>\n <a class=\"post-writer ml-2 mx-md-auto\" href=\"#\">${$.fn.dataTable.render.text().display(data)}</a>\n</div>`;\n }\n },\n {\n data: 'createdAt', title: '작성일', className: 'text-right text-md-center post-posted-at g-font-size-14 g-font-size-default--md',\n render: function(data, type, row){\n let createdAt = moment(data);\n let txt = createdAt.format('YYYY-MM-DD');\n\n if ( moment().diff(createdAt, 'days') === 0 ) txt = `오늘 ${createdAt.format('HH:mm')}`;\n\n return `\n<div class=\"d-flex\">\n <span class=\"ml-auto d-md-none\">작성일:</span>\n <p class=\"ml-2 mx-md-auto my-0\">${txt}</p>\n</div>`;\n }\n },\n { data: 'viewcount', title: '조회수', className: 'post-view-count d-none d-md-block' },\n ];\n\n component.$viewerScrollContainer = $('.post-viewer-scroll-container', component.$postViewerTabContent);\n component.$viewer = $('.post-viewer', component.$postViewerTabContent);\n component.$viewerMetadata = $('.post-viewer-metadata', component.$postViewerTabContent);\n component.$viewerControls = $('.post-viewer-controls', component.$postViewerTabContent);\n component.$viewerAttachments = $('.post-viewer-attachments', component.$postViewerTabContent);\n component.$viewerAttachmentList = $('.post-viewer-attachment-list', component.$postViewerTabContent);\n component.$viewerComments = $('.post-viewer-comments', component.$postViewerTabContent);\n component.$viewerCommentList = $('.post-viewer-comment-list', component.$postViewerTabContent);\n component.$viewerCommentForm = $('.post-viewer-comment-form', component.$postViewerTabContent);\n\n component.editor = null;\n component.$editorScrollContainer = $('.post-editor-scroll-container', component.$postEditorTabContent);\n component.$editor = $('.post-editor', component.$postEditorTabContent);\n component.$editorToolbar = $('.post-editor-toolbar', component.$postEditorTabContent);\n component.$editorPostMetadata = $('.post-editor-metadata-form', component.$postEditorTabContent);\n component.postEditForm = {};\n\n let inputTitleRandomId = $.HSCore.helpers.getRandomId();\n let inputNoticeRandomId = $.HSCore.helpers.getRandomId();\n let inputFileHideRandomId = $.HSCore.helpers.getRandomId();\n\n component.$editorPostMetadataForm = $(`\n<div class=\"form-group m-0 p-2\">\n <div class=\"form-check form-check-inline editor-post-notice-form\">\n <input class=\"form-check-input editor-post-notice\" type=\"checkbox\" id=\"${inputNoticeRandomId}\">\n <label class=\"form-check-label\" for=\"${inputNoticeRandomId}\">공지사항</label>\n </div>\n <div class=\"form-check form-check-inline editor-post-hide-attachment-form d-none\">\n <input class=\"form-check-input editor-post-hide-attachment\" type=\"checkbox\" id=\"${inputFileHideRandomId}\">\n <label class=\"form-check-label\" for=\"${inputFileHideRandomId}\">본문에서 파일 숨기기</label>\n </div>\n</div>`\n );\n component.$editorPostInputTitle = $(`<input type=\"text\" placeholder=\"${config.titlePlaceHolder}\" class=\"form-control rounded-0 border-left-0 border-right-0 border-bottom-0 border-top border-gray editor-post-title\" id=\"${inputTitleRandomId}\">`);\n component.$editorPostFormNotice = $('.editor-post-notice-form', component.$editorPostMetadataForm);\n component.$editorPostFormHideAttachment = $('.editor-post-hide-attachment-form', component.$editorPostMetadataForm);\n component.$editorPostInputNotice = $('input.editor-post-notice', component.$editorPostMetadataForm);\n component.$editorPostInputHideAttachment = $('input.editor-post-hide-attachment', component.$editorPostMetadataForm);\n\n component.$editorControls = $('.post-editor-controls', component.$postEditorTabContent);\n component.$editorAttachments = $('.post-editor-attachments', component.$postEditorTabContent);\n component.$editorAttachmentList = $('.post-editor-attachments-list', component.$postEditorTabContent);\n\n component.$postSnackbar = $('.post-snackbar', component.$el).get(0);\n component.$postConfirm = $('.post-confirm', component.$el).get(0);\n\n component.helpers = {};\n\n /**\n * 에러 메시지 파서\n * @param {Error} err 에러 객체\n * @return {String} 파싱된 메시지\n */\n component.helpers.parseErrorMessage = function(err){\n let errorMessageStart = '<div class=\"error\">';\n let errorMessageEnd = '</div>';\n\n if ( err.responseText )\n {\n if ( err.responseText.indexOf(errorMessageStart) < 0 ) return err.responseText;\n\n let messageStartIndex = err.responseText.indexOf(errorMessageStart) + errorMessageStart.length;\n let messageEndIndex = err.responseText.indexOf(errorMessageEnd, messageStartIndex);\n\n return err.responseText.substring(messageStartIndex, messageEndIndex);\n }\n\n return err.message;\n };\n\n\n component._getFileSize = function(size){\n let _size = size;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} B`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} KB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} MB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} GB`;\n\n return `1TB 이상`;\n };\n // ╦╔═╗╔═╗╔╦╗╔═╗╔═╗╔╗╔╔═╗╔╗╔╔╦╗ ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗\n // ║║ ║ ║║║║╠═╝║ ║║║║║╣ ║║║ ║ ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣\n // ╚╝╚═╝╚═╝╩ ╩╩ ╚═╝╝╚╝╚═╝╝╚╝ ╩ ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝o\n // ─────────────────────────────────────────────────────────\n component.configure = function(key, val, init){\n switch ( key )\n {\n case 'hover':\n case 'bordered':\n case 'striped':\n component.$table.toggleClass(`table-${key}`, val);\n break;\n case 'height':\n let height = !isNaN(val) ? `${val}px` : val;\n component.$el.css({ height: height });\n break;\n }\n };\n\n component.template = `/api/post-groups/${component.postGroupId}/include-notices`;\n component.make = function(response){\n if ( response.err )\n {\n component.$cardTitle.addClass('with-error border border-danger');\n return component.$cardBody.empty().append(`\n<div class=\"d-flex bg-danger text-white p-4\">\n <i class=\"ml-auto mr-2 my-auto fas fa-exclamation-circle fa-2x\"></i> <span class=\"mr-auto my-auto\">${component.helpers.parseErrorMessage(response)}</span>\n</div>`);\n }\n\n component.postGroup = response;\n component.notices = response.notices;\n\n // 세션 권한 계산\n if ( _.get(TOTAL_LOCALS, '_SESSION') )\n {\n component.allowedPermission =\n _.chain(TOTAL_LOCALS._SESSION.parents)\n .map('_id')\n .intersection(_.keys(component.postGroup.permission))\n .map(function(userPermission){\n return component.postGroup.permission[userPermission];\n })\n .flatten()\n .uniq()\n .value();\n\n if ( TOTAL_LOCALS._SESSION.isSysadmin )\n {\n component.allowedPermission = [CONSTANTS.PERMISSIONS.POST.READABLE,CONSTANTS.PERMISSIONS.POST.WRITABLE,CONSTANTS.PERMISSIONS.POST.NOTICE_WRITABLE,CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE,CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE,CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE,CONSTANTS.PERMISSIONS.POST.COMMENT.READABLE,CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE];\n }\n }\n // hljs 설정\n hljs.configure({ languages: ['html', 'javascript', 'css', 'bash'] });\n\n // Quilljs 모듈 확장\n const Parchment = Quill.import('parchment');\n\n const AttributeAttributorTitle = new Parchment.Attributor.Attribute('title', 'title');\n const AttributeAttributorFileId = new Parchment.Attributor.Attribute('data-file-id', 'data-file-id');\n const AttributeAttributorLastModified = new Parchment.Attributor.Attribute('data-last-modified', 'data-last-modified');\n const AttributeAttributorLastModifiedDate = new Parchment.Attributor.Attribute('data-last-modified-date', 'data-last-modified-date');\n const AttributeAttributorName = new Parchment.Attributor.Attribute('data-name', 'data-name');\n const AttributeAttributorSize = new Parchment.Attributor.Attribute('data-size', 'data-size');\n const AttributeAttributorType = new Parchment.Attributor.Attribute('data-type', 'data-type');\n const AttributeAttributorContentEditable = new Parchment.Attributor.Attribute('contenteditable', 'contenteditable');\n\n // const ClassAttributorDisplay = new Parchment.Attributor.Class('display', 'd');\n\n Parchment.register(AttributeAttributorTitle);\n Parchment.register(AttributeAttributorFileId);\n Parchment.register(AttributeAttributorLastModified);\n Parchment.register(AttributeAttributorLastModifiedDate);\n Parchment.register(AttributeAttributorName);\n Parchment.register(AttributeAttributorSize);\n Parchment.register(AttributeAttributorType);\n Parchment.register(AttributeAttributorContentEditable);\n\n // Parchment.register(ClassAttributorDisplay);\n\n Quill.register({\n 'formats/title': AttributeAttributorTitle,\n 'formats/data-file-id': AttributeAttributorFileId,\n 'formats/data-last-modified': AttributeAttributorLastModified,\n 'formats/data-last-modified-date': AttributeAttributorLastModifiedDate,\n 'formats/data-name': AttributeAttributorName,\n 'formats/data-size': AttributeAttributorSize,\n 'formats/data-type': AttributeAttributorType,\n 'formats/contenteditable': AttributeAttributorContentEditable,\n\n // 'attributors/class/display': ClassAttributorDisplay,\n });\n\n const EmbedBlock = Quill.import(\"blots/block/embed\");\n\n class AttachmentBlock extends EmbedBlock {\n static create(options = {}) {\n let node = super.create(options.filename);\n let inner = document.createElement('div');\n let icon = document.createElement('i');\n let fileName = document.createElement('span');\n\n if ( options.file instanceof File )\n {\n options = {\n fileId: options.fileId || $.HSCore.helpers.getRandomId(),\n lastModified: options.file.lastModified,\n lastModifiedDate: options.file.lastModifiedDate,\n name: options.file.name,\n size: options.file.size,\n type: options.file.type,\n };\n AttributeAttributorFileId.add(node, options.fileId);\n AttributeAttributorLastModified.add(node, options.lastModified);\n AttributeAttributorLastModifiedDate.add(node, options.lastModifiedDate);\n AttributeAttributorName.add(node, options.name);\n AttributeAttributorTitle.add(node, options.name);\n AttributeAttributorSize.add(node, options.size);\n AttributeAttributorType.add(node, options.type);\n\n fileName.textContent = node.getAttribute('data-name');\n }\n\n AttributeAttributorContentEditable.add(node, false);\n\n inner.className = 'attachment-inner';\n icon.className = 'attachment-icon fas fa-2x fa-file';\n fileName.className = 'attachment-filename';\n\n inner.appendChild(icon);\n inner.appendChild(fileName);\n\n node.appendChild(inner);\n\n return node;\n }\n\n format(name, value) {\n super.format(name, value);\n if ( name === AttributeAttributorName.attrName )\n this.domNode.querySelector('.attachment-filename').textContent = value;\n\n if ( name === AttributeAttributorSize.attrName )\n this.domNode.querySelector('.attachment-filename').textContent += ` ( ${this._getFileSize(value)} )`;\n }\n\n _getFileSize(size){\n let _size = size;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} B`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} KB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} MB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} GB`;\n\n return `1TB 이상`;\n }\n\n static value(node) {\n return node.className;\n }\n }\n\n AttachmentBlock.blotName = 'attachment';\n AttachmentBlock.className = 'attachment';\n AttachmentBlock.tagName = 'div';\n\n Quill.register(AttachmentBlock, true);\n\n // 탭 ID 임의화\n component.$tabNavs.each(function(){\n let tabId = $.HSCore.helpers.getRandomId();\n let paneId = $.HSCore.helpers.getRandomId();\n\n let $tabNav = $(this);\n let $tabPane = component.$el.find(`.card-body.tab-content ${$tabNav.attr('href')}.tab-pane`);\n\n $tabPane.prop('id', paneId);\n $tabPane.attr('aria-labelledby', tabId);\n\n $tabNav.prop('id', tabId);\n $tabNav.attr({ 'href': `#${paneId}`, 'aria-controls': paneId });\n });\n\n // 뷰어 설정\n component.$viewerControls.on('click', '.to-list', async function($event){\n component._changeHash('#list');\n });\n\n component.$viewerControls.on('click', '.edit-post', async function($event){\n component._changeHash(`#editor-edit${component.$viewer.data('post-id')}`);\n });\n\n component.$viewerControls.on('click', '.remove-post', async function($event){\n component.$postConfirm.$com.confirm('정말로 글을 삭제하시겠습니까?', [`\"check-circle\" 네`, '아니오'], async function(buttonIndex) {\n if ( buttonIndex > 0 ) return; // cancel\n try\n {\n await $.ajax({ url: `/api/posts/${component.postGroupId}/${component.$viewer.data('post-id')}`, method: 'delete' });\n\n component._changeHash('#list');\n\n component.$postSnackbar.$com.success('삭제되었습니다.');\n }\n catch(err)\n {\n\n }\n });\n });\n\n // 목록 설정\n if ( component.mode === CONSTANTS.MODES.POST )\n {\n extendsDataTableMethods();\n\n if ( !component.datatable )\n {\n let buttons = [];\n\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.WRITABLE) >= 0 )\n {\n buttons.push({\n text: '글쓰기',\n className: 'btn-sm btn-primary post-write',\n action: function ( e, dt, node, config ) {\n component._changeHash('#editor');\n }\n });\n }\n\n component.$table.on('init.dt', async function(){\n // 커스텀 페이지네이션 초기화\n component._initCustomPaginate();\n\n // 자동 검색 이벤트 제거\n $('.dataTables_filter input', component.$el).off()\n // 엔터 이벤트로 추가\n .on('keyup', function($event) {\n if ( $event.keyCode !== 13 ) return;\n component.datatable.search( this.value ).draw();\n });\n });\n\n component.$table.on('page.dt', async function(){\n // 페이지네이션 업데이트\n component._refreshCustomPaginate();\n });\n\n component.$table.on('draw.dt', async function(){\n _.each(component.notices, function(notice){\n let colIndex = 0;\n let createdAt = moment(notice.createdAt);\n\n $('tbody', component.$table).prepend(`<tr class=\"post-notice\" data-post-id=\"${ notice._id }\">\n <td class=\"${component.columnsDefines[colIndex++].className} text-primary\"><i class=\"fas fa-bullhorn\"></i> 공지</td>\n <td class=\"${component.columnsDefines[colIndex++].className} text-primary font-weight-bold\">\n <div class=\"text-center text-md-left post-title-wrapper\">\n ${ $.fn.dataTable.render.ellipsis(40)($.fn.dataTable.render.text().display(notice.name)) }\n </div>\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">\n <a class=\"post-writer\" href=\"#\">\n ${ $.fn.dataTable.render.text().display(notice.author.name) }\n </a>\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">\n ${\n moment().diff(createdAt, 'days') === 0 ? `오늘 ${createdAt.format('HH:mm')}`\n : `<p class=\"m-0\">${createdAt.format('YYYY-MM-DD')}</p>`\n }\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">${ notice.viewcount }</td>\n</tr>`);\n });\n setTimeout(function(){ component.datatable.columns.adjust(); });\n component.datatable.processing(false);\n });\n\n component.datatable = component.$table.DataTable({\n processing: true,\n serverSide: true,\n ordering: false,\n searchDelay: 500,\n language: { url: '/libs/jquery-datatables/1.10.19/locales/ko.json' },\n paging: true,\n pagingType: 'simple_numbers',\n lengthMenu: [[ 10, 15, 25, 50, 75, 100 ], [ 10, 15, 25, 50, 75, 100 ]],\n pageLength: 15,\n rowHeight: 40,\n rowCallback: function( row, data ) {\n $(row).addClass( 'g-color-primary--hover' );\n },\n dom: `\n${\n ( config.length || config.searchBar ) ?\n `<'row no-gutters'<'col-sm-12 pl-2 mt-2 col-md-4'${ config.length ? 'l' : '' }><'col-sm-12 mt-2 col-md-4'${ config.searchBar ? 'f' : '' }><'col-sm-12 mt-2 col-md-4 pr-2'>>`\n : ''\n}\n<'row no-gutters table-body-wrapper'<'col-sm-12'tr>>\n<'row no-gutters'<'col-sm-12 pb-2 col-md-${buttons.length > 0 ? 8 : 12 } g-mt-30'p>${ buttons.length > 0 ? `<'col-sm-12 pb-2 col-md-4 g-mt-30 pt-1 pr-2 text-right'B>` : ''}>`,\n buttons: buttons,\n columns: component.columnsDefines,\n ajax: $.fn.dataTable.pipeline({\n url: `/api/posts/${component.postGroupId}`,\n pages: 5, // number of pages to cache\n error: function(){\n console.info(arguments, '<<<<<<<<<<<<<<<<<<<<<<<<<<');\n }\n })\n });\n }\n\n component.$table.on('click', 'tbody tr', function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n let $row = $(this).closest('tr');\n let row = component.datatable.row($row).data();\n\n if ( $row.hasClass('post-notice') ) row = { _id: $row.data('post-id') };\n if ( !row && !$row.hasClass('post-notice') ) return;\n\n\n // 사용자가 클릭했을 때만 이전 스크롤값 유지,\n // 뒤로가기, 앞으로 가기는 관여하지 않음.\n component.prevScrollTop = window.scrollY;\n\n component._changeHash(row._id);\n });\n\n // 에디터 설정\n // 에디터 메타데이터 입력 폼 추가\n component.$editorPostMetadata.append(component.$editorPostMetadataForm, component.$editorPostInputTitle);\n\n // 툴바 ID 임의화\n let toolbarId = $.HSCore.helpers.getRandomId();\n component.$editorToolbar.prop('id', toolbarId);\n\n let editorScrollContainerId = $.HSCore.helpers.getRandomId();\n component.$editorScrollContainer.prop('id', editorScrollContainerId);\n\n component.$postViewerTab.on('shown.bs.tab', async function(){\n // 스크롤 이동\n let offset = component.$el.offset();\n window.scrollTo(offset.left, offset.top);\n\n // 댓글 로드\n // component.$viewerControls.append();\n // component.datatable.fixedHeader.disable();\n });\n\n component.$postListTab.on('shown.bs.tab', async function(){\n // component.datatable.clearPipeline();\n // component.datatable.ajax.reload( null, false );\n // component.datatable.fixedHeader.enable();\n component.autoSwitchingCardStyle();\n\n if ( component.prevScrollTop != null ) window.scrollTo(0, component.prevScrollTop);\n });\n\n component.$postEditorTab.on('shown.bs.tab', async function(){\n window.scrollTo(0, component.$el.offset().top);\n\n let hash = component.getPostIdFromHash();\n if ( !component.editor )\n {\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.NOTICE) < 0\n || component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.NOTICE_WRITABLE) < 0 )\n {\n component.$editorPostFormNotice.remove();\n }\n\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.ATTACHMENT) >= 0 )\n {\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE) >= 0 )\n {\n component.$editorPostFormHideAttachment.removeClass('d-none');\n component.$editorPostInputHideAttachment.on('change', function($event){\n let checked = $event.currentTarget.checked;\n\n if ( checked )\n {\n component.$postConfirm.$com.confirm('하단의 파일 목록은 계속 유지되지만 본문에 위치한 파일박스를 모두 잃게됩니다.\\n계속 진행하시겠습니까?', [`\"check-circle\" 네`, '아니오'], async function(buttonIndex) {\n if ( buttonIndex > 0 ) return $event.currentTarget.checked = false; // cancel\n _.each(component.editor.getLines(), function(line){\n if ( line instanceof AttachmentBlock ) line.remove();\n });\n });\n }\n else\n {\n component._appendFileBox(component.sendFileList);\n }\n });\n\n $(`#${toolbarId} .ql-video`).after(CONSTANTS.TEMPLATES.$TOOLBAR_BTN_FILE_UPLOAD);\n\n CONSTANTS.TEMPLATES.$BROWSE_FILE_INPUT.on('change', function($event){\n component._appendFileBox(this.files);\n\n // reset file input\n this.value = '';\n\n // _.each(files, function(file){\n // var reader = new FileReader();\n\n // reader.onload = function(e) {\n // var data = e.target.result;\n // };\n // if(rABS) reader.readAsBinaryString(f);\n // else reader.readAsArrayBuffer(f);\n // });\n });\n\n CONSTANTS.TEMPLATES.$TOOLBAR_BTN_FILE_UPLOAD.on('click', function(){\n CONSTANTS.TEMPLATES.$BROWSE_FILE_INPUT.click();\n });\n\n component.$editorAttachments.removeClass('d-none');\n }\n }\n\n component.editor = new Quill(component.$editor.get(0), {\n modules: {\n syntax: true,\n toolbar: `#${toolbarId}`,\n imageResize: {},\n imageDrop: {},\n },\n theme: 'snow',\n bounds: `#${editorScrollContainerId}`,\n scrollingContainer: `#${editorScrollContainerId}`,\n placeholder: config.placeholder,\n });\n }\n\n // 에디터 초기화\n component.editor.setContents([]);\n component.$editorPostInputTitle.val('');\n component.$editorPostInputNotice.prop('checked', false);\n component.$editorAttachmentList.empty();\n component.sendFileList = [];\n component.$editorControls.empty();\n\n // 수정 모드일 경우 저장된 데이터 로드\n if ( /^#editor-edit/.test(hash) )\n {\n let postId = hash.replace('#editor-edit', '');\n\n component.postEditForm = {\n _id: component.$viewer.data('post-id'),\n content: component.$viewer.data('post-content'),\n name: component.$viewer.data('post-name'),\n isnotice: component.$viewer.data('post-isnotice'),\n attachments: component.$viewer.data('post-attachments'),\n };\n\n // 이미 로드된 뷰어 값이 없다면 ajax를 통해 받아옴\n if ( !component.postEditForm._id )\n {\n try\n {\n component.postEditForm = await $.ajax(`/api/posts/${component.postGroupId}/${postId}`);\n if ( component.postEditForm ) component.postEditForm = component.postEditForm.doc;\n }\n catch(err)\n {\n return console.error(err);\n }\n }\n\n component.editor.setContents(component.postEditForm.content);\n component.$editorPostInputTitle.val(component.postEditForm.name);\n component.$editorPostInputNotice.prop('checked', component.postEditForm.isnotice);\n\n _.each(component.postEditForm.attachments, function(attachment, idx, src){\n component.$editorAttachmentList.append(`\n<li class=\"d-flex px-3 py-1 ${ idx === src.length - 1 ? 'pb-0' : ''} post-editor-attachment-list-item\" data-file-id=\"${attachment._id}\" data-is-saved=\"true\">\n <span class=\"my-auto ml-0\">${attachment.filename} ( ${component._getFileSize(attachment.length)} )</span>\n <i class=\"text-danger my-auto ml-auto remove-attachment fas fa-times\"></i>\n</li>`\n );\n });\n\n component.$editorControls.append(`\n<button class=\"cancel-edit btn btn-sm btn-danger\"><i class=\"fas fa-times\"></i> 취소</button>\n<button class=\"save-changes btn btn-sm btn-primary ml-auto\"><i class=\"fas fa-save\"></i> 변경 저장</button>`);\n }\n // 신규 작성 모드일 경우\n else\n {\n component.$editorControls.append(`\n<button class=\"cancel-edit btn btn-sm btn-danger\"><i class=\"fas fa-times\"></i> 취소</button>\n<button class=\"create-post btn btn-sm btn-primary ml-auto\"><i class=\"fas fa-save\"></i> 저장</button>`);\n }\n });\n\n // 에디터 툴바 설정\n\n // 에디터 기능 설정\n component.$editorAttachmentList.on('click', 'i.remove-attachment', async function($event){\n let $attachmentListItem = $($event.currentTarget).closest('.post-editor-attachment-list-item');\n\n if ( $attachmentListItem.data('is-saved') )\n {\n let index = _.findIndex(component.postEditForm.attachments, _.matches({ _id: $attachmentListItem.data('file-id') }));\n if ( index > -1 ) component.postEditForm.attachments.splice(index, 1);\n }\n else\n {\n component.sendFileList[$attachmentListItem.data('file-index')] = void(0);\n }\n\n $attachmentListItem.remove();\n });\n\n component.$editorControls.on('click', '.cancel-edit', async function($event){\n component._changeHash('#list');\n });\n\n component.$editorControls.on('click', '.save-changes', async function($event){\n try\n {\n component._showBlocker();\n\n let postData = {\n isnotice: component.$editorPostInputNotice.is(':visible') ? component.$editorPostInputNotice.prop('checked') : false,\n template: 'default',\n name: component.$editorPostInputTitle.val(),\n content: component.editor.getContents(),\n attachments: component.postEditForm.attachments,\n };\n\n // 데이터 유효성 체크\n if ( !postData.name )\n {\n component._hideBlocker();\n component.$postSnackbar.$com.warning('제목을 입력해주세요.');\n return component.$editorPostInputTitle.focus();\n }\n\n postData.attachments = _.map(postData.attachments, '_id');\n\n // 첨부파일 업로드\n let savedFileInfo = await component._fileUpload(component.postGroupId, component.postEditForm._id, component.sendFileList);\n if ( savedFileInfo.length > 0 )\n {\n // 델타내에 첨부파일들에게 매겨진 임시 ID를\n // 저장된 ID로 모두 변경\n postData.content = component._replaceDeltaFileID(postData.content, savedFileInfo);\n\n // 변경된 델타로 에디터 재설정\n component.editor.setContents(postData.content);\n\n // 저장된 파일목록을 기존 저장된 목록에 추가\n postData.attachments = postData.attachments.concat(_.map(savedFileInfo, '_id'));\n }\n // 파일 전송 목록 초기화\n component.sendFileList = [];\n\n component._showBlocker('글 등록 중...');\n // 글 저장\n await $.ajax({\n url: `/api/posts/${component.postGroupId}/${component.postEditForm._id}`, method: 'patch', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(postData),\n });\n\n component._hideBlocker();\n component.$postSnackbar.$com.success('저장되었습니다.');\n\n // 저장된 글로 포스트 읽기로 자동 탭 이동\n component._changeHash(component.postEditForm._id);\n }\n catch(err)\n {\n component._hideBlocker();\n component.$postSnackbar.$com.failed(`저장에 실패하였습니다.: ${err.message}`);\n }\n });\n\n component.$editorControls.on('click', '.create-post', async function($event){\n try\n {\n component._showBlocker();\n\n let postData = {\n isnotice: component.$editorPostInputNotice.is(':visible') ? component.$editorPostInputNotice.prop('checked') : false,\n template: 'default',\n name: component.$editorPostInputTitle.val(),\n content: component.editor.getContents(),\n attachments: [],\n };\n\n // 데이터 유효성 체크\n if ( !postData.name )\n {\n component._hideBlocker();\n component.$postSnackbar.$com.warning('제목을 입력해주세요.');\n return component.$editorPostInputTitle.focus();\n }\n\n // 첨부파일 업로드\n let savedFileInfo = await component._fileUpload(component.sendFileList);\n\n if ( savedFileInfo.length > 0 )\n {\n // 델타내에 첨부파일들에게 매겨진 임시 ID를\n // 저장된 ID로 모두 변경\n postData.content = component._replaceDeltaFileID(postData.content, savedFileInfo);\n\n // 변경된 델타로 에디터 재설정\n component.editor.setContents(postData.content);\n\n // 저장된 파일목록을 별도 필드로 저장\n postData.attachments = _.map(savedFileInfo, '_id');\n\n // 파일 전송 목록 초기화\n component.sendFileList = [];\n }\n\n component._showBlocker('글 등록 중...');\n\n // 글 저장\n let createdPost = await $.ajax({\n url: `/api/posts/${component.postGroupId}`, method: 'post', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(postData),\n });\n\n component._hideBlocker();\n component.$postSnackbar.$com.success('저장되었습니다.');\n\n // 저장된 글로 포스트 읽기로 자동 탭 이동\n component.renderPost({ _id: createdPost._id, doc: createdPost, comments: [] });\n }\n catch(err) {\n component._hideBlocker();\n component.$postSnackbar.$com.failed(`저장에 실패하였습니다.: ${err.message}`);\n }\n });\n }\n else\n {\n component.$table.before(CONSTANTS.$RECENT_POST_LIST).remove();\n component.$title.text(`${component.$title.text()} - 최근글`);\n CONSTANTS.$RECENT_POST_LIST.on('click', 'li.recent-post-list-item', component._viewPost);\n\n component._readRecentPosts();\n }\n\n // 초기화\n $(window).on('resize', _.debounce(component.autoSwitchingCardStyle, 150));\n $(window).on('hashchange', async function(){\n let postId = component.getPostIdFromHash();\n\n if ( !postId || postId === '#list' ) return component.$postListTab.tab('show');\n if ( /^#editor/.test(postId) ) return component.$postEditorTab.tab('show');\n\n try\n {\n let $post = await component.getPost(component.postGroupId, postId);\n if ( $post ) return component.renderPost($post);\n component.$postSnackbar.$com.failed('존재하지 않는 글입니다.');\n }\n catch(err)\n {\n component.$postSnackbar.$com.failed(err.message);\n }\n });\n\n $(window).trigger('resize');\n\n // 초기화 완료 컴포넌트 초기화 로딩 스피너 숨김.\n component.$initSpinner.addClass('d-none').removeClass('d-flex');\n\n // load post via url hash\n let postId = component.getPostIdFromHash();\n if ( !postId ) return;\n\n if ( postId === '#list' ) ;\n else if ( /^#editor/.test(postId) )\n component.$postEditorTab.tab('show');\n else\n component.getPost(component.postGroupId, postId)\n .then(component.renderPost)\n .catch(function(err){\n component.$postSnackbar.$com.failed(err.message);\n });\n };\n\n component._hideProgress = function(){\n component.$progress.addClass('d-none');\n };\n\n component._showProgress = function(){\n component.$progressBar.attr('aria-valuenow', 0).width(0);\n component.$progress.width(component.$blocker.width() * 85 / 100).removeClass('d-none');\n };\n\n component._hideBlocker = function(){\n component.$blockerMessage.addClass('d-none');\n component.$blocker.addClass('d-none');\n };\n\n component._showBlocker = function(message){\n let offset = component.$el.offset();\n component.$blocker.css({\n top: offset.top,\n left: offset.left,\n width: component.$el.width(),\n height: component.$el.height(),\n });\n\n component.$blocker.removeClass('d-none');\n\n if ( message )\n {\n component.$blockerMessageTxt.text(message);\n component.$blockerMessage.removeClass('d-none');\n }\n };\n\n component._replaceDeltaFileID = function(delta, savedFileInfo){\n _.each(savedFileInfo, function(info){\n delta.forEach(function(operation){\n if ( operation.insert && operation.insert.attachment && operation.insert.attachment === 'attachment' )\n {\n if ( operation.attributes['data-file-id'] === info.fileId )\n {\n operation.attributes['data-file-id'] = info._id;\n }\n }\n });\n });\n\n return delta;\n };\n\n component._appendFileBox = function(files){\n let selection = component.editor.getSelection();\n let index = 0;\n\n if ( selection ) index = selection.index;\n if ( index === 0 ) component.editor.insertText(index++, '\\n');\n\n _.each(files, function(file){\n let isExists = file.fileId ? true : false;\n let fileInstance = isExists ? file.file : file;\n\n let randomId = file.fileId || $.HSCore.helpers.getRandomId();\n\n component.editor.insertEmbed(index++, 'attachment', { fileId: randomId, file: fileInstance });\n component.editor.insertText(index++, '\\n');\n\n if ( ! isExists )\n {\n let sameFileIndex = _.findIndex( component.sendFileList, function(f){\n return f.file.lastModified === fileInstance.lastModified && f.file.lastModifiedDate === fileInstance.lastModifiedDate && f.file.name === fileInstance.name && f.file.size === fileInstance.size && f.file.type === fileInstance.type;\n });\n\n if ( sameFileIndex < 0 )\n {\n component.$editorAttachmentList.append(`\n<li class=\"d-flex px-3 py-1 post-editor-attachment-list-item\" data-file-id=\"${randomId}\" data-file-index=\"${component.sendFileList.length}\">\n <span class=\"my-auto ml-0\">${fileInstance.name} ( ${component._getFileSize(file.size)} )</span>\n <i class=\"text-danger my-auto ml-auto remove-attachment fas fa-times\"></i>\n</li>`\n );\n\n component.sendFileList.push({ fileId: randomId, file: fileInstance });\n }\n }\n });\n // move after attachments\n component.editor.setSelection(index + 1, 0);\n };\n\n component._refreshCustomPaginate = function(){\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.empty();\n\n let pagination = component.datatable.page.info();\n\n let per = 3;\n\n let current = pagination.page;\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-13 to-previous ${ current === 0 ? 'u-pagination-v1__item--disabled' : '' }\" href=\"javascript:void(0)\" aria-label=\"이전\">\n <span aria-hidden=\"true\">\n <i class=\"fa fa-angle-left g-mr-5\"></i>\n 이전\n </span>\n <span class=\"sr-only\">이전</span>\n </a>\n</li>`);\n\n if ( current - 1 > 0 )\n {\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page\" href=\"javascript:void(0)\">${1}</a>\n</li>\n<li class=\"list-inline-item g-hidden-sm-down\">\n <span class=\"g-pa-4-11\">...</span>\n</li>`\n );\n }\n\n for ( let i = current - 1, ilen = current - 1 + per; i < ilen; i++ )\n {\n if ( i < 0 ) { ilen += 1; continue; }\n if ( i >= pagination.pages ) { break; }\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page ${ current === i ? 'u-pagination-v1-3--active' : '' }\" href=\"javascript:void(0)\">${i + 1}</a>\n</li>`);\n }\n\n if ( current - 1 + per <= pagination.pages - 1 )\n {\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <span class=\"g-pa-4-11\">...</span>\n</li>\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page\" href=\"javascript:void(0)\">${pagination.pages}</a>\n</li>`\n );\n }\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-13 to-next ${ current >= pagination.pages - 1 ? 'u-pagination-v1__item--disabled' : '' }\" href=\"javascript:void(0)\" aria-label=\"다음\">\n <span aria-hidden=\"true\">\n 다음\n <i class=\"fa fa-angle-right g-ml-5\"></i>\n </span>\n <span class=\"sr-only\">다음</span>\n </a>\n</li>`);\n };\n\n component._initCustomPaginate = function(){\n component.$originalPaginate = $('.dataTables_paginate', component.$el);\n\n component.$originalPaginate.addClass('d-none');\n component.$originalPaginate.after(CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE);\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-page', async function($event){\n component.datatable.page(Number($event.currentTarget.textContent) - 1).draw('page');\n });\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-next', async function($event){\n component.datatable.page('next').draw('page');\n\n if ( !component.$el.hasClass('cards-table') ) return;\n let componentOffset = component.$el.offset();\n window.scrollTo(componentOffset.left, componentOffset.top)\n });\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-previous', async function($event){\n component.datatable.page('previous').draw('page');\n\n if ( !component.$el.hasClass('cards-table') ) return;\n let componentOffset = component.$el.offset();\n window.scrollTo(componentOffset.left, componentOffset.top)\n });\n\n component._refreshCustomPaginate();\n };\n\n component._fileUpload = async function(postGroupId, postId, files){\n // 첨부파일 업로드\n if ( _.filter(files, Boolean).length > 0 )\n {\n let formData = new FormData();\n\n _.each(files, function(f){\n formData.append('file-id', f.fileId);\n formData.append('files', f.file);\n });\n\n component._showBlocker('파일 업로드 중...');\n component._showProgress();\n return await $.ajax({\n url: `/api/posts/${postGroupId}/${postId}/attachment`, method: 'post',\n contentType: false, processData: false, data: formData,\n xhr: function() {\n var xhr = $.ajaxSettings.xhr();\n if ( xhr )\n {\n xhr.upload.addEventListener('progress', function(event){\n if ( !event.lengthComputable ) return;\n\n let percentComplete = Math.round( (event.loaded * 100) / event.total );\n\n component.$progressBar.attr('aria-valuenow', percentComplete).width(`${percentComplete}%`);\n }, false);\n }\n return xhr;\n },\n });\n }\n\n return [];\n };\n\n component.getPostIdFromHash = function(){\n let hash = location.hash.substr(1);\n\n if ( !hash ) return null;\n\n hash = hash.split('&');\n\n for ( let i = 0, ilen = hash.length; i < ilen; i++ )\n if ( hash[i].split('=')[0] === component.instantID )\n return decodeURIComponent(hash[i].split('=')[1]);\n\n return null;\n };\n\n component.getPost = async function(postGroupId, postId){\n if ( !postGroupId ) throw new Error('\"Post Group ID\" is required.');\n if ( !postId ) throw new Error('\"Post ID\" is required.');\n\n try\n {\n return await $.ajax(`/api/posts/${postGroupId}/${postId}`);\n }\n catch(err)\n {\n throw new Error(component.helpers.parseErrorMessage(err));\n }\n };\n\n component.autoSwitchingCardStyle = async function(){\n // 카드 클래스 토글링\n component.$el.toggleClass('cards-table', window.innerWidth < 768);\n // 컬럼 사이즈 재계산\n setTimeout(function(){ component.datatable.columns.adjust(); });\n };\n\n component._viewPost = async function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n let $post;\n\n try\n {\n if ( component.mode === CONSTANTS.MODES.POST )\n {\n let row = component.datatable.row($(this).closest('tr')).data();\n if ( !row ) return;\n\n $post = await component.getPost(component.postGroupId, row._id);\n }\n else if ( component.mode === CONSTANTS.MODES.RECENT )\n {\n $post = await component.getPost(component.postGroupId, $(this).data('post-id'));\n }\n\n component.renderPost($post);\n }\n catch(err)\n {\n component.$postSnackbar.$com.failed(err.message);\n }\n return false;\n };\n\n component._readRecentPosts = async function (){\n let posts = await $.ajax(`/api/posts/${component.postGroupId}/recent/5`);\n let $postListItems = _.map(posts, function(post){\n return $(`<li class=\"d-flex recent-post-list-item\" data-post-id=\"${post._id}\"><a href=\"#\" class=\"post-title my-auto\">${post.name}</a><span class=\"my-auto ml-auto\">${moment(post.createdAt).format('YYYY-MM-DD')}</span></li>`);\n });\n CONSTANTS.$RECENT_POST_LIST.append($postListItems);\n };\n\n // ╔═╗╦═╗╦╦ ╦╔═╗╔╦╗╔═╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗\n // ╠═╝╠╦╝║╚╗╔╝╠═╣ ║ ║╣ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗\n // ╩ ╩╚═╩ ╚╝ ╩ ╩ ╩ ╚═╝ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝o\n // ────────────────────────────────────────────\n // removeHashWithoutReload()\n // extendsDataTableMethods()\n // ────────────────────────────────────────────\n function removeHashWithoutReload () {\n var scrollV, scrollH, loc = window.location;\n if ( _.indexOf(history, 'pushState') < 0 )\n {\n let componentOffset = component.$el.offset();\n loc.hash = '';\n\n // Prevent scrolling by storing the page's current scroll offset\n // Restore the scroll offset, should be flicker free\n window.scrollTo(componentOffset.left, componentOffset.top);\n }\n else\n {\n history.pushState('', document.title, loc.pathname + loc.search);\n }\n }\n\n function extendsDataTableMethods(){\n if ( $.isFunction($.fn.dataTable.pipeline) ) return;\n //\n // https://datatables.net/examples/server_side/pipeline.html\n // Pipelining function for DataTables. To be used to the `ajax` option of DataTables\n //\n $.fn.dataTable.pipeline = function ( opts ) {\n // Configuration options\n let conf = $.extend( {\n error: $.noop,\n pages: 5, // number of pages to cache\n url: '', // script url\n data: null, // function or object with parameters to send to the server\n // matching how `ajax.data` works in DataTables\n method: 'GET' // Ajax HTTP method\n }, opts );\n\n // Private variables for storing the cache\n let cacheLower = -1;\n let cacheUpper = null;\n let cacheLastRequest = null;\n let cacheLastJson = null;\n\n return function ( request, drawCallback, settings ){\n let _datatable = this.api();\n let ajax = false;\n let requestStart = request.start;\n let drawStart = request.start;\n let requestLength = request.length;\n let requestEnd = requestStart + requestLength;\n let info = _datatable.page.info();\n\n if ( settings.clearCache )\n {\n // API requested that the cache be cleared\n ajax = true;\n settings.clearCache = false;\n }\n else if ( cacheLower < 0 || requestStart < cacheLower || requestEnd > cacheUpper )\n {\n // outside cached data - need to make a request\n ajax = true;\n }\n else if (\n JSON.stringify( request.order ) !== JSON.stringify( cacheLastRequest.order )\n || JSON.stringify( request.columns ) !== JSON.stringify( cacheLastRequest.columns )\n || JSON.stringify( request.search ) !== JSON.stringify( cacheLastRequest.search )\n ) {\n // properties changed (ordering, columns, searching)\n ajax = true;\n }\n\n // Store the request for checking next time around\n cacheLastRequest = $.extend( true, {}, request );\n\n if ( ajax )\n {\n // Need data from the server\n if ( requestStart < cacheLower )\n {\n requestStart = requestStart - (requestLength*(conf.pages-1));\n\n if ( requestStart < 0 ) requestStart = 0;\n }\n\n cacheLower = requestStart;\n cacheUpper = requestStart + (requestLength * conf.pages);\n\n request.start = requestStart;\n request.length = requestLength * conf.pages;\n\n // Provide the same `data` options as DataTables.\n if ( typeof conf.data === 'function' )\n {\n // As a function it is executed with the data object as an arg\n // for manipulation. If an object is returned, it is used as the\n // data object to submit\n let d = conf.data( request );\n if ( d ) $.extend( request, d );\n }\n else if ( $.isPlainObject( conf.data ) )\n {\n // As an object, the data given extends the default\n $.extend( request, conf.data );\n }\n\n _datatable.processing(true);\n\n settings.jqXHR = $.ajax({\n type: conf.method,\n url: conf.url,\n data: request,\n dataType: 'json',\n cache: false,\n success: function ( json ) {\n cacheLastJson = $.extend(true, {}, json);\n\n if ( cacheLower != drawStart ) json.data.splice( 0, drawStart-cacheLower );\n if ( requestLength >= -1 ) json.data.splice( requestLength, json.data.length );\n\n drawCallback( json );\n\n // pipeline으로 로드할 경우, length를 잃어버리는 현상이 존재함.\n // DOM 로드가 완료된 후에 재설정하여 length가 표시되도록 하기 위함.\n setTimeout(function(){ _datatable.page.len(_datatable.page.len()); }, 100);\n },\n error: conf.error,\n });\n }\n else\n {\n json = $.extend( true, {}, cacheLastJson );\n json.draw = request.draw; // Update the echo for each response\n json.data.splice( 0, requestStart-cacheLower );\n json.data.splice( requestLength, json.data.length );\n\n drawCallback(json);\n }\n }\n };\n\n // Register an API method that will empty the pipelined data, forcing an Ajax\n // fetch on the next draw (i.e. `table.clearPipeline().draw()`)\n $.fn.dataTable.Api.register( 'clearPipeline()', function () {\n return this.iterator( 'table', function ( settings ) {\n settings.clearCache = true;\n });\n });\n };\n\n // ╔═╗╦ ╦╔╗ ╦ ╦╔═╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗\n // ╠═╝║ ║╠╩╗║ ║║ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗\n // ╩ ╚═╝╚═╝╩═╝╩╚═╝ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝o\n // ────────────────────────────────────────\n // component.renderPost(Post post)\n // component.unescapeHtml(String string)\n // ────────────────────────────────────────\n\n component.unescapeHtml = function(string) {\n let str = '' + string;\n return str.replace(/&gt;/g, `>`)\n .replace(/&lt;/g, `<`)\n .replace(/&#39;/g, `'`)\n .replace(/&quot;/g, `\"`)\n .replace(/&amp;/g, `&`);\n };\n\n component.renderPost = function($post){\n let post = $post.doc;\n let comments = $post.comments;\n\n component.$viewer.data('post-id', post._id);\n component.$viewer.data('post-name', post.name);\n component.$viewer.data('post-content', post.content);\n component.$viewer.data('post-isnotice', post.isnotice);\n component.$viewer.data('post-attachments', post.attachments);\n\n let tabNavText = `글읽기 - ${post.name}`;\n\n component.$postViewerTab.attr('title', tabNavText).text(tabNavText).tab('show');\n\n if ( !component.viewer )\n {\n let viewerScrollConatinerId = $.HSCore.helpers.getRandomId();\n component.$viewerScrollContainer.prop('id', viewerScrollConatinerId);\n\n component.viewer = new Quill(component.$viewer.get(0), {\n modules: {\n syntax: true,\n toolbar: false,\n },\n theme: 'snow',\n bounds: `#${viewerScrollConatinerId}`,\n scrollingContainer: `#${viewerScrollConatinerId}`,\n readOnly: true,\n });\n }\n\n // 뷰어 레이아웃\n // 글 메타데이터 공간\n component.$viewerMetadata.empty().append(`\n<div class=\"post-general-info row no-gutters\">\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom border-right text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">작성자</label>\n <span class=\"m-auto\">${post.author.name}</span>\n </div>\n </div>\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom border-right text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">작성일</label>\n <span class=\"m-auto\">${moment(post.createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n </div>\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">조회수</label>\n <span class=\"m-auto\">${post.viewcount}</span>\n </div>\n </div>\n</div>\n<div class=\"post-detail-info no-gutters\">\n</div>\n<div class=\"post-title-info no-gutters\">\n <div class=\"metadata-group col-12 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">제목</label>\n <span class=\"m-auto text-break\">${post.name}</span>\n </div>\n </div>\n</div>`);\n\n let $attachmentItems = $('<li class=\"d-flex py-1 px-3 post-viewer-attachment-list-item\"></li>');\n\n if ( post.attachments.length === 0 )\n $attachmentItems.text('첨부파일이 없습니다.');\n else\n $attachmentItems = _.map(post.attachments, function(attachment, idx, src){\n return `\n<li class=\"d-flex px-3 py-1 ${ idx === src.length - 1 ? 'pb-0' : ''} post-viewer-attachment-list-item\" data-file-id=\"${attachment._id}\">\n <a href=\"javascript:void(0)\" class=\"post-viewer-attachment-download\">${ attachment.filename } ( ${ component._getFileSize(attachment.length) } )</a>\n</li>`;\n }).join('');\n\n // 첨부파일 공간\n component.$viewerAttachmentList.empty().append($attachmentItems);\n\n // 첨부파일 아이템 다운로드 버튼 이벤트\n $('a.post-viewer-attachment-download', component.$viewerAttachmentList).on('click', async function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) < 0 )\n component.$postSnackbar.$com.failed('다운로드 권한이 없습니다.');\n else\n window.open(`/api/posts/attachment/${$(this).closest('.post-viewer-attachment-list-item').data('file-id')}`);\n\n return false;\n });\n\n // 본문 로드\n component.viewer.setContents(post.content);\n\n // 첨부파일 다운로드 이벤트\n $('.ql-editor .attachment .attachment-inner', component.$viewer).on('click', async function(){\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) < 0 )\n return component.$postSnackbar.$com.failed('다운로드 권한이 없습니다.');\n\n window.open(`/api/posts/attachment/${$(this).closest('.attachment').data('file-id')}`);\n });\n\n // 수정, 삭제 버튼\n // 자신이 쓴 글만 가능\n component.$viewerControls.empty().append(`<button class=\"btn btn-sm btn-secondary to-list ml-2\"><i class=\"fas fa-list-ul\"></i> 목록</button>`);\n\n if ( post.author._id === TOTAL_LOCALS._SESSION._id )\n {\n component.$viewerControls.append(`\n<button class=\"btn btn-sm btn-danger remove-post ml-auto\"><i class=\"fas fa-trash-alt\"></i> 글 삭제</button>\n<button class=\"btn btn-sm btn-info edit-post mx-2\"><i class=\"fas fa-edit\"></i> 글 수정</button>`);\n }\n\n // 게시판 댓글기능 껐을 경우\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.COMMENT) < 0 )\n {\n component.$viewerComments.addClass('d-none');\n }\n // 댓글 로드\n else\n {\n component.$viewerCommentList.empty();\n\n let $commentItems = $('<p class=\"d-flex m-0 py-1 px-2\"></p>');\n\n // 사용자 읽기 권한 체크\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.COMMENT.READABLE) >= 0 )\n {\n if ( post.attachments.length === 0 )\n $commentItems.text('등록된 댓글이 없습니다.');\n else\n $commentItems =\n _.map(_.filter($post.comments, function(comment){ return !_.isEmpty(comment); }), function(comment){\n // 사용자 프로필에 사용된 기본값 svg는 font-awesome `fas fa-user`\n return `\n<div class=\"media g-mb-30\">\n <img class=\"d-flex g-width-50 g-height-50 rounded-circle g-mt-3 g-mr-20 p-1 border\" src=\"${post.author.photo ? post.author.photo : 'data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ1c2VyIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDQ0OCA1MTIiIGNsYXNzPSJzdmctaW5saW5lLS1mYSBmYS11c2VyIGZhLXctMTQgZmEtM3giPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIyNCAyNTZjNzAuNyAwIDEyOC01Ny4zIDEyOC0xMjhTMjk0LjcgMCAyMjQgMCA5NiA1Ny4zIDk2IDEyOHM1Ny4zIDEyOCAxMjggMTI4em04OS42IDMyaC0xNi43Yy0yMi4yIDEwLjItNDYuOSAxNi03Mi45IDE2cy01MC42LTUuOC03Mi45LTE2aC0xNi43QzYwLjIgMjg4IDAgMzQ4LjIgMCA0MjIuNFY0NjRjMCAyNi41IDIxLjUgNDggNDggNDhoMzUyYzI2LjUgMCA0OC0yMS41IDQ4LTQ4di00MS42YzAtNzQuMi02MC4yLTEzNC40LTEzNC40LTEzNC40eiIgY2xhc3M9IiI+PC9wYXRoPjwvc3ZnPgo=' }\" alt=\"Image Description\">\n <div class=\"media-body g-brd-around g-brd-gray-light-v4 g-pa-30 mb-3\">\n <div class=\"g-mb-15\">\n <h5 class=\"d-md-flex align-items-center h5 g-color-gray-dark-v1 mb-0\">\n <span class=\"d-block\">${$.fn.dataTable.render.text().display(comment.author.name)}</span>\n ${ post.author._id === comment.author._id ? '<span class=\"u-label g-bg-info g-rounded-3 u-label-info d-block ml-auto mt-2 mt-md-0\">글쓴이</span>' : '' }\n ${ TOTAL_LOCALS._SESSION._id === comment.author._id ? '<a class=\"u-tags-v1 g-font-size-12 g-brd-around g-brd-gray-light-v4 g-bg-red--hover g-brd-red--hover g-color-black-opacity-0_8 g-color-white--hover rounded g-py-6 g-px-15 ml-md-2 mt-2 mt-md-0 d-block text-center\" href=\"#!\">댓글 삭제</a>' : '' }\n </h5>\n <span class=\"g-color-gray-dark-v4 g-font-size-12\">${moment(comment.createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n\n <p>${$.fn.dataTable.render.text().display(comment.comment)}</p>\n </div>\n</div>`;\n });\n\n }\n else\n {\n $commentItems.text('댓글을 읽을 권한이 없습니다.');\n }\n\n component.$viewerCommentList.append($commentItems);\n // 댓글 입력 폼 초기화\n component.$viewerCommentForm.empty();\n\n // 사용자 쓰기 권한 체크\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) >= 0 )\n {\n component.$viewerCommentForm.append(`\n <div class=\"mb-2\">\n <textarea class=\"form-control g-bg-secondary g-brd-gray-light-v4 g-brd-primary--focus g-resize-none rounded-3 g-py-13 g-px-15 input-comment\" rows=\"5\" placeholder=\"댓글을 입력해주세요...\"></textarea>\n </div>\n\n <div class=\"d-flex align-items-center\">\n <button class=\"btn u-btn-primary g-font-weight-600 g-font-size-12 text-uppercase my-auto ml-auto create-comment\" type=\"button\" role=\"button\">댓글 등록</button>\n </div>`);\n\n $('button.create-comment', component.$viewerCommentForm).on('click', async function($event){\n let $inputComment = $('textarea.input-comment', component.$viewerCommentForm);\n let commentData = {\n comment: $inputComment.val(),\n };\n\n // 데이터 유효성 검사\n if ( !commentData.comment )\n {\n component.$postSnackbar.$com.failed('댓글을 입력해주세요.');\n return $inputComment.focus();\n }\n\n try\n {\n let createdComment = await $.ajax({\n url: `/api/posts/${component.postGroupId}/${post._id}/comments`, method: 'post', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(commentData),\n });\n\n component.$postSnackbar.$com.success('등록되었습니다.');\n }\n catch(err)\n {\n\n }\n });\n }\n else\n {\n component.$viewerCommentForm.addClass('d-none');\n }\n }\n };\n\n component._changeHash = function(postId){\n let hash = location.hash;\n\n if ( !hash || hash === '#!' ) return location.hash = `${component.instantID}=${encodeURIComponent(postId)}`;\n\n let appended = false;\n\n hash = hash.substr(1).split('&');\n\n for ( let i = 0, ilen = hash.length; i < ilen; i++ )\n {\n if ( hash[i].split('=')[0] === component.instantID )\n {\n hash[i] = `${component.instantID}=${encodeURIComponent(postId)}`;\n appended = true;\n }\n }\n\n if ( !appended ) hash.push(`${component.instantID}=${encodeURIComponent(postId)}`);\n\n location.hash = hash.join('&');\n };\n }, [\n '/libs/font-awesome/5.9.0/css/all.min.css',\n\n '/libs/jquery-datatables/1.10.19/css/dataTables.bootstrap4.min.css',\n '/libs/jquery-datatables/1.10.19/js/jquery.dataTables.min.js',\n '/libs/jquery-datatables/1.10.19/js/dataTables.bootstrap4.min.js',\n\n '/libs/jquery-datatables-buttons/1.5.4/css/buttons.bootstrap4.min.css',\n '/libs/jquery-datatables-buttons/1.5.4/js/dataTables.buttons.min.js',\n '/libs/jquery-datatables-buttons/1.5.4/js/buttons.bootstrap4.min.js',\n\n '/libs/jquery-datatables-renderer-ellipsis/1.10.19/ellipsis.js',\n '/libs/jquery-datatables-processing/1.10.19/processing.js',\n\n '/libs/KaTeX/0.10.0/katex.min.css',\n '/libs/KaTeX/0.10.0/katex.min.js',\n\n '/libs/highlight.js/9.14.2/styles/atom-one-dark.min.css',\n '/libs/highlight.js/9.14.2/highlight.min.js',\n\n '/libs/quill/1.3.6/quill.snow.min.css',\n '/libs/quill/1.3.6/quill.min.js',\n '/libs/quill-image-drop-module/1.0.3/image-drop.min.js',\n '/libs/quill-image-resize-module/3.0.0/image-resize.min.js',\n ]);\n</script>\n\n<!-- Hover Rows -->\n<div class=\"g-mb-30 ui-post-v1 CMS_type_post\" data-jc=\"ui-post-v1\">\n <h2 class=\"g-font-size-24 p-3 mb-0 ui-post-v1-title\">\n <i class=\"fa fa-comments-o g-mr-5\"></i>\n <span class=\"post-group-name\">자유게시판</span>\n </h2>\n\n <div class=\"card-header d-none\">\n <ul class=\"nav nav-tabs card-header-tabs\">\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-list active\" id=\"post-list-tab\" data-toggle=\"tab\" href=\"#post-list\" role=\"tab\" aria-controls=\"post-list\" aria-selected=\"true\">글목록</a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-read\" id=\"post-read\" data-toggle=\"tab\" href=\"#post-read\" role=\"tab\" aria-controls=\"post-read\" aria-selected=\"true\">글읽기</a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-editor\" id=\"post-write-tab\" data-toggle=\"tab\" href=\"#post-write\" role=\"tab\" aria-controls=\"post-write\" aria-selected=\"false\">글쓰기</a>\n </li>\n </ul>\n </div>\n\n <div class=\"p-0 card-body tab-content\">\n <div class=\"d-flex p-3 init-spinner text-center CMS_hidden\">\n <div class=\"spinner-icon g-width-80 my-auto ml-auto\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\"><path fill=\"none\" d=\"M82 50A32 32 0 1 1 23.533421623214014 32.01333190873183 L21.71572875253809 21.7157287525381 L32.013331908731814 23.53342162321403 A32 32 0 0 1 82 50\" stroke-width=\"4\" stroke=\"#337ab7\"></path><circle cx=\"50\" cy=\"50\" fill=\"none\" stroke-linecap=\"round\" r=\"22\" stroke-width=\"4\" stroke=\"#337ab7\" stroke-dasharray=\"34.55751918948772 34.55751918948772\" transform=\"rotate(215.429 50 50)\"><animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform></circle></svg>\n </div>\n <span class=\"my-auto ml-3 mr-auto g-font-size-28\">글 불러오는 중...</span>\n </div>\n\n <div class=\"post-list-tab-content tab-pane fade show active rounded\" id=\"post-list\" role=\"tabpanel\" aria-labelledby=\"post-list-tab\">\n <table class=\"table u-table--v2 table-striped text-center g-mb-50 post-list\" style=\"width:100%\"></table>\n </div>\n\n <div class=\"post-read-content tab-pane fade rounded border border-gray\" id=\"post-read\" role=\"tabpanel\" aria-labelledby=\"post-read\">\n <div class=\"post-viewer-metadata\"></div>\n <div class=\"post-viewer-scroll-container CMS_hidden\">\n <div class=\"post-viewer\"></div>\n </div>\n\n <div class=\"border-top border-bottom post-viewer-attachments\">\n <h6 class=\"m-0 p-2\"><i class=\"fas fa-paperclip\"></i> 첨부파일</h6>\n <ul class=\"m-0 px-0 py-2 post-viewer-attachment-list\"></ul>\n </div>\n\n <div class=\"post-viewer-controls border-bottom p-2 d-flex CMS_hidden\"></div>\n\n <div class=\"post-viewer-comments\">\n <h6 class=\"m-0 p-2\"><i class=\"fas fa-comments\"></i> 댓글</h6>\n <div class=\"m-0 py-2 px-4 border-bottom post-viewer-comment-list\"></div>\n <div class=\"m-0 p-2 post-viewer-comment-form\"></div>\n </div>\n </div>\n\n <div class=\"post-editor-tab-content tab-pane fade rounded border border-gray\" id=\"post-write\" role=\"tabpanel\" aria-labelledby=\"post-write-tab\">\n <div class=\"post-editor-metadata-form\"></div>\n <div class=\"post-editor-toolbar CMS_hidden\">\n <span class=\"ql-formats\">\n <select class=\"ql-font\"></select>\n <select class=\"ql-size\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-bold\"></button>\n <button class=\"ql-italic\"></button>\n <button class=\"ql-underline\"></button>\n <button class=\"ql-strike\"></button>\n </span>\n <span class=\"ql-formats\">\n <select class=\"ql-color\"></select>\n <select class=\"ql-background\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-script\" value=\"sub\"></button>\n <button class=\"ql-script\" value=\"super\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-header\" value=\"1\"></button>\n <button class=\"ql-header\" value=\"2\"></button>\n <button class=\"ql-blockquote\"></button>\n <button class=\"ql-code-block\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-list\" value=\"ordered\"></button>\n <button class=\"ql-list\" value=\"bullet\"></button>\n <button class=\"ql-indent\" value=\"-1\"></button>\n <button class=\"ql-indent\" value=\"+1\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-direction\" value=\"rtl\"></button>\n <select class=\"ql-align\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-link\"></button>\n <button class=\"ql-image\"></button>\n <button class=\"ql-video\"></button>\n <button class=\"ql-formula\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-clean\"></button>\n </span>\n </div>\n\n <div class=\"post-editor-scroll-container CMS_hidden\">\n <div class=\"post-editor\"></div>\n </div>\n\n\n <div class=\"border-bottom post-editor-attachments\">\n <h6 class=\"border-bottom m-0 p-2\"><i class=\"fas fa-paperclip\"></i> 첨부파일</h6>\n <ul class=\"m-0 px-0 py-2 post-editor-attachments-list\"></ul>\n </div>\n\n <div class=\"post-editor-controls d-flex p-2 CMS_hidden\"></div>\n </div>\n <div data-jc=\"contrib-snackbar\" class=\"post-snackbar\"></div>\n <div data-jc=\"contrib-confirm\" class=\"post-confirm\"></div>\n </div>\n <div class=\"component-blocker position-absolute d-none\">\n <div class=\"progress g-height-25 position-absolute d-none\">\n <div class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n </div>\n <div class=\"blocker-message position-absolute d-flex\">\n <div class=\"my-auto ml-auto mr-3 g-width-50\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\"><path fill=\"none\" d=\"M82 50A32 32 0 1 1 23.533421623214014 32.01333190873183 L21.71572875253809 21.7157287525381 L32.013331908731814 23.53342162321403 A32 32 0 0 1 82 50\" stroke-width=\"4\" stroke=\"#337ab7\"></path><circle cx=\"50\" cy=\"50\" fill=\"none\" stroke-linecap=\"round\" r=\"22\" stroke-width=\"4\" stroke=\"#337ab7\" stroke-dasharray=\"34.55751918948772 34.55751918948772\" transform=\"rotate(215.429 50 50)\"><animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform></circle></svg>\n </div>\n <span class=\"message my-auto mr-auto g-font-size-28\"></span>\n </div>\n </div>\n</div>\n<!-- End Hover Rows -->","datecreated":"2019-05-12T15:39:58.780Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  12. 2019-06-25 01:00 | Administrator | {"id":"19050823160001usn1","name":"Twbs-jquery-datatable","reference":"bootstrap/table/twbs-jquery-datatable.html","body":"<style>\n .ui-jquery-datatable table.dataTable thead tr th { text-align: center; }\n .ui-jquery-datatable table.dataTable tbody tr.selected { font-weight: bold;}\n</style>\n\n<script>\n COMPONENT('jquery-datatable', 'striped:true;hover:true;selectable:true', function(component, config){\n component.$el = component.element;\n\n component.$el.addClass('ui-jquery-datatable');\n\n component.datasource;\n component.datatable;\n component.datatableAPI;\n component.$table;\n\n component.$rows = $();\n\n let CONSTANTS = {\n TEMPLATES: {\n $TABLE: $(`<table class=\"table\" style=\"width:100%;\"/>`),\n }\n };\n\n component.selectRow = function($row){\n if ( !config.selectable ) return console.warn(`This Component is Setted \"selectable:false\".`);\n if ( !($row instanceof $) ) $row = $($row);\n\n if ( $row.find('td.dataTables_empty').length > 0 ) return;\n\n if ( !$row.hasClass('selected') )\n {\n component.$table.find('tr.selected').removeClass('selected');\n $row.addClass('selected');\n }\n\n component.selected = component.datatableAPI.row($row).data();\n if ( config.onSelectedRow ) EXEC(config.onSelectedRow, component.selected, component.datatable, $row, component);\n };\n\n component.make = function(){\n component.$table = CONSTANTS.TEMPLATES.$TABLE.clone();\n\n component.$table.on('init.dt', function(){\n if ( config.onInit ) EXEC(config.onInit, component);\n });\n\n if ( config.selectable )\n {\n component.$table.on('dblclick', 'tbody tr', function(){\n if ( config.onDoubleClickRow ) EXEC(config.onDoubleClickRow, component.selected, component.datatable, $(this), component);\n });\n\n component.$table.on('click', 'tbody tr', function(){\n let $row = $(this);\n\n if ( $row.find('td.dataTables_empty').length > 0 ) return;\n\n if ( !$row.hasClass('selected') )\n {\n component.$table.find('tr.selected').removeClass('selected');\n $(this).addClass('selected');\n }\n\n if ( $row.hasClass('child') )\n component.selected = component.datatableAPI.row($(this).prev('.parent')).data();\n else\n component.selected = component.datatableAPI.row(this).data();\n if ( config.onSelectedRow ) EXEC(config.onSelectedRow, component.selected, component.datatable, $row, component);\n });\n }\n\n component.$table.on('init.dt', function(){\n component.datatable.fnAdjustColumnSizing();\n });\n\n component.$table.on('draw.dt', function(){\n component.datatableAPI.processing(false);\n if ( config.onRenderComplete ) EXEC(config.onRenderComplete, component);\n });\n\n component.$el.append(component.$table);\n };\n\n component.initDatatables = function(){\n if ( !component.datatable )\n {\n component.datatable = component.$table.dataTable(GET(config.dataTableConfig));\n component.datatableAPI = component.datatable.api();\n }\n };\n\n component.configure = function(key, val, init){\n if ( key === 'hover' )\n {\n CONSTANTS.TEMPLATES.$TABLE.toggleClass('table-hover', val);\n component.$table.toggleClass('table-hover', val);\n }\n else if ( key === 'bordered' )\n {\n CONSTANTS.TEMPLATES.$TABLE.toggleClass('table-bordered', val);\n component.$table.toggleClass('table-bordered', val);\n }\n else if ( key === 'striped' )\n {\n CONSTANTS.TEMPLATES.$TABLE.toggleClass('table-striped', val);\n component.$table.toggleClass('table-striped', val);\n }\n else if ( key === 'height' )\n {\n let height = !isNaN(val) ? `${val}px` : val;\n component.$el.css({ height: height });\n }\n else if ( key === 'minHeight' )\n {\n let minHeight = !isNaN(val) ? `${val}px` : val;\n component.$table.css({ 'min-height': minHeight });\n }\n else if ( key === 'datasource' )\n {\n component.datasource(val, component.datasourceBinder);\n }\n };\n\n component.appendRows = function(rows){\n let prev = component.datatableAPI.page.info().page;\n component.datatableAPI.processing(true);\n component.datatableAPI.rows.add(rows);\n component.datatableAPI.draw();\n component.datatableAPI.page(prev).draw('page');\n };\n\n component.datasourceBinder = function(path, val, type){\n if ( !_.isArray(val) ) return;\n\n let configure = _.clone(GET(config.dataTableConfig));\n if ( _.isEmpty(configure) ) return console.warn(`There is no configuration. path: ${config.dataTableConfig}`);\n\n component.selected = null;\n\n if ( !component.datatable )\n {\n if ( !configure.serverSide && !configure.data ) configure.data = val;\n\n component.datatable = component.$table.dataTable(configure);\n component.datatableAPI = component.datatable.api();\n }\n else\n {\n if ( !component.datatableAPI ) component.datatableAPI = component.datatable.api();\n\n component.datatableAPI.processing(true);\n component.datatableAPI.clear().draw()\n .rows.add(val).draw();\n }\n };\n\n component.setter = function(value){};\n });\n</script>","datecreated":"2019-05-08T14:16:44.724Z","picture":"","icon":"","category":"Bootstrap","dateupdated":"2019-06-25T00:59:34.994Z"}
  13. 2019-06-25 01:00 | Administrator | {"id":"19042615330001ohc1","name":"Ui-breadcrumb-naea-v1","reference":"ui/breadcrumb/naea/ui-breadcrumb-naea-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-breadcrumb-naea-v1', function(component, config){\n component.$el = component.element;\n component.$ul = $('ul.u-list-inline', component.$el);\n component.$header = $('header', component.$el);\n\n component.$ul.empty();\n\n // get mainmenu before `make()` execute\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n if ( !window._TOTALCMS_MAINMENU ) window._TOTALCMS_MAINMENU = mainmenu;\n else mainmenu = window._TOTALCMS_MAINMENU;\n\n let serializedTree = serializeTree([mainmenu]);\n let node = serializedTree.find(function(item){ return item.url === location.pathname });\n\n let nodePath = getPath(serializeTree(serializedTree), node);\n\n nodePath.forEach(function(item, idx){\n let $li = $('<li class=\"list-inline-item g-mr-7\"/>');\n\n if ( item.url === location.pathname )\n $li.removeClass('g-mr-7').append(`<span>${item.name}</span>`);\n else\n $li.append(`<a class=\"u-link-v5 g-color-main\" href=\"${item.url || '/'}\">${!item.url ? 'Home' : item.name}</a>`);\n\n if ( idx < nodePath.length - 1 ) $li.append('<i class=\"fa fa-angle-right g-ml-7\"></i>');\n else $li.addClass('g-color-primary');\n\n component.$ul.append($li);\n });\n };\n\n // ██████╗██╗ ██╗███████╗████████╗ ██████╗ ███╗ ███╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗\n // ██╔════╝██║ ██║██╔════╝╚══██╔══╝██╔═══██╗████╗ ████║ ██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝\n // ██║ ██║ ██║███████╗ ██║ ██║ ██║██╔████╔██║ █████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗\n // ██║ ██║ ██║╚════██║ ██║ ██║ ██║██║╚██╔╝██║ ██╔══╝ ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║\n // ╚██████╗╚██████╔╝███████║ ██║ ╚██████╔╝██║ ╚═╝ ██║ ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║\n // ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝\n // ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████╗\n // ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝\n function serializeTree(tree, parent, store = []){\n for ( var i = 0, ilen = tree.length; i < ilen; i++ )\n {\n let node = $.extend(true, {}, tree[i]);\n if ( parent ) node.parent = parent.id;\n if ( node.children && node.children.length > 0 ) serializeTree(node.children, node, store);\n delete node.children;\n store.push(node);\n }\n return store;\n };\n\n function getPath(serializedTree, node, path = []){\n if ( node.parent ) getPath(serializedTree, serializedTree.find(function(item){ return item.id === node.parent; }), path);\n path.push(node);\n return path;\n }\n\n function getPathName(serializedTree, node, path = ''){\n let nodePath = getPath(serializedTree, node);\n return nodePath.map(function(item){\n if ( nodePath.indexOf(item) !== 0 ) return ` > ${item.name}`;\n return item.name;\n });\n };\n\n function findByUrl(tree, url){\n return findByField(tree, 'url', url);\n };\n\n function findById(tree, id){\n return findByField(tree, 'id', id);\n };\n\n function findByField(tree, field, value){\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n if ( tree[i][field] === value ) return tree[i];\n\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n {\n if ( tree[i].children && tree[i].children.length > 0 )\n {\n let node = findByField(tree[i].children, field, value);\n if ( node ) return node;\n }\n }\n\n return null;\n };\n });\n</script>\n\n<section class=\"g-bg-gray-light-v5 g-py-50 ui-breadcrumb-naea-v1\" data-jc=\"ui-breadcrumb-naea-v1\">\n <div class=\"container g-bg-cover__inner\">\n <header class=\"g-mb-20\">\n <h3 class=\"h5 g-font-weight-300 g-mb-5 CMS_edit\">편집</h3>\n <h2 class=\"h1 g-font-weight-300 text-uppercase CMS_edit\">편집</h2>\n </header>\n <ul class=\"u-list-inline\"></ul>\n </div>\n</section>","datecreated":"2019-04-26T06:33:59.046Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  14. 2019-06-25 01:00 | Administrator | {"id":"19042615330002ohc0","name":"Ui-contactus-naea-v1","reference":"ui/contactus/naea/ui-contactus-naea-v1.html","body":"<style></style>\n\n<script editor>\n option('addressBgImage', 'Background Image for Address Area', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('address.bg-address', $el).css('background-image', `url(${options.addressBgImage})`);\n };\n</script>\n\n<div class=\"g-bg-black-opacity-0_9 g-color-white-opacity-0_8 g-py-60\">\n <div class=\"container\">\n <div class=\"row\">\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">About Us</h2>\n </div>\n\n <p class=\"CMS_edit\">학업성취도 평가 정보 서비스</p>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">보도자료</h2>\n </div>\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">2018년 국가수준 학업성취도 평가 결과 발표</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">2019-04-22</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">2017년 국가수준 학업성취도 평가 결과 발표</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">2017-11-30</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">2016년 국가수준 학업성취도 평가 결과 발표 보도자료</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">2016-12-19</div>\n </article>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">연구지원</h2>\n </div>\n\n <nav class=\"text-uppercase1\">\n <ul class=\"list-unstyled g-mt-minus-10 mb-0\">\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">연구자료지원</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">현장연구지원</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">프로그램지원</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">심포지엄자료짐</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n </ul>\n </nav>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">한국교육과정평가원</h2>\n </div>\n\n <address class=\"g-bg-no-repeat g-font-size-12 mb-0 bg-address\" style=\"background-image: url(//via.placeholder.com/255x154)\">\n <!-- Location -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-map-marker\"></i>\n </span>\n </div>\n <p class=\"mb-0 CMS_edit CMS_multiline\">충청북도 진천군 덕산면 교학로 8\n <br>\n 한국교육과정평가원\n </p>\n </div>\n <!-- End Location -->\n\n <!-- Phone -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-phone\"></i>\n </span>\n </div>\n <p class=\"mb-0 CMS_edit CMS_multiline\">TEL : 043-931-0114<br>FAX : 043-931-0884 </p>\n </div>\n <!-- End Phone -->\n\n <!-- Email and Website -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-globe\"></i>\n </span>\n </div>\n <p class=\"mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"mailto:info@kice.re.kr\">info@kice.re.kr</a>\n <br>\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">naea.kice.re.kr</a>\n </p>\n </div>\n <!-- End Email and Website -->\n </address>\n </div>\n <!-- End Footer Content -->\n </div>\n </div>\n</div>","datecreated":"2019-04-26T06:33:59.046Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  15. 2019-06-25 01:00 | Administrator | {"id":"19042615330003ohc1","name":"Ui-footer-naea-v1","reference":"ui/footer/naea/ui-footer-naea-v1.html","body":"<style></style>\n\n<footer class=\"g-bg-gray-dark-v1 g-color-white-opacity-0_8 g-py-20\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-8 text-center text-md-left g-mb-10 g-mb-0--md\">\n <div class=\"d-lg-flex\">\n <small class=\"d-block g-font-size-default g-mr-10 g-mb-10 g-mb-0--md CMS_edit\">1998-2018 © All Rights Reserved.</small>\n <ul class=\"u-list-inline\">\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">개인정보처리방침</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">뷰어다운로드</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">찾아오시는길</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">사이트맵</a></li>\n </ul>\n </div>\n </div>\n\n <div class=\"col-md-4 align-self-center\">\n <ul class=\"list-inline text-center text-md-right mb-0\">\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Facebook\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-facebook\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Skype\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-skype\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Linkedin\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-linkedin\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Pinterest\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-pinterest\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Twitter\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-twitter\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Dribbble\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-dribbble\"></i></a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n</footer>","datecreated":"2019-04-26T06:33:59.046Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  16. 2019-06-25 01:00 | Administrator | {"id":"19042615300002wmg0","name":"Basics-table","reference":"basics/basics-table.html","body":"<script editor>\n option('caption', '표 설명', '');\n option('caption-side', '표 설명의 위치', 'top', [\n { text: '상단', value: 'top' },\n { text: '하단', value: 'bottom' },\n ]);\n option('caption-align', '표 설명의 글자 정렬', 'center', [\n { text: '좌측정렬', value: 'left' },\n { text: '중앙정렬', value: 'center' },\n { text: '우측정렬', value: 'right' },\n ]);\n\n exports.configure = function(options, $el, prevOptions){\n let $table = $el.find('table');\n let $caption = $el.find('caption');\n\n if ( $caption.length <= 0 ) $caption = $('<caption/>');\n\n $caption\n .text(options['caption'])\n .css({\n 'caption-side': options['caption-side'],\n 'text-align': options['caption-align'],\n })\n .prependTo($table);\n };\n</script>\n\n<div class=\"basics-table table-responsive\">\n <table class=\"table table-hover table-striped table-bordered CMS_attribute\">\n <thead>\n <tr>\n <th><div class=\"CMS_edit CMS_unwrap CMS_repeat CMS_attribute\">제목1</div></th>\n <th><div class=\"CMS_edit CMS_unwrap CMS_repeat CMS_attribute CMS_remove\">제목2</div></th>\n </tr>\n </thead>\n <tbody>\n <tr class=\"CMS_repeat\">\n <td><div class=\"CMS_edit CMS_unwrap CMS_repeat CMS_attribute\">내용1</div></td>\n <td><div class=\"CMS_edit CMS_unwrap CMS_repeat CMS_attribute CMS_remove\">내용2</div></td>\n </tr>\n </tbody>\n <tfoot>\n </tfoot>\n </table>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T00:59:34.994Z"}
  17. 2019-06-25 01:00 | Administrator | {"id":"19042615300004wmg0","name":"Twbs-header-megamenu-v1","reference":"bootstrap/header/twbs-header-megamenu-v1.html","body":"<style>\n .twbs-header-megamenu-v1 { }\n .twbs-header-megamenu-v1 .megamenu-navbar { min-height: 50px; }\n .twbs-header-megamenu-v1 .megamenu-container { padding: .5rem 1rem; width: 100%; position: absolute; background-color: #fff; border-bottom: 1px solid #ccc; z-index: 3; }\n\n .twbs-header-megamenu-v1 .megamenu-container:not(.show) { display: none; }\n\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper { width: 100%; }\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul { padding: 0; list-style-type: none; }\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul li { text-align: center; }\n\n @media (min-width: 992px) {\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul > li > ul > li.nav-item a { padding-right: .5rem; padding-left: .5rem; }\n }\n\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul > li > ul > li.nav-item:first-child a { color: #d9534f; }\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul > li > ul > li:hover:not(:first-child) { background-color: #d9534f; }\n .twbs-header-megamenu-v1 .megamenu-container .megamenu-list-wrapper ul > li > ul > li:hover:not(:first-child) a { color: #fff; }\n</style>\n\n<script>\n COMPONENT('twbs-header-megamenu-v1', function(component, config){\n const CONSTANTS = {\n TEMPLATE: {\n $NAV_ITEM: $('<li class=\"mx-auto nav-item\"/>'),\n $NAV_LINK: $('<a class=\"nav-link\" href=\"#\"/>'),\n\n $MEGAMENU_NAV_LIST: $('<ul class=\"mx-auto\"/>'),\n }\n };\n\n component.$el = component.element;\n\n component.$navbar = $('nav.navbar.megamenu-navbar', component.$el);\n component.$navbarToggler = $('.navbar-toggler', component.$navbar);\n component.$navbarContainer = $('.navbar-collapse', component.$navbar);\n component.$navbarNavList = $('ul.navbar-nav', component.$navbar);\n\n component.$megamenuContainer = $('.megamenu-container', component.$el);\n component.$megamenuListWrapper = $('.megamenu-list-wrapper', component.$megamenuContainer);\n component.$megamenuNavList = $('ul.megamenu-navbar-nav', component.$megamenuContainer);\n\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n if ( window._TOTALCMS_MAINMENU ) mainmenu = window._TOTALCMS_MAINMENU;\n else if ( mainmenu ) window._TOTALCMS_MAINMENU = mainmenu;\n\n // 메가메뉴\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(topmenu, index, src, $megamenuParentList){\n if ( topmenu.url === '/' ) return;\n\n let $navListItem = CONSTANTS.TEMPLATE.$NAV_ITEM.clone();\n let $navLink = CONSTANTS.TEMPLATE.$NAV_LINK.clone();\n\n $navLink\n .attr('href', topmenu.url)\n .text($.TOT.helpers.unescapeHtml(topmenu.name))\n .appendTo($navListItem);\n\n if ( window._TOTALCMS_MAINMENU.children.indexOf(topmenu) >= 0 )\n {\n let $megamenuNavItemSet = CONSTANTS.TEMPLATE.$NAV_ITEM.clone();\n $megamenuParentList = CONSTANTS.TEMPLATE.$MEGAMENU_NAV_LIST.clone();\n $megamenuNavItemSet.append($megamenuParentList);\n component.$megamenuNavList.append($megamenuNavItemSet);\n }\n\n $megamenuParentList.append($navListItem);\n if ( topmenu.children && topmenu.children.length > 0 ) _.each(topmenu.children, function(item, index, src){ return _recursive(item, index, src, $megamenuParentList); });\n });\n\n // 계산용 클론\n let $cloned = component.$el.clone()\n .removeAttr('data-jc')\n .find('.megamenu-container')\n .css({ display: 'block' })\n .end()\n .css({\n position: 'absolute',\n display: 'block',\n visibility: 'hidden',\n transform: 'translate(-10000px, -10000px)'\n }).appendTo(document.body);\n\n // 탑 레벨\n let cnt = 0;\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(topmenu, index, src){\n if ( topmenu.url === '/' ) return;\n\n cnt = cnt + 1;\n let $navListItem = CONSTANTS.TEMPLATE.$NAV_ITEM.clone();\n let $navLink = CONSTANTS.TEMPLATE.$NAV_LINK.clone();\n\n\n $navLink\n .attr('href', topmenu.url)\n .text($.TOT.helpers.unescapeHtml(topmenu.name))\n .appendTo($navListItem);\n\n component.$navbarNavList.append($navListItem);\n });\n\n $cloned.remove();\n\n // randomize id map\n let navRandomId = $.TOT.helpers.getRandomId();\n\n component.$navbarToggler.attr({'aria-controls': navRandomId, 'data-target': `#${navRandomId}`}).data('target', `#${navRandomId}`);\n component.$navbarContainer.prop('id', navRandomId);\n\n component.$el.on('mouseenter mouseleave', async function($event){\n if( window.matchMedia( '(max-width: 991.98px)' ).matches ) return;\n\n let show = $event.type === 'mouseenter';\n component.$megamenuContainer[show ? 'slideDown' : 'slideUp'](function(){ $(this).toggleClass('show', show); });\n });\n\n $(window).on('resize', async function(){\n if( window.matchMedia( '(min-width: 992)' ).matches ) return;\n\n component.$megamenuContainer.slideUp(function(){ $(this).removeClass('show'); });\n });\n };\n });\n</script>\n\n<div data-jc=\"twbs-header-megamenu-v1\" class=\"twbs-header-megamenu-v1\">\n <nav class=\"navbar navbar-expand-lg navbar-light bg-light megamenu-navbar\">\n <button class=\"navbar-toggler\" type=\"button\" data-toggle=\"collapse\" data-target=\"#navbarSupportedContent\" aria-controls=\"navbarSupportedContent\" aria-expanded=\"false\" aria-label=\"Toggle navigation\">\n <span class=\"navbar-toggler-icon\"></span>\n </button>\n <div class=\"collapse navbar-collapse\" id=\"navbarSupportedContent\">\n <ul class=\"navbar-nav w-100 d-flex\"></ul>\n </div>\n </nav>\n\n <div class=\"megamenu-container\" aria-labelledby=\"megamenu-navbar\">\n <div class=\"megamenu-list-wrapper d-flex\">\n <ul class=\"megamenu-navbar-nav w-100 d-flex\"></ul>\n </div>\n </div>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Bootstrap","dateupdated":"2019-06-25T00:59:34.994Z"}
  18. 2019-06-25 01:00 | Administrator | {"id":"19042615300008wmg0","name":"Ui-banner-v1","reference":"ui/banner/ui-banner-v1.html","body":"<!-- Banners -->\n<div class=\"row align-items-stretch\">\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"text-center g-color-white g-overflow-hidden\">\n <div class=\"u-block-hover--scale g-min-height-200 g-flex-middle g-bg-cover g-bg-size-cover g-bg-bluegray-opacity-0_3--after g-transition-0_5\" data-bg-img-src=\"assets/img-temp/500x320/img1.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-1 g-py-50 g-px-20\">\n <h3 class=\"text-uppercase\">Want to hire\n <span class=\"g-font-weight-700\">Best</span>\n people\n </h3>\n <hr class=\"g-brd-3 g-brd-white g-width-30 g-my-20\">\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Hire Unify</a>\n </div>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"text-center g-color-white g-overflow-hidden\">\n <div class=\"u-block-hover--scale g-min-height-200 g-flex-middle g-bg-cover g-bg-size-cover g-bg-bluegray-opacity-0_3--after g-transition-0_5\" data-bg-img-src=\"assets/img-temp/500x320/img2.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-1 g-py-50 g-px-20\">\n <em class=\"d-inline-block info-v3-1__title g-font-style-normal g-font-size-18 text-uppercase\">We are</em>\n <h3 class=\"text-uppercase g-mb-15\">Creative digital agency</h3>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Hire Unify</a>\n </div>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  19. 2019-06-25 01:00 | Administrator | {"id":"19042615300010wmg0","name":"Ui-banner-v3","reference":"ui/banner/ui-banner-v3.html","body":"<!-- Banners -->\n<div class=\"row\">\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"g-bg-size-cover\" data-bg-img-src=\"assets/img-temp/500x450/img4.jpg\">\n <div class=\"w-100 g-min-height-360 g-flex-middle g-width-50x--md g-bg-primary-opacity-0_8 g-pa-25 ml-auto\">\n <div class=\"g-flex-middle-item\">\n <h4 class=\"h5 g-color-white g-font-weight-700 text-uppercase g-mb-10\">Branding Work</h4>\n <p class=\"g-color-white-opacity-0_9 mb-0\">This is where we sit down, grab a cup of coffee and dial in the details.</p>\n </div>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"g-bg-size-cover\" data-bg-img-src=\"assets/img-temp/500x450/img5.jpg\">\n <div class=\"w-100 g-min-height-360 g-flex-middle g-width-50x--md g-bg-primary-opacity-0_8 g-pa-25 ml-auto\">\n <div class=\"g-flex-middle-item\">\n <h4 class=\"h5 g-color-white g-font-weight-700 text-uppercase g-mb-10\">Project planner</h4>\n <p class=\"g-color-white-opacity-0_9 mb-0\">This is where we sit down, grab a cup of coffee and dial in the details.</p>\n </div>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  20. 2019-06-25 01:00 | Administrator | {"id":"19042615300003wmg1","name":"Contrib-snackbar","reference":"contrib/contrib-snackbar.html","body":"<style>\n .ui-snackbar { position: fixed; bottom: 0; left: 0; width: 100%; transition: all 0.3s; transform: translate(0,70px); font-size: 14px; z-index: 1000; }\n .ui-snackbar > div { margin: 0 auto; width: 80%; background-color: #303030; border-radius: 3px 3px 0 0; box-shadow: 0 -15px 25px rgba(0,0,0,0.08); border: 1px solid gray; border-bottom: 0; height: 64px }\n .ui-snackbar-dismiss { float: right; width: 100px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; text-align: center; text-decoration: none; color: gray; padding: 22px 0 0 0; text-transform: uppercase; background-color: #404040; border-radius: 0 3px 0 0; height: 63px; display: block; cursor: pointer; font-weight: bold; color: #A0A0A0; }\n .ui-snackbar-dismiss:hover { background-color: #505050; text-decoration: none; }\n .ui-snackbar-body { margin: 0 100px 0 80px; padding: 22px 15px 15px 0; color: white; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }\n .ui-snackbar-body .fa { margin-right: 5px; }\n .ui-snackbar-visible { transform: translate(0, 0); }\n .ui-snackbar-icon { float: left; font-size: 28px; border-right: 1px solid #505050; padding: 13px 0 0 0; width: 63px; height: 63px; text-align: center; color: #4285F4; }\n .ui-snackbar-icon .fa-times-circle { color: red; }\n .ui-snackbar-icon .fa-check-circle { color: #60CD4F; }\n .ui-snackbar-icon .fa-spinner { color: yellow; }\n\n @media(max-width: 767px) {\n .ui-snackbar > div { width: 94%; }\n .ui-snackbar-body { margin-left: 0; }\n .ui-snackbar-icon { width: 40px; font-size: 18px; border-right: 0; padding-top: 21px; }\n .ui-snackbar-dismiss { width: 70px; }\n }\n</style>\n\n<script>\n COMPONENT('contrib-snackbar', 'timeout:4000;button:OK', function(self, config) {\n\n var show = true;\n var callback;\n var delay;\n\n self.readonly();\n self.blind();\n self.nocompile && self.nocompile();\n\n self.make = function() {\n self.aclass('ui-snackbar hidden');\n self.append('<div><span class=\"ui-snackbar-dismiss\"></span><span class=\"ui-snackbar-icon\"></span><div class=\"ui-snackbar-body\"></div></div>');\n self.event('click', '.ui-snackbar-dismiss', function() {\n self.hide();\n callback && callback();\n });\n };\n\n self.hide = function() {\n clearTimeout2(self.ID);\n self.rclass('ui-snackbar-visible');\n if (delay) {\n clearTimeout(delay);\n self.aclass('hidden');\n delay = null;\n } else {\n delay = setTimeout(function() {\n delay = null;\n self.aclass('hidden');\n }, 1000);\n }\n show = true;\n };\n\n self.waiting = function(message, button, close) {\n self.show(message, button, close, 'fa-spinner fa-pulse');\n };\n\n self.success = function(message, button, close) {\n self.show(message, button, close, 'fa-check-circle');\n };\n\n self.warning = function(message, button, close) {\n self.show(message, button, close, 'fa-times-circle');\n };\n\n self.danger = self.failed = function(message, button, close) {\n self.show(message, button, close, 'fa-exclamation-triangle');\n };\n\n self.info = function(message, button, close) {\n self.show(message, button, close, 'fa-info-circle');\n };\n\n self.show = function(message, button, close, icon) {\n\n if (typeof(button) === 'function') {\n close = button;\n button = null;\n }\n\n callback = close;\n\n self.find('.ui-snackbar-icon').html('<i class=\"fa {0}\"></i>'.format(icon || 'fa-info-circle'));\n self.find('.ui-snackbar-body').html(message).attr('title', message);\n self.find('.ui-snackbar-dismiss').html(button || config.button);\n\n if (show) {\n self.rclass('hidden');\n setTimeout(function() {\n self.aclass('ui-snackbar-visible');\n }, 50);\n }\n\n setTimeout2(self.ID, self.hide, config.timeout + 50);\n show = false;\n };\n });\n</script>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Contrib","dateupdated":"2019-06-25T00:59:34.994Z"}
  21. 2019-06-25 01:00 | Administrator | {"id":"19042615300005wmg1","name":"Twbs-layout-container","reference":"bootstrap/layout/twbs-layout-container.html","body":"<style>\n #CMS .twbs-layout-container {\n padding: 1rem;\n min-height: 2rem;\n }\n</style>\n\n<div class=\"twbs-layout-container container CMS_attribute CMS_widgets\"></div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Bootstrap","dateupdated":"2019-06-25T00:59:34.994Z"}
  22. 2019-06-25 01:00 | Administrator | {"id":"19042615300014wmg0","name":"Ui-banner-v7","reference":"ui/banner/ui-banner-v7.html","body":"<!-- Banners -->\n<div class=\"row align-items-stretch\">\n <div class=\"col-md-6\">\n <!-- Article -->\n <article class=\"h-100 text-uppercase text-center g-flex-middle g-bg-cover g-bg-size-cover g-bg-bluegray-opacity-0_3--after g-color-white g-font-size-12 g-py-100 g-px-40\" data-bg-img-src=\"assets/img-temp/500x650/img1.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-2\">\n <!-- Article Title -->\n <h3 class=\"g-font-weight-700 g-letter-spacing-3 g-mb-40\">Your Perfect Structure</h3>\n <!-- End Article Title -->\n\n <!-- Article Lists -->\n <div class=\"row justify-content-around\">\n <div class=\"col-md-6 col-lg-3 g-mb-50\">\n <ul class=\"list-unstyled mb-0\">\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 1</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 2</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 3</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 4</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 5</li>\n <li class=\"g-py-8\">Step 6</li>\n </ul>\n </div>\n <div class=\"col-md-6 col-lg-3 g-mb-50\">\n <ul class=\"list-unstyled mb-0\">\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 7</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 8</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 9</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 10</li>\n <li class=\"g-brd-bottom g-brd-white-opacity-0_2 g-py-8\">Step 11</li>\n <li class=\"g-py-8\">Step 12</li>\n </ul>\n </div>\n </div>\n <!-- End Article Lists -->\n\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-md-6\">\n <!-- Article -->\n <article class=\"h-100 text-center g-flex-middle g-bg-cover g-bg-size-cover g-bg-bluegray-opacity-0_3--after g-color-white g-py-100 g-px-40\" data-bg-img-src=\"assets/img-temp/500x650/img2.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-2\">\n <em class=\"d-block g-font-style-normal g-mb-15\">We are</em>\n <h3 class=\"g-font-weight-700 text-uppercase g-letter-spacing-3 g-mb-30\">Creative agency</h3>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  23. 2019-06-25 01:00 | Administrator | {"id":"19042615300007wmg1","name":"Contrib-content-partial","reference":"contrib/content/contrib-content-partial.html","body":"<script editor>\n option('id', 'Partial page', '', 'Partial');\n\n exports.configure = function(options, el) {\n\n var text = 'not defined';\n\n if (options.id) {\n var item = this.id.findItem('value', options.id);\n item && (text = item.text);\n }\n\n el.find('.totaljs').text('Partial: ' + text);\n\n };\n\n</script>\n\n<script total>\n exports.render = function(options, html, next) {\n if (!options.id)\n return next('');\n this.CMSpartial(options.id, function(err, response) {\n if (response && response.body)\n next(html.substring(0, html.indexOf('<div class=\"totaljs')) + response.body + '</div>');\n else\n next('');\n });\n };\n</script>\n\n<div class=\"wm wp wb wc146\">\n <div class=\"totaljs\">Partial page</div>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Contrib","dateupdated":"2019-06-25T00:59:34.994Z"}
  24. 2019-06-25 01:00 | Administrator | {"id":"19042615300009wmg1","name":"Ui-banner-v2","reference":"ui/banner/ui-banner-v2.html","body":"<div class=\"ui-banner-v2\">\n <!-- Banners -->\n <div class=\"row\">\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"row align-items-stretch text-center mx-0\">\n <!--Article Content-->\n <div class=\"col-sm-6 g-bg-black g-px-30 g-py-45\">\n <h3 class=\"h4 g-color-white g-font-weight-600 text-uppercase g-mb-25\">Creative\n <span class=\"d-block g-color-primary g-font-weight-700\">Agency</span>\n </h3>\n <p class=\"g-color-gray-dark-v5 g-mb-25\">This is where we sit down, grab a cup of coffee and dial in the details.</p>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Hire Unify</a>\n </div>\n <!-- End Article Content -->\n\n <!-- Article Image -->\n <div class=\"col-sm-6 px-0 g-bg-size-cover\" data-bg-img-src=\"assets/img-temp/500x600/img1.jpg\"></div>\n <!-- End Article Image -->\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-6 g-mb-30\">\n <!-- Article -->\n <article class=\"row align-items-stretch text-center mx-0\">\n <!--Article Content-->\n <div class=\"col-sm-6 g-bg-black g-px-30 g-py-45\">\n <h3 class=\"h4 g-color-white g-font-weight-600 text-uppercase g-mb-25\">Project\n <span class=\"d-block g-color-primary g-font-weight-700\">Planner</span>\n </h3>\n <p class=\"g-color-gray-dark-v5 g-mb-25\">This is where we sit down, grab a cup of coffee and dial in the details.</p>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Hire Unify</a>\n </div>\n <!-- End Article Content -->\n\n <!-- Article Image -->\n <div class=\"col-sm-6 px-0 g-bg-size-cover\" data-bg-img-src=\"assets/img-temp/500x600/img3.jpg\"></div>\n <!-- End Article Image -->\n </article>\n <!-- End Article -->\n </div>\n </div>\n <!-- End Banners -->\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  25. 2019-06-25 01:00 | Administrator | {"id":"19042615300018wmg0","name":"Grt-header-megamenu-v1","reference":"gractor/header/megamenu/grt-header-megamenu-v1.html","body":"<style>\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation ul.navigation-list,\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation ul.full-navigation-list {list-style:none; padding: 0;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation ul.navigation-list a.navigation-link,\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation ul.full-navigation-list a.full-navigation-link {text-decoration:none; color:#000;}\n\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation {background-color:#fff; border-bottom:2px solid #DA4142; height:65px;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation {text-align:center; color:#000;}\n\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation .container{margin:0 auto; overflow: hidden; height:65px;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation .image-wrapper{height: 65px; padding-top: 1rem;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation img{width: 200px;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation .container ul.navigation-list{ overflow:hidden; }\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation .container ul.navigation-list a li.navigation-list-item{text-align:center; color:#979797; font-weight:600; width:150px; height: 65px; line-height: 65px;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation .container ul.navigation-list a li.navigation-list-item:hover{color:#000; border-top: 4px solid #DA4142;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .navigation:hover ~ .full-navigation{ height: calc((2.75rem * 7) + 1.5rem); }\n\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation{ border-bottom:2px solid #DA4142; width:100%; height:0; box-sizing:border-box; transition-property:height; transition-duration:0.5s; transition-timing-function:ease-in; overflow:hidden; position:absolute; background-color:rgba(255,255,255,0.9); z-index:999;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation:hover{height: calc((2.75rem * 7) + 1.5rem); }\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation .full-navigation-list-container .full-navigation-list-wrapper{border-right:1px solid #ddd; width:150px; text-align:center; box-sizing:border-box;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation .full-navigation-list-container ul.full-navigation-list li.full-navigation-list-item{height:2.75rem; line-height:2.75rem;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation .full-navigation-list-container ul.full-navigation-list li.full-navigation-list-item:not(.full-navigation-list-header):hover{background-color:#DA4142; color:#fff; cursor:pointer;}\n .grt-header-megamenu-v1 .grt-header-megamenu-v1-container .full-navigation .full-navigation-list-container ul.full-navigation-list li.full-navigation-list-item.full-navigation-list-header{color:#DA4142; font-weight:900;}\n</style>\n\n<script>\n COMPONENT('grt-header-megamenu-v1', '', function(component, config){\n component.$el = component.element;\n\n component.$navContainer = component.$el.find('.navigation .container');\n component.$navUl = component.$navContainer.find('ul.navigation-list');\n\n component.$subNavContainer = component.$el.find('.full-navigation .full-navigation-list-container');\n\n const CONSTANTS = {\n NAV: {\n $TOPMENU_LIST_ITEM_TEMPLATE: $(`<a class=\"navigation-link d-inline-block\" href=\"#\"><li class=\"navigation-list-item\"></li></a>`),\n $SUBMENU_LIST_WRAPPER_TEMPALTE: $(`<div class=\"full-navigation-list-wrapper d-inline-block mt-4\">`),\n $SUBMENU_LIST_TEMPALTE: $(`<ul class=\"full-navigation-list\">`),\n $SUBMENU_LIST_ITEM_TITLE_TEMPLATE: $(`<li class=\"full-navigation-list-item full-navigation-list-header\"></li>`),\n $SUBMENU_LIST_ITEM_TEMPLATE: $(`<a class=\"full-navigation-link\" href=\"#\"><li class=\"full-navigation-list-item\"></li></a>`),\n }\n };\n\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n if ( window._TOTALCMS_MAINMENU ) mainmenu = window._TOTALCMS_MAINMENU;\n else if ( mainmenu ) window._TOTALCMS_MAINMENU = mainmenu;\n\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(topmenu){\n if ( topmenu.url === '/' ) return;\n\n let $a = CONSTANTS.NAV.$TOPMENU_LIST_ITEM_TEMPLATE.clone();\n\n $a.attr('href', topmenu.url)\n .find('li.navigation-list-item')\n .text(topmenu.name).end()\n .appendTo(component.$navUl);\n\n component.$navUl.append($a);\n\n // make 2 depth\n if ( topmenu.children )\n {\n let $subMenuWrapper = CONSTANTS.NAV.$SUBMENU_LIST_WRAPPER_TEMPALTE.clone();\n let $subMenu = CONSTANTS.NAV.$SUBMENU_LIST_TEMPALTE.clone();\n\n let $subMenuTitle = CONSTANTS.NAV.$SUBMENU_LIST_ITEM_TITLE_TEMPLATE.clone();\n $subMenuTitle.text(topmenu.name)\n .appendTo($subMenu);\n\n _.each(topmenu.children, function(submenu){\n let $subMenuA = CONSTANTS.NAV.$SUBMENU_LIST_ITEM_TEMPLATE.clone();\n\n $subMenuA.attr('href', submenu.url)\n .find('.full-navigation-list-item')\n .text(submenu.name).end()\n .appendTo($subMenu);\n });\n\n $subMenuWrapper.append($subMenu);\n component.$subNavContainer.append($subMenuWrapper);\n }\n\n });\n };\n }, []);\n</script>\n\n<div class=\"grt-header-megamenu-v1\" data-jc=\"grt-header-megamenu-v1\">\n <header class=\"grt-header-megamenu-v1-container\">\n <div class=\"navigation\">\n <div class=\"container d-flex\">\n <div class=\"image-wrapper\">\n <img class=\"CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" />\n </div>\n <ul class=\"navigation-list my-0 ml-auto mr-0\"></ul>\n </div>\n </div>\n <div class=\"full-navigation\">\n <div class=\"container d-flex\">\n <div class=\"full-navigation-list-container ml-auto d-flex\"></div>\n </div>\n </div>\n </header>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Gractor","dateupdated":"2019-06-25T00:59:34.994Z"}
  26. 2019-06-25 01:00 | Administrator | {"id":"19042615300013wmg1","name":"Ui-banner-v6","reference":"ui/banner/ui-banner-v6.html","body":"<!-- Banners -->\n<div class=\"row align-items-stretch\">\n <div class=\"col-md-6 g-mb-30\">\n <!-- Article -->\n <article class=\"h-100 text-center u-block-hover info-v3-2 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_2--after g-color-white text-uppercase g-py-210 g-px-30\" data-bg-img-src=\"assets/img-temp/900x900/img2.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-2\">\n <h3 class=\"g-font-weight-700 g-letter-spacing-3 g-mb-20\">Professional\n <br>\n Teaching\n </h3>\n <em class=\"d-block g-letter-spacing-3 g-font-weight-300 g-font-style-normal g-mb-40\">Examples of our branding projects.</em>\n <a class=\"btn btn-md u-btn-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-md-6 g-mb-30\">\n <!-- Article -->\n <article class=\"h-100 text-center u-block-hover info-v3-2 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_2--after g-color-white text-uppercase g-py-210 g-px-30\" data-bg-img-src=\"assets/img-temp/900x900/img5.jpg\">\n <div class=\"g-flex-middle-item g-pos-rel g-z-index-2\">\n <h3 class=\"g-font-weight-700 g-letter-spacing-3 g-mb-20\">Work\n <br>\n Presentations\n </h3>\n <em class=\"d-block g-letter-spacing-3 g-font-weight-300 g-font-style-normal g-mb-40\">Examples of our branding projects.</em>\n <a class=\"btn btn-md u-btn-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  27. 2019-06-25 01:00 | Administrator | {"id":"19042615300021wmg1","name":"Ui-header-classics-gradient","reference":"ui/header/classics/ui-header-classics-gradient.html","body":"<style>\n #CMS .ui-header-classics-gradient { position: relative; z-index: 0; }\n</style>\n\n<script>\n COMPONENT('ui-header-classics-gradient', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n // initialization of header\n $.HSCore.components.HSHeader.init(component.$el);\n $.HSCore.helpers.HSHamburgers.init(component.$el.find('.hamburger'));\n };\n }, [\n '/libs/ui/1.0.0/css/unify.min.css',\n '/libs/ui/1.0.0/js/hs.core.js',\n '/libs/ui/1.0.0/js/hs.header.js',\n '/libs/ui/1.0.0/js/hs.hamburgers.js',\n '/libs/ui/1.0.0/js/hs.dropdown.js',\n ]);\n</script>\n\n<!-- Header -->\n<header class=\"u-header u-header--sticky-top u-header--toggle-section u-header--change-appearance ui-header-classics-gradient\" data-jc=\"ui-header-classics-gradient\" data-header-fix-moment=\"300\">\n <div class=\"u-header__section u-header__section--dark g-bg-primary-gradient-opacity-v1 g-transition-0_3 g-py-10\" data-header-fix-moment-exclude=\"g-py-10\" data-header-fix-moment-classes=\"g-py-0\">\n <nav class=\"navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"navBar\" data-toggle=\"collapse\" data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"/\" class=\"navbar-brand\"><img class=\"CMS_edit\" src=\"//via.placeholder.com/100x40\" alt=\"Image Description\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg\" id=\"navBar\">\n <ul class=\"navbar-nav text-uppercase g-font-weight-600 ml-auto\">\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Home</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Features</a></li>\n <li class=\"nav-item g-mx-20--lg active\"><a href=\"#!\" class=\"nav-link px-0\">Shortcodes<span class=\"sr-only\">(current)</span></a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Pages</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Demos</a></li>\n <li class=\"nav-item g-ml-20--lg g-mr-0--lg\"><a href=\"#!\" class=\"nav-link px-0\">What's New</a></li>\n </ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n</header>\n<!-- End Header -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  28. 2019-06-25 01:00 | Administrator | {"id":"19042615300015wmg1","name":"Ui-banner-v8","reference":"ui/banner/ui-banner-v8.html","body":"<!-- Banners -->\n<div class=\"row align-items-stretch\">\n <div class=\"col-lg-5\">\n <!-- Article -->\n <article class=\"g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-py-75 g-px-50 g-mb-30\" data-bg-img-src=\"assets/img-temp/400x270/img1.jpg\">\n <div class=\"g-flex-middle-item g-z-index-1 g-width-300\">\n <h3 class=\"g-color-white g-font-weight-700 text-uppercase g-letter-spacing-3 g-mb-15\">Project planner</h3>\n <div class=\"g-line-height-2 g-mb-20\">\n <p class=\"g-color-white-opacity-0_9\">Fusce dolor libero, efficitur et lobortis at, faucibus nec nunc. Proin fermentum eget.</p>\n </div>\n <span class=\"g-color-primary g-font-weight-700 g-font-size-16 text-uppercase\">From $20.00</span>\n </div>\n </article>\n <!-- End Article -->\n\n <!-- Article -->\n <article class=\"text-uppercase text-center g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-color-white g-py-75 g-px-50\" data-bg-img-src=\"assets/img-temp/400x270/img18.jpg\">\n <div class=\"g-flex-middle-item g-z-index-1\">\n <span class=\"d-inline-block g-brd-bottom g-brd-2 g-brd-primary g-font-weight-700 g-font-size-16 g-letter-spacing-1 g-pb-8 g-mb-20\">Examples of branding projects</span>\n <h3 class=\"h2 g-font-weight-700 g-letter-spacing-3 g-mb-35\">Branding work</h3>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-7\">\n <!-- Article -->\n <article class=\"h-100 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-py-75 g-px-50\" data-bg-img-src=\"assets/img-temp/500x650/img10.jpg\">\n <div class=\"g-flex-middle-item g-z-index-1 g-width-370\">\n <h2 class=\"g-font-weight-700 text-uppercase g-color-white g-mb-15\">Pricing plans</h2>\n <strong class=\"d-block g-color-white g-font-size-60 g-font-weight-700 text-uppercase g-line-height-1 g-letter-spacing-3 g-mb-25\">\n <span class=\"g-color-primary\">40%</span>\n OFF</strong>\n <div class=\"g-line-height-2 g-mb-35\">\n <p class=\"g-color-white-opacity-0_9\">This is where we sit down, grab a cup of coffee and dial in the details. Understanding the task at hand and ironing out the wrinkles is key.</p>\n </div>\n <a class=\"btn btn-md u-btn-outline-white g-font-weight-600 g-font-size-11 text-uppercase\" href=\"#!\">Learn More</a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  29. 2019-06-25 01:00 | Administrator | {"id":"19042615300023wmg1","name":"Ui-hero-block-v1","reference":"ui/hero/blocks/ui-hero-block-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-hero-block-v1', function(component, config){}, [\n ]);\n</script>\n\n<!-- Hero Block #01 -->\n<section class=\"g-flex-centered g-height-450 g-bg-gray-light-v5 g-py-30 ui-hero-block-v1\" data-jc=\"ui-hero-block-v1\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-6 align-self-center g-py-20\">\n <h2 class=\"h4 text-uppercase g-letter-spacing-1 g-mb-20 CMS_edit\">Content block</h2>\n <p class=\"lead mb-0 g-line-height-2 CMS_edit\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin hendrerit rhoncus tempus. Donec id orci malesuada, finibus odio quis, tincidunt libero. Fusce in venenatis ligula. Etiam eget lacus id erat scelerisque tempor.</p>\n </div>\n\n <div class=\"col-md-6 align-self-center g-py-20\">\n <img class=\"w-100 CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Iamge Description\">\n </div>\n </div>\n </div>\n</section>\n<!-- End Hero Block #01 -->\n","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  30. 2019-06-25 01:00 | Administrator | {"id":"19042615300017wmg1","name":"Ui-headline-v1","reference":"ui/headline/ui-headline-v1.html","body":"<style></style>\n\n<script editor>\n option('head-level', 'Head Level', 1);\n exports.configure = function(options, $el, prevOptions){\n $el.find('ui-headline-v1--headline').replaceWith(`h${options['head-level']}`);\n };\n</script>\n\n<div class=\"u-heading-v1-1 g-bg-main g-brd-gray-light-v2 g-mb-20\">\n <h2 class=\"h3 u-heading-v1__title ui-headline-v1--headline CMS_edit\">지나가는 별이 별 언덕 시와 까닭입니다. 패, 자랑처럼 별 봅니다.</h2>\n</div>\n","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  31. 2019-06-25 01:00 | Administrator | {"id":"19042615300025wmg1","name":"Ui-hero-info-v3","reference":"ui/hero/info/ui-hero-info-v3.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-hero-info-v3', function(component, config){}, [\n ]);\n</script>\n\n<!-- Hero Info #03 -->\n<section class=\"g-py-200--md g-py-80 ui-hero-info-v3\" data-jc=\"ui-hero-info-v3\" style=\"background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANwAAACKBAMAAAAzozlYAAAALVBMVEXt7e3q6urs7Ozv7+/o6Ojp6enx8fHw8PDl5eXm5ubn5+fk5OTi4uLj4+Pu7u4xuJpqAAAMdElEQVR4Xr3bUY3sOruE4ZdCUSgKReGj8FEwBVMwBVMwhVAIhVAIhjMze5Z+aW8t6Vx1xVGS7ihPX1ruFPd9r/t+NChU+1r3eu7nWbW7XHW2fF3tdy1qatW113Wv/axDPWut3ds6W7X3rt2r1q4Nqt61cvduDUCXooxPc4xJUe/33Z199M149bq+yF6n8Dyr73WX7efac2SMjJ/M+TVmxpz8czV+B5vDnPq5nMmYa2QMf5rLUyOlVefc8lO711nSsWeyFtora2kmros5JmO85AVov6GSgFRWUyCJofHNJ5qZ1RYPScinufGbOef3YaMk20aibdGUeqvbNITL86ikqpLOPrV11KZassuiuhvZIhgMwbKBT3OWWt2INH5ica3cRkYF3i21ZGNewTXrm6KE5JLKapXAaoSt8gZABmNAAPo490siyajmELIBYwBKotQCAdh76iclIaskNVg/8VZJ9dyAFILhjwL+NIf4395ntnGKGOQ+tH88FwaEFUoWllT8IEiXSt1So6ieBUBsDCRCiHyeExIGLF2MahcIYRkKmZZlC8tzlVsSMugnLuxSlS2rW93nEQYDYAMWgD7OFfAKgrj3KNMABgF0t10tJCSDBmVUQn9AWiroXTg1xXUBFNgCDHaE+TwXjIG3rqtaNkIyACkDSDqRDeCJZEkggbuolqogKFQW11Js7MYY2RYCf5yzZDCSrr3a3VjGIdi8QNMBAMSYR/oB93KpSqhLRkiimue+HzCEEOeFCRD4NDfJ7wbJuLTO/ZxzX6dXc85zX891f+W5rlrrujnX2tf62s4S8mmWemZqBrz3rnZf67qe62tf6/ri1/WVVfvT3H0/e529dM5Z+8m4/zWdoOZQJfONMsjektSH1lm1z1e2uuuc2quWrrX3EjTZz3Ub9lM1t3OaT3Pjf5mCmfmv6cRIuDIyZ+acGjPe6jFHpueY+ZnpnQEYa4EhmRC/Y/x8m9FzfG/j0xwmLRlUAJf/NZ2Ys+85MjOJvslR38CAKBGE6VN1GkStLqm7Ca9UkjpMJvcw89OcQIBxAKR/TSd+gPFiLAwvFU/U2EgA2r4RBNkFgAC3YwCMr7li+DSHQYZgY4n/TCcmQVBUGtQBCAAIGXJFArDVwiBApjDiBd+zQB/nfkVkgGBACBmBEaNj20I0tknhVzQF6pTX4pVEgQ1GYBAGDLLuwYCPcw4SAvNnlxEGwCqNMgJLGAzTgGbHSCD3OjQAaUkQAQj+eH2fBOXTnBCCKPgPCQYjUG+G6vcC4x9IACAMavoKfiFJyyJLAAUG2aHvHoHPcyAFI0ODhBAyEuDDKNMoLlrg5sgIGwzA2aF/8ZKQAVBkBOy9UCLzaQ5kCwUwRGAQAdmnN0FhOhTCAAZLhkhggYQJgGQjgNfmTxaMoPlpTq1aalWVu6r3V87u1rLx1p5bquu0unp3Nc8+Z/eus3SdUq3ez6madRqvpTI29qluyWps9li+0Ke5636es3Z9jaLU59vTqdq1VKe0l/r08tl1zppaa8mwHq0665xa9ZDT51rrfI1nrPWIfVHMZMyqtdZeWVZ9mntnooTfFbXMn5N8n2t+ZcyR30+/b8j8Pv3fBMpFGJTq7L16SGOJ+RVm4m9vhCSw2fo01+LYOmsyMjQDsosAmUpJklXQJTFGjlTqtbe0H1Vl7y29zRzsyl6ZSWKUgER+Qo/k01zJBsR8xpBUqlL7nPoa3bIsWQ3V8nzmPDsaKQSpFzBquUu01HjMDRggyKAAL+PweQ6pAD2dKqllUfoTdzeocCP7zuVy4iUlUQQlA4qyBRnDCoASRwITAEkf55Ao1HmuSKo/FJJVpqgWpdMlc4/CRKdmocgigSABUkC3a4IggCFGCCM+zjUgq+bas2QJVCokNdVHqlYJY1QXJQlQDkTIIHhBZBIyh8cLhvxSIAAqn+ZakkrAc6f0neIPSJdbP4B+DuveEiJhVggSQjKRUmGCYAcS0ATzG8V8miuBxBT3mt3+BSXJjeqfrSRM0FVTAIgt4hgkQzbWfENNavAnSKYRxuLjXGO1utb17Kl/GCFhSZQsyQDg7b1XvJ+NAMmFYgAMQL1w7XuGvATya/7SH+fkBMYYGVS7JKkv9e6S1Ja/U6TEde2MquuMidEEJckkmUBIILkfX/f+Wcpaz7r2Ps+11qXr7E9zz/OzVrae9Vx77e+71jpXXFxTN00mRNnLp9jPtfuqc3HW3ednxe7qGS3fc83aW8++LpZXzZXnynqkOad6LPNpjj7nOl/irtXe7m5JV51Ve9U51FGvtdepfa2qklPTP/Oor2SSmacUdml6Dt9FwhgAY4qfG2P6UJ/mWl3lbvdV+6Jb3SWCMDDEZQjsEYAQEqnV2iVnJj4u4YxkkjEmAHMm4GQMMGTvfJpTd9vCDWAaGwyIoO5LE3UjLFkWLbbckmD3WquvnRkoVOq2ZYEkkBEQAff4PCdoAViENAITCMHe1w5GlJEksFvVgKUmDayGIJqSBA7B6NcBMLn2+DTXclkRAAgHgBBb8nPvGUCvrHYhJGwkN6/1mmQdJlalS8xgCgmAEEDgvsmnOdNSFQIwCCELgIRn6cRCyG61UCM3CFESGK/OFNQQNUJBRyBBWYCj+Mr4NEchgY2NgvlNhOnz1Ajgni4bdamxoWUsIbDqHavU/WhKJxaWMWqAQDD3zKc5VTkUSIJGELAkJPm6NCKpo2VLxxIGi6k61V2mlzKpS4wkJJIEAA0gUSDBpzmkkkCy9BIAy9jAy7U8AxYtoY6VEPCsNlgYP/ttqia5BFIHKIQSCEg4+3k+zd1PgZHkrtY65+x91rmu+yfPmJSSAH5lW71L676ulttm+rnT5ymVL88WNUM01huBsAOxn8qnOTJyPz8Pvvz/ehftuq99zjq7r/s36Vvnun9+6znPc59dV9+5v6Lruqr3WZutT3NjDJIwYxKzCsuaM/FOgrCt1um2TcnT/iIG53p2Le7qvVoInAhCkhBswDqbLjd35dOc1TYyct7RIUSgbTJHIInm5Jxnda/TS+te1zvumyvpo8M69rk1K374Xc3DlWtQrPJp19rXHh/nyhIIwHiDWu2SjWTJbWiVsb3W1fu6G9qwtdccs9fqdXXS0hXa7t5rRvpSC97GhV/W6E9zIDCAzFtHqi5bMsLYtii3JdH2uS6MMCDnvu+9luelKG8rMbI7zphDObEkI+M96tOcu1WS1RI+4ypK30FyA8KWGstY57pny9hGFtAANMAhkTCckIjmyJREO5XSp7mi1CUsgc+13aC2tA2WkBAygFnXNUG2DCoMIGEDSVmAs6kZW3S7bGj8nU9zlmhZsqpd5Qa/gIVBlmRhA4jrHkOWDBbmNxECbgJTUF7TqF8ZhFBorE9zEAy2ZVh4l2xhDMI2phH+Dlfm/MMIBcDwmgJHMIPZrTlFY3BhAwI+zTXEQi0BfUrYlqQW4BJgjA1n7z2nbSAgfhM0AzBrFiLHexRIQNkSLX+eAxL3WmufvWrHdmOg1Q1uNzQYhc49R8JLSBELMGFmjDkzUQCzvGc7SKj5zZuPc6p13+fS0amasB7q3Gsdjmpf17N18Syvta59Lc11rnU/X+fb2ms+z37muDXyrX2ZJBeV5Hqu/fNOWt9/8n6Y+1tPQVF/aZtnrpNW7dOsEnMcztLUkC/P1NYYX+Coaxez0CtegszQufaI7/s5XYj+MPf3ngIqrTmZY7zP9ecf0Eo0QHvMjL7GqDFGhsYML3tsJpAZ1qtn7Wt0DQ08R2rOT3N/rUUk4wcetU++sB8wmeQ545988cw5MrJy9lWnKT1dqt6eGfM7P49hjPtb+jT3l56ChSzprkw8RhBNhzEyvwOWAiCveoxeaLoQmBc4+5VB8PqZV6r4NPe3ngKRkKnMOYIDwtBtF0AwQu3XkSwgfi1bCIwEANhwZwc+zv2lp2ALA+pM0eBKp14wAAGCCCG6hVBkZMsxgPxiIxx8D83Pc/ytpyAA5GYUFgjRgEhjABNab/uUIwNELQHAf6oH4I9zf+0pgEDamhIyxjE2mmCMIkQin0vuCHhLEsT/bTrMoHya+0tPwQZj9/GwKBRkYVAOgJEhyPQVYyBWS7D136bDxHyc+0tPQSEi8nlnKZqCjnCELbBl0wLeAoReQG0E/Ld6kPl5jr/1FCLA6o0hxImD0WwJ07YognTeKr0GJYACxPNf1YOo3o9zf+kpnLOkrlU1m3U/z7Pvda1anFvPvvbSVWs9W9Z8rutR1tt/liWfnHP/q3pwUqkPc/8HKXwEGlzr6tkAAAAASUVORK5CYII=);\">\n <div class=\"container text-center\">\n <h2 class=\"text-uppercase g-font-weight-600 g-mb-20\">Most quality solution for you</h2>\n\n <p class=\"lead g-px-100--md g-mb-40\">Morbi a suscipit ipsum. Suspendisse mollis libero ante. Pellentesque finibus convallis nulla vel placerat. Nulla ipsum dolor sit amet, consectetur adipiscing elitut eleifend nisl.</p>\n\n <a href=\"#!\" class=\"btn btn-xl u-btn-outline-darkgray text-uppercase g-font-weight-600 g-font-size-12 g-rounded-50 g-mb-15 g-mr-30--md\">Try trial version</a>\n <div class=\"g-hidden-md-up\"></div>\n <a href=\"#!\" class=\"btn btn-xl u-btn-primary text-uppercase g-font-weight-600 g-font-size-12 g-rounded-50 g-mb-15\">Buy full version</a>\n </div>\n</section>\n<!-- Hero Info #03 -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  32. 2019-06-25 01:00 | Administrator | {"id":"19042615300020wmg0","name":"Gallery-grid-v1","reference":"ui/gallery/grid/gallery-grid-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('gallery-grid-v1', function(component, config){}, [\n ]);\n</script>\n\n<div class=\"gallery-grid-v1\" data-jc=\"gallery-grid-v1\">\n <div class=\"row\">\n <div class=\"col-md-6 col-lg-4 g-mb-30\">\n <!-- Article -->\n <article class=\"u-block-hover u-bg-overlay g-color-white g-bg-black-opacity-0_3--after text-center g-rounded-3 h-100\">\n <!-- Article Image -->\n <img class=\"w-100 u-block-hover__main--zoom-v1 CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"u-block-hover__additional g-flex-middle g-z-index-2 g-brd-around g-brd-2 g-brd-white-opacity-0_3 g-pa-15 g-ma-20\">\n <div class=\"g-flex-middle-item\">\n <h3 class=\"h4 d-inline-block\">Photography</h3>\n <span class=\"d-block g-font-weight-600 g-font-size-40 text-uppercase g-mb-20\">40% Off</span>\n <a class=\"js-fancybox btn btn-md u-btn-primary g-font-weight-600 g-font-size-12 text-uppercase\" href=\"javascript:;\" data-fancybox=\"lightbox-gallery-01-1\" data-src=\"../../assets/img-temp/400x270/img2.jpg\">View portfolio\n </a>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n\n <img class=\"g-hidden-xs-up\" src=\"../../assets/img-temp/400x270/img4.jpg\" alt=\"Image Description\" data-fancybox-gallery=\"lightbox-gallery-01-1\">\n <img class=\"g-hidden-xs-up\" src=\"../../assets/img-temp/400x270/img5.jpg\" alt=\"Image Description\" data-fancybox-gallery=\"lightbox-gallery-01-1\">\n <img class=\"g-hidden-xs-up\" src=\"../../assets/img-temp/400x270/img6.jpg\" alt=\"Image Description\" data-fancybox-gallery=\"lightbox-gallery-01-1\">\n </div>\n\n <div class=\"col-md-6 col-lg-4 g-mb-30\">\n <!-- Article -->\n <article class=\"u-block-hover u-bg-overlay g-color-white g-bg-black-opacity-0_3--after text-center g-rounded-3 h-100\">\n <!-- Article Image -->\n <img class=\"w-100 u-block-hover__main--zoom-v1 CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"u-block-hover__additional g-flex-middle g-z-index-2 g-brd-around g-brd-2 g-brd-white-opacity-0_3 g-pa-15 g-ma-20\">\n <div class=\"g-flex-middle-item\">\n <h3 class=\"h4 d-inline-block\">Videography</h3>\n <span class=\"d-block g-font-weight-600 g-font-size-40 text-uppercase g-mb-20\">25% Off</span>\n <a class=\"js-fancybox-media btn btn-md u-btn-primary g-font-weight-600 g-font-size-12 text-uppercase\" href=\"javascript:;\" data-fancybox=\"lightbox-gallery-01-2\" data-src=\"//www.youtube.com/embed/BNpiwOkKIJ8?autoplay=1\">View portfolio\n </a>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n\n <iframe class=\"g-hidden-xs-up\" src=\"http://www.youtube.com/embed/p2_O6M1m6xg?autoplay=0\" data-fancybox-hidden-gallery=\"lightbox-gallery-01-2\" data-fancybox-type=\"iframe\"></iframe>\n <iframe class=\"g-hidden-xs-up\" src=\"http://www.youtube.com/embed/wbpBdMUrqV8?autoplay=0\" data-fancybox-hidden-gallery=\"lightbox-gallery-01-2\" data-fancybox-type=\"iframe\"></iframe>\n <iframe class=\"g-hidden-xs-up\" src=\"http://www.youtube.com/embed/R27KHLQ0cIU?autoplay=0\" data-fancybox-hidden-gallery=\"lightbox-gallery-01-2\" data-fancybox-type=\"iframe\"></iframe>\n </div>\n\n <div class=\"col-lg-4 g-mb-30\">\n <div class=\"row\">\n <div class=\"col-md-6 col-lg-12\">\n <!-- Article -->\n <article class=\"u-block-hover g-color-white g-bg-cyan-gradient-opacity-v1 text-center rounded g-mb-30\">\n <div class=\"g-z-index-2 g-brd-around g-brd-2 g-brd-white-opacity-0_3 g-pa-30 g-ma-20\">\n <div class=\"g-flex-middle-item\">\n <span class=\"d-block g-color-white g-font-weight-700 g-font-size-25 mb-1\">$99.00</span>\n <h3 class=\"h4 d-inline-block g-mb-13\">Web Design</h3>\n </div>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-md-6 col-lg-12\">\n <!-- Article -->\n <article class=\"u-block-hover u-bg-overlay g-color-white g-bg-black-opacity-0_3--after text-center g-rounded-3\">\n <!-- Article Image -->\n <img class=\"w-100 u-block-hover__main--zoom-v1 CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"u-block-hover__additional g-flex-middle g-z-index-2 g-brd-around g-brd-2 g-brd-white-opacity-0_3 g-pa-15 g-ma-20\">\n <div class=\"g-flex-middle-item\">\n <h3 class=\"h4 d-inline-block\">Branding</h3>\n <span class=\"d-block g-color-white-opacity-0_9\">Understanding who you are and what you want is our strategy for your brand.</span>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n </div>\n </div>\n </div>\n </div>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  33. 2019-06-25 01:00 | Administrator | {"id":"19042615300011wmg1","name":"Ui-banner-v4","reference":"ui/banner/ui-banner-v4.html","body":"<!-- Banners -->\n<div class=\"row align-items-stretch\">\n <div class=\"col-lg-4 g-mb-30\">\n <!-- Article -->\n <article class=\"h-100 text-center g-overflow-hidden\">\n <div class=\"h-100 u-block-hover--scale g-min-height-400 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-transition-0_5 g-pa-20\" data-bg-img-src=\"assets/img-temp/650x850/img1.jpg\">\n <div class=\"g-flex-middle-item g-brd-y g-brd-top-5 g-brd-bottom-5 g-brd-white-opacity-0_5 g-font-size-24 g-pos-rel g-z-index-1\">\n <h3 class=\"h1 g-color-white g-font-weight-700 text-uppercase g-letter-spacing-3 mb-0\">Women</h3>\n <span class=\"d-block g-color-white-opacity-0_8\">56 items</span>\n </div>\n <a class=\"u-link-v2\" href=\"#!\"></a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-4 g-mb-30\">\n <!-- Article -->\n <article class=\"h-100 text-center g-overflow-hidden\">\n <div class=\"h-100 u-block-hover--scale g-min-height-400 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-transition-0_5 g-pa-20\" data-bg-img-src=\"assets/img-temp/650x850/img2.jpg\">\n <div class=\"g-flex-middle-item g-brd-y g-brd-top-5 g-brd-bottom-5 g-brd-white-opacity-0_5 g-font-size-24 g-pos-rel g-z-index-1\">\n <h3 class=\"h1 g-color-white g-font-weight-700 text-uppercase g-letter-spacing-3 mb-0\">Children</h3>\n <span class=\"d-block g-color-white-opacity-0_8\">56 items</span>\n </div>\n <a class=\"u-link-v2\" href=\"#!\"></a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-4 g-mb-30\">\n <!-- Article -->\n <article class=\"h-100 text-center g-overflow-hidden\">\n <div class=\"h-100 u-block-hover--scale g-min-height-400 g-flex-middle g-bg-cover g-bg-size-cover g-bg-black-opacity-0_3--after g-transition-0_5 g-pa-20\" data-bg-img-src=\"assets/img-temp/650x850/img3.jpg\">\n <div class=\"g-flex-middle-item g-brd-y g-brd-top-5 g-brd-bottom-5 g-brd-white-opacity-0_5 g-font-size-24 g-pos-rel g-z-index-1\">\n <h3 class=\"h1 g-color-white g-font-weight-700 text-uppercase g-letter-spacing-3 mb-0\">Men</h3>\n <span class=\"d-block g-color-white-opacity-0_8\">56 items</span>\n </div>\n <a class=\"u-link-v2\" href=\"#!\"></a>\n </div>\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  34. 2019-06-25 01:00 | Administrator | {"id":"19042615300006wmg0","name":"Twbs-layout-rowcols","reference":"bootstrap/layout/twbs-layout-rowcols.html","body":"<style>\n .bg-gray {\n background-color: $gray;\n }\n .brd-bottom-darkgray{\n border-bottom: 1px solid $darkgray;\n }\n .brd-top-darkgray{\n border-top: 1px solid $darkgray;\n }\n\n #CMS .row {\n border-top: 6px solid #E0E0E0;\n cursor: crosshair;\n padding: .25rem 1rem;\n margin: 0;\n }\n\n #CMS .row,\n #CMS .row .twbs-layout-rowcols {\n padding: 1rem;\n min-height: 2rem;\n }\n</style>\n\n<script editor>\n option('number-of-columns', 'Number of Columns', 1);\n option('breakpoint', 'Breakpoint', 'md', [\n { text: 'None', value: '' },\n { text: 'Extra Small', value: 'xs' },\n { text: 'Small', value: 'sm' },\n { text: 'Middle', value: 'md' },\n { text: 'Large', value: 'lg' },\n { text: 'Extra Large', value: 'xl' },\n ]);\n option('no-gutters', 'No Gutters', false);\n\n exports.configure = function(options, $el, prev){\n let $component = $el.find('.twbs-layout-rowcols');\n let $columns = $component.find('.twbs-layout-rowcols-col');\n\n $component.toggleClass('no-gutters', options['no-gutters']);\n\n let numberOfColumns = options['number-of-columns'];\n let eachColumnsSize = 12 / numberOfColumns;\n let columnBreakpointPrefix = options['breakpoint'] ? `${options['breakpoint']}-` : '';\n\n for ( let i = 0, ilen = numberOfColumns; i < ilen; i++ )\n {\n let $column = $columns.eq(i);\n if ( $column.length <= 0 )\n {\n $component.append(`<div class=\"twbs-layout-rowcols-col col-${columnBreakpointPrefix}${eachColumnsSize} CMS_widgets CMS_attribute\"/>`);\n }\n else\n {\n $column.removeAttr('class')\n .addClass(`twbs-layout-rowcols-col col-${columnBreakpointPrefix}${eachColumnsSize} CMS_widgets CMS_attribute`);\n }\n }\n\n for ( let i = $columns.length, ilen = numberOfColumns; i > ilen; i-- )\n {\n let $column = $columns.eq(i - 1);\n $column.children().appendTo($columns.eq(ilen - 1));\n $column.remove();\n }\n };\n</script>\n\n<div class=\"row twbs-layout-rowcols CMS_attribute\">\n <div class=\"twbs-layout-rowcols-col col-12 CMS_widgets CMS_attribute\"></div>\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Bootstrap","dateupdated":"2019-06-25T00:59:34.994Z"}
  35. 2019-06-25 01:00 | Administrator | {"id":"19042615300031wmg1","name":"Ui-footer-classic-v1","reference":"ui/footer/ui-footer-classic-v1.html","body":"<style>\n .ui-footer-classic-v1 .container address {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPoAAACWCAMAAADABGUuAAAAaVBMVEUAAABERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERERdclAKAAAAI3RSTlMARlRPSllBPV1hajRlOjcrMHIldm4SDRgEex9/iIMIjpWer3hRq5QAABnsSURBVHja7MGhDQAgDACwGQQzBEkIgv+f3B9L2wAAAACgv/XH2Xnny+iouK0OLbZBGGgNNhiwsV2vOO3/f2QhryvdfZ2vIkOcxRH5FJnbsAzTsGzbUp3qPsCtWUUxSRXAQV+vzt3/ZssdDz5wtf7EEy/5APNq7UqXzOAFejKU/eW6/8iU6lUfsBdJ9AQKHfbYMCcl1ZcTSVccSJFkSUFGcPANyo0dzM2ZOdL0mYAXNHT/gBWrrUGhvPNKE6ykpaCKYZaaSAdbcUEkQDAyURF3e/8GpXyp90fG00nX8rmIu+r+qk3r0IFgAmIQwTgfkGiYihJQMSmkaLh/4FxnQIzS136wfSKzV6Y3yryZzhsI0N0sGB+05vbcWJCxL39V9+UeO7MzMRI/VOeC2HWj1IwsQINg1sr2VgniN6rz8bkCHlawfInrHYDZHFVza2nlC6/5OfXVYjj/VuoWuxe7QsjDTRATMwbtym1m9lIofKiOUgCgM0kHYsA6JzBx/uR/awUShzzdbvP77OY2qR+P8fGa2+SDdxny0v1JezG/uL24WVFTl1mc8Vas9kRaWnndOoFeejCkUZOp32RkDkVqJMOaBOX4KeFuEAVfy+0HnqUnnyHD+GdTdyuM4EnWwm5pYM23u3kWam3aCEQnAsqH6oEAsDfJVKdp3lT/XJlOhXPq+vsPHiVK76fuz9ltckVZqQVMt7Wf5onGbZoGi1qu0yN1o4zULEhw6/SkVemtFMgVq2iJn02daupp/2yRLfPUxvOCqSLLKMvQnOdrLb4W0K+3uHOGUQlBB2U4zX3rxmzsBxE7F7bomaB1eBaPhxsSI9TBNv5oKz34or3s84e3I5sCpzIBhbai6KcFF4z4W1rgZnpO3JNynCiCo6WTknGalml78YggBwl6qVihQinJ6XdHGsVSDz/WWJbNR4qg4J2ObScGat3TNVpJ8LTEOxF0nKZf3gbiy9joryewyu5XfPm0mTiokNXCwqhWbg5Z1OLqf+xOX/hye4bEgStpC6ustEJ4tObpZr3kTMaO6/3XFv1tGGjYpmFLXTdMw7RNSzMVQi8jzk8/OjqZZF9xHjjK4KRTTqU4/Yjm20ZDaiksyzbVsWzLkiqbq7Q0UJR9JZWhXX2MtsgtNSBuQ/rF5X63BQ/xsvkv9SXuPhe7rjpbM9L9o6zMrkdf8QNOzN6PIuudfuy0eNizf3i+bnLovaz+xJVGbUY8+ODKX0QxY664ON6ciOY7HvHnjmppcx9jM7sQYYAlDhtErvpXLZdJBedUhLoiDu83raqrig81PjYnqiS//pPq8g9bQYzBQVNySqkul5UlJBXraFOuxMqlUIP6PiRZp3Ib4pY2qCFDtfhRyfZb+r6ao0PcP2232ooDRlyrzHz0Xdh17b1n1rrQCgeOJb8L1iNWPPMud7JGFyxi5K9uiYfNH0z309srVEddjX/EUne3OFIWpvApdy66IcKbhq6cjV/x4hGtsdbm86MaeuVfqu7bNlAPjpIMjiMOOFWIe6VA4TBHdCwDJUyyBykVzAsqcCJiqji/rRVFgZXihDUepOKeA7ovlRgljvUt5Tso0NxswUix8bhKoCBAT45DpW3bgWpIAAmBEyaquKvxNVDWASGoxAMN4AIkTvNtftF929LOGgxZgRl2dSwVWrURyOPQhULZoFFWjSQQde5eFBJwKssFIePbsvGgiYwqfXmcbcCKlb9U6ieNcg1aiHeQPt7831/zah3KjeMw1KgsIqkaO25J9v7/Iw+gnOzV3ezM5aCMpLwBQcEQQeJReg5uBxJEFo5QNBHuK6WE5BiScNKshSlqrt4vophSLHTWO6wYM1vfn5AZIqN3AkJQhjzYjZsXJhYour4cjy/ZXE8shIiERNt4nO4vR6QYsu6uJ0V+X80RR/FC5J/TWSaxflLta34zC9J7i4SiecjfXUcPiCamVMuwaSTXI2QBj4TjwV03yETA8NLmajjWpBHy+HPXRVmxYiVQmeIkikgUSEFJS8s9KkMcMDCYEKmm5TBZnBhoeLgO2Fj3yq0xUor/2t9xY64yyoRmJxRzHRmVWZgkxNH6AX8a7z9QsDs3K+MH+QPKAVscdnyQQODCQc3slCbDecAa4ec1/pkTJiqcuQhtutaZU8SCSezMm844F8k660ZJTDAKrqPVdPaDxKwb39x52bjgY0mznl5eXh5Z8ulvlVsRKTyHVTNihhkyRxRMdkiC1XGRzJkKRrL+IaMUNRzyQ0siebu9eZi5GBJ7czN71s30wZpRkmzj6EcSGysHamTXilMcsXmeCqCGN5qwc3CTTFRBXUCDXMyJ52cv0Kltns44Vgrq2bBoOfYXOyrr/PQXz18EVKFZPxOFYPoDVVYI4MbZomu4BjazFDhwo4Gqkj2VDBwMMU1/2sHahU4FYuOwPxWY2VFGxxuaNgLw9kPX7xRRKEGGhJR1bRtEoeQoRs7qOHvUM0cRFCkRrRAf3miNaDg8nIoJsej19JHHr3Id/vaKZRTEzDnMWlx/08K9J4qICeYdT5A5GZY4QyFMFvUZCroWua63y9T1XVHEQMQIW11Dx/31O8DbT5b2KRDQY6wHaaXTLL3cZqAqgzQClRYH6WOdlcHrkiUZDtJkeLjO6C9B+ejtKbqay+kdO108EqZmrVJDhT5mAwEr2RnI/k2D47WTP0pVKqpiS67/IHrtbO1aV4tDrNRtevMqk+cAtuYtToch/2xRc1p7uZ1UiEuYW1bEXm4LRSghB0H2S2IkTmdv8W04jDPY23TDK70zT5vnqdfL3xfnr++1eTkjIRU+jLewhUTkaa7PTj3HY9Tccc/hhsp7ho861xzeMzwKJy2h41vN4BCSnTdObauFaaYzX/Uzi9jkUQ/SM7ylaAnE0KkXMHSIFT3qqUn/wcEDamN9KVXG0RcnDweDMuCE//BDaz0810s7HoAYmAnaRENpAiAWPU/RdigBYE0edfB+Az6eqr8cZfKody1iUqnWrut/RJ2ZdUh71HWCkZdP1YqCnKCo5HjlV7ixIPuYEuxR14RcdAvF8Pmj1bhqzNufonsv/16lvtFv04GSoAjTb3qYVi0e9cdYp8hCPepzSORvm0fdr5oYnfKb1XDHPAP1p7Lm/anWknMuVuzY+6s3vpbz4bNyHBpWHqjSchlP02kaaM/woFxxkoE14LBneG7h0WqJk+E66vLh+rj8W8UAS1ym4yFwN6qmtqTBM/wge4ZXsLOy9eJm9/km2FE903cGxDO8fmT4CR/NJ5nGpcs4wZAukxVb4y+Rjz6v5/cKAN72eT1JoRlmzmKXs2ZMUcr7uK3foEQ80/0zpemN7v48+Rpxzhnttt3NnmywwkZFfL4uEguseu7zOmUqjtHMptapCuhURekL1uztDOdV39rHwL3T7fTLlTkNMODL8YMWD0GH0OyYYOzUA9sFhlBrDaoPmnrkOsAI0093xDTAiJduWPnlCYfDBRcaoTXYzXpfg7aqExuuhk8w6WCHXWGoOlJnQJohLZi+tTO8geHfi5QjT/hrW7vWtNoUHP9YtJacJccSM1qqpC3hCndeJScTr1R7d3BOsrKzE88/7mArK7xOXWm72y6OPLdXvcIa48pnXtF78v5SpqveYTYcV5wly4YrrZJmuIc7zSllw1x35SuvEme++/j7iDq+/lrUS0CVy58xYA5aQ/VSliYIjUecqGkIqlVBk8/rO04D1HT5gX2IQb1G3VyJFaAqNXoUp2adBzNZoVl/MNGII3u/3Eyv2bUXrRZdnEwfGrRgOJsi7/plecQ8Qhh+kaq/km+r/BlLJWO0I3HWDVJf22umKCiCMcrZiPS74xkyJLqOP4i5nEWKzuHVn7FEEfHSIJxDRirgq7LkfdkZC6x11eI3mQsmStBXc0XPdQZTSA/dfZHu+vq+Yny6JoFfHOtn+Gc8MzJqbCkga6ylCiATs52hWNS3igSGayKk9cc/96znxeYzQQIiZgxxcHsgNVVRZAIEdDwNpSGDqKGOqQQBllpc3xSAgNjR1JuHMkRdl4fz9+FXd8zDP0cr7pObL2R9cotTL1odBI2XPjuBGt43mX/yppWal8NYmEHBhBo6r67qVSdWN8uBVLnJ0HGq2Mgwv6ICDdH1XYsVgENv52WKTFjL5eH6PP1HG423tKVMM6ycI1o+ghlTjClJtj+6+WwTZcWu7ATtT+TpG5UUi1nodApceY59cpsxR7Pok1tmw2ETyTxT9tRHM28YOzf34OGlpIIzr6ZmuCVLshfqv5VnGGoLoy7OjU4wwqJTczF4p0jH0CaduvL481nliKbfnFDthK5PVjuD61Ru7Uf7wB+MrF/fGVnuRO/UNSf1x9n1YeSv2IEdXvHKV1pLvNKNz5JLKTnNzrff4b7jb3KAb2XOv/3oZ/yWvtEV51zsyCXNeOcbnVNc+U5nmUvuDHuZ8QY3Wg3HM65pjqs4D19W2vG0xS3lPBt67Tje+Oo8/H8vJ57gEd2RLTy1edCCR93wxW5ghPGytGVa6o8MtUtzwrw2C1lr36Ou1tyiblC12H+PrjraN3D8rjkOhhuy8/B7O9f/qqiP132sSx/rG+ZoIiVK5rMPvoh3O36jT1UKr2zNk8S0lUKrXiGXYiGjKyaDi+Q+1h0X2WijLAUfYz3DtaeW6BkhxYIbn3lGyfzKM12/IuoXaV4odOIK3wmqAOoEeMfDeBovP91ZG4a66CINdhppmag54YUXq5bsmCiAHRzUeXXxpQs36jw8dx5eOw8/cDDENBVqJ6hqoGG0R5SviPoyQwmbRnQKoxetJiTYOYJNZZs/lTC3fOYbJFpLQZpXq5I5W/P5o/wlociR0My2XrQmKKYv7NfCmMLcNk2IriWOO4md0h3tIyP+krG+FF/LBCGVVmoEJBMgZgnFCfPL5VOuE0sz/fFyOWG8TJmXIQ+pRdgph4BMjGDnfUkjDKJREahfI3jzqVRhMi1g0xVnd06n5XQ4rsOXfI66ZE3ddUg1v7tOdpaQW8HrJ6fJvlOxL2Rta+zllex1MuejYC9aARndHaa+BVGFOLrLLA/XSeo2leAKXZdo1aL58YnRV8Tcox5iixVZYyeoiE2AGDw82/Hps66ztDw+PffvYjKi2m+6E1ROL+4LWQKE/ZPBwaMeDHUs2J3hzXdrkIH84DQ8DRb1vgqvZTl8hYx3SCFrJOi0JAh6zAU9PGF9/uziKKcY1uXZxbcgZrGtG7jzLDGG8rHxJIQSct36xpMmiI49aMnwTkuCadE2HcIdstu7hm08fI1M2yAN/0BGP6LepM3PnzeTahxmLlHi9ie8mOFORsNORte4k85YpVPkj42n72S0um50b0Mi3Ki0L3N9OIeyZ/jtjxneM+z1064/HTfNNZsdktK//no5HfuMNHPezb7Tklv9oCUTve+0/oWWtKhbU9iYOqE1Hb5K6uqDLNbHWCdi9Aw/v3y+GsgJBwtbQGKCzuKON3iQQvP3ohUYQ2m5yjsZjY+xvn+TVtFw7WN9D7TI4WtlEk9zD9c/Xviaj593HRnbu+tMeDqdxtw2+y7M/nJ7uK7krqe2p7/g6MN1hV6vf+fhy+464uGrhTJShlkzScpeaeS08vUXqKAkWVedOQnaIbmsvIWZzqmcaXts5cU4k/ge2s7S8IOl4Qy5szc73ncCZd0pCVscfbVM4IwsjTDA1Alv/5NPR/20hArWnCcN1Y4Qqu726oOR1RoaLKPhEy0d18k6a9p0ALuz5rjQBLV2RrbS4mYv43j4akm3JFdyRva2vGPjb59eOJdbTCvf4Ypz2YvWsuEVbuzFKb/iKnPxjfDjbb7KG73h3XBce9E6e/EqxdT03otW2WJON+eVrEr+enk61WnnFPjjLX+5fDK/X8ZehPaPyuo0taGZVGdAFsfH0xOMzWBdJhv7x8vRPvh1DkJNexhNsRenC1zA7dTJ0Gn/xsxPXy/X7UZGLUSL+q+KxfGc/Nszvssj6rnM4oST4Xc5PL1hTlvatttHmziveJY1bk5V4DmWM9/15tXPJte85jc4/G9ytA+JfifmXHTdBmEwjMGAIZCQa5O0aXf2/g+5GdrTXbReJi37NanbtNOMOgX79+fIggeLmW0jccDOdHVVPYl9x1ZGaZM0mNiqiNHG3GXB3GVpTIfJ5o6KScmUSEbD24rJf4f8Fb/xp9HYacp80q7aTFB5DqH3mxPpAzYcvBePhV9gQIZeTG8HdIXE0dqxD288QxxH7BXp7MOTu0420dDKkHd4KqCoM4vtjSPnnNhfcx+plC9KT2fuuHGJxalOfEQYTzbY3EcPn+e6wnJ+xzIhVU/Jl5RG4rV/lmm1fK4PB47x6C378GE9E82Mqls/iv00b8YDML+AUrDSAkGqBTcUj8SFX7brE5XSC5G5EKqoVG4hiaYHMMpiUOIubyUgbIcy82QouRgmEfwsRDfsPAYznafYZmDHBX1MYuZ4nM/zExwTer7hufFUyhfNcjCYHvIXoTVbnNQgnfbns7hrUUEu9fXNp/FkehOozbMeXY/LQeyr5DiHt/DOeAM3b6zFJrOzYK7uXpVhtMzUJteJqECi/hVps/rzKmeXkVx5vjqGNIqdNX/XuQ1aLo1gtc99GjyW6Pa3qBNlZLBEvaB/vf79Bj4pJ+89tOlkjhB0OxYXQXn1ReyvWSET0wfOPUDxy2F6GPWY0cDM9kF2ZHNvLelKmU+gRK/jL6NMZJWhe9RDogpN6fGsZFXlxO4q7Vxf2iESjuooL+ZJ9aNUYJsFSEqdfxF423NdHjIeTAxch+XXHh0IcY/6KtLHePtjas0i/ocmqE2idO206qhj9eirZ6UBZtIynIaQz0eb2T6A4sgiKAbPfrmKjvGeN/uubvR0HW8QHdVe/C91X0z2551hyutLeoQWHBkSy4msCxnf5qjXPoYcdcNU929RZ1VfzQ9Zxcd6uxukqC/yi/hfGm3jGBtWiZk+GesmNvEPn1LFN0e9Vl1qmivUGXmqIROg3F/nt0lN9fs1xuvCTUcNJeCom0rW4hBXK/6fmg2ClkNOVIla6t0ft/t+OKEo8o5IX+ntcq6DR6LH7fnpQ/exxxPH/aS9Ev9VB9kUvChhYgLIVBDxj7fI+HkIgLHfBRUf9BAj8LtUqjvMj682UqOYQTXRMMvEHdqE4v+o+QJHZKoCeCSJKbOWFobKnmhw30W+UBWa2tzALVvF47jjgttB9CFo51rdq01uhXybxc46FMcoR90UJ6kCY/SzLA8hWjaisPlEBuX60rHiGjkKzD1ZW0HChAZUrTkZ3Ffd0fb8XWeONDOlyitP+ljNrAcoVdDXbG65ZXPH6qXP+gL9OrfOkyfHP4ctOWZptmouEjtpbW9dGd7hMfPbktGooR5geJTeBWVyl4Vyl+X7i2te7X1iKJ9CsPo6jGQpaW5IEblB7KS1rV2dWxNkpWEbvcyv177y5pFHP4d7vZ55+BheXHoTgKZr//e2dOOqfF0ltRO7KR2NK4cbI8zEKHMxzFv12KMf+x8ON2jf6BzpoXV9aAOpgBchQhgUw2OetlGIafMnELtolY0pZHSGqJP5/loVhBkfemcTVjZBGcrmbQ7XlwtgY5ikjezZSSGszRhRMjU7xWdkf34XxQ/Ver2oTHk5Nk97ykPcz+jBw8kNeoMTO7KM05+qtw9W+sgmYXu3jD/2POMOtjE1+/ONqcq8deSoY6emJ64+ZNTPdFd4DNfi+Y4vJ9Gxs+N4nmLM1uw8ziJZsavUKegFF9kSM52tbMm1uMHl4RrmS+tc7r5kHl4dMUe93Za3ChL80CdNF2L372sj9tahsRETVjmRraHOialsnnTj0PKIRebeY2QeXnaiqyK+PrEy1l3iUas6YqrW6vtv60nsLd8Gw8A6ScKATqpg2icjhfMWilXRmgy6D+YShT6qVrys5kILDPzjDk64KU2Mwe+sdXWWrIRyrks+p/3azI8zk0aDtHQ7113djMJqcG9c1Zk8HweZn2cG4e5SrpRopx3PG2cz0QUEhKhjOz7NxgNqE6K3+d/7motwwvDG0gdDNkTCwrAZiX76oZQ+7rd0c1u6Vqjj8PTeG11ZeixLr3jpDl5d+qC0lEDW3ZfOKKGfb9nubtZVWfrteXOqh8v6dOm3qLt71LU+itfUSl46z8L+EHUlbzhXOsFpp6gHqz+fMig1vlJAjR6lpYIMkh3y0pfx1f+vK7Mv8Tr74irKnCncqLS9Srhp4qgz95Wj7uVLZ1MvNYSMBqKHhZMRPL2cz3iUikFCXyaZ78+qcGJXBadruj1lEF+++CFYykCoEm+rVXwxrvhKA5evX/q/YldJRdGZzHgCAb28RXvDMBgpKd5WyChx3uGBAe06gEbGlknsKi05pclElyJa3sDsfWyj8168rzPT845nZsg5O1gPjne+0ItdxQY6jymAhRqTfd3YK4lsVYn3NcloatWYLqakkmpUDdYYqKK4a/332V0IPM475Mnfj3eqTn1SX/Tl7yqHi1+4aGVd3KVMA7vl5zZtK/616trkQbSYzGF94569PzDufc3r4fMZh/wu39q3m9yGQSAKwDP8DQbHBpFY1FbsqPc/ZEU2VepNNwhLzHeCQbNB8N67IeU/y9To59p7d678tCbrxrKGJh7fOo5DPD6OvulDQ10l6YkzBbUuGRpZVuXxzxc/Bpyp7mvVMESV5CiOAO1MxymUkUTEl4R6vEBT6hCD3zO0k/dTlqx0vKvORK7k5DV9ETR1vgdbTApqonfcMeKSoa28wC9KQjtReaYg6KbmkeBSgjVGK6gLkxVR7BkuxWz2Xn2mIM0kb3AxQaOE2sgJlQT0yGtl5AQ9olFY6JPXiDRDj+RTqE1Djx6rMdLLAD26x6d89bl3xhhjjDHGGGOMMcYY+48fpA2R18oVwI4AAAAASUVORK5CYII=\");\n }\n</style>\n\n<script>\n COMPONENT('ui-footer-classic-v1', function(component, config){}, [\n ]);\n</script>\n\n<div class=\"ui-footer-classic-v1\" data-jc=\"ui-footer-classic-v1\">\n <!-- Footer -->\n <div class=\"g-bg-black-opacity-0_9 g-color-white-opacity-0_8 g-py-60\">\n <div class=\"container\">\n <div class=\"row\">\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <a class=\"d-block g-mb-20\" href=\"index.html\">\n <img class=\"img-fluid CMS_edit\" src=\"//via.placeholder.com/105x40\" alt=\"Logo\">\n </a>\n <p class=\"CMS_edit\">이 항목을 편집하십시오.</p>\n <p class=\"mb-0 CMS_edit\">이 항목을 편집하십시오.</p>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v3-1 g-brd-white-opacity-0_3 g-mb-25\">\n <h2 class=\"u-heading-v3__title h6 text-uppercase g-brd-primary CMS_edit\">이 항목을 편집하십시오.</h2>\n </div>\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">Incredible template</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">May 8, 2017</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">New features</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">June 23, 2017</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">New terms and conditions</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6\">September 15, 2017</div>\n </article>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v3-1 g-brd-white-opacity-0_3 g-mb-25\">\n <h2 class=\"u-heading-v3__title h6 text-uppercase g-brd-primary CMS_edit\">이 항목을 편집하십시오.</h2>\n </div>\n\n <nav class=\"text-uppercase1\">\n <ul class=\"list-unstyled g-mt-minus-10 mb-0\">\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">About Us</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">Portfolio</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">Our Services</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">Latest Jobs</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover\" href=\"#!\">Contact Us</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n </ul>\n </nav>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6\">\n <div class=\"u-heading-v3-1 g-brd-white-opacity-0_3 g-mb-25\">\n <h2 class=\"u-heading-v3__title h6 text-uppercase g-brd-primary CMS_edit\">이 항목을 편집하십시오.</h2>\n </div>\n\n <address class=\"g-bg-no-repeat g-line-height-2 g-mt-minus-4 CMS_edit CMS_multiline\">\n 이 항목을 편집하십시오.\n <br>\n 엔터가 포함될 수 있습니다.\n <a href=\"mailto:info@example.com\" class=\"CMS_edit\">info@example.com</a>\n </address>\n </div>\n <!-- End Footer Content -->\n </div>\n </div>\n </div>\n <!-- End Footer -->\n\n <!-- Copyright Footer -->\n <footer class=\"g-bg-gray-dark-v1 g-color-white-opacity-0_8 g-py-20\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-8 text-center text-md-left g-mb-15 g-mb-0--md\">\n <div class=\"d-lg-flex\">\n <small class=\"d-block g-font-size-default g-mr-10 g-mb-10 g-mb-0--md CMS_edit\">2018 © All Rights Reserved.</small>\n <ul class=\"u-list-inline\">\n <li class=\"list-inline-item\">\n <a href=\"#!\" class=\"CMS_edit\">Privacy Policy</a>\n </li>\n <li class=\"list-inline-item\">\n <span>|</span>\n </li>\n <li class=\"list-inline-item\">\n <a href=\"#!\" class=\"CMS_edit\">Terms of Use</a>\n </li>\n </ul>\n </div>\n </div>\n\n <div class=\"col-md-4 align-self-center\">\n <ul class=\"list-inline text-center text-md-right mb-0\">\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Facebook\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-facebook\"></i>\n </a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Skype\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-skype\"></i>\n </a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Linkedin\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-linkedin\"></i>\n </a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Pinterest\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-pinterest\"></i>\n </a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Twitter\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-twitter\"></i>\n </a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"Dribbble\">\n <a href=\"#!\" class=\"CMS_edit\" class=\"g-color-white-opacity-0_5 g-color-white--hover\">\n <i class=\"fab fa-dribbble\"></i>\n </a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n </footer>\n <!-- End Copyright Footer -->\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  36. 2019-06-25 01:00 | Administrator | {"id":"19042615300012wmg0","name":"Ui-banner-v5","reference":"ui/banner/ui-banner-v5.html","body":"<!-- Banners -->\n<div class=\"row no-gutters\">\n <div class=\"col-lg-4\">\n <!-- Article -->\n <article class=\"h-100 text-center info-v3-3 g-bg-gray-light-v5 g-bg-cover g-bg-primary-opacity-0_8--after g-color-gray-dark-v3 g-color-white--hover\">\n <!-- Article Image -->\n <img class=\"info-v3-3__img\" src=\"assets/img-temp/230x338/img1.png\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"info-v3-3__description g-flex-middle\">\n <div class=\"g-flex-middle-item g-pa-30\">\n <h4 class=\"h3 text-uppercase g-line-height-1 g-mb-20\">\n <a class=\"info-v3-3__title g-color-gray-dark-v2 g-color-white--hover g-text-underline--none--hover g-transition-0_3\" href=\"#!\">\n <span class=\"d-block g-font-weight-700\">Small</span>\n <span>Objects</span>\n </a>\n </h4>\n <em class=\"info-v3-3__category g-font-style-normal g-font-size-11 text-uppercase\">Shipping &amp; Package</em>\n <div class=\"info-v3-3__content g-opacity-0_7\">\n <p class=\"mb-0\">More than a look, design is functional.</p>\n </div>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-4\">\n <!-- Article -->\n <article class=\"h-100 text-center info-v3-3 g-bg-gray-light-v5 g-bg-cover g-bg-primary-opacity-0_8--after g-color-gray-dark-v3 g-color-white--hover\">\n <!-- Article Image -->\n <img class=\"info-v3-3__img\" src=\"assets/img-temp/230x338/img1.png\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"info-v3-3__description g-flex-middle\">\n <div class=\"g-flex-middle-item g-pa-30\">\n <h4 class=\"h3 text-uppercase g-line-height-1 g-mb-20\">\n <a class=\"info-v3-3__title g-color-gray-dark-v2 g-color-white--hover g-text-underline--none--hover g-transition-0_3\" href=\"#!\">\n <span class=\"d-block g-font-weight-700\">Medium</span>\n <span>Objects</span>\n </a>\n </h4>\n <em class=\"info-v3-3__category g-font-style-normal g-font-size-11 text-uppercase\">Shipping &amp; Package</em>\n <div class=\"info-v3-3__content g-opacity-0_7\">\n <p class=\"mb-0\">More than a look, design is functional.</p>\n </div>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n </div>\n\n <div class=\"col-lg-4\">\n <!-- Article -->\n <article class=\"h-100 text-center info-v3-3 g-bg-gray-light-v5 g-bg-cover g-bg-primary-opacity-0_8--after g-color-gray-dark-v3 g-color-white--hover\">\n <!-- Article Image -->\n <img class=\"info-v3-3__img\" src=\"assets/img-temp/230x338/img1.png\" alt=\"Image Description\">\n <!-- End Article Image -->\n\n <!-- Article Content -->\n <div class=\"info-v3-3__description g-flex-middle\">\n <div class=\"g-flex-middle-item g-pa-30\">\n <h4 class=\"h3 text-uppercase g-line-height-1 g-mb-20\">\n <a class=\"info-v3-3__title g-color-gray-dark-v2 g-color-white--hover g-text-underline--none--hover g-transition-0_3\" href=\"#!\">\n <span class=\"d-block g-font-weight-700\">Large</span>\n <span>Objects</span>\n </a>\n </h4>\n <em class=\"info-v3-3__category g-font-style-normal g-font-size-11 text-uppercase\">Shipping &amp; Package</em>\n <div class=\"info-v3-3__content g-opacity-0_7\">\n <p class=\"mb-0\">More than a look, design is functional.</p>\n </div>\n </div>\n </div>\n <!-- End Article Content -->\n </article>\n <!-- End Article -->\n </div>\n</div>\n<!-- End Banners -->\n","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  37. 2019-06-25 01:00 | Administrator | {"id":"19042615300034wmg0","name":"Ui-header-classics-style-nav-1","reference":"ui/header/classics/style/ui-header-classics-style-nav-1.html","body":"<style>\n .ui-header-classics-style-nav-1 ul.navbar-nav li { word-break: keep-all; }\n</style>\n\n<script>\n COMPONENT('ui-header-classics-style-nav-1', function(component, config){\n const CONSTANTS = {\n TEMPLATE: {\n // 탑 레벨 - 일반 아이템\n $NAV_LIST_ITEM: $('<li class=\"nav-item g-mx-2--md g-mx-5--xl g-mb-5 g-mb-0--lg\"><a href=\"#!\" class=\"nav-link\"></a></li>'),\n\n // 탑 레벨 - 서브메뉴 있는 아이템\n $HAS_SUBMENU_LIST_ITEM: $('<li class=\"nav-item hs-has-sub-menu g-mx-2--md g-mx-5--xl g-mb-5 g-mb-0--lg\">'),\n // 탑 레벨 - 서브메뉴 토글러\n $SUBMENU_TOGGLER: $('<a href=\"#!\" class=\"nav-link\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 탑 레벨 - 서브메뉴\n $SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled g-mt-17--lg g-mt-7--lg--scrolling\">'),\n\n // 서브메뉴 - 일반 아이템\n $SUBMENU_LIST_ITEM: $('<li><a href=\"#!\"></a></li>'),\n\n // 서브메뉴 - 서브메뉴 있는 아이템\n $SUBMENU_HAS_SUBMENU_LIST_ITEM: $('<li class=\"hs-has-sub-menu\">'),\n // 서브메뉴 - 서브메뉴 토글러\n $SUBMENU_SUBMENU_TOGGLER: $('<a href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 서브메뉴 - 서브메뉴 리스트\n $SUBMENU_SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled\">'),\n // 서브메뉴 - 서브메뉴 일반 아이템\n $SUBMENU_SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item\"><a href=\"#!\"></a></li>'),\n }\n };\n\n component.$el = component.element;\n\n component.$loginLink = $('.header-topbar a.header-login-link', component.$el);\n\n component.$jsHeader = $('.js-header', component.$el);\n\n component.$navbarBrand = $('.navbar-brand', component.$jsHeader);\n component.$megamenu = $('.js-mega-menu', component.$jsHeader);\n\n component.$navbarToggler = $('.navbar-toggler', component.$megamenu);\n component.$navbarCollapse = $('.navbar-collapse', component.$megamenu);\n component.$navbar = $('ul.navbar-nav', component.$megamenu);\n\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n if ( window._TOTALCMS_MAINMENU ) mainmenu = window._TOTALCMS_MAINMENU;\n else if ( mainmenu ) window._TOTALCMS_MAINMENU = mainmenu;\n\n // 네비게이션 접기 펴기 ID 랜덤화\n let navRandomId = $.TOT.helpers.getRandomId();\n\n component.$navbarToggler\n .attr({\n 'aria-controls': navRandomId,\n 'data-target': `#${navRandomId}`,\n 'data-toggle': 'collapse',\n })\n .data('target', `#${navRandomId}`);\n\n component.$navbarCollapse.prop('id', navRandomId);\n\n // 메가메뉴 목록 생성\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(menuItem, index, src, $parent, depth){\n depth = depth || 0;\n menuItem.hasChild = menuItem.children && menuItem.children.length > 0;\n\n if ( menuItem.url === '/' ) return;\n\n let $cursor;\n if ( depth === 0 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n component.$navbar.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$NAV_LIST_ITEM.clone();\n $listItem.attr('href', menuItem.url).find('.nav-link').text($.TOT.helpers.unescapeHtml(menuItem.name));\n component.$navbar.append($listItem);\n }\n }\n else if ( depth === 1 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$SUBMENU_HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n $parent.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n }\n else if ( depth === 2 )\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n\n if ( !menuItem.hasChild ) return;\n\n depth += 1;\n _.each(menuItem.children, function(menuItem, index, src){\n return _recursive(menuItem, index, src, $cursor, depth);\n });\n });\n\n $(`a[href=\"${location.pathname}\"]`, component.$navbar).parents('li').addClass('active');\n\n $.HSCore.components.HSHeader.init(component.$jsHeader);\n $.HSCore.helpers.HSHamburgers.init($('.hamburger', component.$jsHeader));\n\n component.$loginLink.attr('href', _.get(TOTAL_LOCALS, '_SESSION._id') ? '/logout' : '/login')\n .text(_.get(TOTAL_LOCALS, '_SESSION._id') ? 'LOGOUT' : 'LOGIN');\n\n component.$megamenu.HSMegaMenu({\n event: 'hover',\n pageContainer: $('.container'),\n breakpoint: 991,\n });\n };\n }, [\n '/libs/htmlstream-icon/1.0.0/hs-icon.min.css',\n '/libs/ui/1.0.0/css/components/hs.hamburgers.min.css',\n '/libs/ui/1.0.0/css/components/hs.megamenu.css',\n\n '/libs/ui/1.0.0/js/components/hs.megamenu.js',\n\n '/libs/ui/1.0.0/js/components/hs.header.js',\n '/libs/ui/1.0.0/js/helpers/hs.hamburgers.js',\n '/libs/ui/1.0.0/js/components/hs.dropdown.js',\n ]);\n</script>\n\n<!-- Header -->\n<div class=\"ui-header-classics-style-nav-1\" data-jc=\"ui-header-classics-style-nav-1\">\n <!-- Top Bar -->\n <div class=\"u-header__section u-header__section--hidden u-header__section--dark g-bg-black g-py-15 header-topbar\">\n <div class=\"container\">\n <div class=\"row flex-column flex-md-row justify-content-between align-items-center text-uppercase g-font-weight-600 g-color-white\">\n <div class=\"col-auto g-px-15\">\n <i class=\"fa fa-phone-square g-valign-middle g-mr-3\"></i>1 800 88 4411\n </div>\n\n <div class=\"col-auto g-pos-rel g-px-15\">\n <ul class=\"list-inline g-overflow-hidden g-font-size-12 g-mt-minus-10 g-mx-minus-4 mb-0\">\n <li class=\"list-inline-item g-mx-4 g-mt-10\"><a href=\"#!\" class=\"g-color-white g-color-primary--hover g-text-underline--none--hover\">FAQ</a></li>\n <li class=\"list-inline-item g-mx-4 g-mt-10\">|</li>\n <li class=\"list-inline-item g-mx-4 g-mt-10\"><a href=\"#!\" class=\"g-color-white g-color-primary--hover g-text-underline--none--hover header-login-link\"></a></li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n <!-- End Top Bar -->\n <header class=\"js-header u-header u-header--static--lg u-header--show-hide--lg u-header--change-appearance--lg\" data-header-fix-moment=\"500\" data-header-fix-effect=\"slide\">\n <div class=\"u-header__section u-header__section--light g-bg-white g-transition-0_3 g-py-10\" data-header-fix-moment-exclude=\"g-bg-white g-py-10\" data-header-fix-moment-classes=\"g-bg-white-opacity-0_7 u-shadow-v18 g-py-0\">\n <nav class=\"js-mega-menu navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"/\" class=\"navbar-brand\"><img class=\"CMS_edit\" src=\"//via.placeholder.com/130x39\" alt=\"Home\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg\">\n <ul class=\"navbar-nav ml-auto text-uppercase g-font-weight-600 u-main-nav-v1 u-sub-menu-v1\"></ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n </header>\n <!-- End Header -->\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  38. 2019-06-25 01:00 | Administrator | {"id":"19042615300027wmg1","name":"Ui-hero-naea-v1","reference":"ui/hero/naea/ui-hero-naea-v1.html","body":"<script editor>\n option('bgImage', 'Background Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('.g-bg-img-hero', $el).css('background-image', `url(${options.bgImage})`)\n };\n</script>\n\n<section class=\"g-bg-img-hero ui-header-naea-v1\" data-jc=\"ui-header-naea-v1\">\n <div class=\"container g-py-100 CMS_attribute CMS_widgets\"></div>\n</section>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  39. 2019-06-25 01:00 | Administrator | {"id":"19042615300029wmg1","name":"Ui-header-classics-classic-whitebg","reference":"ui/header/classics/classic/ui-header-classics-classic-whitebg.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-header-classics-classic-whitebg', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n // initialization of header\n $.HSCore.components.HSHeader.init(component.$el);\n $.HSCore.helpers.HSHamburgers.init('.hamburger');\n };\n }, [\n '/libs/unify/1.0.0/css/unify.min.css',\n '/libs/unify/1.0.0/js/hs.core.js',\n '/libs/unify/1.0.0/js/hs.header.js',\n '/libs/unify/1.0.0/js/hs.hamburgers.js',\n ]);\n</script>\n\n<header class=\"u-header u-header--static u-header--floating g-mt-40--lg js-header-fix-moment u-shadow-v18 g-bg-white ui-header-classics-classic-whitebg\" data-jc=\"ui-header-classics-classic-whitebg\" data-header-fix-moment-classes=\"u-shadow-v18 g-bg-white\">\n <div class=\"container\">\n <div class=\"u-header__section u-header__section--light g-transition-0_3 g-py-5\" data-header-fix-moment-exclude=\"g-bg-white g-py-10\" data-header-fix-moment-classes=\"g-py-5\">\n <nav class=\"navbar navbar-expand-lg\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"navBar\" data-toggle=\"collapse\" data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"/\" class=\"navbar-brand\">\n <img class=\"CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image Description\">\n </a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg g-mr-40--sm\" id=\"navBar\">\n <ul class=\"navbar-nav text-uppercase g-font-weight-600 mx-auto\">\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Home\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Features\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg active\">\n <a href=\"#!\" class=\"nav-link px-0\">Shortcodes\n <span class=\"sr-only\">(current)</span>\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Pages\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Demos\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg g-mr-0--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">What's New\n\n </a>\n </li>\n </ul>\n </div>\n <!-- End Navigation -->\n\n <!-- Search -->\n <div class=\"d-inline-block g-pos-rel g-valign-middle g-ml-30 g-ml-0--lg\">\n <a href=\"#!\" class=\"g-font-size-18 g-color-main\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"searchform-1\" data-dropdown-target=\"#searchform-1\" data-dropdown-type=\"css-animation\" data-dropdown-duration=\"300\" data-dropdown-animation-in=\"fadeInUp\" data-dropdown-animation-out=\"fadeOutDown\">\n <i class=\"fa fa-search\"></i>\n </a>\n\n <!-- Search Form -->\n <form id=\"searchform-1\" class=\"u-searchform-v1 u-dropdown--css-animation u-dropdown--hidden g-bg-white g-pa-10 g-mt-25--lg g-mt-20--lg--scrolling\" style=\"animation-duration: 300ms;\">\n <div class=\"input-group g-brd-primary--focus\">\n <input class=\"form-control rounded-0 u-form-control\" type=\"search\" placeholder=\"Enter Your Search Here...\">\n\n <div class=\"input-group-addon p-0\">\n <button class=\"btn rounded-0 btn-primary btn-md g-font-size-14 g-px-18\" type=\"submit\">Go</button>\n </div>\n </div>\n </form>\n <!-- End Search Form -->\n </div>\n <!-- End Search -->\n </nav>\n </div>\n </div>\n</header>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  40. 2019-06-25 01:00 | Administrator | {"id":"19042615300001wmg1","name":"Basics-paragraph","reference":"basics/basics-paragraph.html","body":"<style>\n #CMS .ui-basics-paragraph { min-height: 1rem; }\n</style>\n\n<p class=\"ui-basics-paragraph CMS_edit CMS_attribute\"></p>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T00:59:34.994Z"}
  41. 2019-06-25 01:00 | Administrator | {"id":"19042615300032wmg0","name":"Ui-header-megamenu-v1","reference":"ui/header/megamenu/ui-header-megamenu-v1.html","body":"<style>\n</style>\n\n<script>\n COMPONENT('ui-header-megamenu-v1', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSHeader.init(component.$el);\n $.HSCore.helpers.HSHamburgers.init(component.$el.find('.hamburger'));\n component.$el.find('.js-mega-menu').HSMegaMenu();\n\n $(window).on('resize', _.debounce(function(){\n $.HSCore.components.HSTabs.init(component.$el.find('[data-tabs-mobile-type]'));\n }, 200));\n\n $(window).trigger('resize');\n };\n }, [\n '/libs/ui/1.0.0/css/unify.min.css',\n '/libs/htmlstream-icon/1.0.0/hs-icon.min.css',\n '/libs/hamburgers/1.1.3/hamburgers.min.css',\n '/libs/ui/1.0.0/css/components/hs.megamenu.css',\n\n '/libs/ui/1.0.0/js/hs.core.js',\n '/libs/ui/1.0.0/js/components/hs.megamenu.js',\n '/libs/ui/1.0.0/js/components/hs.header.js',\n '/libs/ui/1.0.0/js/helpers/hs.hamburgers.js',\n '/libs/ui/1.0.0/js/components/hs.tabs.js',\n ]);\n</script>\n\n<!-- Header -->\n<header id=\"js-header\" class=\"u-header u-header--static--lg u-header--show-hide--lg u-header--change-appearance--lg ui-header-megamenu-v1\" data-jc=\"ui-header-megamenu-v1\" data-header-fix-moment=\"500\" data-header-fix-effect=\"slide\">\n <div class=\"u-header__section u-header__section--light g-bg-white g-transition-0_3 g-py-10\" data-header-fix-moment-exclude=\"g-bg-white g-py-10\" data-header-fix-moment-classes=\"g-bg-white-opacity-0_7 u-shadow-v18 g-py-0\">\n <nav class=\"js-mega-menu navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"navBar\" data-toggle=\"collapse\" data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"index.html\" class=\"navbar-brand\"><img class=\"CMS_edit\" src=\"//via.placeholder.com/100x40\" alt=\"Image Description\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg\" id=\"navBar\">\n <ul class=\"navbar-nav text-uppercase g-font-weight-600 ml-auto\">\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Home</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Features</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Pages</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Demos</a></li>\n <!-- Mega Menu Item -->\n <li class=\"hs-has-mega-menu nav-item g-mx-20--lg active\" data-animation-in=\"fadeIn\" data-animation-out=\"fadeOut\" data-position=\"right\">\n <a id=\"mega-menu-label-1\" class=\"nav-link active g-px-0\" href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\">Shortcodes\n <span class=\"sr-only\">(current)</span>\n <i class=\"hs-icon hs-icon-arrow-bottom g-font-size-11 g-ml-7\"></i>\n </a>\n\n <!-- Mega Menu -->\n <div class=\"w-100 hs-mega-menu u-shadow-v11 font-weight-normal g-text-transform-none g-brd-top g-brd-primary g-brd-top-2 g-mt-17 g-mt-7--lg--scrolling\" aria-labelledby=\"mega-menu-label-1\">\n <ul class=\"nav justify-content-center u-nav-v5-1 u-nav-gray-light-v5 g-brd-bottom--md g-brd-gray-light-v4 g-width-auto g-mx-20\" data-tabs-mobile-type=\"slide-up-down\" data-destroy-res=\"768\" data-btn-classes=\"btn btn-md btn-block u-btn-outline-black g-width-auto g-mt-20 g-mx-20\">\n <li class=\"nav-item\"><a class=\"nav-link active g-py-10--md g-px-15--md\" href=\"#tab-blocks\" role=\"tab\" data-toggle=\"tab\">Blocks</a></li>\n <li class=\"nav-item\"><a class=\"nav-link g-py-10--md g-px-15--md\" href=\"#tab-elements\" role=\"tab\" data-toggle=\"tab\">Elements</a></li>\n </ul>\n\n <div class=\"tab-content\">\n <div class=\"tab-pane fade in active show\" id=\"tab-blocks\" role=\"tabpanel\">\n <div class=\"row align-items-stretch\">\n <div class=\"col-lg-3\">\n <section class=\"g-pa-20\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n\n <div class=\"col-lg-3 g-brd-left--lg g-brd-gray-light-v5\">\n <section class=\"g-pa-20 g-pl-5--lg\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link active g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n\n <div class=\"col-lg-3 g-brd-left--lg g-brd-gray-light-v5\">\n <section class=\"g-pa-20 g-pl-5--lg\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n\n <div class=\"col-lg-3 g-brd-left--lg g-brd-gray-light-v5\">\n <section class=\"g-pa-20 g-pl-5--lg\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n </div>\n </div>\n\n <div class=\"tab-pane fade in\" id=\"tab-elements\" role=\"tabpanel\">\n <div class=\"row align-items-stretch\">\n <div class=\"col-lg-4\">\n <section class=\"g-pa-20\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n\n <div class=\"col-lg-4 g-brd-left--lg g-brd-gray-light-v5\">\n <section class=\"g-pa-20 g-pl-5--lg\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n\n <div class=\"col-lg-4 g-brd-left--lg g-brd-gray-light-v5\">\n <section class=\"g-pa-20 g-pl-5--lg\">\n <ul class=\"list-unstyled\">\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">General Typography</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Headings Options</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Dividers</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Blockquote Blocks</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Box Shadows</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Testimonials</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Tagline Boxes</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Grid Layouts</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Alerts &amp; Messages</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Labels &amp; Badges</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Audio/Videos &amp; Images</a></li>\n <li><a href=\"#!\" class=\"nav-link g-px-0 g-color-primary--hover\">Paginations</a></li>\n </ul>\n </section>\n </div>\n </div>\n </div>\n </div>\n </div>\n <!-- End Mega Menu -->\n </li>\n <!-- End Mega Menu Item -->\n <li class=\"nav-item g-ml-20--lg g-mr-0--lg\"><a href=\"#!\" class=\"nav-link px-0\">What's New</a></li>\n </ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n</header>\n<!-- End Header -->\n","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  42. 2019-06-25 01:00 | Administrator | {"id":"19042615300022wmg0","name":"Ui-header-classics-whitebg","reference":"ui/header/classics/ui-header-classics-whitebg.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-header-classics-whitebg', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n // initialization of header\n $.HSCore.components.HSHeader.init(component.$el);\n $.HSCore.helpers.HSHamburgers.init('.hamburger');\n };\n }, [\n '/libs/unify/1.0.0/css/unify.min.css',\n '/libs/unify/1.0.0/js/hs.core.js',\n '/libs/unify/1.0.0/js/hs.header.js',\n '/libs/unify/1.0.0/js/hs.hamburgers.js',\n ]);\n</script>\n\n<header class=\"u-header u-header--static u-header--floating g-mt-40--lg js-header-fix-moment u-shadow-v18 g-bg-white ui-header-classics-whitebg\" data-jc=\"ui-header-classics-whitebg\" data-header-fix-moment-classes=\"u-shadow-v18 g-bg-white\">\n <div class=\"container\">\n <div class=\"u-header__section u-header__section--light g-transition-0_3 g-py-5\" data-header-fix-moment-exclude=\"g-bg-white g-py-10\" data-header-fix-moment-classes=\"g-py-5\">\n <nav class=\"navbar navbar-expand-lg\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"navBar\" data-toggle=\"collapse\" data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"/\" class=\"navbar-brand\">\n <img class=\"CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image Description\">\n </a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg g-mr-40--sm\" id=\"navBar\">\n <ul class=\"navbar-nav text-uppercase g-font-weight-600 mx-auto\">\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Home\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Features\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg active\">\n <a href=\"#!\" class=\"nav-link px-0\">Shortcodes\n <span class=\"sr-only\">(current)</span>\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Pages\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">Demos\n\n </a>\n </li>\n <li class=\"nav-item g-mx-25--lg g-mr-0--lg\">\n <a href=\"#!\" class=\"nav-link px-0\">What's New\n\n </a>\n </li>\n </ul>\n </div>\n <!-- End Navigation -->\n\n <!-- Search -->\n <div class=\"d-inline-block g-pos-rel g-valign-middle g-ml-30 g-ml-0--lg\">\n <a href=\"#!\" class=\"g-font-size-18 g-color-main\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"searchform-1\" data-dropdown-target=\"#searchform-1\" data-dropdown-type=\"css-animation\" data-dropdown-duration=\"300\" data-dropdown-animation-in=\"fadeInUp\" data-dropdown-animation-out=\"fadeOutDown\">\n <i class=\"fa fa-search\"></i>\n </a>\n\n <!-- Search Form -->\n <form id=\"searchform-1\" class=\"u-searchform-v1 u-dropdown--css-animation u-dropdown--hidden g-bg-white g-pa-10 g-mt-25--lg g-mt-20--lg--scrolling\" style=\"animation-duration: 300ms;\">\n <div class=\"input-group g-brd-primary--focus\">\n <input class=\"form-control rounded-0 u-form-control\" type=\"search\" placeholder=\"Enter Your Search Here...\">\n\n <div class=\"input-group-addon p-0\">\n <button class=\"btn rounded-0 btn-primary btn-md g-font-size-14 g-px-18\" type=\"submit\">Go</button>\n </div>\n </div>\n </form>\n <!-- End Search Form -->\n </div>\n <!-- End Search -->\n </nav>\n </div>\n </div>\n</header>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  43. 2019-06-25 01:00 | Administrator | {"id":"19042615300016wmg0","name":"Ui-breadcrumb-v1","reference":"ui/breadcrumb/ui-breadcrumb-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-breadcrumb-v1', function(component, config){\n component.$el = component.element;\n component.$ul = component.$el.find('.breadcrumb-list');\n\n component.$ul.empty();\n\n // get mainmenu before `make()` execute\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n\n if ( !window._TOTALCMS_MAINMENU ) window._TOTALCMS_MAINMENU = mainmenu;\n else mainmenu = window._TOTALCMS_MAINMENU;\n\n let serializedTree = serializeTree([mainmenu]);\n let node = serializedTree.find(function(item){ return item.url === location.pathname });\n\n let nodePath = getPath(serializeTree(serializedTree), node);\n\n nodePath.forEach(function(item){\n let $li = $('<li class=\"list-inline-item g-mr-5\"/>');\n\n if ( item.url === location.pathname )\n $li.addClass('g-color-primary').append(`<span>${item.name}</span>`);\n else if ( item.id === 'mainmenu' )\n $li.append(`<a class=\"u-link-v5 g-color-main\" href=\"/\"><i class=\"fas fa-home\"></i></a><i class=\"g-color-gray-light-v2 g-ml-5\"></i>`);\n else\n $li.append(`<a class=\"u-link-v5 g-color-main\" href=\"/\">${item.name}</a><i class=\"g-color-gray-light-v2 g-ml-5\"></i>`);\n\n component.$ul.append($li);\n });\n };\n\n // ██████╗██╗ ██╗███████╗████████╗ ██████╗ ███╗ ███╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗\n // ██╔════╝██║ ██║██╔════╝╚══██╔══╝██╔═══██╗████╗ ████║ ██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝\n // ██║ ██║ ██║███████╗ ██║ ██║ ██║██╔████╔██║ █████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗\n // ██║ ██║ ██║╚════██║ ██║ ██║ ██║██║╚██╔╝██║ ██╔══╝ ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║\n // ╚██████╗╚██████╔╝███████║ ██║ ╚██████╔╝██║ ╚═╝ ██║ ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║\n // ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝\n // ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████╗\n // ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝\n function serializeTree(tree, parent, store = []){\n for ( var i = 0, ilen = tree.length; i < ilen; i++ )\n {\n let node = $.extend(true, {}, tree[i]);\n if ( parent ) node.parent = parent.id;\n if ( node.children && node.children.length > 0 ) serializeTree(node.children, node, store);\n delete node.children;\n store.push(node);\n }\n return store;\n };\n\n function getPath(serializedTree, node, path = []){\n if ( node.parent ) getPath(serializedTree, serializedTree.find(function(item){ return item.id === node.parent; }), path);\n path.push(node);\n return path;\n }\n\n function getPathName(serializedTree, node, path = ''){\n let nodePath = getPath(serializedTree, node);\n return nodePath.map(function(item){\n if ( nodePath.indexOf(item) !== 0 ) return ` > ${item.name}`;\n return item.name;\n });\n };\n\n function findByUrl(tree, url){\n return findByField(tree, 'url', url);\n };\n\n function findById(tree, id){\n return findByField(tree, 'id', id);\n };\n\n function findByField(tree, field, value){\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n if ( tree[i][field] === value ) return tree[i];\n\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n {\n if ( tree[i].children && tree[i].children.length > 0 )\n {\n let node = findByField(tree[i].children, field, value);\n if ( node ) return node;\n }\n }\n\n return null;\n };\n }, [\n ]);\n</script>\n\n<script total> ROUTE('GET /api/v3/nav/{id}', ['*Navigation --> @read']);</script>\n\n<section class=\"g-brd-top g-brd-bottom g-brd-gray-light-v4 g-py-50 ui-breadcrumb-v1\" data-jc=\"ui-breadcrumb-v1\">\n <div class=\"container\">\n <div class=\"d-sm-flex text-center\">\n <div class=\"align-self-center\">\n <h2 class=\"h3 g-font-weight-300 w-100 g-mb-10 g-mb-0--md\">Breadcrumbs</h2>\n </div>\n\n <div class=\"align-self-center ml-auto\">\n <ul class=\"u-list-inline breadcrumb-list\">\n <li class=\"list-inline-item g-mr-5\">\n <a class=\"u-link-v5 g-color-main\" href=\"#!\">Home</a>\n <i class=\"g-color-gray-light-v2 g-ml-5\">/</i>\n </li>\n <li class=\"list-inline-item g-mr-5\">\n <a class=\"u-link-v5 g-color-main\" href=\"#!\">Pages</a>\n <i class=\"g-color-gray-light-v2 g-ml-5\">/</i>\n </li>\n <li class=\"list-inline-item g-color-primary\">\n <span>About Us</span>\n </li>\n </ul>\n </div>\n </div>\n </div>\n</section>\n","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  44. 2019-06-25 01:00 | Administrator | {"id":"19042615300026wmg0","name":"Ui-hero-info-v4","reference":"ui/hero/info/ui-hero-info-v4.html","body":"<style>\n #CMS .ui-hero-info-v4 .d-editor-block { width: 100%; height: 150px; }\n</style>\n\n<script editor>\n option('background-image', 'Background Image', '/noimage.jpg', 'file');\n exports.configure = function(options, $el, prev){\n $el.find('.divimage').css({\n 'background-image': `url(${options['background-image']})`\n });\n };\n</script>\n\n<script>\n COMPONENT('ui-hero-info-v4', function(component, config){\n }, [\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.css',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.css',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.css',\n\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.js',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.js',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.js',\n ]);\n</script>\n\n<div data-jc=\"ui-hero-info-v4\" class=\"ui-hero-info-v4\">\n <!-- Hero Info #04 -->\n <section class=\"dzsparallaxer auto-init height-is-based-on-content use-loading\">\n <!-- Parallax Image -->\n <div class=\"divimage dzsparallaxer--target w-100 u-bg-overlay g-bg-black-opacity-0_4--after\" style=\"height: 140%;\"></div>\n <!-- End Parallax Image -->\n\n <div class=\"container text-center g-color-white g-py-200--md g-py-80\">\n <h2 class=\"text-uppercase g-font-weight-700 g-mb-20 CMS_edit\">Most quality solution for you</h2>\n\n <p class=\"lead g-px-100--md g-mb-40 CMS_edit\">Morbi a suscipit ipsum. Suspendisse mollis libero ante. Pellentesque finibus convallis nulla vel placerat. Nulla ipsum dolor sit amet, consectetur adipiscing elitut eleifend nisl.</p>\n\n <a href=\"#!\" class=\"btn btn-xl u-btn-outline-white text-uppercase g-font-weight-600 g-font-size-12 g-rounded-50 g-mb-15 g-mr-30--md CMS_edit\">Try trial version</a>\n <div class=\"g-hidden-md-up\"></div>\n <a href=\"#!\" class=\"btn btn-xl u-btn-primary text-uppercase g-font-weight-600 g-font-size-12 g-rounded-50 g-mb-15 CMS_edit\">Buy full version</a>\n </div>\n </section>\n <!-- Hero Info #04 -->\n</div>","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  45. 2019-06-25 01:00 | Administrator | {"id":"19042615300028wmg0","name":"Ui-header-classics-classic-gradient","reference":"ui/header/classics/classic/ui-header-classics-classic-gradient.html","body":"<style>\n #CMS .ui-header-classics-classic-gradient { position: relative; z-index: 0; }\n</style>\n\n<script>\n COMPONENT('ui-header-classics-classic-gradient', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n // initialization of header\n $.HSCore.components.HSHeader.init(component.$el);\n $.HSCore.helpers.HSHamburgers.init(component.$el.find('.hamburger'));\n };\n }, [\n '/libs/ui/1.0.0/css/unify.min.css',\n '/libs/ui/1.0.0/js/hs.core.js',\n '/libs/ui/1.0.0/js/hs.header.js',\n '/libs/ui/1.0.0/js/hs.hamburgers.js',\n '/libs/ui/1.0.0/js/hs.dropdown.js',\n ]);\n</script>\n\n<!-- Header -->\n<header class=\"u-header u-header--sticky-top u-header--toggle-section u-header--change-appearance ui-header-classics-classic-gradient\" data-jc=\"ui-header-classics-classic-gradient\" data-header-fix-moment=\"300\">\n <div class=\"u-header__section u-header__section--dark g-bg-primary-gradient-opacity-v1 g-transition-0_3 g-py-10\" data-header-fix-moment-exclude=\"g-py-10\" data-header-fix-moment-classes=\"g-py-0\">\n <nav class=\"navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"navBar\" data-toggle=\"collapse\" data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a href=\"/\" class=\"navbar-brand\"><img class=\"CMS_edit\" src=\"//via.placeholder.com/100x40\" alt=\"Image Description\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-10 g-pt-5--lg\" id=\"navBar\">\n <ul class=\"navbar-nav text-uppercase g-font-weight-600 ml-auto\">\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Home</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Features</a></li>\n <li class=\"nav-item g-mx-20--lg active\"><a href=\"#!\" class=\"nav-link px-0\">Shortcodes<span class=\"sr-only\">(current)</span></a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Pages</a></li>\n <li class=\"nav-item g-mx-20--lg\"><a href=\"#!\" class=\"nav-link px-0\">Demos</a></li>\n <li class=\"nav-item g-ml-20--lg g-mr-0--lg\"><a href=\"#!\" class=\"nav-link px-0\">What's New</a></li>\n </ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n</header>\n<!-- End Header -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  46. 2019-06-25 01:00 | Administrator | {"id":"19042615300030wmg0","name":"Ui-header-naea-v1","reference":"ui/header/naea/ui-header-naea-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-header-naea-v1', function(component, config){\n // 컴포넌트 상수 설정\n const CONSTANTS = {\n TEMPLATE: {\n // 탑 레벨 - 일반 아이템\n $NAV_LIST_ITEM: $('<li class=\"nav-item g-mx-5--lg g-mx-10--xl\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n // $NAV_LIST_ITEM: $('<li class=\"nav-item g-mx-2--md g-mx-5--xl g-mb-5 g-mb-0--lg\"><a href=\"#!\" class=\"nav-link\"></a></li>'),\n\n // 탑 레벨 - 서브메뉴 있는 아이템\n $HAS_SUBMENU_LIST_ITEM: $('<li class=\"nav-item hs-has-sub-menu g-mx-5--lg g-mx-10--xl\"/>'),\n // 탑 레벨 - 서브메뉴 토글러\n $SUBMENU_TOGGLER: $('<a id=\"nav-link--home\" class=\"nav-link text-uppercase g-color-primary--hover g-px-5 g-py-20 text-nowrap\" href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 탑 레벨 - 서브메뉴\n $SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled u-shadow-v11 g-min-width-220 g-brd-top g-brd-primary g-brd-top-2 g-mt-17\">'),\n\n // 서브메뉴 - 일반 아이템\n $SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n\n // 서브메뉴 - 서브메뉴 있는 아이템\n $SUBMENU_HAS_SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item hs-has-sub-menu\"/>'),\n // 서브메뉴 - 서브메뉴 토글러\n $SUBMENU_SUBMENU_TOGGLER: $('<a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 서브메뉴 - 서브메뉴 리스트\n $SUBMENU_SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled u-shadow-v11 g-min-width-220 g-brd-top g-brd-primary g-brd-top-2 g-mt-minus-2\"/>'),\n // 서브메뉴 - 서브메뉴 일반 아이템\n $SUBMENU_SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n }\n };\n\n // 컴포넌트 객체 변수 지정\n component.$navbar = component.$el = component.element;\n\n component.$navbarToggler = $('.navbar-toggler', component.$navbar);\n\n component.$nav = $('nav.js-mega-menu', component.$navbar);\n component.$navbarCollapse = $('.navbar-collapse', component.$nav);\n component.$navbarNav = $('ul.navbar-nav', component.$navbarCollapse);\n\n component.$navContainer = $('.container', component.$nav);\n\n component.$loginAnchor = $('.navbar-login-link', component.$el);\n\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n // 공용 네비게이션 객체 초기화\n if ( window._TOTALCMS_MAINMENU ) mainmenu = window._TOTALCMS_MAINMENU;\n else if ( mainmenu ) window._TOTALCMS_MAINMENU = mainmenu;\n\n // 토글 매핑 아이디 임의화\n // 네비게이션 접기 펴기 ID 랜덤화\n let navRandomId = $.TOT.helpers.getRandomId();\n\n component.$navbarToggler\n .attr({\n 'aria-controls': navRandomId,\n 'data-target': `#${navRandomId}`,\n 'data-toggle': 'collapse',\n })\n .data('target', `#${navRandomId}`);\n\n component.$navbarCollapse.prop('id', navRandomId);\n\n\n // 네비게이션 생성\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(menuItem, index, src, $parent, depth){\n depth = depth || 0;\n menuItem.hasChild = menuItem.children && menuItem.children.length > 0;\n\n if ( menuItem.url === '/' ) return;\n\n let $cursor;\n if ( depth === 0 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n component.$navbarNav.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$NAV_LIST_ITEM.clone();\n $('a.nav-link', $listItem).attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n component.$navbarNav.append($listItem);\n }\n }\n else if ( depth === 1 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$SUBMENU_HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n $parent.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n }\n else if ( depth === 2 )\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n\n if ( !menuItem.hasChild ) return;\n\n depth += 1;\n _.each(menuItem.children, function(menuItem, index, src){\n return _recursive(menuItem, index, src, $cursor, depth);\n });\n });\n\n // 검색바 ID 임의화\n let searchFormRandomId = $.HSCore.helpers.getRandomId();\n $('#searchform', component.$el).prop('id', searchFormRandomId);\n $('[data-dropdown-target=\"#searchform\"]', component.$el).attr({\n 'data-dropdown-target': `#${searchFormRandomId}`,\n 'aria-controls': searchFormRandomId,\n }).data('dropdown-target', `#${searchFormRandomId}`);\n\n let redirectTo = encodeURIComponent(location.href.replace(location.origin, ''));\n\n\n // 세션에 따른 링크 변경\n if ( _.get(TOTAL_LOCALS, '_SESSION.isLoggedIn') )\n component.$loginAnchor.text('로그아웃').attr('href', `/logout?from=${redirectTo}`);\n else\n component.$loginAnchor.attr('href', `/login?from=${redirectTo}`);\n\n component.$loginAnchor.removeClass('d-none');\n\n // 현재 URL 네비게이션 강조\n $(`a[href=\"${location.pathname}\"]`, component.$navbarNav).parents('li').addClass('active');\n\n $.HSCore.components.HSHeader.init(component.$navbar);\n $.HSCore.helpers.HSHamburgers.init($('.hamburger', component.$el));\n\n component.$nav.HSMegaMenu({\n event: 'hover',\n pageContainer: component.$navContainer,\n breakpoint: 991\n });\n\n $.HSCore.components.HSDropdown.init($('[data-dropdown-target]', component.$el), {\n afterOpen: function () {\n $(this).find('input[type=\"search\"]').focus();\n }\n });\n\n setTimeout(function(){\n component.$nav.data('HSMegaMenu').smartPositions();\n });\n }\n });\n</script>\n\n<!-- Header -->\n<header class=\"u-header u-header--static u-shadow-v19 ui-header-naea-v1\" data-jc=\"ui-header-naea-v1\">\n <div class=\"u-header__section u-header__section--hidden u-header__section--dark g-bg-black-opacity-0_9 g-color-white-opacity-0_8 g-py-13\">\n <div class=\"container\">\n <div class=\"row flex-column flex-md-row align-items-center justify-content-between text-uppercase g-color-white g-font-size-11 g-mx-minus-15\">\n <div class=\"col-auto ml-auto\">\n <ul class=\"list-inline g-line-height-1 g-mt-minus-10 g-mx-minus-4 mb-0\">\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <a href=\"/login\" class=\"navbar-login-link g-color-white g-color-primary--hover g-text-underline--none--hover d-none\">로그인</a>\n </li>\n\n <!--li class=\"list-inline-item g-mx-4 g-mt-10\">|</li>\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <a href=\"/joinus\" class=\"g-color-white g-color-primary--hover g-text-underline--none--hover\">회원가입</a>\n </li-->\n\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <!-- Search -->\n <div class=\"g-pos-rel\">\n <a href=\"#!\" class=\"g-font-size-14 g-color-white g-color-primary--hover g-ml-5\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"searchform\" data-dropdown-target=\"#searchform\" data-dropdown-type=\"css-animation\" data-dropdown-duration=\"300\" data-dropdown-animation-in=\"fadeInUp\" data-dropdown-animation-out=\"fadeOutDown\">\n <i class=\"fa fa-search\"></i>\n </a>\n\n <!-- Search Form -->\n <form id=\"searchform\" class=\"u-searchform-v1 g-bg-black g-box-shadow-none g-pa-10 g-mt-10 u-dropdown--css-animation u-dropdown--hidden\" style=\"animation-duration: 300ms;\">\n <div class=\"input-group g-brd-primary--focus\">\n <input class=\"form-control rounded-0 u-form-control g-brd-white\" type=\"search\" placeholder=\"Enter Your Search Here...\">\n <div class=\"input-group-addon p-0\">\n <button class=\"btn rounded-0 btn-primary btn-md g-font-size-14 g-px-18\" type=\"submit\">Go</button>\n </div>\n </div>\n </form>\n <!-- End Search Form -->\n </div>\n <!-- End Search -->\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"u-header__section u-header__section--light g-bg-white g-transition-0_3 g-py-10\">\n <nav class=\"js-mega-menu navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\"\n aria-label=\"Toggle navigation\"\n aria-expanded=\"false\"\n aria-controls=\"navBar\"\n data-toggle=\"collapse\"\n data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider g-pr-0\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a class=\"navbar-brand\" href=\"/\"><img class=\"CMS_edit\" src=\"//via.placeholder.com/200x45\" alt=\"Image Description\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-15 g-pt-0--lg\">\n <ul class=\"navbar-nav ml-auto\"></ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n</header>\n<!-- End Header -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  47. 2019-06-25 01:00 | Administrator | {"id":"19042615300024wmg0","name":"Ui-hero-info-v1","reference":"ui/hero/info/ui-hero-info-v1.html","body":"<style></style>\n\n<script>\n COMPONENT('ui-hero-info-v1', function(component, config){}, [\n ]);\n</script>\n\n<!-- Hero Info #01 -->\n<section class=\"text-center g-py-120--md g-py-80 ui-hero-info-v1\" data-jc=\"ui-hero-info-v1\">\n <div class=\"container\">\n <div class=\"u-heading-v2-5--bottom g-brd-primary g-mb-30\">\n <h2 class=\"u-heading-v2__title text-uppercase g-font-weight-300 mb-0 CMS_edit\">We are hiring</h2>\n </div>\n\n <p class=\"lead g-px-60--md g-mb-40 CMS_edit\">Lorem ipsum dolor\n <span class=\"g-color-primary CMS_edit\">sit amet consectetur</span>\n adipiscing elit. Nam eget varius leo, at elementum eros. Fusce tristique, ipsum egestas fermentum imperdiet, ex nunc iaculis sem, a semper augue turpis ut nulla.\n </p>\n\n <img class=\"img-fluid CMS_edit\" src=\"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" alt=\"Image description\">\n </div>\n</section>\n<!-- End Hero Info #01 -->","datecreated":"2019-04-26T06:30:18.689Z","picture":"","icon":"","category":"Ui","dateupdated":"2019-06-25T00:59:34.994Z"}
  48. 2019-06-25 01:01 | Administrator | {"id":"19062510000001bof1","name":"Partial","reference":"view-engine/partial.html","body":"<script editor>\n option('id', 'Partial page', '', 'Partial');\n\n exports.configure = function(options, el) {\n\n var text = 'not defined';\n\n if (options.id) {\n var item = this.id.findItem('value', options.id);\n item && (text = item.text);\n }\n\n el.find('.totaljs').text('Partial: ' + text);\n\n };\n\n</script>\n\n<script total>\n exports.render = function(options, html, next) {\n if (!options.id)\n return next('');\n this.CMSpartial(options.id, function(err, response) {\n if (response && response.body)\n next(html.substring(0, html.indexOf('<div class=\"totaljs')) + response.body + '</div>');\n else\n next('');\n });\n };\n</script>\n\n<div class=\"wm wp wb wc146\">\n <div class=\"totaljs\">Partial page</div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"View","dateupdated":"2019-06-25T01:00:59.113Z"}
  49. 2019-06-25 01:01 | Administrator | {"id":"19062510000002bof0","name":"V1","reference":"basics/addon/goto/v1.html","body":"<style></style>\n<script>\n COMPONENT('basics-addon-goto-v1', function(component, config){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSGoTo.init('.js-go-to', component.$el);\n };\n }, [\n '/libs/ui/1.0.0/js/components/hs.go-to.js',\n ]);\n</script>\n\n<div class=\"basics-addon-goto-v1\" data-jc=\"basics-addon-goto-v1\">\n <a class=\"js-go-to u-go-to-v2\" href=\"#!\" data-type=\"fixed\" data-position='{\"bottom\": 15,\"right\": 15}' data-offset-top=\"400\" data-compensation=\"#js-header\" data-show-effect=\"zoomIn\">\n <i class=\"fa fa-angle-up\"></i>\n </a>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  50. 2019-06-25 01:01 | Administrator | {"id":"19062510000003bof1","name":"V1","reference":"basics/footer/bottombar/v1.html","body":"<style></style>\n\n<footer class=\"g-bg-gray-dark-v1 g-color-white-opacity-0_8 g-py-20 basics-footer-bottombar-v1\">\n <div class=\"container\">\n <div class=\"row\">\n <div class=\"col-md-8 text-center text-md-left g-mb-10 g-mb-0--md\">\n <div class=\"d-lg-flex\">\n <small class=\"d-block g-font-size-default g-mr-10 g-mb-10 g-mb-0--md CMS_edit\">1998-2018 © All Rights Reserved.</small>\n <ul class=\"u-list-inline\">\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">개인정보처리방침</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">뷰어다운로드</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">찾아오시는길</a></li>\n <li class=\"list-inline-item\"><span>|</span></li>\n <li class=\"list-inline-item\"><a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">사이트맵</a></li>\n </ul>\n </div>\n </div>\n\n <div class=\"col-md-4 align-self-center\">\n <ul class=\"list-inline text-center text-md-right mb-0\">\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Facebook\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-facebook\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Skype\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-skype\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Linkedin\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-linkedin\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Pinterest\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-pinterest\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Twitter\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-twitter\"></i></a>\n </li>\n <li class=\"list-inline-item g-mx-10\" data-toggle=\"tooltip\" data-placement=\"top\" title=\"\" data-original-title=\"Dribbble\">\n <a href=\"#!\" class=\"g-color-white-opacity-0_5 g-color-white--hover CMS_edit\"><i class=\"fab fa-dribbble\"></i></a>\n </li>\n </ul>\n </div>\n </div>\n </div>\n</footer>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  51. 2019-06-25 01:01 | Administrator | {"id":"19062510000004bof0","name":"V1","reference":"basics/footer/contactus/v1.html","body":"<style></style>\n\n<script editor>\n option('addressBgImage', 'Background Image for Address Area', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('address.bg-address', $el).css('background-image', `url(${options.addressBgImage})`);\n };\n</script>\n\n<div class=\"g-bg-black-opacity-0_9 g-color-white-opacity-0_8 g-py-60 basics-footer-contactus-v1\">\n <div class=\"container\">\n <div class=\"row\">\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">여기를 편집하세요.</h2>\n </div>\n\n <p class=\"CMS_edit\">여기를 편집하세요.</p>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">여기를 편집하세요.</h2>\n </div>\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6 CMS_edit\">2019-01-01</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6 CMS_edit\">2019-01-01</div>\n </article>\n\n <hr class=\"g-brd-white-opacity-0_1 g-my-10\">\n\n <article>\n <h3 class=\"h6 g-mb-2\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n </h3>\n <div class=\"small g-color-white-opacity-0_6 CMS_edit\">2019-01-01</div>\n </article>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6 g-mb-40 g-mb-0--lg\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">여기를 편집하세요.</h2>\n </div>\n\n <nav class=\"text-uppercase1\">\n <ul class=\"list-unstyled g-mt-minus-10 mb-0\">\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n <li class=\"g-pos-rel g-brd-bottom g-brd-white-opacity-0_1 g-py-10\">\n <h4 class=\"h6 g-pr-20 mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"#!\">여기를 편집하세요.</a>\n <i class=\"fa fa-angle-right g-absolute-centered--y g-right-0\"></i>\n </h4>\n </li>\n </ul>\n </nav>\n </div>\n <!-- End Footer Content -->\n\n <!-- Footer Content -->\n <div class=\"col-lg-3 col-md-6\">\n <div class=\"u-heading-v2-3--bottom g-brd-white-opacity-0_8 g-mb-20\">\n <h2 class=\"u-heading-v2__title h6 text-uppercase mb-0 CMS_edit\">여기를 편집하세요.</h2>\n </div>\n\n <address class=\"g-bg-no-repeat g-font-size-12 mb-0 bg-address\" style=\"background-image: url(//via.placeholder.com/255x154)\">\n <!-- Location -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-map-marker\"></i>\n </span>\n </div>\n <p class=\"mb-0 CMS_edit CMS_multiline\">여기를 편집하세요.\n <br>\n 여러줄 입력이 가능합니다.\n </p>\n </div>\n <!-- End Location -->\n\n <!-- Phone -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-phone\"></i>\n </span>\n </div>\n <p class=\"mb-0 CMS_edit CMS_multiline\">TEL : 000-000-0000\n <br>\n FAX : 000-000-0000\n </p>\n </div>\n <!-- End Phone -->\n\n <!-- Email and Website -->\n <div class=\"d-flex g-mb-20\">\n <div class=\"g-mr-10\">\n <span class=\"u-icon-v3 u-icon-size--xs g-bg-white-opacity-0_1 g-color-white-opacity-0_6\">\n <i class=\"fa fa-globe\"></i>\n </span>\n </div>\n <p class=\"mb-0\">\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit CMS_attribute\" href=\"mailto:info@kice.re.kr\">example@example.com</a>\n <br>\n <a class=\"g-color-white-opacity-0_8 g-color-white--hover CMS_edit\" href=\"#!\">example.com</a>\n </p>\n </div>\n <!-- End Email and Website -->\n </address>\n </div>\n <!-- End Footer Content -->\n </div>\n </div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  52. 2019-06-25 01:01 | Administrator | {"id":"19062510000005bof1","name":"V1","reference":"basics/header/breadcrumb/v1.html","body":"<style></style>\n\n<script>\n COMPONENT('basics-header-breadcrumb-v1', function(component, config){\n component.$el = component.element;\n component.$ul = $('ul.u-list-inline', component.$el);\n component.$header = $('header', component.$el);\n\n component.$ul.empty();\n\n // get mainmenu before `make()` execute\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n if ( !window._TOTALCMS_MAINMENU ) window._TOTALCMS_MAINMENU = mainmenu;\n else mainmenu = window._TOTALCMS_MAINMENU;\n\n let serializedTree = serializeTree([mainmenu]);\n let node = serializedTree.find(function(item){ return item.url === location.pathname });\n\n let nodePath = getPath(serializeTree(serializedTree), node);\n\n nodePath.forEach(function(item, idx){\n let $li = $('<li class=\"list-inline-item g-mr-7\"/>');\n\n if ( item.url === location.pathname )\n $li.removeClass('g-mr-7').append(`<span>${item.name}</span>`);\n else\n $li.append(`<a class=\"u-link-v5 g-color-main\" href=\"${item.url || '/'}\">${!item.url ? 'Home' : item.name}</a>`);\n\n if ( idx < nodePath.length - 1 ) $li.append('<i class=\"fa fa-angle-right g-ml-7\"></i>');\n else $li.addClass('g-color-primary');\n\n component.$ul.append($li);\n });\n };\n\n // ██████╗██╗ ██╗███████╗████████╗ ██████╗ ███╗ ███╗ ███████╗██╗ ██╗███╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗███████╗\n // ██╔════╝██║ ██║██╔════╝╚══██╔══╝██╔═══██╗████╗ ████║ ██╔════╝██║ ██║████╗ ██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝\n // ██║ ██║ ██║███████╗ ██║ ██║ ██║██╔████╔██║ █████╗ ██║ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗\n // ██║ ██║ ██║╚════██║ ██║ ██║ ██║██║╚██╔╝██║ ██╔══╝ ██║ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║\n // ╚██████╗╚██████╔╝███████║ ██║ ╚██████╔╝██║ ╚═╝ ██║ ██║ ╚██████╔╝██║ ╚████║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║███████║\n // ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝\n // ███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████╗\n // ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝\n function serializeTree(tree, parent, store = []){\n for ( var i = 0, ilen = tree.length; i < ilen; i++ )\n {\n let node = $.extend(true, {}, tree[i]);\n if ( parent ) node.parent = parent.id;\n if ( node.children && node.children.length > 0 ) serializeTree(node.children, node, store);\n delete node.children;\n store.push(node);\n }\n return store;\n };\n\n function getPath(serializedTree, node, path = []){\n if ( node.parent ) getPath(serializedTree, serializedTree.find(function(item){ return item.id === node.parent; }), path);\n path.push(node);\n return path;\n }\n\n function getPathName(serializedTree, node, path = ''){\n let nodePath = getPath(serializedTree, node);\n return nodePath.map(function(item){\n if ( nodePath.indexOf(item) !== 0 ) return ` > ${item.name}`;\n return item.name;\n });\n };\n\n function findByUrl(tree, url){\n return findByField(tree, 'url', url);\n };\n\n function findById(tree, id){\n return findByField(tree, 'id', id);\n };\n\n function findByField(tree, field, value){\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n if ( tree[i][field] === value ) return tree[i];\n\n for ( let i = 0, ilen = tree.length; i < ilen; i++ )\n {\n if ( tree[i].children && tree[i].children.length > 0 )\n {\n let node = findByField(tree[i].children, field, value);\n if ( node ) return node;\n }\n }\n\n return null;\n };\n });\n</script>\n\n<section class=\"g-bg-gray-light-v5 g-py-50 basics-header-breadcrumb-v1\" data-jc=\"basics-header-breadcrumb-v1\">\n <div class=\"container g-bg-cover__inner\">\n <header class=\"g-mb-20\">\n <h3 class=\"h5 g-font-weight-300 g-mb-5 CMS_edit\">편집</h3>\n <h2 class=\"h1 g-font-weight-300 text-uppercase CMS_edit\">편집</h2>\n </header>\n <ul class=\"u-list-inline\"></ul>\n </div>\n</section>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  53. 2019-06-25 01:01 | Administrator | {"id":"19062510000006bof0","name":"V1","reference":"basics/layout/container/v1.html","body":"<style>\n #CMS .layout-container-v1 {\n padding: 1rem;\n min-height: 2rem;\n }\n</style>\n\n<div class=\"layout-container-v1 container CMS_attribute CMS_widgets\"></div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  54. 2019-06-25 01:01 | Administrator | {"id":"19062510000007bof1","name":"V1","reference":"basics/layout/rowcol/v1.html","body":"<style>\n #CMS .row {\n border-top: 6px solid #E0E0E0;\n cursor: crosshair;\n padding: .25rem 1rem;\n margin: 0;\n }\n\n #CMS .row,\n #CMS .row .basics-layout-rowcol-v1 {\n padding: 1rem;\n min-height: 2rem;\n }\n</style>\n\n<script editor>\n option('number-of-columns', 'Number of Columns', 1);\n option('breakpoint', 'Breakpoint', 'md', [\n { text: 'None', value: '' },\n { text: 'Extra Small', value: 'xs' },\n { text: 'Small', value: 'sm' },\n { text: 'Middle', value: 'md' },\n { text: 'Large', value: 'lg' },\n { text: 'Extra Large', value: 'xl' },\n ]);\n option('no-gutters', 'No Gutters', false);\n\n exports.configure = function(options, $el, prev){\n let $component = $el.find('.basics-layout-rowcol-v1');\n let $columns = $component.find('.basics-layout-rowcol-v1-col');\n\n $component.toggleClass('no-gutters', options['no-gutters']);\n\n let numberOfColumns = options['number-of-columns'];\n let eachColumnsSize = 12 / numberOfColumns;\n let columnBreakpointPrefix = options['breakpoint'] ? `${options['breakpoint']}-` : '';\n\n for ( let i = 0, ilen = numberOfColumns; i < ilen; i++ )\n {\n let $column = $columns.eq(i);\n if ( $column.length <= 0 )\n {\n $component.append(`<div class=\"basics-layout-rowcol-v1-col col-${columnBreakpointPrefix}${eachColumnsSize} CMS_widgets CMS_attribute\"/>`);\n }\n else\n {\n $column.removeAttr('class')\n .addClass(`basics-layout-rowcol-v1-col col-${columnBreakpointPrefix}${eachColumnsSize} CMS_widgets CMS_attribute`);\n }\n }\n\n for ( let i = $columns.length, ilen = numberOfColumns; i > ilen; i-- )\n {\n let $column = $columns.eq(i - 1);\n $column.children().appendTo($columns.eq(ilen - 1));\n $column.remove();\n }\n };\n</script>\n\n<div class=\"row basics-layout-rowcol-v1 CMS_attribute\">\n <div class=\"basics-layout-rowcol-v1-col col-12 CMS_widgets CMS_attribute\"></div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  55. 2019-06-25 01:01 | Administrator | {"id":"19062510000008bof0","name":"V1","reference":"basics/main/carousel/v1.html","body":"<style>\n #CMS .basics-main-carousel-v1 { min-height: 10rem; }\n</style>\n\n<script>\n COMPONENT('basics-main-carousel-v1', function(component){\n component.$el = component.element;\n\n component.make = function(){\n $.HSCore.components.HSCarousel.init(component.$el);\n };\n }, [\n '/libs/slick-carousel/1.9.0/slick.min.css',\n '/libs/slick-carousel/1.9.0/slick.min.js',\n\n '/libs/ui/1.0.0/js/components/hs.carousel.js',\n ]);\n</script>\n\n<section class=\"js-carousel g-overflow-hidden g-max-height-100vh basics-main-carousel-v1 CMS_widgets\" data-cms-importable=\"basics-main-carousel-piece-v1\" data-autoplay=\"true\" data-infinite=\"true\" data-speed=\"15000\" data-pagi-classes=\"u-carousel-indicators-v29 container text-center text-uppercase g-absolute-centered--x g-bottom-50 g-line-height-1_2 g-font-size-12 g-color-white g-brd-white-opacity-0_2\" data-jc=\"basics-main-carousel-v1\"></section>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  56. 2019-06-25 01:01 | Administrator | {"id":"19062510000009bof1","name":"V2","reference":"basics/main/carousel/v2.html","body":"<style>\n #CMS .basics-main-carousel-v2 { min-height: 10rem; }\n</style>\n\n<script editor>\n option('bgImage', 'Background Image', '', 'file');\n\n exports.configure = function(options, $el, prevOptions){\n $('.divimage', $el).css('background-image', `url(${options.bgImage})`);\n };\n</script>\n\n<script>\n COMPONENT('basics-main-carousel-v2', function(component, config){\n component.$el = component.element;\n component.$carousel = $('.js-carousel', component.$el);\n\n component.make = function(){\n $.HSCore.components.HSCarousel.init(component.$carousel);\n };\n }, [\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.css',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.css',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.css',\n\n '/libs/dzsparallaxer/2.6.3/dzsparallaxer.js',\n '/libs/dzsparallaxer/2.6.3/dzsscroller/scroller.js',\n '/libs/dzsparallaxer/2.6.3/advancedscroller/plugin.js',\n ]);\n</script>\n\n<div class=\"dzsparallaxer auto-init height-is-based-on-content use-loading g-bg-cover g-bg-black-opacity-0_7--after basics-main-carousel-v2\" data-jc=\"basics-main-carousel-v2\">\n <div class=\"divimage dzsparallaxer--target w-100\" style=\"height: 140%; background-image: url(//via.placeholder.com/1920x1280/eeeeee);\"></div>\n <div class=\"container g-bg-cover__inner g-py-120\">\n <div class=\"js-carousel g-pb-80 CMS_widgets\" data-cms-importable=\"basics-main-carousel-piece-v2\" data-infinite=\"true\" data-arrows-classes=\"u-arrow-v1 g-width-40 g-height-40 g-brd-1 g-brd-style-solid g-brd-white-opacity-0_6 g-brd-primary--hover g-color-white-opacity-0_5 g-bg-primary--hover g-color-white--hover g-absolute-centered--x g-bottom-0\" data-arrow-left-classes=\"fa fa-angle-left g-ml-minus-25\" data-arrow-right-classes=\"fa fa-angle-right g-ml-25\">\n </div>\n </div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  57. 2019-06-25 01:01 | Administrator | {"id":"19062510000010bof0","name":"V1","reference":"basics/main/hero/v1.html","body":"<script editor>\n option('bgImage', 'Background Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('.g-bg-img-hero', $el).css('background-image', `url(${options.bgImage})`)\n };\n</script>\n\n<section class=\"g-bg-img-hero basics-main-hero-v1\" data-jc=\"basics-main-hero-v1\">\n <div class=\"container g-py-100 CMS_attribute CMS_widgets\"></div>\n</section>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  58. 2019-06-25 01:01 | Administrator | {"id":"19062510000011bof1","name":"V1","reference":"basics/main/topic/v1.html","body":"<style></style>\n\n<section class=\"g-brd-top g-brd-bottom g-brd-gray-light-v4 basics-main-topic-v1\">\n <div class=\"container text-center g-py-50--md g-py-20\">\n <h2 class=\"h2 text-uppercase g-font-weight-300 CMS_edit\">이곳을 편집하세요.</h2>\n <p class=\"lead g-px-100--md g-mb-20 CMS_edit CMS_multiline\">\n 이곳을 편집하세요.<br/>\n 이곳을 <strong class=\"g-color-primary\">편집</strong>하세요.\n </p>\n </div>\n</section>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  59. 2019-06-25 01:01 | Administrator | {"id":"19062510000012bof0","name":"V1","reference":"basics/main/carousel/piece/v1.html","body":"<style>\n #CMS .basics-main-carousel-piece-v1 { visibility: visible; height: auto; margin-bottom: .5rem; }\n #CMS .basics-main-carousel-piece-v1 .js-slide { visibility: visible; }\n #CMS .basics-main-carousel-piece-v1 .slide-image { max-height: 500px; }\n #CMS .CMS_selected > .basics-main-carousel-piece-v1 { border: .5rem solid #1A88E0; }\n</style>\n\n<script editor>\n option('slideImage', 'Slide Image', '', 'file');\n\n exports.configure = async function(options, $el, prevOptions){\n $('.slide-image', $el).css('background-image', `url(${options.slideImage})`);\n };\n</script>\n\n<div class=\"js-slide basics-main-carousel-piece-v1\" data-jc=\"basics-main-carousel-piece-v1\" data-cms-need-parent>\n <div class=\"g-flex-centered g-height-100vh g-min-height-500--md g-bg-cover g-bg-pos-center g-bg-img-hero g-bg-black-opacity-0_5--after slide-image\" style=\"background-image: url(//via.placeholder.com/1920x1280/eeeeee);\">\n <div class=\"container text-center g-z-index-1\">\n <h2 class=\"text-uppercase g-font-weight-700 g-font-size-22 g-font-size-36--md g-color-white g-mb-20 CMS_edit\">여기를\n <span class=\"g-color-primary\">편집 </span> 해주세요.\n </h2>\n <p class=\"g-hidden-xs-down g-max-width-645 g-color-white-opacity-0_9 mx-auto g-mb-35 CMS_edit\">\n 여기를 편집해주세요.\n </p>\n <a class=\"btn btn-lg u-btn-primary g-font-weight-700 g-font-size-12 text-uppercase g-rounded-50 g-px-40 g-py-15 CMS_edit\" href=\"#!\">더 알아보기</a>\n </div>\n </div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  60. 2019-06-25 01:01 | Administrator | {"id":"19062510000013bof1","name":"V2","reference":"basics/main/carousel/piece/v2.html","body":"<style>\n #CMS .basics-main-carousel-piece-v2 { visibility: visible; height: auto; margin-bottom: .5rem; }\n #CMS .basics-main-carousel-piece-v2 .js-slide { visibility: visible; }\n #CMS .basics-main-carousel-piece-v2 .slide-image { max-height: 500px; }\n #CMS .CMS_selected > .basics-main-carousel-piece-v2 { border: .5rem solid #1A88E0; }\n</style>\n\n<div class=\"js-slide basics-main-carousel-piece-v2\" data-jc=\"basics-main-carousel-piece-v2\">\n <div class=\"text-center g-px-100--lg\">\n <i class=\"d-block g-color-primary g-font-size-60 g-line-height-0_7 g-pos-rel g-top-20\">“</i>\n <blockquote class=\"g-color-white g-font-size-25 g-py-40 CMS_edit\">여기를 편집해주세요.</blockquote>\n <h4 class=\"h6 g-color-white-opacity-0_7 text-uppercase g-mb-0 CMS_edit\">\n 여기를 <em class=\"g-font-style-normal g-color-primary\">편집해주세요.</em>\n </h4>\n </div>\n</div>","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  61. 2019-06-25 01:01 | Administrator | {"id":"19062510000014bof0","name":"V1","reference":"basics/header/nav/megamenu/v1.html","body":"<style></style>\n\n<script>\n COMPONENT('basics-header-nav-megamenu-v1', function(component, config){\n // 컴포넌트 상수 설정\n const CONSTANTS = {\n TEMPLATE: {\n // 탑 레벨 - 일반 아이템\n $NAV_LIST_ITEM: $('<li class=\"nav-item g-mx-5--lg g-mx-10--xl\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n // $NAV_LIST_ITEM: $('<li class=\"nav-item g-mx-2--md g-mx-5--xl g-mb-5 g-mb-0--lg\"><a href=\"#!\" class=\"nav-link\"></a></li>'),\n\n // 탑 레벨 - 서브메뉴 있는 아이템\n $HAS_SUBMENU_LIST_ITEM: $('<li class=\"nav-item hs-has-sub-menu g-mx-5--lg g-mx-10--xl\"/>'),\n // 탑 레벨 - 서브메뉴 토글러\n $SUBMENU_TOGGLER: $('<a id=\"nav-link--home\" class=\"nav-link text-uppercase g-color-primary--hover g-px-5 g-py-20 text-nowrap\" href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 탑 레벨 - 서브메뉴\n $SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled u-shadow-v11 g-min-width-220 g-brd-top g-brd-primary g-brd-top-2 g-mt-17\">'),\n\n // 서브메뉴 - 일반 아이템\n $SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n\n // 서브메뉴 - 서브메뉴 있는 아이템\n $SUBMENU_HAS_SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item hs-has-sub-menu\"/>'),\n // 서브메뉴 - 서브메뉴 토글러\n $SUBMENU_SUBMENU_TOGGLER: $('<a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\" aria-haspopup=\"true\" aria-expanded=\"false\"></a>'),\n // 서브메뉴 - 서브메뉴 리스트\n $SUBMENU_SUBMENU_LIST: $('<ul class=\"hs-sub-menu list-unstyled u-shadow-v11 g-min-width-220 g-brd-top g-brd-primary g-brd-top-2 g-mt-minus-2\"/>'),\n // 서브메뉴 - 서브메뉴 일반 아이템\n $SUBMENU_SUBMENU_LIST_ITEM: $('<li class=\"dropdown-item\"><a class=\"nav-link g-color-gray-dark-v4\" href=\"#!\"></a></li>'),\n }\n };\n\n // 컴포넌트 객체 변수 지정\n component.$navbar = component.$el = component.element;\n\n component.$navbarToggler = $('.navbar-toggler', component.$navbar);\n\n component.$nav = $('nav.js-mega-menu', component.$navbar);\n component.$navbarCollapse = $('.navbar-collapse', component.$nav);\n component.$navbarNav = $('ul.navbar-nav', component.$navbarCollapse);\n\n component.$navContainer = $('.container', component.$nav);\n\n component.$loginAnchor = $('.navbar-login-link', component.$el);\n\n if ( !window._TOTALCMS_MAINMENU ) component.template = '/api/v3/nav/mainmenu';\n\n component.make = function(mainmenu){\n // 공용 네비게이션 객체 초기화\n if ( window._TOTALCMS_MAINMENU ) mainmenu = window._TOTALCMS_MAINMENU;\n else if ( mainmenu ) window._TOTALCMS_MAINMENU = mainmenu;\n\n // 토글 매핑 아이디 임의화\n // 네비게이션 접기 펴기 ID 랜덤화\n let navRandomId = $.TOT.helpers.getRandomId();\n\n component.$navbarToggler\n .attr({\n 'aria-controls': navRandomId,\n 'data-target': `#${navRandomId}`,\n 'data-toggle': 'collapse',\n })\n .data('target', `#${navRandomId}`);\n\n component.$navbarCollapse.prop('id', navRandomId);\n\n\n // 네비게이션 생성\n _.each(window._TOTALCMS_MAINMENU.children, function _recursive(menuItem, index, src, $parent, depth){\n depth = depth || 0;\n menuItem.hasChild = menuItem.children && menuItem.children.length > 0;\n\n if ( menuItem.url === '/' ) return;\n\n let $cursor;\n if ( depth === 0 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n component.$navbarNav.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$NAV_LIST_ITEM.clone();\n $('a.nav-link', $listItem).attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n component.$navbarNav.append($listItem);\n }\n }\n else if ( depth === 1 )\n {\n if ( menuItem.hasChild )\n {\n let $hasSubmenuListItem = CONSTANTS.TEMPLATE.$SUBMENU_HAS_SUBMENU_LIST_ITEM.clone();\n let $toggler = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_TOGGLER.clone();\n let $ul = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST.clone();\n\n // 서브메뉴 토글러 아이디 매핑\n let submenuId = $.TOT.helpers.getRandomId();\n let togglerId = $.TOT.helpers.getRandomId();\n\n $toggler.prop('id', togglerId).attr({ 'aria-controls': submenuId }).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $ul.prop('id', submenuId).attr({ 'aria-labelledby': togglerId });\n\n $hasSubmenuListItem.append($toggler, $ul);\n $parent.append($hasSubmenuListItem);\n\n $cursor = $ul;\n }\n else\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n }\n else if ( depth === 2 )\n {\n let $listItem = CONSTANTS.TEMPLATE.$SUBMENU_SUBMENU_LIST_ITEM.clone();\n $listItem.find('a').attr('href', menuItem.url).text($.TOT.helpers.unescapeHtml(menuItem.name));\n $parent.append($listItem);\n }\n\n if ( !menuItem.hasChild ) return;\n\n depth += 1;\n _.each(menuItem.children, function(menuItem, index, src){\n return _recursive(menuItem, index, src, $cursor, depth);\n });\n });\n\n // 검색바 ID 임의화\n let searchFormRandomId = $.HSCore.helpers.getRandomId();\n $('#searchform', component.$el).prop('id', searchFormRandomId);\n $('[data-dropdown-target=\"#searchform\"]', component.$el).attr({\n 'data-dropdown-target': `#${searchFormRandomId}`,\n 'aria-controls': searchFormRandomId,\n }).data('dropdown-target', `#${searchFormRandomId}`);\n\n let redirectTo = encodeURIComponent(location.href.replace(location.origin, ''));\n\n\n // 세션에 따른 링크 변경\n if ( _.get(TOTAL_LOCALS, '_SESSION.isLoggedIn') )\n component.$loginAnchor.text('로그아웃').attr('href', `/logout?from=${redirectTo}`);\n else\n component.$loginAnchor.attr('href', `/login?from=${redirectTo}`);\n\n component.$loginAnchor.removeClass('d-none');\n\n // 현재 URL 네비게이션 강조\n $(`a[href=\"${location.pathname}\"]`, component.$navbarNav).parents('li').addClass('active');\n\n $.HSCore.components.HSHeader.init(component.$navbar);\n $.HSCore.helpers.HSHamburgers.init($('.hamburger', component.$el));\n\n component.$nav.HSMegaMenu({\n event: 'hover',\n pageContainer: component.$navContainer,\n breakpoint: 991\n });\n\n $.HSCore.components.HSDropdown.init($('[data-dropdown-target]', component.$el), {\n afterOpen: function () {\n $(this).find('input[type=\"search\"]').focus();\n }\n });\n\n setTimeout(function(){\n component.$nav.data('HSMegaMenu').smartPositions();\n });\n }\n });\n</script>\n\n<!-- Header -->\n<header class=\"u-header u-header--static u-shadow-v19 basics-header-nav-megamenu-v1\" data-jc=\"basics-header-nav-megamenu-v1\">\n <div class=\"u-header__section u-header__section--hidden u-header__section--dark g-bg-black-opacity-0_9 g-color-white-opacity-0_8 g-py-13\">\n <div class=\"container\">\n <div class=\"row flex-column flex-md-row align-items-center justify-content-between text-uppercase g-color-white g-font-size-11 g-mx-minus-15\">\n <div class=\"col-auto ml-auto\">\n <ul class=\"list-inline g-line-height-1 g-mt-minus-10 g-mx-minus-4 mb-0\">\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <a href=\"/login\" class=\"navbar-login-link g-color-white g-color-primary--hover g-text-underline--none--hover d-none\">로그인</a>\n </li>\n\n <!--li class=\"list-inline-item g-mx-4 g-mt-10\">|</li>\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <a href=\"/joinus\" class=\"g-color-white g-color-primary--hover g-text-underline--none--hover\">회원가입</a>\n </li-->\n\n <li class=\"list-inline-item g-mx-4 g-mt-10\">\n <!-- Search -->\n <div class=\"g-pos-rel\">\n <a href=\"#!\" class=\"g-font-size-14 g-color-white g-color-primary--hover g-ml-5\" aria-haspopup=\"true\" aria-expanded=\"false\" aria-controls=\"searchform\" data-dropdown-target=\"#searchform\" data-dropdown-type=\"css-animation\" data-dropdown-duration=\"300\" data-dropdown-animation-in=\"fadeInUp\" data-dropdown-animation-out=\"fadeOutDown\">\n <i class=\"fas fa-search\"></i>\n </a>\n\n <!-- Search Form -->\n <form id=\"searchform\" class=\"u-searchform-v1 g-bg-black g-box-shadow-none g-pa-10 g-mt-10 u-dropdown--css-animation u-dropdown--hidden\" style=\"animation-duration: 300ms;\">\n <div class=\"input-group g-brd-primary--focus\">\n <input class=\"form-control rounded-0 u-form-control g-brd-white\" type=\"search\" placeholder=\"Enter Your Search Here...\">\n <div class=\"input-group-addon p-0\">\n <button class=\"btn rounded-0 btn-primary btn-md g-font-size-14 g-px-18\" type=\"submit\">Go</button>\n </div>\n </div>\n </form>\n <!-- End Search Form -->\n </div>\n <!-- End Search -->\n </li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"u-header__section u-header__section--light g-bg-white g-transition-0_3 g-py-10\">\n <nav class=\"js-mega-menu navbar navbar-expand-lg\">\n <div class=\"container\">\n <!-- Responsive Toggle Button -->\n <button class=\"navbar-toggler navbar-toggler-right btn g-line-height-1 g-brd-none g-pa-0 g-pos-abs g-top-3 g-right-0\" type=\"button\"\n aria-label=\"Toggle navigation\"\n aria-expanded=\"false\"\n aria-controls=\"navBar\"\n data-toggle=\"collapse\"\n data-target=\"#navBar\">\n <span class=\"hamburger hamburger--slider g-pr-0\">\n <span class=\"hamburger-box\">\n <span class=\"hamburger-inner\"></span>\n </span>\n </span>\n </button>\n <!-- End Responsive Toggle Button -->\n\n <!-- Logo -->\n <a class=\"navbar-brand\" href=\"/\"><img class=\"CMS_edit g-width-200\" src=\"//via.placeholder.com/200x45\" alt=\"Image Description\"></a>\n <!-- End Logo -->\n\n <!-- Navigation -->\n <div class=\"collapse navbar-collapse align-items-center flex-sm-row g-pt-15 g-pt-0--lg\">\n <ul class=\"navbar-nav ml-auto\"></ul>\n </div>\n <!-- End Navigation -->\n </div>\n </nav>\n </div>\n</header>\n<!-- End Header -->","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}
  62. 2019-06-25 01:01 | Administrator | {"id":"19062510000015bof1","name":"V1","reference":"basics/main/bbs/v1.html","body":"<style>\n .basics-main-bbs-v1 .component-blocker { background: #eeeeee80; }\n .basics-main-bbs-v1 .component-blocker .progress { top: 50%; left: 50%; transform: translate(-50%, -50%); }\n .basics-main-bbs-v1 .component-blocker .blocker-message { top: 60%; left: 50%; transform: translate(-50%, -50%); }\n\n .basics-main-bbs-v1 .card-body.tab-content .tab-pane .post-editor { height: 500px; }\n .basics-main-bbs-v1 .tab-post-read {\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n max-width: 15rem;\n }\n\n .basics-main-bbs-v1 ul.recent-post-list { padding-left: 0; }\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item { cursor: pointer; min-height: 40px; padding: .25rem; }\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item:nth-child(even) { background-color:#f7f7f7; }\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item:hover { background-color:rgba(0,0,0,.075); }\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item a.post-title { max-width: 70%; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item a.post-title:hover { text-decoration: none; }\n\n @media (min-width: 768px) {\n .basics-main-bbs-v1 ul.recent-post-list li.recent-post-list-item a.post-title { max-width: 80%; }\n }\n\n /* error message style */\n .basics-main-bbs-v1 .basics-main-bbs-v1-title.with-error {\n border-top-left-radius: .25rem;\n border-top-right-radius: .25rem;\n }\n\n .basics-main-bbs-v1 .basics-main-bbs-v1-title.with-error + .card-body {\n border-bottom-left-radius: .25rem;\n border-bottom-right-radius: .25rem;\n }\n\n /* ╔╦╗╔═╗╔╦╗╔═╗╔╦╗╔═╗╔╗ ╦ ╔═╗╔═╗ ╔═╗╔╦╗╦ ╦╦ ╦╔╗╔╔═╗ */\n /* ║║╠═╣ ║ ╠═╣ ║ ╠═╣╠╩╗║ ║╣ ╚═╗ ╚═╗ ║ ╚╦╝║ ║║║║║ ╦ */\n /* ═╩╝╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩╚═╝╩═╝╚═╝╚═╝ ╚═╝ ╩ ╩ ╩═╝╩╝╚╝╚═╝ */\n .basics-main-bbs-v1 .post-list-tab-content .dataTables_wrapper .dataTables_filter {\n width:100%;\n }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable thead tr th { white-space: nowrap; }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr { cursor: pointer; }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr.post-notice {\n border-bottom: 1px solid --var(primary);\n }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr:hover {\n background-color:rgba(0,0,0,.075);\n }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr td.post-title {\n white-space: normal;\n max-width: 100%;\n outline: 0;\n }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr td.post-title div.post-title-wrapper a.post-title,\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr td.post-writer a.post-writer {\n color: unset;\n }\n\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr td.post-title div.post-title-wrapper a.post-title:hover,\n .basics-main-bbs-v1 .post-list-tab-content table.dataTable tbody tr td.post-writer a.post-writer:hover {\n text-decoration: none;\n }\n\n .basics-main-bbs-v1 .post-list-tab-content .dataTables_wrapper .dataTables_filter {text-align: center!important;}\n .basics-main-bbs-v1 .post-list-tab-content .dataTables_paginate ul.pagination {justify-content: center!important;}\n\n /* ╦═╗╔═╗╔═╗╔═╗╔═╗╔╗╔╔═╗╦╦ ╦╔═╗ ╔═╗╔═╗╦═╗╔╦╗ ╔╦╗╔═╗╔╗ ╦ ╔═╗ */\n /* ╠╦╝║╣ ╚═╗╠═╝║ ║║║║╚═╗║╚╗╔╝║╣ ║ ╠═╣╠╦╝ ║║ ║ ╠═╣╠╩╗║ ║╣ */\n /* ╩╚═╚═╝╚═╝╩ ╚═╝╝╚╝╚═╝╩ ╚╝ ╚═╝ ╚═╝╩ ╩╩╚══╩╝ ╩ ╩ ╩╚═╝╩═╝╚═╝ */\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable thead { display: none; }\n\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr,\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody td { display: block; }\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody td { border: 0; }\n .basics-main-bbs-v1.cards-table .post-list-tab-content .dataTables_paginate ul.pagination li.paginate_button.page-item a.page-link {padding: .25rem .5rem;}\n\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr:first-child { margin-top: 1.5rem; }\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr {\n margin-bottom: 2.14286rem !important;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);\n border: solid 1px #0000 !important;\n border-color: #ccc !important;\n padding: 2.85714rem !important;\n /* border-right-width: 0!important; */\n /* border-left-width: 0!important; */\n background-color: #0000;\n }\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr:hover {\n background-color: #00000013;\n }\n\n/* .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-title a { text-decoration: none; } */\n/* .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-title { text-align: center; font-size: 1.5rem */; }\n/* .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-posted-at { text-align: right; } */\n\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-seq,\n .basics-main-bbs-v1.cards-table .post-list-tab-content table.dataTable tbody tr td.post-view-count { display: none; }\n\n /* ╔═╗ ╦ ╦╦╦ ╦ ╦╔═╗ ╔═╗╔╦╗╦ ╦╦ ╔═╗ */\n /* ║═╬╗║ ║║║ ║ ║╚═╗ ╚═╗ ║ ╚╦╝║ ║╣ */\n /* ╚═╝╚╚═╝╩╩═╝╩═╝╚╝╚═╝ ╚═╝ ╩ ╩ ╩═╝╚═╝ */\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-title { margin-bottom: .5rem; }\n .basics-main-bbs-v1 .post-editor-tab-content .ql-toolbar {\n border-bottom: 0 !important;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-title,\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-toolbar,\n .basics-main-bbs-v1 .ql-container {\n border-left: 0 !important;\n border-right: 0 !important;\n border-radius: 0;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item {\n cursor: pointer;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item span.filename {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:nth-child(even) {\n background-color:#f7f7f7;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:not(:last-child) {\n border-bottom: 1px solid #ccc;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments .attachment-list .attachment-list-item:hover {\n background-color: #3398dc2d;\n }\n\n .basics-main-bbs-v1 .post-read-content .post-viewer {\n border: 0;\n }\n\n /* 본문 첨부파일 객체 스타일 */\n .basics-main-bbs-v1 .ql-container .ql-editor div.attachment {\n display: flex;\n }\n\n .basics-main-bbs-v1 .ql-container .ql-editor div.attachment div.attachment-inner {\n min-width: 200px;\n max-width: 500px;\n padding: 1.5rem;\n margin: auto;\n cursor: pointer;\n text-align: center;\n border: 1px solid lightgray;\n display: flex;\n }\n\n .basics-main-bbs-v1 .ql-container .ql-editor div.attachment div.attachment-inner:hover {\n background-color: #3398dc2d;\n }\n\n .basics-main-bbs-v1 .ql-container .ql-editor div.attachment div.attachment-inner i.attachment-icon {\n margin: auto 0 auto auto;\n }\n .basics-main-bbs-v1 .ql-container .ql-editor div.attachment div.attachment-inner span.attachment-filename {\n margin: auto auto auto .75rem;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 100%;\n }\n\n /* 뷰어 하단 첨부파일 리스트 스타일 */\n .basics-main-bbs-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item:nth-child(odd),\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item:nth-child(odd) {\n background-color: #f7f7f7;\n }\n\n .basics-main-bbs-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item:hover,\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item:hover {\n background-color: #00000013;\n }\n\n .basics-main-bbs-v1 .post-read-content .post-viewer-attachments ul.post-viewer-attachment-list li.post-viewer-attachment-list-item a.post-viewer-attachment-download:hover,\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item a {\n text-decoration: none;\n }\n\n .basics-main-bbs-v1 .post-editor-tab-content .post-editor-attachments ul.post-editor-attachments-list li.post-editor-attachment-list-item i.remove-attachment {\n cursor: pointer;\n }\n</style>\n\n<script total>\n const fs = require('fs');\n const CONSTANTS = {\n PERMISSIONS: {\n POST: {\n READABLE: 'post.read',\n WRITABLE: 'post.write',\n NOTICE_WRITABLE: 'post.notice.write',\n FILE: {\n UPLOADABLE: 'post.file.upload',\n DOWNLOADABLE: 'post.file.download',\n },\n COMMENT: {\n READABLE: 'post.comment.read',\n WRITABLE: 'post.comment.write',\n }\n },\n },\n };\n const Helpers = {};\n\n const readChunk = require('read-chunk');\n const fileType = require('file-type');\n\n const Post = DB('post');\n const PostGroup = DB('postgroup');\n\n const Attachment = DB('attachment', {});\n const AttachmentFile = DB('attachment.files');\n\n const ObjectID = require('mongodb').ObjectID;\n\n // ╔═╗╔═╗╔═╗╔╦╗ ╦ ╦╔╦╗╦╦ ╦╔╦╗╦╔═╗╔═╗\n // ╠═╝║ ║╚═╗ ║ ║ ║ ║ ║║ ║ ║ ║║╣ ╚═╗\n // ╩ ╚═╝╚═╝ ╩ ╚═╝ ╩ ╩╩═╝╩ ╩ ╩╚═╝╚═╝o\n // ────────────────────────────────────\n Helpers.cache = [];\n\n /**\n * 세션에 필요한 권한이 존재하는지 확인\n * @param {Object} session this.req.me 객체 (User 스키마)\n * @param {ObjectID} postGroupId postGroupId\n * @param {String} need 필요한 권한 ( CONSTANTS.PERMISSIONS )\n * @return {Boolean}\n */\n Helpers.hasPermission = async function(session, postGroupId, need){\n if ( session.isSysadmin ) return true;\n let postGroup = Helpers.cache[postGroupId.toString()];\n\n if ( !postGroup )\n postGroup = Helpers.cache[postGroupId.toString()] = await PostGroup.findOne({ _id: postGroupId });\n\n let sessionHas = _.map(session.parents, function(parent){ return parent._id; });\n\n let exchanged = _.chain(sessionHas).map(function(_id){\n return postGroup.permission[_id.toString()];\n }).flatten().uniq().value();\n\n if ( exchanged.indexOf(need) < 0 )\n return false;\n return true;\n };\n\n // ╦═╗╔═╗╔═╗╔╦╗╔═╗╦ ╦╦ ╔═╗╔═╗╦ ╦═╗╔═╗╦ ╦╔╦╗╔═╗\n // ╠╦╝║╣ ╚═╗ ║ ╠╣ ║ ║║ ╠═╣╠═╝║ ╠╦╝║ ║║ ║ ║ ║╣\n // ╩╚═╚═╝╚═╝ ╩ ╚ ╚═╝╩═╝ ╩ ╩╩ ╩ ╩╚═╚═╝╚═╝ ╩ ╚═╝o\n // ────────────────────────────────────────────────\n\n // 게시판 API\n ROUTE('POST /api/post-groups', ['*PostGroup --> @create-post-group' ]);\n ROUTE('DELETE /api/post-groups/{postgroupid}', ['*PostGroup --> @remove' ]);\n ROUTE('PUT /api/post-groups/{postgroupid}', ['*PostGroup --> @update' ]);\n ROUTE('GET /api/post-groups', ['*PostGroup --> @query' ]);\n ROUTE('GET /api/post-groups/{postgroupid}/include-notices', ['*PostGroup --> @get-with-notices' ]);\n ROUTE('GET /api/post-groups/{postgroupid}', ['*PostGroup --> @get' ]);\n\n // 파일 다운로드\n ROUTE('GET /api/posts/attachment/{fileid}', async function(fileid){\n let controller = this;\n\n fileid = ObjectID(fileid);\n let attachment = await AttachmentFile.findOne({ _id: fileid });\n\n if ( !(await Helpers.hasPermission(controller.req.me, attachment.metadata.postgroupid, CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) ) )\n return controller.throw403(TRANSLATE(controller.req.me.language, 'You Do Not Have Permission.'));\n\n controller.header('Content-Type', attachment.metadata.mime);\n controller.header('Content-Disposition', `attachment; filename=\"${attachment.filename}\"`);\n\n Attachment.openDownloadStream(fileid).pipe(controller.res);\n });\n\n // 파일 업로드\n ROUTE('POST /api/posts/{postgroupid}/{postid}/attachment', async function(postgroupid, postid){\n let controller = this;\n let uploadResult = [];\n let progressedCount = 0;\n\n let postGroupId = ObjectID(postgroupid);\n\n if ( !(await Helpers.hasPermission(controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE) ) )\n return controller.throw403(TRANSLATE(controller.req.me.language, 'You Do Not Have Permission.'));\n\n let postId = ObjectID(postid);\n\n try\n {\n controller.files.wait(function(file, next){\n // 바이너리로부터 파일 유형 확인\n let ft = fileType(readChunk.sync(file.path, 0, fileType.minimumBytes));\n\n // 알 수 없는 파일\n if ( !ft )\n ft = _.assign({}, { ext: '', mime: 'application/octet-stream' });\n\n // svg에 대해서는 예외처리\n if ( ft.ext === 'xml' && /\\.svg$/.test(file.filename) )\n _.assign(ft, { ext: 'svg', mime: 'image/svg+xml'});\n\n fs.createReadStream(file.path)\n .pipe(Attachment.openUploadStream(file.filename, {\n metadata: {\n postgroupid: postGroupId,\n postid: postId,\n mime: ft.mime,\n extension: ft.ext,\n size: file.length,\n }\n }))\n .on('finish', function(meta) {\n let result = _.assign({}, meta, {\n downloadURI: `/api/posts/attachment/${meta._id}`,\n fileId: _.isArray(controller.body['file-id']) ? controller.body['file-id'][progressedCount++] : controller.body['file-id'],\n });\n\n if ( file.width ) result.width = file.width;\n if ( file.height ) result.height = file.height;\n\n // HttpFile 객체에 속성 추가\n uploadResult.push(result);\n\n if ( controller.files.length === uploadResult.length ) controller.json(uploadResult);\n });\n\n setImmediate(next);\n });\n }\n catch(err)\n {\n U.log.error(err);\n controller.throw500(err);\n }\n }, ['upload'], 51200); // 50MB\n\n ROUTE('GET /api/posts/{postgroupid}', ['*Post --> @get-posts' ]);\n ROUTE('GET /api/posts/{postgroupid}/recent/{length}', ['*Post --> @get-recent-posts' ]);\n ROUTE('GET /api/posts/{postgroupid}/notices', ['*Post --> @get-notices' ]);\n ROUTE('GET /api/posts/{postgroupid}/{postid}', ['*Post --> @get-post' ]);\n ROUTE('PATCH /api/posts/{postgroupid}/{postid}', ['*Post --> @patch-post' ]);\n ROUTE('POST /api/posts/{postgroupid}', ['*Post --> @create-post' ]);\n ROUTE('DELETE /api/posts/{postgroupid}/{postid}', ['*Post --> @remove-post' ]);\n\n\n // 게시글 - 댓글 API\n ROUTE('POST /api/posts/{postgroupid}/{postid}/comments', ['*Post --> @create-comment' ]);\n ROUTE('PATCH /api/posts/{postgroupid}/{postid}/comments/{commentid}', ['*Post --> @patch-comment' ]);\n ROUTE('DELETE /api/posts/{postgroupid}/{postid}/comments/{commentid}', ['*Post --> @remove-comment' ]);\n\n\n // ╔╦╗╔═╗╔═╗╦╔╗╔╔═╗ ╔═╗╔═╗╦ ╦╔═╗╔╦╗╔═╗\n // ║║║╣ ╠╣ ║║║║║╣ ╚═╗║ ╠═╣║╣ ║║║╠═╣\n // ═╩╝╚═╝╚ ╩╝╚╝╚═╝ ╚═╝╚═╝╩ ╩╚═╝╩ ╩╩ ╩o\n // ─────────────────────────────────────\n let postSchema = GETSCHEMA('Post');\n let postGroupSchema = GETSCHEMA('PostGroup');\n let postCommentSchema = GETSCHEMA('PostComment');\n\n if ( !postSchema ) NEWSCHEMA('Post', makePostSchema);\n else makePostSchema(postSchema);\n\n if ( !postGroupSchema ) NEWSCHEMA('PostGroup', makePostGroupSchema);\n else makePostGroupSchema(postGroupSchema);\n\n if ( !postCommentSchema ) NEWSCHEMA('PostComment', makePostCommentSchema);\n else makePostCommentSchema(postCommentSchema);\n\n function makePostCommentSchema(schema){\n schema.define('_id', 'String(24)');\n\n schema.define('content', 'String(1000)');\n\n schema.define('writer', 'String(24)');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n };\n\n function makePostGroupSchema(schema){\n schema.define('_id', 'String(24)');\n schema.define('name', 'String(100)');\n schema.define('features', 'Array');\n schema.define('description', 'String(300)');\n schema.define('seqCursor', 'Number');\n schema.define('permission', 'Object');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n\n schema.setQuery(async function($){\n let postGroups = PostGroup.find({}).sort({ name: 1 }).collation({ locale: $.controller.req.me.language, numericOrdering: true });\n $.callback( await postGroups.toArray() );\n });\n\n schema.setGet(async function($){\n $.callback( await PostGroup.findOne({ _id: ObjectID($.params.postgroupid) }) );\n });\n\n schema.setRemove(async function($){\n if ( await Post.countDocuments({ postgroupid: ObjectID($.params.postgroupid) }) > 0 )\n return $.controller.throw404(TRANSLATE($.controller.req.me.language, 'There Are Still Exists Post.'));\n\n await PostGroup.removeOne({ _id: ObjectID($.params.postgroupid) });\n\n delete Helpers.cache[postGroupId.toString()];\n\n $.controller.status = 204;\n $.controller.plain();\n });\n\n schema.setUpdate(async function($){\n let model = $.model.$clean();\n let postGroupId = ObjectID($.params.postgroupid);\n\n await PostGroup.updateOne({ _id: postGroupId }, {\n $set: _.omit(model, '_id')\n });\n\n Helpers.cache[postGroupId.toString()] = await PostGroup.findOne({ _id: postGroupId });\n\n $.callback( Helpers.cache[postGroupId.toString()] );\n });\n\n schema.addWorkflow('create-post-group', async function($){\n let model = $.model.$clean();\n\n let created = (await PostGroup.insertOne(_.omit(model, '_id'))).ops[0]\n\n Helpers.cache[created._id.toString()] = created;\n\n $.callback( created );\n });\n\n // make와 관여하는 함수로 error status로 반환되면 make 함수 자체가 동작하지 않으므로 err 속성에 에러여부를 포함하여 200 상태로 반환\n schema.addWorkflow('get-with-notices', async function($){\n if ( !$.params.postgroupid || $.params.postgroupid === 'dummy' )\n return $.callback({ err: true, message: TRANSLATE($.controller.req.me.language, 'There is no Post Group.') });\n\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.callback({ err: true, message: TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.') })\n\n let postGroup = await PostGroup.findOne({ _id: postGroupId });\n\n let notices = await Post.aggregate([\n { $match: { postgroupid: postGroup._id, isnotice: true } },\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'content': 0, 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray();\n\n $.callback( _.assign(postGroup, { notices: notices }) );\n });\n };\n\n function escapeHtml (string) {\n let str = '' + string;\n let match = /[\"'&<>]/.exec(str);\n\n if (!match) return str;\n\n let escaped;\n let html = '';\n let index = 0;\n let lastIndex = 0;\n\n for ( index = match.index; index < str.length; index++ )\n {\n switch ( str.charCodeAt(index) )\n {\n case 34: // \"\n escape = '&quot;'; break;\n case 38: // &\n escape = '&amp;'; break;\n case 39: // '\n escape = '&#39;'; break;\n case 60: // <\n escape = '&lt;'; break;\n case 62: // >\n escape = '&gt;'; break;\n default: continue;\n }\n\n if (lastIndex !== index) html += str.substring(lastIndex, index);\n\n lastIndex = index + 1;\n html += escaped;\n }\n\n return lastIndex !== index ? html + str.substring(lastIndex, index) : html;\n }\n\n function makePostSchema(schema){\n schema.define('postgroupid', 'String(24)');\n schema.define('_id', 'String(24)');\n schema.define('id', 'UID');\n schema.define('seq', 'Number');\n schema.define('idcategory', 'String(50)');\n schema.define('template', 'String(30)');\n schema.define('type', ['html', 'markdown']);\n schema.define('name', 'String(100)');\n schema.define('author', 'String(24)');\n schema.define('description', 'String(300)');\n schema.define('summary', 'String(500)');\n schema.define('keywords', 'String(200)');\n schema.define('language', 'String'); // Only information\n schema.define('search', 'String(1000)');\n schema.define('pictures', '[String]'); // URL addresses for first 5 pictures\n schema.define('content', 'Object'); // quill editor delta object\n schema.define('bodywidgets', '[String(22)]'); // List of all used widgets\n schema.define('ispublished', 'Boolean');\n schema.define('isnotice', 'Boolean');\n schema.define('date', 'Date');\n schema.define('widgets', '[Object]'); // List of dynamic widgets, contains Array of ID widget\n schema.define('signals', '[String(30)]');\n schema.define('viewcount', 'Number');\n schema.define('attachments', '[String(24)]');\n schema.define('comments', '[Object]');\n\n schema.define('createdAt', 'Date');\n schema.define('updatedAt', 'Date');\n\n schema.define('comment', 'String(1000)');\n\n schema.addWorkflow('create-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let post = $.model.$clean();\n let postGroup = await PostGroup.findOneAndUpdate({ _id: postGroupId }, { $inc: { seqCursor: 1 } });\n\n post.postgroupid = postGroup.value._id;\n post.seq = postGroup.value.seqCursor + 1;\n post.viewcount = 0;\n\n post.name = escapeHtml(post.name);\n post.attachments = _.map(post.attachments, function(attachment){ return ObjectID(attachment); });\n\n let created = (\n await Post.insertOne(\n _.assign(\n _.omit(post, '_id'),\n { author: $.controller.req.me._id }\n )\n )\n ).ops[0];\n\n await AttachmentFile.updateMany({ _id: { $in: post.attachments } }, {\n $set: {\n 'metadata.postgroupid': created.postgroupid,\n 'metadata.postid': created._id,\n }\n });\n\n $.callback(\n _.assign({}, created, {\n attachments: await AttachmentFile.find({ _id: { $in: post.attachments }}).toArray(),\n author: $.controller.req.me\n })\n );\n });\n\n schema.addWorkflow('get-recent-posts', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n posts = await Post.find({ postgroupid: postGroupId }).sort({ createdAt: -1 }).limit(Number($.params.length || 5)).toArray();\n $.callback(posts);\n });\n\n schema.addWorkflow('get-notices', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n $.callback(\n await Post.aggregate([\n { $match: { 'postgroupid': postGroupId, 'isnotice': true } },\n { $sort: { createdAt: -1 } },\n\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray()\n );\n });\n\n schema.addWorkflow('get-posts', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n $.controller.req.query = U.controller.req.parseQueryString($.controller.req.uri.query, { extended: true });\n\n // 정렬 기본값\n if ( !$.controller.req.query.order ) $.controller.req.query.order = [{ column: 0, dir: 'desc' }];\n $.controller.req.query.order = $.controller.req.query.order.map(function(item){ return _.assign(item, { column: Number(item.column) }); });\n\n let sortor = {};\n\n _.each($.query.order, function(order){\n sortor[$.query.columns[order.column].data] = order.dir.toLowerCase() === 'desc' ? -1 : 1;\n });\n\n let query = { 'postgroupid': postGroupId, 'isnotice': false };\n\n if ( $.query.search.value )\n {\n let searchKeyword = new RegExp($.query.search.value);\n query.$or = [ { name: searchKeyword } ];\n }\n\n // 페이징 기본값\n let pagination = { start: 0, length: 15 };\n\n if ( $.query.start ) pagination.start = Number($.query.start);\n if ( $.query.length ) pagination.length = Number($.query.length);\n\n if ( pagination.length > 200 )\n return $.controller.throw400(TRANSLATE($.controller.req.me.language, '\"length\" parameter must be least of 200'));\n\n let totalCount = await Post.countDocuments(query);\n\n let posts = await Post.aggregate([\n { $match: query },\n { $sort: sortor },\n\n { $skip: Number($.query.start) },\n { $limit: Number($.query.length) },\n\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.permission': 0, 'author.isSysadmin': 0, 'author.secret': 0, 'author.salt': 0 } },\n ]).toArray();\n\n $.callback({\n draw: Number($.query.draw),\n recordsTotal: totalCount,\n recordsFiltered: totalCount,\n data: posts,\n });\n });\n\n schema.addWorkflow('get-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n // 권한 체크\n if ( !await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.READABLE) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n try\n {\n let post = await Post.aggregate([\n {\n $match: {\n $and: [ { postgroupid: postGroupId }, { _id: ObjectID($.params.postid) } ]\n }\n },\n { $lookup: { from: 'attachment.files', localField: 'attachments', foreignField: '_id', as: 'attachments' } },\n { $lookup: { from: 'user', localField: 'author', foreignField: '_id', as: 'author' } },\n { $unwind: { path: '$author', preserveNullAndEmptyArrays: true } },\n { $project: { 'author.secret': 0, 'author.salt': 0 } },\n\n { $unwind: { path: '$comments', preserveNullAndEmptyArrays: true } },\n { $lookup: { from: 'user', localField: 'comments.author', foreignField: '_id', as: 'comments.author' } },\n { $unwind: { path: '$comments.author', preserveNullAndEmptyArrays: true } },\n { $project: { 'comments.author.secret': 0, 'comments.author.salt': 0 } },\n\n { $group: { _id: '$_id', doc: { $first: '$$ROOT' }, comments: { $push: '$comments'} } },\n ]);\n\n if ( !await post.hasNext() ) return $.controller.throw404();\n\n post = await post.next();\n\n // 조회수 올림\n await Post.updateOne({\n _id: post._id,\n postgroupid: post.postgroupid,\n },\n {\n $inc: { viewcount: 1 }\n });\n\n $.callback( post );\n }\n catch(err)\n {\n U.log.error(err);\n controller.throw500(err);\n }\n });\n\n schema.addWorkflow('patch-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let post = $.model.$clean();\n\n post.name = escapeHtml(post.name);\n post.attachments = _.map(post.attachments, function(attachment){ return ObjectID(attachment); });\n\n let updated = await Post.updateOne({ postgroupid: postGroupId, _id: ObjectID($.params.postid), author: $.controller.req.me._id }, {\n $set: {\n name: post.name,\n content: post.content,\n isnotice: post.isnotice,\n attachments: post.attachments,\n }\n });\n\n $.controller.status = 200;\n $.controller.plain();\n });\n\n schema.addWorkflow('remove-post', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n await Post.removeOne({\n postgroupid: postGroupId,\n _id: ObjectID($.params.postid),\n author: $.controller.req.me._id\n });\n\n $.controller.status = 204;\n $.controller.plain();\n });\n\n schema.addWorkflow('create-comment', async function($){\n let postGroupId = ObjectID($.params.postgroupid);\n\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let model = $.model.$clean();\n\n let now = new Date();\n let comment = {\n _id: ObjectID(),\n comment: model.comment,\n author: $.controller.req.me._id,\n createdAt: now,\n updatedAt: now,\n };\n\n await Post.updateOne({\n postgroupid: postGroupId,\n _id: ObjectID($.params.postid),\n },\n {\n $push: { comments: comment }\n });\n\n $.callback(comment);\n });\n\n schema.addWorkflow('patch-comment', async function($){\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let model = $.model.$clean();\n\n let postGroupId = ObjectID($.params.postgroupid);\n let postId = ObjectID($.params.postid);\n let commentId = ObjectID($.params.commentid);\n\n await Post.updateOne({ postgroupid: postGroupId, _id: postId },\n { $set: { 'comments.$[element].comment': model.conmment } },\n { arrayFilters: [ { 'element._id': commentId, author: $.controller.req.me._id } ] }\n );\n\n $.callback(\n await Post.findOne({ postgroupid: postGroupId, _id: postId })\n );\n });\n\n schema.addWorkflow('remove-comment', async function($){\n if ( !(await Helpers.hasPermission($.controller.req.me, postGroupId, CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) ) )\n return $.controller.throw403(TRANSLATE($.controller.req.me.language, 'You Do Not Have Permission.'));\n\n let postGroupId = ObjectID($.params.postgroupid);\n let postId = ObjectID($.params.postid);\n let commentId = ObjectID($.params.commentid);\n\n await Post.updateOne({ postgroupid: postGroupId, _id: postId },\n { $pull: { comments: { _id: commentId, author: $.controller.req.me._id } } },\n );\n\n $.controller.status = 200;\n $.controller.plain();\n });\n };\n</script>\n\n<script editor>\n // let postGroupId = ObjectID().toString();\n // option('post-group-name', '게시판 명', '자유게시판');\n // option('post-group-id', '게시판 ID', postGroupId);\n // option('post-group-id', '게시판 ID', '아이디는 자동생성됩니다.');\n // option('post-group-description', '게시판 설명', '');\n // option('theme', '테마', 'blue', [\n // { text: '파랑', value: 'blue' },\n // { text: '초록', value: 'green' },\n // { text: '분홍', value: 'pink' },\n // { text: '어두운 보라', value: 'darkpurple' },\n // { text: '물', value: 'teal' },\n // ]);\n\n option('showTitle', '게시판 명 숨기기', false);\n option('mode', '모드', 'post', [\n { text: '게시판', value: 'post' },\n { text: '최신글', value: 'recent' },\n ]);\n\n option.type = 'post';\n\n exports.configure = async function(options, $el, prevOptions){\n let $component = $('.basics-main-bbs-v1', $el);\n\n let themes = ['blue','green','pink','darkpurple','teal'];\n\n let postGroup = _.find(options._datasrc.postGroups, { _id: options.selectedPostGroup });\n\n // 설정값 data 속성 저장\n $component.attr({\n 'data-post-group-id': postGroup._id,\n 'data-post-mode': options['mode'],\n 'data-instant-id': $component.attr('data-instant-id') || helpers.getRandomId('short'),\n });\n\n // 테마 적용\n // $component.removeClass(themes.map(function(item){ return `g-brd-${item}`; }).join(' ')).addClass(`g-brd-${options['theme']}`);\n // $('.basics-main-bbs-v1-title', $component).removeClass(themes.map(function(item){ return `g-bg-${item}`; }).join(' ')).addClass(`g-bg-${options['theme']}`);\n\n // 컴포넌트 타이틀\n $('.post-group-name', $component).text(postGroup.name.replace(/\\s\\(\\d+\\)$/, ''));\n\n // 컴포넌트 타이틀 숨기기\n $('.basics-main-bbs-v1-title', $component).toggleClass('d-editor-block', option.showTitle);\n };\n</script>\n\n<script>\n COMPONENT('basics-main-bbs-v1', 'striped:true;bordered:false;totalCount:false;length:false;searchBar:false;hover:true;placeholder:내용을 입력해주세요...;titlePlaceHolder:제목을 입력해주세요...', function(component, config){\n const CONSTANTS = {\n $RECENT_POST_LIST: $(`<ul class=\"recent-post-list\"></ul>`),\n MODES: {\n RECENT: 'recent',\n POST: 'post',\n },\n POST_GROUP: {\n FEATURES: {\n NOTICE: 'notice',\n ATTACHMENT: 'attachment',\n COMMENT: 'comment',\n }\n },\n POST_GROUP_TYPES: {\n FILE_UPLOAD: 'file',\n NORMAL: 'normal',\n },\n TEMPLATES: {\n DATATABLE: {\n $PAGINATE: $(`<nav class=\"text-center\" aria-label=\"Page Navigation\"><ul class=\"list-inline\"></ul></nav>`)\n },\n VIEWER: {\n ATTACHMENT: {\n ITEM: $('<li class=\"d-flex\"></li>'),\n }\n },\n $BROWSE_FILE_INPUT: $('<input type=\"file\" multiple/>'),\n $TOOLBAR_BTN_FILE_UPLOAD: $('<button class=\"browse-file fas fa-upload\"></button>'),\n },\n PERMISSIONS: {\n POST: {\n READABLE: 'post.read',\n WRITABLE: 'post.write',\n NOTICE_WRITABLE: 'post.notice.write',\n FILE: {\n UPLOADABLE: 'post.file.upload',\n DOWNLOADABLE: 'post.file.download',\n },\n COMMENT: {\n READABLE: 'post.comment.read',\n WRITABLE: 'post.comment.write',\n }\n },\n },\n };\n\n component.postGroupSettings = {};\n component.allowedPermission = [];\n\n component.prevScrollTop = 0;\n component.sendFileList = [];\n\n component.$el = component.element;\n component.$blocker = $('.component-blocker', component.$el);\n component.$title = $('.basics-main-bbs-v1-title span', component.$el);\n component.$cardTitle = $('.basics-main-bbs-v1-title', component.$el);\n component.$cardBody = $('.card-body.tab-content', component.$el);\n component.$initSpinner = $('.init-spinner', component.$el);\n\n component.$progress = $('.progress', component.$blocker);\n component.$progressBar = $('.progress-bar', component.$progress);\n component.$blockerMessage = $('.blocker-message', component.$blocker);\n component.$blockerMessageTxt = $('.message', component.$blockerMessage);\n\n component.notices = [];\n\n component.instantID = component.$el.data('instant-id');\n if ( !component.instantID )\n {\n component.instantID = $.HSCore.helpers.getRandomId('short');\n component.$el.attr('data-instant-id', component.instantID).data('instant-id', component.instantID);\n }\n\n component.postGroupId = component.$el.data('post-group-id') || 'dummy';\n component.mode = component.$el.data('post-mode');\n\n component.$tabNavs = $('.nav.nav-tabs [data-toggle=\"tab\"]', component.$el);\n component.$tabContents = $('[role=\"tabpanel\"]', component.$el);\n\n component.$postListTab = component.$tabNavs.filter('.tab-post-list');\n component.$postViewerTab = component.$tabNavs.filter('.tab-post-read');\n component.$postEditorTab = component.$tabNavs.filter('.tab-post-editor');\n\n component.$postListTabContent = component.$tabContents.filter('.post-list-tab-content');\n component.$postViewerTabContent = component.$tabContents.filter('.post-read-content');\n component.$postEditorTabContent = component.$tabContents.filter('.post-editor-tab-content');\n\n component.$table = $('table.post-list', component.$postListTabContent);\n component.datatable = null; // datatables.api\n component.$originalPaginate = null;\n component.columnsDefines = [\n {\n data: 'seq', title: '번호', className: 'post-seq d-none d-md-block',\n render: function(data, type, row){\n return `<p class=\"m-0 px-3\">${data}</p>`;\n }\n },\n {\n data: 'name', title: '제목', className: 'post-title g-font-size-24 g-font-size-default--md',\n render: function(data, type, row){\n return `<div class=\"text-center text-md-left post-title-wrapper\">\n <a class=\"post-title\" href=\"#\">${ $.fn.dataTable.render.ellipsis(40)($.fn.dataTable.render.text().display(data), type, row) }</a>\n</div>`;\n }\n },\n // { data: 'seq', title: '글번호', width: 60, },\n {\n data: 'author.name', title: '작성자', className: 'post-writer g-font-size-14 g-font-size-default--md',\n render: function(data, type, row){\n return `\n<div class=\"d-flex\">\n <span class=\"ml-auto d-md-none\">작성자:</span>\n <a class=\"post-writer ml-2 mx-md-auto\" href=\"#\">${$.fn.dataTable.render.text().display(data)}</a>\n</div>`;\n }\n },\n {\n data: 'createdAt', title: '작성일', className: 'text-right text-md-center post-posted-at g-font-size-14 g-font-size-default--md',\n render: function(data, type, row){\n let createdAt = moment(data);\n let txt = createdAt.format('YYYY-MM-DD');\n\n if ( moment().diff(createdAt, 'days') === 0 ) txt = `오늘 ${createdAt.format('HH:mm')}`;\n\n return `\n<div class=\"d-flex\">\n <span class=\"ml-auto d-md-none\">작성일:</span>\n <p class=\"ml-2 mx-md-auto my-0\">${txt}</p>\n</div>`;\n }\n },\n { data: 'viewcount', title: '조회수', className: 'post-view-count d-none d-md-block' },\n ];\n\n component.$viewerScrollContainer = $('.post-viewer-scroll-container', component.$postViewerTabContent);\n component.$viewer = $('.post-viewer', component.$postViewerTabContent);\n component.$viewerMetadata = $('.post-viewer-metadata', component.$postViewerTabContent);\n component.$viewerControls = $('.post-viewer-controls', component.$postViewerTabContent);\n component.$viewerAttachments = $('.post-viewer-attachments', component.$postViewerTabContent);\n component.$viewerAttachmentList = $('.post-viewer-attachment-list', component.$postViewerTabContent);\n component.$viewerComments = $('.post-viewer-comments', component.$postViewerTabContent);\n component.$viewerCommentList = $('.post-viewer-comment-list', component.$postViewerTabContent);\n component.$viewerCommentForm = $('.post-viewer-comment-form', component.$postViewerTabContent);\n\n component.editor = null;\n component.$editorScrollContainer = $('.post-editor-scroll-container', component.$postEditorTabContent);\n component.$editor = $('.post-editor', component.$postEditorTabContent);\n component.$editorToolbar = $('.post-editor-toolbar', component.$postEditorTabContent);\n component.$editorPostMetadata = $('.post-editor-metadata-form', component.$postEditorTabContent);\n component.postEditForm = {};\n\n let inputTitleRandomId = $.HSCore.helpers.getRandomId();\n let inputNoticeRandomId = $.HSCore.helpers.getRandomId();\n let inputFileHideRandomId = $.HSCore.helpers.getRandomId();\n\n component.$editorPostMetadataForm = $(`\n<div class=\"form-group m-0 p-2\">\n <div class=\"form-check form-check-inline editor-post-notice-form\">\n <input class=\"form-check-input editor-post-notice\" type=\"checkbox\" id=\"${inputNoticeRandomId}\">\n <label class=\"form-check-label\" for=\"${inputNoticeRandomId}\">공지사항</label>\n </div>\n <div class=\"form-check form-check-inline editor-post-hide-attachment-form d-none\">\n <input class=\"form-check-input editor-post-hide-attachment\" type=\"checkbox\" id=\"${inputFileHideRandomId}\">\n <label class=\"form-check-label\" for=\"${inputFileHideRandomId}\">본문에서 파일 숨기기</label>\n </div>\n</div>`\n );\n component.$editorPostInputTitle = $(`<input type=\"text\" placeholder=\"${config.titlePlaceHolder}\" class=\"form-control rounded-0 border-left-0 border-right-0 border-bottom-0 border-top border-gray editor-post-title\" id=\"${inputTitleRandomId}\">`);\n component.$editorPostFormNotice = $('.editor-post-notice-form', component.$editorPostMetadataForm);\n component.$editorPostFormHideAttachment = $('.editor-post-hide-attachment-form', component.$editorPostMetadataForm);\n component.$editorPostInputNotice = $('input.editor-post-notice', component.$editorPostMetadataForm);\n component.$editorPostInputHideAttachment = $('input.editor-post-hide-attachment', component.$editorPostMetadataForm);\n\n component.$editorControls = $('.post-editor-controls', component.$postEditorTabContent);\n component.$editorAttachments = $('.post-editor-attachments', component.$postEditorTabContent);\n component.$editorAttachmentList = $('.post-editor-attachments-list', component.$postEditorTabContent);\n\n component.$postSnackbar = $('.post-snackbar', component.$el).get(0);\n component.$postConfirm = $('.post-confirm', component.$el).get(0);\n\n component.helpers = {};\n\n /**\n * 에러 메시지 파서\n * @param {Error} err 에러 객체\n * @return {String} 파싱된 메시지\n */\n component.helpers.parseErrorMessage = function(err){\n let errorMessageStart = '<div class=\"error\">';\n let errorMessageEnd = '</div>';\n\n if ( err.responseText )\n {\n if ( err.responseText.indexOf(errorMessageStart) < 0 ) return err.responseText;\n\n let messageStartIndex = err.responseText.indexOf(errorMessageStart) + errorMessageStart.length;\n let messageEndIndex = err.responseText.indexOf(errorMessageEnd, messageStartIndex);\n\n return err.responseText.substring(messageStartIndex, messageEndIndex);\n }\n\n return err.message;\n };\n\n\n component._getFileSize = function(size){\n let _size = size;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} B`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} KB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} MB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} GB`;\n\n return `1TB 이상`;\n };\n // ╦╔═╗╔═╗╔╦╗╔═╗╔═╗╔╗╔╔═╗╔╗╔╔╦╗ ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗\n // ║║ ║ ║║║║╠═╝║ ║║║║║╣ ║║║ ║ ║ ║╠╣ ║╣ ║ ╚╦╝║ ║ ║╣\n // ╚╝╚═╝╚═╝╩ ╩╩ ╚═╝╝╚╝╚═╝╝╚╝ ╩ ╩═╝╩╚ ╚═╝╚═╝ ╩ ╚═╝╩═╝╚═╝o\n // ─────────────────────────────────────────────────────────\n component.configure = function(key, val, init){\n switch ( key )\n {\n case 'hover':\n case 'bordered':\n case 'striped':\n component.$table.toggleClass(`table-${key}`, val);\n break;\n case 'height':\n let height = !isNaN(val) ? `${val}px` : val;\n component.$el.css({ height: height });\n break;\n }\n };\n\n component.template = `/api/post-groups/${component.postGroupId}/include-notices`;\n component.make = function(response){\n if ( response.err )\n {\n component.$cardTitle.addClass('with-error border border-danger');\n return component.$cardBody.empty().append(`\n<div class=\"d-flex bg-danger text-white p-4\">\n <i class=\"ml-auto mr-2 my-auto fas fa-exclamation-circle fa-2x\"></i> <span class=\"mr-auto my-auto\">${component.helpers.parseErrorMessage(response)}</span>\n</div>`);\n }\n\n component.postGroup = response;\n component.notices = response.notices;\n\n // 세션 권한 계산\n if ( _.get(TOTAL_LOCALS, '_SESSION') )\n {\n component.allowedPermission =\n _.chain(TOTAL_LOCALS._SESSION.parents)\n .map('_id')\n .intersection(_.keys(component.postGroup.permission))\n .map(function(userPermission){\n return component.postGroup.permission[userPermission];\n })\n .flatten()\n .uniq()\n .value();\n\n if ( TOTAL_LOCALS._SESSION.isSysadmin )\n {\n component.allowedPermission = [CONSTANTS.PERMISSIONS.POST.READABLE,CONSTANTS.PERMISSIONS.POST.WRITABLE,CONSTANTS.PERMISSIONS.POST.NOTICE_WRITABLE,CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE,CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE,CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE,CONSTANTS.PERMISSIONS.POST.COMMENT.READABLE,CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE];\n }\n }\n // hljs 설정\n hljs.configure({ languages: ['html', 'javascript', 'css', 'bash'] });\n\n // Quilljs 모듈 확장\n const Parchment = Quill.import('parchment');\n\n const AttributeAttributorTitle = new Parchment.Attributor.Attribute('title', 'title');\n const AttributeAttributorFileId = new Parchment.Attributor.Attribute('data-file-id', 'data-file-id');\n const AttributeAttributorLastModified = new Parchment.Attributor.Attribute('data-last-modified', 'data-last-modified');\n const AttributeAttributorLastModifiedDate = new Parchment.Attributor.Attribute('data-last-modified-date', 'data-last-modified-date');\n const AttributeAttributorName = new Parchment.Attributor.Attribute('data-name', 'data-name');\n const AttributeAttributorSize = new Parchment.Attributor.Attribute('data-size', 'data-size');\n const AttributeAttributorType = new Parchment.Attributor.Attribute('data-type', 'data-type');\n const AttributeAttributorContentEditable = new Parchment.Attributor.Attribute('contenteditable', 'contenteditable');\n\n // const ClassAttributorDisplay = new Parchment.Attributor.Class('display', 'd');\n\n Parchment.register(AttributeAttributorTitle);\n Parchment.register(AttributeAttributorFileId);\n Parchment.register(AttributeAttributorLastModified);\n Parchment.register(AttributeAttributorLastModifiedDate);\n Parchment.register(AttributeAttributorName);\n Parchment.register(AttributeAttributorSize);\n Parchment.register(AttributeAttributorType);\n Parchment.register(AttributeAttributorContentEditable);\n\n // Parchment.register(ClassAttributorDisplay);\n\n Quill.register({\n 'formats/title': AttributeAttributorTitle,\n 'formats/data-file-id': AttributeAttributorFileId,\n 'formats/data-last-modified': AttributeAttributorLastModified,\n 'formats/data-last-modified-date': AttributeAttributorLastModifiedDate,\n 'formats/data-name': AttributeAttributorName,\n 'formats/data-size': AttributeAttributorSize,\n 'formats/data-type': AttributeAttributorType,\n 'formats/contenteditable': AttributeAttributorContentEditable,\n\n // 'attributors/class/display': ClassAttributorDisplay,\n });\n\n const EmbedBlock = Quill.import(\"blots/block/embed\");\n\n class AttachmentBlock extends EmbedBlock {\n static create(options = {}) {\n let node = super.create(options.filename);\n let inner = document.createElement('div');\n let icon = document.createElement('i');\n let fileName = document.createElement('span');\n\n if ( options.file instanceof File )\n {\n options = {\n fileId: options.fileId || $.HSCore.helpers.getRandomId(),\n lastModified: options.file.lastModified,\n lastModifiedDate: options.file.lastModifiedDate,\n name: options.file.name,\n size: options.file.size,\n type: options.file.type,\n };\n AttributeAttributorFileId.add(node, options.fileId);\n AttributeAttributorLastModified.add(node, options.lastModified);\n AttributeAttributorLastModifiedDate.add(node, options.lastModifiedDate);\n AttributeAttributorName.add(node, options.name);\n AttributeAttributorTitle.add(node, options.name);\n AttributeAttributorSize.add(node, options.size);\n AttributeAttributorType.add(node, options.type);\n\n fileName.textContent = node.getAttribute('data-name');\n }\n\n AttributeAttributorContentEditable.add(node, false);\n\n inner.className = 'attachment-inner';\n icon.className = 'attachment-icon fas fa-2x fa-file';\n fileName.className = 'attachment-filename';\n\n inner.appendChild(icon);\n inner.appendChild(fileName);\n\n node.appendChild(inner);\n\n return node;\n }\n\n format(name, value) {\n super.format(name, value);\n if ( name === AttributeAttributorName.attrName )\n this.domNode.querySelector('.attachment-filename').textContent = value;\n\n if ( name === AttributeAttributorSize.attrName )\n this.domNode.querySelector('.attachment-filename').textContent += ` ( ${this._getFileSize(value)} )`;\n }\n\n _getFileSize(size){\n let _size = size;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} B`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} KB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} MB`;\n _size = _size / 1024;\n\n if ( _size < 1024 ) return `${_size.toFixed(2)} GB`;\n\n return `1TB 이상`;\n }\n\n static value(node) {\n return node.className;\n }\n }\n\n AttachmentBlock.blotName = 'attachment';\n AttachmentBlock.className = 'attachment';\n AttachmentBlock.tagName = 'div';\n\n Quill.register(AttachmentBlock, true);\n\n // 탭 ID 임의화\n component.$tabNavs.each(function(){\n let tabId = $.HSCore.helpers.getRandomId();\n let paneId = $.HSCore.helpers.getRandomId();\n\n let $tabNav = $(this);\n let $tabPane = component.$el.find(`.card-body.tab-content ${$tabNav.attr('href')}.tab-pane`);\n\n $tabPane.prop('id', paneId);\n $tabPane.attr('aria-labelledby', tabId);\n\n $tabNav.prop('id', tabId);\n $tabNav.attr({ 'href': `#${paneId}`, 'aria-controls': paneId });\n });\n\n // 뷰어 설정\n component.$viewerControls.on('click', '.to-list', async function($event){\n component._changeHash('#list');\n });\n\n component.$viewerControls.on('click', '.edit-post', async function($event){\n component._changeHash(`#editor-edit${component.$viewer.data('post-id')}`);\n });\n\n component.$viewerControls.on('click', '.remove-post', async function($event){\n component.$postConfirm.$com.confirm('정말로 글을 삭제하시겠습니까?', [`\"check-circle\" 네`, '아니오'], async function(buttonIndex) {\n if ( buttonIndex > 0 ) return; // cancel\n try\n {\n await $.ajax({ url: `/api/posts/${component.postGroupId}/${component.$viewer.data('post-id')}`, method: 'delete' });\n\n component._changeHash('#list');\n\n component.$postSnackbar.$com.success('삭제되었습니다.');\n }\n catch(err)\n {\n\n }\n });\n });\n\n // 목록 설정\n if ( component.mode === CONSTANTS.MODES.POST )\n {\n extendsDataTableMethods();\n\n if ( !component.datatable )\n {\n let buttons = [];\n\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.WRITABLE) >= 0 )\n {\n buttons.push({\n text: '글쓰기',\n className: 'btn-sm btn-primary post-write',\n action: function ( e, dt, node, config ) {\n component._changeHash('#editor');\n }\n });\n }\n\n component.$table.on('init.dt', async function(){\n // 커스텀 페이지네이션 초기화\n component._initCustomPaginate();\n\n // 자동 검색 이벤트 제거\n $('.dataTables_filter input', component.$el).off()\n // 엔터 이벤트로 추가\n .on('keyup', function($event) {\n if ( $event.keyCode !== 13 ) return;\n component.datatable.search( this.value ).draw();\n });\n });\n\n component.$table.on('page.dt', async function(){\n // 페이지네이션 업데이트\n component._refreshCustomPaginate();\n });\n\n component.$table.on('draw.dt', async function(){\n _.each(component.notices, function(notice){\n let colIndex = 0;\n let createdAt = moment(notice.createdAt);\n\n $('tbody', component.$table).prepend(`<tr class=\"post-notice\" data-post-id=\"${ notice._id }\">\n <td class=\"${component.columnsDefines[colIndex++].className} text-primary\"><i class=\"fas fa-bullhorn\"></i> 공지</td>\n <td class=\"${component.columnsDefines[colIndex++].className} text-primary font-weight-bold\">\n <div class=\"text-center text-md-left post-title-wrapper\">\n ${ $.fn.dataTable.render.ellipsis(40)($.fn.dataTable.render.text().display(notice.name)) }\n </div>\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">\n <a class=\"post-writer\" href=\"#\">\n ${ $.fn.dataTable.render.text().display(notice.author.name) }\n </a>\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">\n ${\n moment().diff(createdAt, 'days') === 0 ? `오늘 ${createdAt.format('HH:mm')}`\n : `<p class=\"m-0\">${createdAt.format('YYYY-MM-DD')}</p>`\n }\n </td>\n <td class=\"${component.columnsDefines[colIndex++].className}\">${ notice.viewcount }</td>\n</tr>`);\n });\n setTimeout(function(){ component.datatable.columns.adjust(); });\n component.datatable.processing(false);\n });\n\n component.datatable = component.$table.DataTable({\n processing: true,\n serverSide: true,\n ordering: false,\n searchDelay: 500,\n language: { url: '/libs/jquery-datatables/1.10.19/locales/ko.json' },\n paging: true,\n pagingType: 'simple_numbers',\n lengthMenu: [[ 10, 15, 25, 50, 75, 100 ], [ 10, 15, 25, 50, 75, 100 ]],\n pageLength: 15,\n rowHeight: 40,\n rowCallback: function( row, data ) {\n $(row).addClass( 'g-color-primary--hover' );\n },\n dom: `\n${\n ( config.length || config.searchBar ) ?\n `<'row no-gutters'<'col-sm-12 pl-2 mt-2 col-md-4'${ config.length ? 'l' : '' }><'col-sm-12 mt-2 col-md-4'${ config.searchBar ? 'f' : '' }><'col-sm-12 mt-2 col-md-4 pr-2'>>`\n : ''\n}\n<'row no-gutters table-body-wrapper'<'col-sm-12'tr>>\n<'row no-gutters'<'col-sm-12 pb-2 col-md-${buttons.length > 0 ? 8 : 12 } g-mt-30'p>${ buttons.length > 0 ? `<'col-sm-12 pb-2 col-md-4 g-mt-30 pt-1 pr-2 text-right'B>` : ''}>`,\n buttons: buttons,\n columns: component.columnsDefines,\n ajax: $.fn.dataTable.pipeline({\n url: `/api/posts/${component.postGroupId}`,\n pages: 5, // number of pages to cache\n error: function(){\n console.info(arguments, '<<<<<<<<<<<<<<<<<<<<<<<<<<');\n }\n })\n });\n }\n\n component.$table.on('click', 'tbody tr', function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n let $row = $(this).closest('tr');\n let row = component.datatable.row($row).data();\n\n if ( $row.hasClass('post-notice') ) row = { _id: $row.data('post-id') };\n if ( !row && !$row.hasClass('post-notice') ) return;\n\n\n // 사용자가 클릭했을 때만 이전 스크롤값 유지,\n // 뒤로가기, 앞으로 가기는 관여하지 않음.\n component.prevScrollTop = window.scrollY;\n\n component._changeHash(row._id);\n });\n\n // 에디터 설정\n // 에디터 메타데이터 입력 폼 추가\n component.$editorPostMetadata.append(component.$editorPostMetadataForm, component.$editorPostInputTitle);\n\n // 툴바 ID 임의화\n let toolbarId = $.HSCore.helpers.getRandomId();\n component.$editorToolbar.prop('id', toolbarId);\n\n let editorScrollContainerId = $.HSCore.helpers.getRandomId();\n component.$editorScrollContainer.prop('id', editorScrollContainerId);\n\n component.$postViewerTab.on('shown.bs.tab', async function(){\n // 스크롤 이동\n let offset = component.$el.offset();\n window.scrollTo(offset.left, offset.top);\n\n // 댓글 로드\n // component.$viewerControls.append();\n // component.datatable.fixedHeader.disable();\n });\n\n component.$postListTab.on('shown.bs.tab', async function(){\n // component.datatable.clearPipeline();\n // component.datatable.ajax.reload( null, false );\n // component.datatable.fixedHeader.enable();\n component.autoSwitchingCardStyle();\n\n if ( component.prevScrollTop != null ) window.scrollTo(0, component.prevScrollTop);\n });\n\n component.$postEditorTab.on('shown.bs.tab', async function(){\n window.scrollTo(0, component.$el.offset().top);\n\n let hash = component.getPostIdFromHash();\n if ( !component.editor )\n {\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.NOTICE) < 0\n || component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.NOTICE_WRITABLE) < 0 )\n {\n component.$editorPostFormNotice.remove();\n }\n\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.ATTACHMENT) >= 0 )\n {\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.UPLOADABLE) >= 0 )\n {\n component.$editorPostFormHideAttachment.removeClass('d-none');\n component.$editorPostInputHideAttachment.on('change', function($event){\n let checked = $event.currentTarget.checked;\n\n if ( checked )\n {\n component.$postConfirm.$com.confirm('하단의 파일 목록은 계속 유지되지만 본문에 위치한 파일박스를 모두 잃게됩니다.\\n계속 진행하시겠습니까?', [`\"check-circle\" 네`, '아니오'], async function(buttonIndex) {\n if ( buttonIndex > 0 ) return $event.currentTarget.checked = false; // cancel\n _.each(component.editor.getLines(), function(line){\n if ( line instanceof AttachmentBlock ) line.remove();\n });\n });\n }\n else\n {\n component._appendFileBox(component.sendFileList);\n }\n });\n\n $(`#${toolbarId} .ql-video`).after(CONSTANTS.TEMPLATES.$TOOLBAR_BTN_FILE_UPLOAD);\n\n CONSTANTS.TEMPLATES.$BROWSE_FILE_INPUT.on('change', function($event){\n component._appendFileBox(this.files);\n\n // reset file input\n this.value = '';\n\n // _.each(files, function(file){\n // var reader = new FileReader();\n\n // reader.onload = function(e) {\n // var data = e.target.result;\n // };\n // if(rABS) reader.readAsBinaryString(f);\n // else reader.readAsArrayBuffer(f);\n // });\n });\n\n CONSTANTS.TEMPLATES.$TOOLBAR_BTN_FILE_UPLOAD.on('click', function(){\n CONSTANTS.TEMPLATES.$BROWSE_FILE_INPUT.click();\n });\n\n component.$editorAttachments.removeClass('d-none');\n }\n }\n\n component.editor = new Quill(component.$editor.get(0), {\n modules: {\n syntax: true,\n toolbar: `#${toolbarId}`,\n imageResize: {},\n imageDrop: {},\n },\n theme: 'snow',\n bounds: `#${editorScrollContainerId}`,\n scrollingContainer: `#${editorScrollContainerId}`,\n placeholder: config.placeholder,\n });\n }\n\n // 에디터 초기화\n component.editor.setContents([]);\n component.$editorPostInputTitle.val('');\n component.$editorPostInputNotice.prop('checked', false);\n component.$editorAttachmentList.empty();\n component.sendFileList = [];\n component.$editorControls.empty();\n\n // 수정 모드일 경우 저장된 데이터 로드\n if ( /^#editor-edit/.test(hash) )\n {\n let postId = hash.replace('#editor-edit', '');\n\n component.postEditForm = {\n _id: component.$viewer.data('post-id'),\n content: component.$viewer.data('post-content'),\n name: component.$viewer.data('post-name'),\n isnotice: component.$viewer.data('post-isnotice'),\n attachments: component.$viewer.data('post-attachments'),\n };\n\n // 이미 로드된 뷰어 값이 없다면 ajax를 통해 받아옴\n if ( !component.postEditForm._id )\n {\n try\n {\n component.postEditForm = await $.ajax(`/api/posts/${component.postGroupId}/${postId}`);\n if ( component.postEditForm ) component.postEditForm = component.postEditForm.doc;\n }\n catch(err)\n {\n return console.error(err);\n }\n }\n\n component.editor.setContents(component.postEditForm.content);\n component.$editorPostInputTitle.val(component.postEditForm.name);\n component.$editorPostInputNotice.prop('checked', component.postEditForm.isnotice);\n\n _.each(component.postEditForm.attachments, function(attachment, idx, src){\n component.$editorAttachmentList.append(`\n<li class=\"d-flex px-3 py-1 ${ idx === src.length - 1 ? 'pb-0' : ''} post-editor-attachment-list-item\" data-file-id=\"${attachment._id}\" data-is-saved=\"true\">\n <span class=\"my-auto ml-0\">${attachment.filename} ( ${component._getFileSize(attachment.length)} )</span>\n <i class=\"text-danger my-auto ml-auto remove-attachment fas fa-times\"></i>\n</li>`\n );\n });\n\n component.$editorControls.append(`\n<button class=\"cancel-edit btn btn-sm btn-danger\"><i class=\"fas fa-times\"></i> 취소</button>\n<button class=\"save-changes btn btn-sm btn-primary ml-auto\"><i class=\"fas fa-save\"></i> 변경 저장</button>`);\n }\n // 신규 작성 모드일 경우\n else\n {\n component.$editorControls.append(`\n<button class=\"cancel-edit btn btn-sm btn-danger\"><i class=\"fas fa-times\"></i> 취소</button>\n<button class=\"create-post btn btn-sm btn-primary ml-auto\"><i class=\"fas fa-save\"></i> 저장</button>`);\n }\n });\n\n // 에디터 툴바 설정\n\n // 에디터 기능 설정\n component.$editorAttachmentList.on('click', 'i.remove-attachment', async function($event){\n let $attachmentListItem = $($event.currentTarget).closest('.post-editor-attachment-list-item');\n\n if ( $attachmentListItem.data('is-saved') )\n {\n let index = _.findIndex(component.postEditForm.attachments, _.matches({ _id: $attachmentListItem.data('file-id') }));\n if ( index > -1 ) component.postEditForm.attachments.splice(index, 1);\n }\n else\n {\n component.sendFileList[$attachmentListItem.data('file-index')] = void(0);\n }\n\n $attachmentListItem.remove();\n });\n\n component.$editorControls.on('click', '.cancel-edit', async function($event){\n component._changeHash('#list');\n });\n\n component.$editorControls.on('click', '.save-changes', async function($event){\n try\n {\n component._showBlocker();\n\n let postData = {\n isnotice: component.$editorPostInputNotice.is(':visible') ? component.$editorPostInputNotice.prop('checked') : false,\n template: 'default',\n name: component.$editorPostInputTitle.val(),\n content: component.editor.getContents(),\n attachments: component.postEditForm.attachments,\n };\n\n // 데이터 유효성 체크\n if ( !postData.name )\n {\n component._hideBlocker();\n component.$postSnackbar.$com.warning('제목을 입력해주세요.');\n return component.$editorPostInputTitle.focus();\n }\n\n postData.attachments = _.map(postData.attachments, '_id');\n\n // 첨부파일 업로드\n let savedFileInfo = await component._fileUpload(component.postGroupId, component.postEditForm._id, component.sendFileList);\n if ( savedFileInfo.length > 0 )\n {\n // 델타내에 첨부파일들에게 매겨진 임시 ID를\n // 저장된 ID로 모두 변경\n postData.content = component._replaceDeltaFileID(postData.content, savedFileInfo);\n\n // 변경된 델타로 에디터 재설정\n component.editor.setContents(postData.content);\n\n // 저장된 파일목록을 기존 저장된 목록에 추가\n postData.attachments = postData.attachments.concat(_.map(savedFileInfo, '_id'));\n }\n // 파일 전송 목록 초기화\n component.sendFileList = [];\n\n component._showBlocker('글 등록 중...');\n // 글 저장\n await $.ajax({\n url: `/api/posts/${component.postGroupId}/${component.postEditForm._id}`, method: 'patch', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(postData),\n });\n\n component._hideBlocker();\n component.$postSnackbar.$com.success('저장되었습니다.');\n\n // 저장된 글로 포스트 읽기로 자동 탭 이동\n component._changeHash(component.postEditForm._id);\n }\n catch(err)\n {\n component._hideBlocker();\n component.$postSnackbar.$com.failed(`저장에 실패하였습니다.: ${err.message}`);\n }\n });\n\n component.$editorControls.on('click', '.create-post', async function($event){\n try\n {\n component._showBlocker();\n\n let postData = {\n isnotice: component.$editorPostInputNotice.is(':visible') ? component.$editorPostInputNotice.prop('checked') : false,\n template: 'default',\n name: component.$editorPostInputTitle.val(),\n content: component.editor.getContents(),\n attachments: [],\n };\n\n // 데이터 유효성 체크\n if ( !postData.name )\n {\n component._hideBlocker();\n component.$postSnackbar.$com.warning('제목을 입력해주세요.');\n return component.$editorPostInputTitle.focus();\n }\n\n // 첨부파일 업로드\n let savedFileInfo = await component._fileUpload(component.sendFileList);\n\n if ( savedFileInfo.length > 0 )\n {\n // 델타내에 첨부파일들에게 매겨진 임시 ID를\n // 저장된 ID로 모두 변경\n postData.content = component._replaceDeltaFileID(postData.content, savedFileInfo);\n\n // 변경된 델타로 에디터 재설정\n component.editor.setContents(postData.content);\n\n // 저장된 파일목록을 별도 필드로 저장\n postData.attachments = _.map(savedFileInfo, '_id');\n\n // 파일 전송 목록 초기화\n component.sendFileList = [];\n }\n\n component._showBlocker('글 등록 중...');\n\n // 글 저장\n let createdPost = await $.ajax({\n url: `/api/posts/${component.postGroupId}`, method: 'post', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(postData),\n });\n\n component._hideBlocker();\n component.$postSnackbar.$com.success('저장되었습니다.');\n\n // 저장된 글로 포스트 읽기로 자동 탭 이동\n component.renderPost({ _id: createdPost._id, doc: createdPost, comments: [] });\n }\n catch(err) {\n component._hideBlocker();\n component.$postSnackbar.$com.failed(`저장에 실패하였습니다.: ${err.message}`);\n }\n });\n }\n else\n {\n component.$table.before(CONSTANTS.$RECENT_POST_LIST).remove();\n component.$title.text(`${component.$title.text()} - 최근글`);\n CONSTANTS.$RECENT_POST_LIST.on('click', 'li.recent-post-list-item', component._viewPost);\n\n component._readRecentPosts();\n }\n\n // 초기화\n $(window).on('resize', _.debounce(component.autoSwitchingCardStyle, 150));\n $(window).on('hashchange', async function(){\n let postId = component.getPostIdFromHash();\n\n if ( !postId || postId === '#list' ) return component.$postListTab.tab('show');\n if ( /^#editor/.test(postId) ) return component.$postEditorTab.tab('show');\n\n try\n {\n let $post = await component.getPost(component.postGroupId, postId);\n if ( $post ) return component.renderPost($post);\n component.$postSnackbar.$com.failed('존재하지 않는 글입니다.');\n }\n catch(err)\n {\n component.$postSnackbar.$com.failed(err.message);\n }\n });\n\n $(window).trigger('resize');\n\n // 초기화 완료 컴포넌트 초기화 로딩 스피너 숨김.\n component.$initSpinner.addClass('d-none').removeClass('d-flex');\n\n // load post via url hash\n let postId = component.getPostIdFromHash();\n if ( !postId ) return;\n\n if ( postId === '#list' ) ;\n else if ( /^#editor/.test(postId) )\n component.$postEditorTab.tab('show');\n else\n component.getPost(component.postGroupId, postId)\n .then(component.renderPost)\n .catch(function(err){\n component.$postSnackbar.$com.failed(err.message);\n });\n };\n\n component._hideProgress = function(){\n component.$progress.addClass('d-none');\n };\n\n component._showProgress = function(){\n component.$progressBar.attr('aria-valuenow', 0).width(0);\n component.$progress.width(component.$blocker.width() * 85 / 100).removeClass('d-none');\n };\n\n component._hideBlocker = function(){\n component.$blockerMessage.addClass('d-none');\n component.$blocker.addClass('d-none');\n };\n\n component._showBlocker = function(message){\n let offset = component.$el.offset();\n component.$blocker.css({\n top: offset.top,\n left: offset.left,\n width: component.$el.width(),\n height: component.$el.height(),\n });\n\n component.$blocker.removeClass('d-none');\n\n if ( message )\n {\n component.$blockerMessageTxt.text(message);\n component.$blockerMessage.removeClass('d-none');\n }\n };\n\n component._replaceDeltaFileID = function(delta, savedFileInfo){\n _.each(savedFileInfo, function(info){\n delta.forEach(function(operation){\n if ( operation.insert && operation.insert.attachment && operation.insert.attachment === 'attachment' )\n {\n if ( operation.attributes['data-file-id'] === info.fileId )\n {\n operation.attributes['data-file-id'] = info._id;\n }\n }\n });\n });\n\n return delta;\n };\n\n component._appendFileBox = function(files){\n let selection = component.editor.getSelection();\n let index = 0;\n\n if ( selection ) index = selection.index;\n if ( index === 0 ) component.editor.insertText(index++, '\\n');\n\n _.each(files, function(file){\n let isExists = file.fileId ? true : false;\n let fileInstance = isExists ? file.file : file;\n\n let randomId = file.fileId || $.HSCore.helpers.getRandomId();\n\n component.editor.insertEmbed(index++, 'attachment', { fileId: randomId, file: fileInstance });\n component.editor.insertText(index++, '\\n');\n\n if ( ! isExists )\n {\n let sameFileIndex = _.findIndex( component.sendFileList, function(f){\n return f.file.lastModified === fileInstance.lastModified && f.file.lastModifiedDate === fileInstance.lastModifiedDate && f.file.name === fileInstance.name && f.file.size === fileInstance.size && f.file.type === fileInstance.type;\n });\n\n if ( sameFileIndex < 0 )\n {\n component.$editorAttachmentList.append(`\n<li class=\"d-flex px-3 py-1 post-editor-attachment-list-item\" data-file-id=\"${randomId}\" data-file-index=\"${component.sendFileList.length}\">\n <span class=\"my-auto ml-0\">${fileInstance.name} ( ${component._getFileSize(file.size)} )</span>\n <i class=\"text-danger my-auto ml-auto remove-attachment fas fa-times\"></i>\n</li>`\n );\n\n component.sendFileList.push({ fileId: randomId, file: fileInstance });\n }\n }\n });\n // move after attachments\n component.editor.setSelection(index + 1, 0);\n };\n\n component._refreshCustomPaginate = function(){\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.empty();\n\n let pagination = component.datatable.page.info();\n\n let per = 3;\n\n let current = pagination.page;\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-13 to-previous ${ current === 0 ? 'u-pagination-v1__item--disabled' : '' }\" href=\"javascript:void(0)\" aria-label=\"이전\">\n <span aria-hidden=\"true\">\n <i class=\"fa fa-angle-left g-mr-5\"></i>\n 이전\n </span>\n <span class=\"sr-only\">이전</span>\n </a>\n</li>`);\n\n if ( current - 1 > 0 )\n {\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page\" href=\"javascript:void(0)\">${1}</a>\n</li>\n<li class=\"list-inline-item g-hidden-sm-down\">\n <span class=\"g-pa-4-11\">...</span>\n</li>`\n );\n }\n\n for ( let i = current - 1, ilen = current - 1 + per; i < ilen; i++ )\n {\n if ( i < 0 ) { ilen += 1; continue; }\n if ( i >= pagination.pages ) { break; }\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page ${ current === i ? 'u-pagination-v1-3--active' : '' }\" href=\"javascript:void(0)\">${i + 1}</a>\n</li>`);\n }\n\n if ( current - 1 + per <= pagination.pages - 1 )\n {\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item g-hidden-sm-down\">\n <span class=\"g-pa-4-11\">...</span>\n</li>\n<li class=\"list-inline-item g-hidden-sm-down\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-11 to-page\" href=\"javascript:void(0)\">${pagination.pages}</a>\n</li>`\n );\n }\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.append(`\n<li class=\"list-inline-item\">\n <a class=\"u-pagination-v1__item u-pagination-v1-3 g-pa-4-13 to-next ${ current >= pagination.pages - 1 ? 'u-pagination-v1__item--disabled' : '' }\" href=\"javascript:void(0)\" aria-label=\"다음\">\n <span aria-hidden=\"true\">\n 다음\n <i class=\"fa fa-angle-right g-ml-5\"></i>\n </span>\n <span class=\"sr-only\">다음</span>\n </a>\n</li>`);\n };\n\n component._initCustomPaginate = function(){\n component.$originalPaginate = $('.dataTables_paginate', component.$el);\n\n component.$originalPaginate.addClass('d-none');\n component.$originalPaginate.after(CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE);\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-page', async function($event){\n component.datatable.page(Number($event.currentTarget.textContent) - 1).draw('page');\n });\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-next', async function($event){\n component.datatable.page('next').draw('page');\n\n if ( !component.$el.hasClass('cards-table') ) return;\n let componentOffset = component.$el.offset();\n window.scrollTo(componentOffset.left, componentOffset.top)\n });\n\n CONSTANTS.TEMPLATES.DATATABLE.$PAGINATE.on('click', '.to-previous', async function($event){\n component.datatable.page('previous').draw('page');\n\n if ( !component.$el.hasClass('cards-table') ) return;\n let componentOffset = component.$el.offset();\n window.scrollTo(componentOffset.left, componentOffset.top)\n });\n\n component._refreshCustomPaginate();\n };\n\n component._fileUpload = async function(postGroupId, postId, files){\n // 첨부파일 업로드\n if ( _.filter(files, Boolean).length > 0 )\n {\n let formData = new FormData();\n\n _.each(files, function(f){\n formData.append('file-id', f.fileId);\n formData.append('files', f.file);\n });\n\n component._showBlocker('파일 업로드 중...');\n component._showProgress();\n return await $.ajax({\n url: `/api/posts/${postGroupId}/${postId}/attachment`, method: 'post',\n contentType: false, processData: false, data: formData,\n xhr: function() {\n var xhr = $.ajaxSettings.xhr();\n if ( xhr )\n {\n xhr.upload.addEventListener('progress', function(event){\n if ( !event.lengthComputable ) return;\n\n let percentComplete = Math.round( (event.loaded * 100) / event.total );\n\n component.$progressBar.attr('aria-valuenow', percentComplete).width(`${percentComplete}%`);\n }, false);\n }\n return xhr;\n },\n });\n }\n\n return [];\n };\n\n component.getPostIdFromHash = function(){\n let hash = location.hash.substr(1);\n\n if ( !hash ) return null;\n\n hash = hash.split('&');\n\n for ( let i = 0, ilen = hash.length; i < ilen; i++ )\n if ( hash[i].split('=')[0] === component.instantID )\n return decodeURIComponent(hash[i].split('=')[1]);\n\n return null;\n };\n\n component.getPost = async function(postGroupId, postId){\n if ( !postGroupId ) throw new Error('\"Post Group ID\" is required.');\n if ( !postId ) throw new Error('\"Post ID\" is required.');\n\n try\n {\n return await $.ajax(`/api/posts/${postGroupId}/${postId}`);\n }\n catch(err)\n {\n throw new Error(component.helpers.parseErrorMessage(err));\n }\n };\n\n component.autoSwitchingCardStyle = async function(){\n // 카드 클래스 토글링\n component.$el.toggleClass('cards-table', window.innerWidth < 768);\n // 컬럼 사이즈 재계산\n setTimeout(function(){ component.datatable.columns.adjust(); });\n };\n\n component._viewPost = async function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n let $post;\n\n try\n {\n if ( component.mode === CONSTANTS.MODES.POST )\n {\n let row = component.datatable.row($(this).closest('tr')).data();\n if ( !row ) return;\n\n $post = await component.getPost(component.postGroupId, row._id);\n }\n else if ( component.mode === CONSTANTS.MODES.RECENT )\n {\n $post = await component.getPost(component.postGroupId, $(this).data('post-id'));\n }\n\n component.renderPost($post);\n }\n catch(err)\n {\n component.$postSnackbar.$com.failed(err.message);\n }\n return false;\n };\n\n component._readRecentPosts = async function (){\n let posts = await $.ajax(`/api/posts/${component.postGroupId}/recent/5`);\n let $postListItems = _.map(posts, function(post){\n return $(`<li class=\"d-flex recent-post-list-item\" data-post-id=\"${post._id}\"><a href=\"#\" class=\"post-title my-auto\">${post.name}</a><span class=\"my-auto ml-auto\">${moment(post.createdAt).format('YYYY-MM-DD')}</span></li>`);\n });\n CONSTANTS.$RECENT_POST_LIST.append($postListItems);\n };\n\n // ╔═╗╦═╗╦╦ ╦╔═╗╔╦╗╔═╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗\n // ╠═╝╠╦╝║╚╗╔╝╠═╣ ║ ║╣ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗\n // ╩ ╩╚═╩ ╚╝ ╩ ╩ ╩ ╚═╝ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝o\n // ────────────────────────────────────────────\n // removeHashWithoutReload()\n // extendsDataTableMethods()\n // ────────────────────────────────────────────\n function removeHashWithoutReload () {\n var scrollV, scrollH, loc = window.location;\n if ( _.indexOf(history, 'pushState') < 0 )\n {\n let componentOffset = component.$el.offset();\n loc.hash = '';\n\n // Prevent scrolling by storing the page's current scroll offset\n // Restore the scroll offset, should be flicker free\n window.scrollTo(componentOffset.left, componentOffset.top);\n }\n else\n {\n history.pushState('', document.title, loc.pathname + loc.search);\n }\n }\n\n function extendsDataTableMethods(){\n if ( $.isFunction($.fn.dataTable.pipeline) ) return;\n //\n // https://datatables.net/examples/server_side/pipeline.html\n // Pipelining function for DataTables. To be used to the `ajax` option of DataTables\n //\n $.fn.dataTable.pipeline = function ( opts ) {\n // Configuration options\n let conf = $.extend( {\n error: $.noop,\n pages: 5, // number of pages to cache\n url: '', // script url\n data: null, // function or object with parameters to send to the server\n // matching how `ajax.data` works in DataTables\n method: 'GET' // Ajax HTTP method\n }, opts );\n\n // Private variables for storing the cache\n let cacheLower = -1;\n let cacheUpper = null;\n let cacheLastRequest = null;\n let cacheLastJson = null;\n\n return function ( request, drawCallback, settings ){\n let _datatable = this.api();\n let ajax = false;\n let requestStart = request.start;\n let drawStart = request.start;\n let requestLength = request.length;\n let requestEnd = requestStart + requestLength;\n let info = _datatable.page.info();\n\n if ( settings.clearCache )\n {\n // API requested that the cache be cleared\n ajax = true;\n settings.clearCache = false;\n }\n else if ( cacheLower < 0 || requestStart < cacheLower || requestEnd > cacheUpper )\n {\n // outside cached data - need to make a request\n ajax = true;\n }\n else if (\n JSON.stringify( request.order ) !== JSON.stringify( cacheLastRequest.order )\n || JSON.stringify( request.columns ) !== JSON.stringify( cacheLastRequest.columns )\n || JSON.stringify( request.search ) !== JSON.stringify( cacheLastRequest.search )\n ) {\n // properties changed (ordering, columns, searching)\n ajax = true;\n }\n\n // Store the request for checking next time around\n cacheLastRequest = $.extend( true, {}, request );\n\n if ( ajax )\n {\n // Need data from the server\n if ( requestStart < cacheLower )\n {\n requestStart = requestStart - (requestLength*(conf.pages-1));\n\n if ( requestStart < 0 ) requestStart = 0;\n }\n\n cacheLower = requestStart;\n cacheUpper = requestStart + (requestLength * conf.pages);\n\n request.start = requestStart;\n request.length = requestLength * conf.pages;\n\n // Provide the same `data` options as DataTables.\n if ( typeof conf.data === 'function' )\n {\n // As a function it is executed with the data object as an arg\n // for manipulation. If an object is returned, it is used as the\n // data object to submit\n let d = conf.data( request );\n if ( d ) $.extend( request, d );\n }\n else if ( $.isPlainObject( conf.data ) )\n {\n // As an object, the data given extends the default\n $.extend( request, conf.data );\n }\n\n _datatable.processing(true);\n\n settings.jqXHR = $.ajax({\n type: conf.method,\n url: conf.url,\n data: request,\n dataType: 'json',\n cache: false,\n success: function ( json ) {\n cacheLastJson = $.extend(true, {}, json);\n\n if ( cacheLower != drawStart ) json.data.splice( 0, drawStart-cacheLower );\n if ( requestLength >= -1 ) json.data.splice( requestLength, json.data.length );\n\n drawCallback( json );\n\n // pipeline으로 로드할 경우, length를 잃어버리는 현상이 존재함.\n // DOM 로드가 완료된 후에 재설정하여 length가 표시되도록 하기 위함.\n setTimeout(function(){ _datatable.page.len(_datatable.page.len()); }, 100);\n },\n error: conf.error,\n });\n }\n else\n {\n json = $.extend( true, {}, cacheLastJson );\n json.draw = request.draw; // Update the echo for each response\n json.data.splice( 0, requestStart-cacheLower );\n json.data.splice( requestLength, json.data.length );\n\n drawCallback(json);\n }\n }\n };\n\n // Register an API method that will empty the pipelined data, forcing an Ajax\n // fetch on the next draw (i.e. `table.clearPipeline().draw()`)\n $.fn.dataTable.Api.register( 'clearPipeline()', function () {\n return this.iterator( 'table', function ( settings ) {\n settings.clearCache = true;\n });\n });\n };\n\n // ╔═╗╦ ╦╔╗ ╦ ╦╔═╗ ╔╦╗╔═╗╔╦╗╦ ╦╔═╗╔╦╗╔═╗\n // ╠═╝║ ║╠╩╗║ ║║ ║║║║╣ ║ ╠═╣║ ║ ║║╚═╗\n // ╩ ╚═╝╚═╝╩═╝╩╚═╝ ╩ ╩╚═╝ ╩ ╩ ╩╚═╝═╩╝╚═╝o\n // ────────────────────────────────────────\n // component.renderPost(Post post)\n // component.unescapeHtml(String string)\n // ────────────────────────────────────────\n\n component.unescapeHtml = function(string) {\n let str = '' + string;\n return str.replace(/&gt;/g, `>`)\n .replace(/&lt;/g, `<`)\n .replace(/&#39;/g, `'`)\n .replace(/&quot;/g, `\"`)\n .replace(/&amp;/g, `&`);\n };\n\n component.renderPost = function($post){\n let post = $post.doc;\n let comments = $post.comments;\n\n component.$viewer.data('post-id', post._id);\n component.$viewer.data('post-name', post.name);\n component.$viewer.data('post-content', post.content);\n component.$viewer.data('post-isnotice', post.isnotice);\n component.$viewer.data('post-attachments', post.attachments);\n\n let tabNavText = `글읽기 - ${post.name}`;\n\n component.$postViewerTab.attr('title', tabNavText).text(tabNavText).tab('show');\n\n if ( !component.viewer )\n {\n let viewerScrollConatinerId = $.HSCore.helpers.getRandomId();\n component.$viewerScrollContainer.prop('id', viewerScrollConatinerId);\n\n component.viewer = new Quill(component.$viewer.get(0), {\n modules: {\n syntax: true,\n toolbar: false,\n },\n theme: 'snow',\n bounds: `#${viewerScrollConatinerId}`,\n scrollingContainer: `#${viewerScrollConatinerId}`,\n readOnly: true,\n });\n }\n\n // 뷰어 레이아웃\n // 글 메타데이터 공간\n component.$viewerMetadata.empty().append(`\n<div class=\"post-general-info row no-gutters\">\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom border-right text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">작성자</label>\n <span class=\"m-auto\">${post.author.name}</span>\n </div>\n </div>\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom border-right text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">작성일</label>\n <span class=\"m-auto\">${moment(post.createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n </div>\n <div class=\"metadata-group col-sm-12 col-md-4 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">조회수</label>\n <span class=\"m-auto\">${post.viewcount}</span>\n </div>\n </div>\n</div>\n<div class=\"post-detail-info no-gutters\">\n</div>\n<div class=\"post-title-info no-gutters\">\n <div class=\"metadata-group col-12 d-flex\">\n <div class=\"p-3 m-0 w-100 g-brd-right-0 g-brd-right-1--md border-gray border-bottom text-center font-weight-bold d-flex\">\n <label class=\"mx-3 mx-md-5 my-auto text-nowrap\">제목</label>\n <span class=\"m-auto text-break\">${post.name}</span>\n </div>\n </div>\n</div>`);\n\n let $attachmentItems = $('<li class=\"d-flex py-1 px-3 post-viewer-attachment-list-item\"></li>');\n\n if ( post.attachments.length === 0 )\n $attachmentItems.text('첨부파일이 없습니다.');\n else\n $attachmentItems = _.map(post.attachments, function(attachment, idx, src){\n return `\n<li class=\"d-flex px-3 py-1 ${ idx === src.length - 1 ? 'pb-0' : ''} post-viewer-attachment-list-item\" data-file-id=\"${attachment._id}\">\n <a href=\"javascript:void(0)\" class=\"post-viewer-attachment-download\">${ attachment.filename } ( ${ component._getFileSize(attachment.length) } )</a>\n</li>`;\n }).join('');\n\n // 첨부파일 공간\n component.$viewerAttachmentList.empty().append($attachmentItems);\n\n // 첨부파일 아이템 다운로드 버튼 이벤트\n $('a.post-viewer-attachment-download', component.$viewerAttachmentList).on('click', async function($event){\n $event.preventDefault();\n $event.stopPropagation();\n\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) < 0 )\n component.$postSnackbar.$com.failed('다운로드 권한이 없습니다.');\n else\n window.open(`/api/posts/attachment/${$(this).closest('.post-viewer-attachment-list-item').data('file-id')}`);\n\n return false;\n });\n\n // 본문 로드\n component.viewer.setContents(post.content);\n\n // 첨부파일 다운로드 이벤트\n $('.ql-editor .attachment .attachment-inner', component.$viewer).on('click', async function(){\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.FILE.DOWNLOADABLE) < 0 )\n return component.$postSnackbar.$com.failed('다운로드 권한이 없습니다.');\n\n window.open(`/api/posts/attachment/${$(this).closest('.attachment').data('file-id')}`);\n });\n\n // 수정, 삭제 버튼\n // 자신이 쓴 글만 가능\n component.$viewerControls.empty().append(`<button class=\"btn btn-sm btn-secondary to-list ml-2\"><i class=\"fas fa-list-ul\"></i> 목록</button>`);\n\n if ( post.author._id === TOTAL_LOCALS._SESSION._id )\n {\n component.$viewerControls.append(`\n<button class=\"btn btn-sm btn-danger remove-post ml-auto\"><i class=\"fas fa-trash-alt\"></i> 글 삭제</button>\n<button class=\"btn btn-sm btn-info edit-post mx-2\"><i class=\"fas fa-edit\"></i> 글 수정</button>`);\n }\n\n // 게시판 댓글기능 껐을 경우\n if ( component.postGroup.features.indexOf(CONSTANTS.POST_GROUP.FEATURES.COMMENT) < 0 )\n {\n component.$viewerComments.addClass('d-none');\n }\n // 댓글 로드\n else\n {\n component.$viewerCommentList.empty();\n\n let $commentItems = $('<p class=\"d-flex m-0 py-1 px-2\"></p>');\n\n // 사용자 읽기 권한 체크\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.COMMENT.READABLE) >= 0 )\n {\n if ( post.attachments.length === 0 )\n $commentItems.text('등록된 댓글이 없습니다.');\n else\n $commentItems =\n _.map(_.filter($post.comments, function(comment){ return !_.isEmpty(comment); }), function(comment){\n // 사용자 프로필에 사용된 기본값 svg는 font-awesome `fas fa-user`\n return `\n<div class=\"media g-mb-30\">\n <img class=\"d-flex g-width-50 g-height-50 rounded-circle g-mt-3 g-mr-20 p-1 border\" src=\"${post.author.photo ? post.author.photo : 'data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJ1c2VyIiByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDQ0OCA1MTIiIGNsYXNzPSJzdmctaW5saW5lLS1mYSBmYS11c2VyIGZhLXctMTQgZmEtM3giPjxwYXRoIGZpbGw9ImN1cnJlbnRDb2xvciIgZD0iTTIyNCAyNTZjNzAuNyAwIDEyOC01Ny4zIDEyOC0xMjhTMjk0LjcgMCAyMjQgMCA5NiA1Ny4zIDk2IDEyOHM1Ny4zIDEyOCAxMjggMTI4em04OS42IDMyaC0xNi43Yy0yMi4yIDEwLjItNDYuOSAxNi03Mi45IDE2cy01MC42LTUuOC03Mi45LTE2aC0xNi43QzYwLjIgMjg4IDAgMzQ4LjIgMCA0MjIuNFY0NjRjMCAyNi41IDIxLjUgNDggNDggNDhoMzUyYzI2LjUgMCA0OC0yMS41IDQ4LTQ4di00MS42YzAtNzQuMi02MC4yLTEzNC40LTEzNC40LTEzNC40eiIgY2xhc3M9IiI+PC9wYXRoPjwvc3ZnPgo=' }\" alt=\"Image Description\">\n <div class=\"media-body g-brd-around g-brd-gray-light-v4 g-pa-30 mb-3\">\n <div class=\"g-mb-15\">\n <h5 class=\"d-md-flex align-items-center h5 g-color-gray-dark-v1 mb-0\">\n <span class=\"d-block\">${$.fn.dataTable.render.text().display(comment.author.name)}</span>\n ${ post.author._id === comment.author._id ? '<span class=\"u-label g-bg-info g-rounded-3 u-label-info d-block ml-auto mt-2 mt-md-0\">글쓴이</span>' : '' }\n ${ TOTAL_LOCALS._SESSION._id === comment.author._id ? '<a class=\"u-tags-v1 g-font-size-12 g-brd-around g-brd-gray-light-v4 g-bg-red--hover g-brd-red--hover g-color-black-opacity-0_8 g-color-white--hover rounded g-py-6 g-px-15 ml-md-2 mt-2 mt-md-0 d-block text-center\" href=\"#!\">댓글 삭제</a>' : '' }\n </h5>\n <span class=\"g-color-gray-dark-v4 g-font-size-12\">${moment(comment.createdAt).format('YYYY-MM-DD HH:mm:ss')}</span>\n </div>\n\n <p>${$.fn.dataTable.render.text().display(comment.comment)}</p>\n </div>\n</div>`;\n });\n\n }\n else\n {\n $commentItems.text('댓글을 읽을 권한이 없습니다.');\n }\n\n component.$viewerCommentList.append($commentItems);\n // 댓글 입력 폼 초기화\n component.$viewerCommentForm.empty();\n\n // 사용자 쓰기 권한 체크\n if ( component.allowedPermission.indexOf(CONSTANTS.PERMISSIONS.POST.COMMENT.WRITABLE) >= 0 )\n {\n component.$viewerCommentForm.append(`\n <div class=\"mb-2\">\n <textarea class=\"form-control g-bg-secondary g-brd-gray-light-v4 g-brd-primary--focus g-resize-none rounded-3 g-py-13 g-px-15 input-comment\" rows=\"5\" placeholder=\"댓글을 입력해주세요...\"></textarea>\n </div>\n\n <div class=\"d-flex align-items-center\">\n <button class=\"btn u-btn-primary g-font-weight-600 g-font-size-12 text-uppercase my-auto ml-auto create-comment\" type=\"button\" role=\"button\">댓글 등록</button>\n </div>`);\n\n $('button.create-comment', component.$viewerCommentForm).on('click', async function($event){\n let $inputComment = $('textarea.input-comment', component.$viewerCommentForm);\n let commentData = {\n comment: $inputComment.val(),\n };\n\n // 데이터 유효성 검사\n if ( !commentData.comment )\n {\n component.$postSnackbar.$com.failed('댓글을 입력해주세요.');\n return $inputComment.focus();\n }\n\n try\n {\n let createdComment = await $.ajax({\n url: `/api/posts/${component.postGroupId}/${post._id}/comments`, method: 'post', headers: { 'Content-Type': 'application/json' },\n data: JSON.stringify(commentData),\n });\n\n component.$postSnackbar.$com.success('등록되었습니다.');\n }\n catch(err)\n {\n\n }\n });\n }\n else\n {\n component.$viewerCommentForm.addClass('d-none');\n }\n }\n };\n\n component._changeHash = function(postId){\n let hash = location.hash;\n\n if ( !hash || hash === '#!' ) return location.hash = `${component.instantID}=${encodeURIComponent(postId)}`;\n\n let appended = false;\n\n hash = hash.substr(1).split('&');\n\n for ( let i = 0, ilen = hash.length; i < ilen; i++ )\n {\n if ( hash[i].split('=')[0] === component.instantID )\n {\n hash[i] = `${component.instantID}=${encodeURIComponent(postId)}`;\n appended = true;\n }\n }\n\n if ( !appended ) hash.push(`${component.instantID}=${encodeURIComponent(postId)}`);\n\n location.hash = hash.join('&');\n };\n }, [\n '/libs/font-awesome/5.9.0/css/all.min.css',\n\n '/libs/jquery-datatables/1.10.19/css/dataTables.bootstrap4.min.css',\n '/libs/jquery-datatables/1.10.19/js/jquery.dataTables.min.js',\n '/libs/jquery-datatables/1.10.19/js/dataTables.bootstrap4.min.js',\n\n '/libs/jquery-datatables-buttons/1.5.4/css/buttons.bootstrap4.min.css',\n '/libs/jquery-datatables-buttons/1.5.4/js/dataTables.buttons.min.js',\n '/libs/jquery-datatables-buttons/1.5.4/js/buttons.bootstrap4.min.js',\n\n '/libs/jquery-datatables-renderer-ellipsis/1.10.19/ellipsis.js',\n '/libs/jquery-datatables-processing/1.10.19/processing.js',\n\n '/libs/KaTeX/0.10.0/katex.min.css',\n '/libs/KaTeX/0.10.0/katex.min.js',\n\n '/libs/highlight.js/9.14.2/styles/atom-one-dark.min.css',\n '/libs/highlight.js/9.14.2/highlight.min.js',\n\n '/libs/quill/1.3.6/quill.snow.min.css',\n '/libs/quill/1.3.6/quill.min.js',\n '/libs/quill-image-drop-module/1.0.3/image-drop.min.js',\n '/libs/quill-image-resize-module/3.0.0/image-resize.min.js',\n ]);\n</script>\n\n<!-- Hover Rows -->\n<div class=\"g-mb-30 basics-main-bbs-v1 CMS_type_post\" data-jc=\"basics-main-bbs-v1\">\n <h2 class=\"g-font-size-24 p-3 mb-0 basics-main-bbs-v1-title\">\n <i class=\"fa fa-comments-o g-mr-5\"></i>\n <span class=\"post-group-name\">자유게시판</span>\n </h2>\n\n <div class=\"card-header d-none\">\n <ul class=\"nav nav-tabs card-header-tabs\">\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-list active\" id=\"post-list-tab\" data-toggle=\"tab\" href=\"#post-list\" role=\"tab\" aria-controls=\"post-list\" aria-selected=\"true\">글목록</a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-read\" id=\"post-read\" data-toggle=\"tab\" href=\"#post-read\" role=\"tab\" aria-controls=\"post-read\" aria-selected=\"true\">글읽기</a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link tab-post-editor\" id=\"post-write-tab\" data-toggle=\"tab\" href=\"#post-write\" role=\"tab\" aria-controls=\"post-write\" aria-selected=\"false\">글쓰기</a>\n </li>\n </ul>\n </div>\n\n <div class=\"p-0 card-body tab-content\">\n <div class=\"d-flex p-3 init-spinner text-center CMS_hidden\">\n <div class=\"spinner-icon g-width-80 my-auto ml-auto\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\"><path fill=\"none\" d=\"M82 50A32 32 0 1 1 23.533421623214014 32.01333190873183 L21.71572875253809 21.7157287525381 L32.013331908731814 23.53342162321403 A32 32 0 0 1 82 50\" stroke-width=\"4\" stroke=\"#337ab7\"></path><circle cx=\"50\" cy=\"50\" fill=\"none\" stroke-linecap=\"round\" r=\"22\" stroke-width=\"4\" stroke=\"#337ab7\" stroke-dasharray=\"34.55751918948772 34.55751918948772\" transform=\"rotate(215.429 50 50)\"><animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform></circle></svg>\n </div>\n <span class=\"my-auto ml-3 mr-auto g-font-size-28\">글 불러오는 중...</span>\n </div>\n\n <div class=\"post-list-tab-content tab-pane fade show active rounded\" id=\"post-list\" role=\"tabpanel\" aria-labelledby=\"post-list-tab\">\n <table class=\"table u-table--v2 table-striped text-center g-mb-50 post-list\" style=\"width:100%\"></table>\n </div>\n\n <div class=\"post-read-content tab-pane fade rounded border border-gray\" id=\"post-read\" role=\"tabpanel\" aria-labelledby=\"post-read\">\n <div class=\"post-viewer-metadata\"></div>\n <div class=\"post-viewer-scroll-container CMS_hidden\">\n <div class=\"post-viewer\"></div>\n </div>\n\n <div class=\"border-top border-bottom post-viewer-attachments\">\n <h6 class=\"m-0 p-2\"><i class=\"fas fa-paperclip\"></i> 첨부파일</h6>\n <ul class=\"m-0 px-0 py-2 post-viewer-attachment-list\"></ul>\n </div>\n\n <div class=\"post-viewer-controls border-bottom p-2 d-flex CMS_hidden\"></div>\n\n <div class=\"post-viewer-comments\">\n <h6 class=\"m-0 p-2\"><i class=\"fas fa-comments\"></i> 댓글</h6>\n <div class=\"m-0 py-2 px-4 border-bottom post-viewer-comment-list\"></div>\n <div class=\"m-0 p-2 post-viewer-comment-form\"></div>\n </div>\n </div>\n\n <div class=\"post-editor-tab-content tab-pane fade rounded border border-gray\" id=\"post-write\" role=\"tabpanel\" aria-labelledby=\"post-write-tab\">\n <div class=\"post-editor-metadata-form\"></div>\n <div class=\"post-editor-toolbar CMS_hidden\">\n <span class=\"ql-formats\">\n <select class=\"ql-font\"></select>\n <select class=\"ql-size\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-bold\"></button>\n <button class=\"ql-italic\"></button>\n <button class=\"ql-underline\"></button>\n <button class=\"ql-strike\"></button>\n </span>\n <span class=\"ql-formats\">\n <select class=\"ql-color\"></select>\n <select class=\"ql-background\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-script\" value=\"sub\"></button>\n <button class=\"ql-script\" value=\"super\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-header\" value=\"1\"></button>\n <button class=\"ql-header\" value=\"2\"></button>\n <button class=\"ql-blockquote\"></button>\n <button class=\"ql-code-block\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-list\" value=\"ordered\"></button>\n <button class=\"ql-list\" value=\"bullet\"></button>\n <button class=\"ql-indent\" value=\"-1\"></button>\n <button class=\"ql-indent\" value=\"+1\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-direction\" value=\"rtl\"></button>\n <select class=\"ql-align\"></select>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-link\"></button>\n <button class=\"ql-image\"></button>\n <button class=\"ql-video\"></button>\n <button class=\"ql-formula\"></button>\n </span>\n <span class=\"ql-formats\">\n <button class=\"ql-clean\"></button>\n </span>\n </div>\n\n <div class=\"post-editor-scroll-container CMS_hidden\">\n <div class=\"post-editor\"></div>\n </div>\n\n\n <div class=\"border-bottom post-editor-attachments\">\n <h6 class=\"border-bottom m-0 p-2\"><i class=\"fas fa-paperclip\"></i> 첨부파일</h6>\n <ul class=\"m-0 px-0 py-2 post-editor-attachments-list\"></ul>\n </div>\n\n <div class=\"post-editor-controls d-flex p-2 CMS_hidden\"></div>\n </div>\n <div data-jc=\"contrib-snackbar\" class=\"post-snackbar\"></div>\n <div data-jc=\"contrib-confirm\" class=\"post-confirm\"></div>\n </div>\n <div class=\"component-blocker position-absolute d-none\">\n <div class=\"progress g-height-25 position-absolute d-none\">\n <div class=\"progress-bar progress-bar-striped progress-bar-animated\" role=\"progressbar\" aria-valuenow=\"0\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div>\n </div>\n <div class=\"blocker-message position-absolute d-flex\">\n <div class=\"my-auto ml-auto mr-3 g-width-50\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\"><path fill=\"none\" d=\"M82 50A32 32 0 1 1 23.533421623214014 32.01333190873183 L21.71572875253809 21.7157287525381 L32.013331908731814 23.53342162321403 A32 32 0 0 1 82 50\" stroke-width=\"4\" stroke=\"#337ab7\"></path><circle cx=\"50\" cy=\"50\" fill=\"none\" stroke-linecap=\"round\" r=\"22\" stroke-width=\"4\" stroke=\"#337ab7\" stroke-dasharray=\"34.55751918948772 34.55751918948772\" transform=\"rotate(215.429 50 50)\"><animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform></circle></svg>\n </div>\n <span class=\"message my-auto mr-auto g-font-size-28\"></span>\n </div>\n </div>\n</div>\n<!-- End Hover Rows -->","datecreated":"2019-06-25T01:00:34.287Z","picture":"","icon":"","category":"Basics","dateupdated":"2019-06-25T01:00:59.113Z"}