1let robots_css = ` 2 .robots-button {all: unset; display: inline-block; background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-align: center; text-decoration: none; line-height: normal; -webkit-appearance: none; -moz-appearance: none;} 3 .robots-svg:not(.robots-form__itemCancel):not(.robots-form__itemConfirm) {width: 15px;height: 15px;} 4 .robots-divider {width: 100%;height: 1px;background-color: #e6e8ea;} 5 6 .robots-background {box-sizing: border-box;display: none;position: fixed;top: 50%; left: 50%;transform: translate(-50%, -50%);width: 100%;height: 100%;opacity: 0.9;background-color: #eeeff3;z-index: 9998;} 7 .robots-modal > :nth-child(1 of .robots-divider) {position: sticky;top: 130px;z-index: 10000;} 8 .robots-modal > :nth-child(2 of .robots-divider) {position: sticky;top: 181px;z-index: 10000;} 9 .robots-modal > :nth-child(3 of .robots-divider) {position: sticky;top: 182px;height: 30px;background: #fff;z-index: 10000;} 10 .robots-modal {box-sizing: border-box;display: none;position: fixed;top: 50%; left: 50%;transform: translate(-50%, -50%);width: 900px;height: 100%;background-color: #fff;font-family: 'Noto Sans KR';z-index: 9999;overflow-y: auto;-ms-overflow-style: none;} 11 .robots-modal::-webkit-scrollbar {width: 3px;} 12 .robots-modal::-webkit-scrollbar-thumb {background-color: #d1d3d5;} 13 .robots-modal::-webkit-scrollbar-track {background-color: #f3f4f5;} 14 .robots-modal .tab-content {display: none;} 15 .robots-modal .tab-content.active {display: block;} 16 17 .robots-header {padding: 24px 40px 24px;position: sticky;top: 1px;z-index: 10000;background: #fff;height: 142px;border-bottom: 1px solid #e6e8ea;} 18 .robots-header2 {display:flex; justify-content: space-between;} 19 .robots-header__title {height: 35px;font-family: 'Noto Sans KR';font-size: 24px;font-weight: normal;font-stretch: normal;font-style: normal;line-height: 1.46;letter-spacing: normal;text-align: left;color: #222428;margin-bottom:20px;} 20 .robots-header__title-explain {line-height: 1.63;font-size: 12px;color: #696c71;background: #fff;} 21 .robots-header__btns {display: flex;justify-content: space-between;} 22 .robots-header__btn {width: 120px;height: 40px;border-radius: 3px;border: none;color: #696c71;} 23 .robots-header__btn--close {background-color: #f5f5f5;margin-right:10px;} 24 .robots-header__btn--save {background-color: #4789e7;color: #fff;} 25 26 .robots-site-menu {border-bottom:1px solid #e6e8ea; background-color:#fff; padding: 0 40px; position: sticky; top: 143px; z-index:10000; height: 50px;} 27 .robots-site-menu ul {display: flex;} 28 .robots-site-menu li {display: inline-block; position:relative; padding:0;} 29 .robots-site-menu li a {padding:10px 15px; font-size:14px; color:#222428; min-width:104px; text-align: center; box-sizing: border-box; line-height: 1.43; letter-spacing: -0.35px; height: 50px; display: flex; justify-content: center; align-items: center;} 30 .robots-site-menu li.active a {border-bottom:3px solid #4789e7; color:#222428;} 31 .robots-site-menu li:not(.active) a {opacity: 0.3;} 32 .robots-site-menu li:hover:not(.active) a {border-bottom:3px solid rgba(107,137,231, .15); color:#222428; opacity: 1;} 33 .robots-site-menu .robots-site-menu-container:before, .site-menu .container:after {display:block; content:none;} 34 35 #robots-content .robots-toggle {display: flex;align-items: center;width: 100%;height: 50px;padding: 0 40px;position: sticky;top: 193px;z-index: 10000;background: white;border-bottom: 1px solid #e6e8ea;} 36 #robots-content .robots-toggle__switch {position: relative;display: inline-block;width: 34px;height: 18px;} 37 #robots-content .robots-toggle__switch input {display: none;} 38 #robots-content .robots-toggle__slider {position: absolute;cursor: pointer;background-color: var(--color-bg-disabled, #d6d9dc);border: solid 1px var(--color-content-placeholder, #b4b5b5);border-radius: 50px;width: 34px;height: 18px;transition: background-color 0.2s ease, border-color 0.2s ease;} 39 #robots-content .robots-toggle__slider::before {position: absolute;content: "";height: 16px;width: 16px;background-color: #fff;border-radius: 50%;transition: transform 0.2s ease;left: 0;top: 0;} 40 #robots-content .robots-toggle__slider:hover {border: solid 1px #adcbf3;background-color: #c3dbfc;} 41 #robots-content .robots-toggle__switch input:checked + .robots-toggle__slider {background-color: #3b89ff;border: 1px solid #0e62cd;} 42 #robots-content .robots-toggle__switch input:checked + .robots-toggle__slider::before {transform: translateX(16px);} 43 #robots-content .robots-toggle__switch input:checked + .robots-toggle__slider:hover {background-color: #1b6eeb;border: 1px solid #0759c2;} 44 #robots-content .robots-toggle div {display: flex;align-items: center;width: 164px;} 45 #robots-content .robots-toggle div > span {font-family: 'Noto Sans KR';font-size: 13px;font-weight: normal;font-stretch: normal;font-style: normal;line-height: 1.46;letter-spacing: -0.33px;text-align: left;margin-right: 10px;} 46 47 #robots-content .robots-form {display: flex;flex-direction: column;padding: 0 40px;margin-top: 2px;} 48 #robots-content .robots-form table {width: 100%;border-radius: 3px;border: solid 1px #e6e8ea;border-collapse: collapse;} 49 #robots-content .robots-form table tr {box-sizing: border-box;width: 100%;border-bottom: solid 1px #e6e8ea;} 50 #robots-content .robots-form table th {padding: 10px 0 11px 0;height: 19px;font-family: 'Noto Sans KR';font-size: 13px;font-weight: normal;font-stretch: normal;font-style: normal;line-height: 1.46;letter-spacing: -0.13px;} 51 #robots-content .robots-form table th:first-child, 52 #robots-content .robots-form table td:first-child {width: 38px;text-align: center;vertical-align: middle;position: relative;} 53 #robots-content .robots-form table th:nth-child(2), 54 #robots-content .robots-form table td:nth-child(2) {width: 50px;text-align: center;} 55 #robots-content .robots-form table th:nth-child(3), 56 #robots-content .robots-form table td:nth-child(3) {width: 236px;} 57 #robots-content .robots-form table th:nth-child(4), 58 #robots-content .robots-form table td:nth-child(4) {width: 122px;} 59 #robots-content .robots-form table th:last-child, 60 #robots-content .robots-form table td:last-child {width: auto;} 61 #robots-content .robots-form table td {height: 40px;padding: 0;} 62 #robots-content .robots-form table tbody tr.ui-sortable-helper {display: table;table-layout: fixed;background-color: #fff;box-sizing: border-box;} 63 #robots-content .robots-form table tbody tr.ui-sortable-helper td, 64 #robots-content .robots-form table tbody tr.ui-sortable-helper input {box-sizing: border-box;} 65 #robots-content .robots-form__dragHandle {opacity: 0;transition: opacity 0.5s;cursor: move;display: block;
65width: 20px;height: 20px;margin: 0 auto;fill: #949fab;} 66 #robots-content .robots-form__dragHandle:hover {fill: #4789E7;} 67 #robots-content .robots-form table tr:hover .robots-form__dragHandle {opacity: 1;} 68 #robots-content .robots-form table tbody tr:hover{outline: 1px solid #4789e7;} 69 #robots-content .robots-form table td:nth-child(3) input {color: var(--color-content-secondary);width: 90%;height: 30px;border-radius: 3px;border: solid 1px #dbdcdf;padding: 5px;} 70 #robots-content .robots-form table td:last-child {display: grid;grid-template-columns: minmax(0, 1fr) 32px 32px;align-items: center;column-gap: 8px;padding: 4px 10px;} 71 #robots-content .robots-form table td:last-child input {color: var(--color-content-secondary);width: 100%;height: 30px;border-radius: 3px;border: solid 1px #dbdcdf;padding: 6px 10px;} 72 #robots-content .robots-form table td:last-child button {position: static;width: 32px;height: 32px;display: flex;justify-content: center;align-items: center;} 73 #robots-content .robots-form table td:last-child .robots-form__cancelBtn {background-color: transparent;} 74 #robots-content .robots-form table td:last-child .robots-form__confirmBtn {border-radius: 3px;background-color: #4789e7;fill: #fff;transition: background-color 0.2s ease;} 75 #robots-content .robots-form table td:last-child .robots-form__confirmBtn:hover {background-color: #5997F1;} 76 #robots-content .robots-form table input:focus {border-color: #4789e7;} 77 #robots-content .robots-form table input[name="user-agent"]:not([readonly]):hover, 78 #robots-content .robots-form table input[name="value"]:not([readonly]):hover {border-color: #9bc5ff;} 79 #robots-content .robots-form table input[readonly] {pointer-events: none;background-color: #fafafa;color: #b1b3b7 !important;} 80 #robots-content .robots-form table select {width: 85%;height: 30px;border-radius: 3px;border: solid 1px #e6e8ea;background-color: #fff;} 81 #robots-content .robots-form table select:focus {border-color: #4789e7;} 82 #robots-content .robots-form table .bootstrap-select {width: 85% !important;height: 32px; cursor:default;} 83 #robots-content .robots-form table .bootstrap-select > .dropdown-toggle {height: 32px;padding: 6px 10px;border-radius: 3px;border: solid 1px var(--color-line-primary);background-color: var(--color-bg-white);font-size: 14px;line-height: 1.5;color: #222428;} 84 #robots-content .robots-form table .bootstrap-select:not(.disabled) .filter-option, 85 #robots-content .robots-form table .bootstrap-select:not(.disabled) .filter-option-inner-inner {font-size: 14px;line-height: 1.5;color: #696c71;} 86 #robots-content .robots-form table .bootstrap-select.disabled .filter-option, 87 #robots-content .robots-form table .bootstrap-select.disabled .filter-option-inner-inner {font-size: 14px;line-height: 1.5;color: var(--color-content-placeholder);} 88 #robots-content .robots-form table .bootstrap-select.disabled > .dropdown-toggle, 89 #robots-content .robots-form table .bootstrap-select > .dropdown-toggle.disabled {padding: 6px 10px;border-radius: 3px;border: solid 1px var(--color-line-primary);background-color: var(--color-bg-white);} 90 #robots-content .robots-form table .bootstrap-select .btn.btn-default.dropdown-toggle.disabled {background-color: #ffffff !important;border: solid 1px var(--color-line-primary) !important;opacity: 1;} 91 #robots-content .robots-form table .bootstrap-select.open > .dropdown-toggle, 92 #robots-content .robots-form table .dropdown.bootstrap-select.bs3.open > .dropdown-toggle {border: solid 1px var(--color-brand-primary);background-color: var(--color-bg-white);} 93 #robots-content .robots-form table .bootstrap-select > .dropdown-toggle .bs-caret .caret {border-top-color: #696c71;} 94 #robots-content .robots-form table .bootstrap-select > .dropdown-toggle:focus, 95 #robots-content .robots-form table .bootstrap-select > .dropdown-toggle:active, 96 #robots-content .robots-form table .bootstrap-select.open > .dropdown-toggle, 97 #robots-content .robots-form table .bootstrap-select.open > .dropdown-toggle:focus, 98 #robots-content .robots-form table .bootstrap-select.open > .dropdown-toggle:active {outline: none !important;box-shadow: none !important;} 99 #robots-content .robots-form table .bootstrap-select.open > .dropdown-toggle .bs-caret .caret, 100 #robots-content .robots-form table .dropdown.bootstrap-select.bs3.open > .dropdown-toggle .bs-caret .caret {border-top: 0;border-bottom: 4px dashed #4789e7;border-right: 4px solid transparent;border-left: 4px solid transparent;} 101 .robots-modal .bootstrap-select .dropdown-menu > li > a {padding: 7px 10px;font-size: 14px;line-height: 1.5;} 102 .robots-modal .bootstrap-select .dropdown-menu > li:not(.selected):not(.active) > a {color: #696c71;} 103 .robots-modal .bootstrap-select .dropdown-menu > li > a:hover, 104 .robots-modal .bootstrap-select .dropdown-menu > li > a:focus {background-color: #fafafa;} 105 .robots-modal .bootstrap-select .dropdown-menu > li.selected > a, 106 .robots-modal .bootstrap-select .dropdown-menu > li.active > a {background-color: var(--color-bg-white);color: var(--color-brand-primary);} 107 .robots-modal .bootstrap-select .dropdown-menu > li.selected > a:hover, 108 .robots-modal .bootstrap-select .dropdown-menu > li.active > a:hover, 109 .robots-modal .bootstrap-select .dropdown-menu > li.selected > a:focus, 110 .robots-modal .bootstrap-select .dropdown-menu > li.active > a:focus {background-color: #fafafa;} 111 .robots-modal .bootstrap-select .dropdown-menu.open {z-index: 10002;border-radius: 3px;box-shadow: 0 10px 20px 0 rgba(0, 0, 0, 0.1);border: solid 1px var(--color-line-primary);background-color: var(--color-bg-white);} 112 #robots-content .robots-form__itemModify, 113 #robots-content .robots-form__itemConfirm, 114 #robots-content .robots-form__itemDelete {cursor: pointer;position: static;justify-self: center;align-self: center;margin: 0;} 115 #robots-content .robots-form__itemModify, 116 #robots-content .robots-form__itemDelete {width: 24px;height: 24px;fill: #b1b3b7;} 117 #robots-content .robots-form__itemCancel {fill: #b1b3b7;} 118 #robots-content .robots-form__itemCancel:hover, 119 #robots-content .robots-form__itemModify:hover, 120 #robots-content .robots-form__itemDelete:hover {fill: #696C71 !important;} 121 #robots-content .robots-form-btns {display: flex;margin-top: 15.5px;margin-bottom: 25px;justify-content: space-between;} 122 #robots-content .robots-form-btns > div {display: flex;} 123 #robots-content .robots-form-btns__btn {display: inline-flex;align-items: center;justify-content: center;gap: 3px;
123height: 36px;border-radius: 3px;border: solid 1px var(--color-line-primary);background-color: var(--color-bg-white);color: #222428;fill: #222428;} 124 #robots-content .robots-form-btns__btn:hover {background-color: #FAFAFA;} 125 #robots-content .robots-form-btns__addItem, 126 #robots-content .robots-form-btns__loadFileBtn {width: 98px;} 127 #robots-content .robots-form-btns__resetItem {width: 82px;} 128 #robots-content .robots-form-btns__btn svg {width: 20px;height: 20px;flex: 0 0 20px;} 129 #robots-content .robots-form-btns > div > .robots-form-btns__addItem {margin-right: 10px;} 130 #robots-content .robots-form-btns__btn span {font-family: 'Noto Sans KR';font-size: 14px;font-weight: normal;font-stretch: normal;font-style: normal;line-height: 1;color: #222428;} 131 132 #robots-content .robots-tooltip + .tooltip > .tooltip-inner {width: 200px;white-space: normal; border-color: #b1b3b7;} 133 #robots-content .robots-preview {display: flex;flex-direction: column;padding: 0 40px 30px 40px;} 134 #robots-content .robots-preview__title {width: 100%;height: 20px;font-family: 'Noto Sans KR';color: #222428;margin: 25px 0 15px 0;font-size: 14px;} 135 #robots-content .robots-preview__content {width: 100%;height: 554px;border: solid 1px var(--color-line-primary); background-color: var(--color-bg-hover-primary);resize: none;padding: 20px;color: #696c71;letter-spacing: -0.13px;line-height: 1.5;font-size: 14px;overflow-y: auto;-ms-overflow-style: none;} 136 #robots-content .robots-preview__content::-webkit-scrollbar {width: 3px;} 137 #robots-content .robots-preview__content::-webkit-scrollbar-thumb {background-color: #d1d3d5; cursor: pointer;} 138 #robots-content .robots-preview__content::-webkit-scrollbar-track {background-color: #f3f4f5;} 139 #robots-content .sortable-placeholder {background-color: #f0f0f0;height: 40px;} 140 141 #sitemap-content .sitemap-btns {padding: 30px 40px 0 40px;} 142 #sitemap-content .sitemap-btns .sitemap-btn {width: 104px; height: 30px; border-radius:3px; border:1px solid #e6e8ea; background-color: #fdfdfd;} 143 #sitemap-content .sitemap-btns .sitemap-btn .robots-svg {transform: translateY(3px);} 144 #sitemap-content .sitemap-btns .sitemap-btn#sitemap-btn__upload {margin-right: 10px;} 145 #sitemap-content .sitemap-btns .sitemap-btn#sitemap-btn__upload + .tooltip > .tooltip-inner {max-width: 130px;white-space: normal; border-color: #b1b3b7;} 146 #sitemap-content .sitemap-preview {display: flex;flex-direction: column;padding: 0 40px 30px 40px;} 147 #sitemap-content .sitemap-preview__title {width: 100%;height: 20px;font-family: 'Noto Sans KR';color: #222428;margin: 25px 0 15px 0;font-size: 14px;} 148 #sitemap-content .sitemap-preview__content {width: 100%;height: 554px;border: solid 1px #e6e8ea;background-color: #fdfdfd;resize: none;padding: 15px 20px;color: #222428;letter-spacing: -0.13px;line-height: 1.46;font-size: 13px;overflow-y: auto;-ms-overflow-style: none;} 149 #sitemap-content .sitemap-preview__content::-webkit-scrollbar {width: 3px;} 150 #sitemap-content .sitemap-preview__content::-webkit-scrollbar-thumb {background-color: #d1d3d5; cursor: pointer;} 151 #sitemap-content .sitemap-preview__content::-webkit-scrollbar-track {background-color: #f3f4f5;} 152 153 @media (max-width:900px) { 154 .robots-modal {width: 100%;} 155 } 156`; 157 158(function ($) { 159 $.seoConfig = { 160 initialState: { 161 searchBotSwitch: false, 162 aiBotSwitch: false, 163 robotsText: '', 164 sitemapText: '' 165 }, 166 } 167 168 $.robots = { 169 isOpen: false, 170 robotsText: '', 171 robotsRules: {}, 172 siteMaps: new Set(), 173 domain: '', 174 itemIndex: 1, 175 searchBots: [ 176 "AhrefsBot", "Applebot", "archive.org_bot", "Baiduspider", "Bingbot", 177 "CCBot", "DaumBot", "Daumoa", "DotBot", "Exabot", "facebot", 178 "Gigablast", "Googlebot", "ia_archiver", "LinkedInBot", "Magpie-crawler", 179 "MegaIndex", "MJ12bot", "MojeekBot", "NaverBot", "Pinterestbot", 180 "Qwantify", "SerpstatBot", "Slurp", "Twitterbot", "UpdownerBot", 181 "VelenPublicWebCrawler", "Yeti", "ZumBot" 182 ], 183 aiBots: [ 184 "AIbot", "Amazonbot", "Bytespider", "ChatGPT-User", "ClaudeBot", 185 "Claude-Web", "Cohere-ai", "DeepSeekBot", "Diffbot", "FacebookBot", 186 "GroqBot", "GPTBot", "Huggingfacebot", "MetaBot", "MistralAI-User/1.0", 187 "OpenAI-User", "OpenRetriever", "Omigili", "OmigiliBot", "PalmBot", 188 "PerplexityBot", "Quora-Quivr", "YouBot" 189 ], 190 daumWebmaster: '', 191 open: function (options = {}) { 192 const { 193 searchBot = false, 194 aiBot = false, 195 isToggle = false, 196 } = options; 197 this.getDaumWebmaster(); 198 // ì¤ë³µì¤í ì²´í¬ 199 if($.robots.isOpen) return; 200 $.robots.isOpen = true; 201
202 $.processON('robots íì¼ì ê°ì ¸ì¤ëì¤...'); 203 $.ajax({ 204 url: '/template/open_robots', 205 type: 'POST', 206 data: { 207 'user_dir': RPATH, 208 'sid': SID, 209 'host': HOST, 210 211 }, 212 success: function (response) { 213 $.processOFF(); 214 $.robots.display(searchBot, aiBot, isToggle); 215 $.robots.renderInitialRobotsRules(response.content); // robots.txt ì´ê¸° ë ëë§ 216 $.robots.domain = response.domain; 217 if (response.source === "default") { 218 $.robots.siteMaps.add(response.sitemap); // Storageì robots.txt ìë¤ë©´ sitemap ì¶ê° 219 } else if (response.source === "file") { // 2025.09.09: ì¸ë¶ ëë©ì¸ ì°ê²° + robots.txt ìì í ê³ ê°ì Sitemapì´ ê¸°ë³¸ ëë©ì¸ì´ê¸° ë문ì ì´ë¥¼ ì¸ë¶ ëë©ì¸ì¼ë¡ ë³ê²½í기 ìí´ ì¶ê°í¨ 220 let lines = response.content.split('\n'); 221 const targetSitemap = ($.robots.domain || "").replace(/\/$/, '') + '/user-sitemap'; 222 for (let line of lines) { 223 line = line.trim(); 224 225 if (!line.toLowerCase().startsWith('sitemap:') || !line.includes('creatorlink.net')) continue; 226 227 const url = line.replace(/^sitemap:\s*/i, '').trim(); // "Sitemap: " ì ê±°í´ì ìì URLì ì»ì 228 229 if (url === targetSitemap) break; // ì¸ë¶ ëë©ì¸ ì°ê²° ìí기 ë문ì ëë©ì¸ì´ 기본 ëë©ì¸ì´ë¼ë©´ 230 231 const arr = [...$.robots.siteMaps]; 232 const idx = arr.findIndex(v => v.replace(/\/$/, '') === url.replace(/\/$/, '')); // Setì ë°°ì´ë¡ ë°ê¿ ìì¹(ì¸ë±ì¤)를 ì°¾ì ê°ì ì리ì êµì²´ 233 234 if (idx !== -1) { 235 arr[idx] = targetSitemap; 236 $.robots.siteMaps = new Set(arr); 237 } 238 } 239 } 240 $.robots.event(); 241 }, 242 error: function (xhr, status, error) { 243 alert('기존 ë°ì´í°ê° ì¡´ì¬íì§ ììµëë¤', error); 244 $.processOFF(); 245 } 246 }); 247 $.sitemap.init(); 248 // ì´ê¸° ìí ì ì¥ 249 setTimeout(() => { 250 $.seoConfig.initialState = { 251 searchBotSwitch: $('input[name="searchBot"]').prop('checked'), 252 aiBotSwitch: $('input[name="aiBot"]').prop('checked'), 253 robotsText: $.robots.robotsText || '', 254 sitemapText: $.sitemap.sitemapPreviewText || '' 255 }; 256 }, 500); 257 }, 258 event: function () { 259 // ì¬ì´í¸ë§µ íì¼ input change ì´ë²¤í¸ (í ë²ë§ ë±ë¡) 260 $(document) 261 .off('change', '#sitemap-content input[type="file"]') 262 .on('change', '#sitemap-content input[type="file"]', function(e) { 263 const file = e.target.files[0]; 264 if(!file) return; 265 266 // íì¥ì ì²´í¬ 267 if(!file.name.match(/\.xml$/i)){ 268 alert($.lang[LANG]['editor.sitemap.upload.error.filename-extension']); 269 $(this).val(''); 270 return; 271 } 272 // íì¼ëª 문ì ê²ì¦ (ì문 ëì문ì, ì«ì, íì´í, ë°ì¤, ì ë§ íì©) 273 if(!/^[a-zA-Z0-9._-]+$/.test(file.name)){ 274 alert($.lang[LANG]['editor.sitemap.upload.error.filename']); 275 $(this).val(''); 276 return; 277 } 278 $.sitemap.loadFile(e); 279 }); 280 // ì¬ì´í¸ë§µ ì ë¡ë ë²í¼ 281 $(document).off('click', '#sitemap-btn__upload').on('click', '#sitemap-btn__upload', function() { 282 const $btn = $(this); 283 $btn.tooltip('hide'); 284 $btn.blur(); 285 $('#sitemap-content input[type="file"]').click(); 286 }); 287 // ì¬ì´í¸ë§µ ì´ê¸°í ë²í¼ 288 $(document).on('click', '#sitemap-btn__reset', function() { 289 $.sitemap.reset(); 290 }); 291 // ë«ê¸° 292 $(document) 293 .off('click', '.robots-header__btn--close') 294 .on('click', '.robots-header__btn--close', function (e) { 295 $.robots.close(); 296 }); 297 // ì ì¥ 298 $(document) 299 .off('click', '.robots-header__btn--save') 300 .on('click', '.robots-header__btn--save', function () { 301 $.robots.save(); 302 // if($.sitemap.isFileUploaded) $.sitemap.save(); 303 $.sitemap.save(); 304 }); 305 // ë©ë´í 306 $(document).on('click', '.robots-site-menu li', function(e){ 307 e.preventDefault(); 308 let tabName = $(this).data('tab'); 309 $('.robots-site-menu li').removeClass('active'); 310 $('.tab-content').removeClass('active'); 311 $(this).addClass('active'); 312 $('#' + tabName + '-content').addClass('active'); 313 }); 314 // ê²ìë´ ì¤ìì¹ í´ë¦ ë°ì¸ë© 315 $(`.robots-toggle div:first-child input[type='checkbox']`).off('change').on('change', (e) => this.blockSearchBot(e.target.checked)); 316 // AIë´ ì¤ìì¹ í´ë¦ ë°ì¸ë© 317 $(`.robots-toggle div:nth-child(2) input[type='checkbox']`).off('change').on('change', (e) => this.blockAIBot(e.target.checked)); 318 // Confirm(ì²´í¬) ë²í¼ í´ë¦ ë°ì¸ë© 319 $(document) 320 .off('click', '.robots-form__itemConfirm') 321 .on('click', '.robots-form__itemConfirm', () => this.previewText()); 322 // ì·¨ìë²í¼ 323 $(document) 324 .off('click', '.robots-form__itemCancel') 325 .on('click', '.robots-form__itemCancel', (e) => { 326 $.robots.cancelItem(e); 327 }); 328 // íì¸ë²í¼ 329 $(document) 330 .off('click', '.robots-form__confirmBtn') 331 .on('click', '.robots-form__confirmBtn', (e) => { 332 $.robots.confirmItem(e); 333 }); 334 // ìì ë²í¼ 335 $(document) 336 .off('click', '.robots-form__itemModify') 337 .on('click', '.robots-form__itemModify', (e) => { 338 $.robots.modifyItem(e); 339 }); 340 // ìì ë²í¼ 341 $(document) 342 .off('click', '.robots-form__itemDelete') 343 .on('click', '.robots-form__itemDelete', (e) => { 344 $.robots.deleteItem(e); 345 }); 346 // í목ì¶ê° ë²í¼ 347 $('.robots-form-btns__addItem') 348 .off('click', '.robots-form-btns__addItem') 349 .on('click', () => { 350 $.robots.addItem(); 351 });
352 // ê°ì ¸ì¤ê¸° ë²í¼ 353 $('.robots-form-btns__loadFileBtn').on('click', function () { 354 const $fileInput = $(`.robots-form-btns input[type="file"]`); 355 $fileInput.click(); // í´ë¦ ì ì¨ê²¨ì§ input[type=file] í´ë¦ ì ë 356 // íì¼ ì í í ì²ë¦¬ 357 $fileInput.off('change').on('change', function (e) { 358 $.robots.loadFile(e); 359 }); 360 }); 361 // ì´ê¸°í ë²í¼ 362 $('.robots-form-btns__resetItem').on('click', function () { 363 if (confirm($.lang[LANG]['editor.robots.reset.message'])) { 364 $.robots.resetItem(); 365 } 366 }) 367 }, 368 display: function (searchBot, aiBot, isToggle) { 369 $('head').append(`<style data-robots-style>${robots_css}</style>`); 370 // $('head').append(`<script data-robots-script src="https://code.jquery.com/ui/1.13.0/jquery-ui.min.js"></script>`); 371 $('head').append(`<link data-robots-css rel="stylesheet" href="https://code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css" />`); 372 $('body').append(` 373 <div class="robots-background"></div> 374 <div class="robots-modal"> 375 <div class="robots-header"> 376 <div class="robots-header2"> 377 <span class="robots-header__title">` + $.lang[LANG]['editor.robots.advanced-settings'] + `</span> 378 <div class="robots-header__btns"> 379 <button class="robots-header__btn robots-header__btn--close">` + $.lang[LANG]['editor.robots.close'] + `</button> 380 <button class="robots-header__btn robots-header__btn--save">` + $.lang[LANG]['editor.robots.save'] + `</button> 381 </div> 382 </div> 383 <div class="robots-header__title-explain">` + $.lang[LANG]['editor.robots.explain'] + `</div> 384 </div> 385 386 <div class="robots-site-menu"> 387 <div class="robots-site-menu-container"> 388 <ul> 389 <li class="active" data-tab="robots"><a href="#">Robots.txt</a></li> 390 <li class="" data-tab="sitemap"><a href="#">Sitemap</a></li> 391 </ul> 392 </div> 393 </div> 394 395 <div class="tab-content active" id="robots-content"> 396 <div class="robots-toggle"> 397 <div> 398 <span>` + $.lang[LANG]['editor.robots.block.webcrawler'] + `</span> 399 <label class="robots-toggle__switch" style="margin-bottom: 0;"> 400 <input type="checkbox" name="searchBot" ${searchBot ? 'checked' : ''}> 401 <span class="robots-toggle__slider"></span> 402 </label> 403 </div> 404 <div> 405 <span>` + $.lang[LANG]['editor.robots.block.aibot'] + `</span> 406 <label class="robots-toggle__switch" style="margin-bottom: 0;"> 407 <input type="checkbox" name="aiBot" ${aiBot ? 'checked' : ''}> 408 <span class="robots-toggle__slider"></span> 409 </label> 410 </div> 411 </div> 412 <div style="height:30px; background-color: white; position:sticky; top: 243px; z-index: 10000"></div> 413 <div class="robots-form"> 414 <table> 415 <thead> 416 <tr> 417 <th></th> 418 <th>#</th> 419 <th>User Agent</th> 420 <th>Directive</th> 421 <th>Value</th> 422 </tr> 423 </thead> 424 <tbody> 425 </tbody> 426 </table> 427 <div class="robots-form-btns"> 428 <div> 429 <button class="robots-form-btns__btn robots-form-btns__addItem"> 430 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="M9.38 2.5v6.88H2.5v1.25h6.88v6.88h1.25v-6.88h6.88V9.38h-6.88V2.5z"/></svg> 431 <span>` + $.lang[LANG]['editor.robots.additem'] + `</span> 432 </button> 433 <button data-html="true" data-placement="bottom" data-toggle="tooltip" data-original-title="` + $.lang[LANG]['editor.robots.tooltip'] + `" class="robots-form-btns__btn robots-form-btns__loadFileBtn robots-tooltip"> 434 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><g data-name="그룹_6486"><path d="M12.38 3H7C5.55 3 4.38 4.18 4.38 5.62v8.75c0 1.45 1.18 2.62 2.62 2.62h6c1.45 0 2.62-1.18 2.62-2.62V6.25L12.37 3Zm-.13 1.64 1.73 1.73H13c-.41 0-.75-.34-.75-.75zm2.13 9.74c0 .76-.62 1.37-1.38 1.38H7c-.76 0-1.37-.62-1.38-1.38V5.62c0-.76.62-1.37 1.38-1.38h4v1.38c0 1.1.9 2 2 2h1.38v6.75Z"/></g></svg>
435 <span>` + $.lang[LANG]['editor.robots.import'] + `</span> 436 </button> 437 <input type="file" accept=".txt" style="display:none" /> 438 </div> 439 <div> 440 <button class="robots-form-btns__btn robots-form-btns__resetItem"> 441 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="M7.32 5.34c1.66-.95 3.7-.95 5.37 0l.63-1.08a6.68 6.68 0 0 0-6.62 0 6.61 6.61 0 0 0-3.31 5.73h-.88l1.5 2.5 1.5-2.5h-.88c0-1.92 1.03-3.69 2.69-4.65M16 7.49l-1.5 2.5h.88c0 1.92-1.03 3.69-2.69 4.65-1.66.95-3.71.95-5.37 0l-.63 1.08a6.62 6.62 0 0 0 9.05-2.44c.58-1 .88-2.14.88-3.3h.88L16 7.48Z"/></svg> 442 <span>` + $.lang[LANG]['editor.robots.reset'] + `</span> 443 </button> 444 </div> 445 </div> 446 </div> 447 <div class="robots-preview"> 448 <div class="robots-divider"></div> 449 <span class="robots-preview__title">` + $.lang[LANG]['editor.robots.preview'] + `</span> 450 <textarea class="robots-preview__content" readonly></textarea> 451 </div> 452 </div> 453 454 <div class="tab-content" id="sitemap-content"> 455 <div class="sitemap-btns"> 456 <button id="sitemap-btn__upload" class="sitemap-btn" data-html="true" data-placement="bottom" data-toggle="tooltip" data-original-title="` + $.lang[LANG]['editor.sitemap.upload.tooltip'] + `" class="sitemap-tooltip"> 457 <svg class="robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M13 9v3H3V9H2v4h12V9z"/><path d="m8 3-.71.71-2.83 2.83.71.71L7.5 4.92v5.09h1V4.92l2.33 2.33.71-.71-2.83-2.83z"/></svg> 458 <span>` + $.lang[LANG]['editor.sitemap.import'] + `</span> 459 </button> 460 <input type="file" accept=".xml" style="display:none" /> 461 <button id="sitemap-btn__reset" class="sitemap-btn"> 462 <svg class="robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M8.83 2.5c-2.46 0-4.62 1.63-5.29 4L2.48 5.09l-.8.6 2.11 2.8.8-.6 2-1.5-.6-.8-1.42 1.06a4.514 4.514 0 0 1 5.65-2.94c2.37.75 3.68 3.28 2.94 5.65a4.514 4.514 0 0 1-5.65 2.94c-.86-.27-1.61-.79-2.17-1.5l-.75.7a5.504 5.504 0 0 0 7.74.76c2.35-1.93 2.68-5.4.76-7.74a5.5 5.5 0 0 0-4.25-2.01"/></svg> 463 <span>` + $.lang[LANG]['editor.sitemap.reset'] + `</span> 464 </button> 465 </div> 466 <div class="sitemap-preview"> 467 <span class="sitemap-preview__title">` + $.lang[LANG]['editor.sitemap.preview'] + `</span> 468 <textarea class="sitemap-preview__content" readonly></textarea> 469 </div> 470 </div> 471 472 </div> 473 `); 474 requestAnimationFrame(() => { 475 setTimeout(() => { 476 $('[data-toggle="tooltip"]').tooltip(); 477 }, 0); 478 }); 479 setTimeout(function (e) { 480 if (!isToggle) { 481 $('.robots-background').show(); 482 $('.robots-modal').show(); 483 $('body').css('overflow', 'hidden'); 484 } 485 // $.robots.updateToggleStatus(); 486 $.robots.previewText(); 487 $.sitemap.previewText(); 488 $.robots.dragAndDrop(); 489 }, 50); 490 }, 491 renderInitialRobotsRules: function (content) { 492 const lines = content.split('\n'); 493 let currentAgent = null; 494 495 lines.forEach(line => { 496 line = line.trim(); 497 if (!line || line.startsWith('#')) return; 498 499 if (line.toLowerCase().startsWith('user-agent')) { 500 const idx = line.indexOf(':'); 501 currentAgent = $.robots.escapeHtml(line.slice(idx + 1).trim()); 502 if (!$.robots.robotsRules[currentAgent]) { 503 $.robots.robotsRules[currentAgent] = []; 504 } 505 return; 506 } 507 if (line.toLowerCase().startsWith('disallow:') || line.toLowerCase().startsWith('allow:')) { 508 if (!currentAgent) return; 509 const idx = line.indexOf(':'); 510 let directive = line.slice(0, idx).trim(); 511 let value = $.robots.escapeHtml(line.slice(idx + 1).trim()); 512 $.robots.robotsRules[currentAgent].push({ 513 directive: directive.trim(), 514 value: value.trim() 515 }); 516 return; 517 } 518 if (line.toLowerCase().startsWith('sitemap:')) { 519 const sitemapUrl = line.substring(line.indexOf(':') + 1).trim(); 520 $.robots.siteMaps.add(sitemapUrl); 521 } 522 }); 523 524 for (const userAgent in $.robots.robotsRules) { 525 const rules = $.robots.robotsRules[userAgent];
526 rules.forEach(rule => { 527 const [directive, value] = [rule.directive.toLowerCase(), rule.value]; 528 $.robots.addItemRow(userAgent, directive, value); 529 }); 530 } 531 }, 532 close: function () { 533 $.robots.isOpen = false; 534 // ì¶ê°ë head ìì ì ê±° 535 $('head style[data-robots-style]').remove(); 536 // $('head script[data-robots-script]').remove(); 537 $('head link[data-robots-css]').remove(); 538 539 $('.robots-modal').remove(); 540 $('.robots-background').remove(); 541 $('body').css('overflow', ''); // body ì¤í¬ë¡¤ë° íì 542 $.robots.robotsText = ''; 543 $.robots.robotsRules = {}; 544 $.robots.itemIndex = 1; 545 $.robots.siteMaps = new Set(); 546 547 // ì´ê¸° ìí ì´ê¸°í 548 $.seoConfig.initialState = { 549 searchBotSwitch: false, 550 aiBotSwitch: false, 551 robotsText: '', 552 sitemapText: '' 553 }; 554 }, 555 save: function () { 556 $.processON('robots.txt íì¼ì ì ì¥ì¤ì ëë¤...'); 557 $.ajax({ 558 url: '/template/save_robots', 559 type: 'POST', 560 data: { 561 'robots_text': $.robots.robotsText, 562 'user_dir': RPATH, 563 'sid': SID, 564 'host': HOST, 565 }, 566 success: function (r) { 567 $.processOFF(); 568 const isSearchBotToggleChecked = $(".robots-toggle input[name='searchBot']").prop('checked'); 569 const isAIBotToggleChecked = $(".robots-toggle input[name='aiBot']").prop('checked'); 570 $('#site-searchBot-click').prop('checked', isSearchBotToggleChecked); 571 $('#site-aiBot-click').prop('checked', isAIBotToggleChecked); 572 573 const searchBotChecked = $('#site-searchBot-click').prop('checked'); 574 const aiBotchecked = $('#site-aiBot-click').prop('checked'); 575 var settings = { 576 searchBotClick: searchBotChecked, 577 aiBotClick: aiBotchecked, 578 }; 579 580 $.post('/template/settings', { sid: SID, settings: JSON.stringify(settings), el: 'site_settings' }, function (data) { 581 checkError(data); 582 }, 'json'); 583 584 // ë³ê²½ ì¬í íì¸ í ë¡ê·¸ ì ì¥ 585 const currentState = { 586 searchBotSwitch: $('input[name="searchBot"]').prop('checked'), 587 aiBotSwitch: $('input[name="aiBot"]').prop('checked'), 588 robotsText: $.robots.robotsText || '', 589 sitemapText: $.sitemap.sitemapPreviewText || '' 590 }; 591 592 // ë³ê²½ë íëª©ë§ changes ê°ì²´ì ì¶ê° 593 const changes = { 594 anything_changed: false, 595 searchBotSwitch_changed: false, 596 aiBotSwitch_changed: false, 597 robotsText_changed: false, 598 sitemapText_changed: false 599 }; 600 601 // ê²ìë´ ë³ê²½ íì¸ 602 if ($.seoConfig.initialState.searchBotSwitch !== currentState.searchBotSwitch) { 603 changes.anything_changed = true; 604 changes.searchBotSwitch_changed = true; 605 changes.searchBotSwitch_before = $.seoConfig.initialState.searchBotSwitch ? 'ON' : 'OFF'; 606 changes.searchBotSwitch_after = currentState.searchBotSwitch ? 'ON' : 'OFF'; 607 } 608 609 // AIë´ ë³ê²½ íì¸ 610 if ($.seoConfig.initialState.aiBotSwitch !== currentState.aiBotSwitch) { 611 changes.anything_changed = true; 612 changes.aiBotSwitch_changed = true; 613 changes.aiBotSwitch_before = $.seoConfig.initialState.aiBotSwitch ? 'ON' : 'OFF'; 614 changes.aiBotSwitch_after = currentState.aiBotSwitch ? 'ON' : 'OFF'; 615 } 616 617 // robots.txt ë³ê²½ íì¸ 618 if ($.seoConfig.initialState.robotsText !== currentState.robotsText) { 619 changes.anything_changed = true; 620 changes.robotsText_changed = true; 621 changes.robotsText_before = $.seoConfig.initialState.robotsText; 622 changes.robotsText_after = currentState.robotsText; 623 } 624 625 // sitemap.xml ë³ê²½ íì¸ 626 if ($.seoConfig.initialState.sitemapText !== currentState.sitemapText) { 627 changes.anything_changed = true; 628 changes.sitemapText_changed = true; 629 changes.sitemapText_before = btoa(unescape(encodeURIComponent($.robots.escapeHtml($.seoConfig.initialState.sitemapText)))); 630 changes.sitemapText_after = btoa(unescape(encodeURIComponent($.robots.escapeHtml(currentState.sitemapText)))); 631 } 632 633 // ë¡ê·¸ ë¨ê¸°ê¸° 634 if(changes.anything_changed) { 635 setSiteLogs( 636 'config.settings.robots-sitemap', // action 637 'edit', // v(val) 638 changes, // obj 639 'config' // page 640 ); 641 } 642 643 $.robots.close(); 644 }, 645 error: function (xhr, status, error) { 646 alert(error); 647 } 648 }); 649 }, 650 blockSearchBot: function (on) { 651 if (on) {
652 this.searchBots.forEach(bot => { 653 if (!this.robotsRules[bot] || this.robotsRules[bot].length === 0) { 654 this.robotsRules[bot] = [{ directive: "Disallow", value: "/" }]; 655 $.robots.addItemRow(bot, 'disallow', '/'); 656 } 657 }); 658 // $('input[name="searchBot"]').prop('checked', true); 659 660 } else { 661 this.searchBots.forEach(bot => { 662 delete this.robotsRules[bot]; 663 }); 664 $.robots.deleteItemRow(this.searchBots); 665 // $('input[name="searchBot"]').prop('checked', false); 666 } 667 this.previewText(); 668 }, 669 blockAIBot: function (on) { 670 if (on) { 671 this.aiBots.forEach(bot => { 672 if (!this.robotsRules[bot] || this.robotsRules[bot].length === 0) { 673 this.robotsRules[bot] = [{ directive: "Disallow", value: "/" }]; 674 $.robots.addItemRow(bot, 'disallow', '/'); 675 } 676 }); 677 // $('input[name="aiBot"]').prop('checked', true); 678 } else { 679 this.aiBots.forEach(bot => { 680 delete this.robotsRules[bot]; 681 }); 682 $.robots.deleteItemRow(this.aiBots); 683 // $('input[name="aiBot"]').prop('checked', false); 684 } 685 this.previewText(); 686 }, 687 cancelItem: function (e) { 688 const $row = $(e.currentTarget).closest('tr'); 689 const $isNew = $row.is('[data-type="new"]'); 690 691 // ìë¡ì´ í목ì ì·¨ìíëê²ì´ë©´ -> ê·¸ëë¡ ìì íê³ ì¢ ë£(return) 692 if ($isNew) { 693 $.robots.deleteItem(e, true); 694 return; 695 } 696 697 // 기존 í목ì ì·¨ìíëê²ì´ë©´ -> í¸ì§ ì·¨ì(íì¬ ì ë ¥ê°ì 무ìíê³ Confirm UIë¡ ë³µê·) 698 const $userAgent = $row.find('input[name="user-agent"]'); 699 const $directive = $row.find('select[name="directive"]'); 700 const $value = $row.find('input[name="value"]'); 701 702 // ì´ì ê° ë³µêµ¬ (edit ë¤ì´ê° ë data-prev-* ì ì¥í´ë¨ë¤ê³ ê°ì ) 703 if ($userAgent.data('prev') !== undefined) $userAgent.val($userAgent.data('prev')); 704 if ($directive.data('prev') !== undefined) $directive.val($directive.data('prev')); 705 if ($value.data('prev') !== undefined) $value.val($value.data('prev')); 706 707 // ê·ì¹ ì¬ì¶ê° ìì´ UIë§ Confirm ìíë¡ 708 $.robots.confirmItem(null, $row, $userAgent, $directive, $value, { skipRuleAdd: true }); 709 }, 710 confirmItem: function (e, $row, $userAgent, $directive, $value, opts) { 711 opts = opts || {}; 712 const skipRuleAdd = !!opts.skipRuleAdd; 713 714 // eë¡ í¸ì¶ë ê²½ì° DOM ë¤ì 찾기 715 if (!$row) { 716 $row = $(e.currentTarget).closest('tr'); 717 $userAgent = $row.find('input[name="user-agent"]'); 718 $directive = $row.find('select[name="directive"]'); 719 $value = $row.find('input[name="value"]'); 720 } 721 722 // 모ë ë¤ ì ì±ìì¡ì¼ë©´ ì í¨ì± ê²ì¬ í return 723 if (!$userAgent.val() || !$directive.val() || !$value.val()) { 724 if (!$userAgent.val()) { 725 $userAgent[0].reportValidity(); 726 return; 727 } 728 if (!$directive.val()) { 729 $directive[0].reportValidity(); 730 return; 731 } 732 if (!$value.val()) { 733 $value[0].reportValidity(); 734 return; 735 } 736 } 737 738 // 주ì(#)ì´ ìë¤ë©´ ì ê±° 739 let userAgentVal = $userAgent.val(); 740 let valueVal = $value.val(); 741 userAgentVal = userAgentVal.indexOf('#') === -1 ? userAgentVal : userAgentVal.slice(0, userAgentVal.indexOf('#')).trim(); 742 valueVal = valueVal.indexOf('#') === -1 ? valueVal : valueVal.slice(0, valueVal.indexOf('#')).trim(); 743 $userAgent.val(userAgentVal); 744 $value.val(valueVal); 745 746 // robotsRulesì ìì§ ìë ê·ì¹ì¸ ê²½ì° ë¹ ë°°ì´ ìì± 747 if (!$.robots.robotsRules[$userAgent.val()]) { 748 $.robots.robotsRules[$userAgent.val()] = []; 749 }; 750 751 // ì´ë¯¸ ê·ì¹ì´ ìë¤ë©´ ì림 ëì°ê³ return 752 // ê·ì¹ ìë¤ë©´ robotsRulesì ê·ì¹ ì¶ê° 753 if (!skipRuleAdd) { 754 const exists = $.robots.robotsRules[$userAgent.val().trim()].some(rule => 755 rule.directive === $directive.val().charAt(0).toUpperCase() + $directive.val().slice(1) && rule.value === $value.val().trim() 756 ); 757 if (exists) { 758 alert("í´ë¹ ê·ì¹ì´ ì´ë¯¸ ë±ë¡ëì´ ììµëë¤."); 759 return; 760 } else { 761 $.robots.robotsRules[$userAgent.val()].push({ 762 directive: `${$directive.val().charAt(0).toUpperCase() + $directive.val().slice(1)}`, 763 value: $value.val() 764 }); 765 } 766 } 767 768 // íë ì 그기 769 $userAgent.prop('readonly', true); 770 $directive.prop('disabled', true); 771 $value.prop('readonly', true); 772 $.robots.refreshDirectiveSelects($directive); 773 774 // ë²í¼ ìì ìì´ì½ ë³ê²½ 775 const $btnCell = $row.find('td').eq(4); 776 $btnCell.find('.robots-form__cancelBtn, .robots-form__confirmBtn').hide(); 777 $btnCell.find('.robots-form__itemModify, .robots-form__itemDelete') 778 .show() 779 .css({ 'pointer-events': 'all' }); 780 781 // 미리보기 ë¤ì 그리기 782 $.robots.previewText(); 783 784 // data-type="new" ì ê±° 785 $row.removeAttr('data-type').removeData('type'); // jQuery ìºìë ì ê±° 786 787 // value input ëì´ 98%ì¸ê±° ìì 기 788 $row.find('td').eq(4).find('input').removeClass('robots-modify__input'); 789 }, 790 modifyItem: function (e) { 791 const $targetRow = $(e.currentTarget).closest('tr'); 792 const $userAgent = $targetRow.find('td').eq(2).find('input'); 793 const $directive = $targetRow.find('td').eq(3).find('select'); 794 const $value = $targetRow.find('td').eq(4).find('input'); 795
796 // CancelItem ë©ìë ë문ì data-prevì ì ì¥ 797 $userAgent.data('prev', $userAgent.val()); 798 $directive.data('prev', $directive.val()); 799 $value.data('prev', $value.val()); 800 801 // ìì , ìì ë²í¼ ì ê±° 802 $(e.currentTarget).hide(); 803 $(e.currentTarget).closest('td').find('.robots-form__itemDelete').hide(); 804 805 // ì·¨ì, íì¸ ë²í¼ íì 806 $(e.currentTarget).closest('td').find('input').addClass('robots-modify__input'); 807 $(e.currentTarget).closest('td').find('.robots-form__cancelBtn').css('display', 'flex'); 808 $(e.currentTarget).closest('td').find('.robots-form__confirmBtn').css('display', 'flex'); 809 810 // 미리보기 ë¤ì 그리기(previewTextììë Confirmed ëê²ë§ 그리기 ë문ì ìì ë²í¼ ë른건 먼ì ì¨ê¸°ê¸°) 811 $.robots.previewText(); 812 813 // ìì ìíê³ ê·¸ëë¡ ì ì¥íë©´ ì¤ë³µê°ì¼ë¡ ì¸ìí기 ë문ì 814 // ìì ë²í¼ ë르ë ìê° í´ë¹ ê°ì ê·ì¹ìì ì ì¸ 815 const rules = $.robots.robotsRules[$userAgent.val()] || []; 816 $.robots.robotsRules[$userAgent.val()] = rules.filter(rule => 817 !(rule.directive === $directive.val().charAt(0).toUpperCase() + $directive.val().slice(1) && rule.value === $value.val()) 818 ); 819 820 // íë ì 그기 821 $userAgent.prop('readonly', false); 822 $directive.prop('disabled', false); 823 $value.prop('readonly', false); 824 $.robots.refreshDirectiveSelects($directive); 825 }, 826 resetItem: function () { 827 $.robots.deleteItemRow(); 828 this.robotsRules = { 829 '*': [ 830 { directive: "Disallow", value: "/_forgot/id" }, 831 { directive: "Disallow", value: "/_register" }, 832 { directive: "Disallow", value: "/_login" }, 833 { directive: "Disallow", value: "/_cart" }, 834 { directive: "Disallow", value: "/member/login" }, 835 { directive: "Disallow", value: "/member/join" }, 836 { directive: "Disallow", value: "/terms" }, 837 { directive: "Disallow", value: "/notice" }, 838 { directive: "Disallow", value: "/support" }, 839 { directive: "Disallow", value: "/lang" }, 840 { directive: "Disallow", value: "/contact" }, 841 { directive: "Disallow", value: "/privacy" }, 842 { directive: "Disallow", value: "/abuse" }, 843 ] 844 } 845 846 const rules = this.robotsRules; 847 for (const agent in rules) { 848 rules[agent].forEach(rule => $.robots.addItemRow(agent, rule.directive, rule.value)); 849 } 850 851 this.siteMaps = new Set(); 852 this.siteMaps.add(`${this.domain}/user-sitemap`); 853 this.previewText(); 854 this.updateToggleStatus(); 855 }, 856 /** 857 * 1. ìì í´ë¦íë©´ 858 * 2. í´ë¦í svgìì ê°ì¥ ê°ê¹ì´ tr ìì 859 * 3. itemIndex íë ê°ìíê³ 860 * 4. í´ë¦í itemIndex + 1 ë¶í° ëê¹ì§ ì«ì íë ê°ìí´ì ë¤ì 그리기 861 */ 862 deleteItem: function (e, skipConfirm = false) { 863 let isDelete = true; 864 // ìì ì¬ë¶ íì¸ 865 if (!skipConfirm) { 866 isDelete = confirm('ìì íìê² ìµëê¹?'); 867 } 868 if (!isDelete) return; 869 870 const $targetRow = $(e.currentTarget).closest('tr'); 871 const removedIndex = parseInt($targetRow.find('td').eq(1).find('span').text(), 10); 872 $targetRow.remove(); 873 this.itemIndex--; 874 $('.robots-form table tbody tr').each(function () { 875 const $firstTd = $(this).find('td').eq(1).find('span'); 876 const currentIndex = parseInt($firstTd.text(), 10); 877 if (currentIndex > removedIndex) { 878 $firstTd.text(currentIndex - 1); 879 } 880 }) 881 this.previewText(); 882 // this.updateToggleStatus(); 883 }, 884 updateToggleStatus: function () { 885 const rules = this.robotsRules || {}; 886 887 // searchBots 모ë robotsRulesì ì¡´ì¬í´ì¼ true 888 const searchBotOn = this.searchBots.every(bot => rules[bot] && rules[bot].length > 0); 889 890 // aiBots 모ë robotsRulesì ì¡´ì¬í´ì¼ true 891 const aiBotOn = this.aiBots.every(bot => rules[bot] && rules[bot].length > 0); 892 893 $(".robots-toggle input[name='searchBot']").prop('checked', searchBotOn); 894 $(".robots-toggle input[name='aiBot']").prop('checked', aiBotOn); 895 }, 896 refreshDirectiveSelects: function ($select) { 897 if (!$.fn.selectpicker) return; 898 899 const $target = $select && $select.length ? $select : $('.robots-form select[name="directive"]'); 900 $target.each(function () { 901 const $this = $(this); 902 if (!$this.data('selectpicker')) { 903 $this.selectpicker({ 904 width: '85%', 905 dropupAuto: false, 906 container: '.robots-modal' 907 }); 908 } else { 909 $this.selectpicker('refresh'); 910 } 911 }); 912 }, 913 addItem: function () { 914 $('.robots-form table tbody').append(` 915 <tr data-type="new"> 916 <td> 917 ${clSVG('move_handle', 20, 20, true, 'robots-form__dragHandle robots-svg')} 918 </td> 919 <td>
920 <span>${this.itemIndex++}</span> 921 </td> 922 <td><input required type="text" name="user-agent"></td> 923 <td> 924 <select class="selectpicker" required name="directive"> 925 <option value="">` + $.lang[LANG]['editor.robots.select'] + `</option> 926 <option value="allow">allow</option> 927 <option value="disallow">disallow</option> 928 </select> 929 </td> 930 <td> 931 <input class="robots-modify__input" required type="text" name="value" > 932 <button class="robots-form__cancelBtn robots-button"> 933 <svg class="robots-form__itemCancel robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="M13.94 5.18 10 9.12 6.06 5.18l-.89.88L9.11 10l-3.94 3.94.89.88L10 10.88l3.94 3.94.88-.88L10.88 10l3.94-3.94-.89-.88Z"/></svg> 934 </button> 935 <button class="robots-form__confirmBtn robots-button"> 936 <svg class="robots-form__itemConfirm robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="m15.94 5.25-7.4 7.38-4.47-4.46L3 9.22l5.54 5.53L17 6.31z"/></svg> 937 </button> 938 <svg class="robots-form__itemModify robots-svg" style="display: none;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><g data-name="그룹_6448"><g data-name="그룹_6449"><path d="M18.35 3.56c-.47-.36-1.06-.52-1.64-.44-.68.09-1.3.44-1.72.99L5.45 16.33l-.64 4.58 4.28-1.73 9.54-12.22c.86-1.02.73-2.53-.28-3.39M8.31 18.13l-1.97.79.29-2.1 7.38-9.45 1.67 1.31-7.38 9.45Zm9.34-11.95L16.47 7.7 14.8 6.39l1.18-1.51c.22-.28.53-.47.88-.52.26-.04.52.03.72.19.21.17.34.43.36.7.02.34-.08.67-.29.94"/></g></g></svg> 939 <svg class="robots-form__itemDelete robots-svg" style="display: none;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><g data-name="그룹_7089"><g data-name="그룹_7090"><path d="M20 7.06h-3.38v-3H7.38v3H4v1.25h2.12v9.62c0 1.1.9 2 2 2h7.75c1.1 0 2-.9 2-2V8.31h2.12V7.06ZM8.62 5.31h6.75v1.75H8.62zm8 12.63c0 .41-.34.75-.75.75H8.12c-.41 0-.75-.34-.75-.75V8.32h9.25zM11 16.31H9.75v-5.62H11zm3.25 0H13v-5.62h1.25z"/></g></g></svg> 940 </td> 941 </tr> 942 `); 943 $.robots.refreshDirectiveSelects(); 944 }, 945 addItemRow: function (userAgent, directive, value) { 946 $('.robots-form table tbody').append(` 947 <tr> 948 <td> 949 ${clSVG('move_handle', 20, 20, true, 'robots-form__dragHandle robots-svg')} 950 </td> 951 <td> 952 <span>${$.robots.itemIndex++}</span> 953 </td> 954 <td><input required readonly type="text" name="user-agent" value="${userAgent}"></td> 955 <td> 956 <select class="selectpicker" required disabled name="directive"> 957 <option value="">ì ííì¸ì</option> 958 <option value="allow" ${directive.toLowerCase() === 'allow' ? 'selected' : ''}>allow</option> 959 <option value="disallow" ${directive.toLowerCase() === 'disallow' ? 'selected' : ''}>disallow</option> 960 </select> 961 </td> 962 <td> 963 <input required readonly type="text" name="value" value="${value}"> 964 <button class="robots-form__cancelBtn robots-button" style="display: none;"> 965 <svg class="robots-form__itemCancel robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="M13.94 5.18 10 9.12 6.06 5.18l-.89.88L9.11 10l-3.94 3.94.89.88L10 10.88l3.94 3.94.88-.88L10.88 10l3.94-3.94-.89-.88Z"/></svg> 966 </button> 967 <button class="robots-form__confirmBtn robots-button" style="display: none;"> 968 <svg class="robots-form__itemConfirm robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20"><path d="m15.94 5.25-7.4 7.38-4.47-4.46L3 9.22l5.54 5.53L17 6.31z"/></svg> 969 </button> 970 <svg class="robots-form__itemModify robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><g data-name="그룹_6448"><g data-name="그룹_6449"><path d="M18.35 3.56c-.47-.36-1.06-.52-1.64-.44-.68.09-1.3.44-1.72.99L5.45 16.33l-.64 4.58 4.28-1.73 9.54-12.22c.86-1.02.73-2.53-.28-3.39M8.31 18.13l-1.97.79.29-2.1 7.38-9.45 1.67 1.31-7.38 9.45Zm9.34-11.95L16.47 7.7 14.8 6.39l1.18-1.51c.22-.28.53-.47.88-.52.26-.04.52.03.72.19.21.17.34.43.36.7.02.34-.08.67-.29.94"/></g></g></svg> 971 <svg class="robots-form__itemDelete robots-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><g data-name="그룹_7089"><g data-name="그룹_7090"><path d="M20 7.06h-3.38v-3H7.38v3H4v1.25h2.12v9.62c0 1.1.9 2 2 2h7.75c1.1 0 2-.9 2-2V8.31h2.12V7.06ZM8.62 5.31h6.75v1.75H8.62zm8 12.63c0 .41-.34.75-.75.75H8.12c-.41 0-.75-.34-.75-.75V8.32h9.25zM11 16.31H9.7
9715v-5.62H11zm3.25 0H13v-5.62h1.25z"/></g></g></svg> 972 </td> 973 </tr> 974 `); 975 $.robots.refreshDirectiveSelects(); 976 }, 977 deleteItemRow: function (bots) { 978 const self = this; 979 980 if (!bots || bots.length === 0) { 981 $('.robots-form table tbody tr').remove(); 982 } else { 983 bots.forEach(bot => { 984 $('.robots-form table tbody tr').each(function () { 985 const userAgent = $(this).find('input[name="user-agent"]').val()?.trim(); 986 if (userAgent === bot) { 987 $(this).remove(); 988 } 989 }); 990 }); 991 } 992 993 // ì¸ë±ì¤ ë¤ì 매기기 994 self.itemIndex = 1; 995 $('.robots-form table tbody tr').each(function () { 996 $(this).find('td').eq(1).find('span').text(self.itemIndex++); 997 }); 998 this.previewText(); 999 }, 1000 loadFile: function (e) { 1001 // íì¼ ì í í ì²ë¦¬ 1002 const file = e.target.files[0]; 1003 if (!file) return; 1004 if (file.type !== 'text/plain') { 1005 $('#robots-loadFile-error').remove(); 1006 const robotsLoadFileDialog = ` 1007 <div id="robots-loadFile-error" title="` + $.lang[LANG]['editor.robots.load.error'] + `"> 1008 <p>` + $.lang[LANG]['editor.robots.load.message'] + `</p> 1009 </div>`; 1010 $('body').append(robotsLoadFileDialog); 1011 1012 // ë¤ì´ì¼ë¡ê·¸ ì¤í 1013 $('#robots-loadFile-error').dialog({ 1014 modal: true, 1015 buttons: { 1016 [$.lang[LANG]['editor.robots.ok']]: function () { 1017 $(this).dialog("close").remove(); 1018 } 1019 }, 1020 open: function () { 1021 // z-index ì¡°ì 1022 $('.ui-dialog').css('z-index', 9999); 1023 1024 // ë¤ì´ì¼ë¡ê·¸ íì´íë° ì¤íì¼ 1025 $('.ui-dialog-titlebar').css({ 1026 background: '#949fab', 1027 color: '#fff' 1028 }); 1029 1030 // ë²í¼ ì¤íì¼ 1031 $('.ui-dialog-buttonpane button').css({ 1032 background: '#f5f5f5', 1033 color: '#222428', 1034 border: '0.5px solid #e6e8ea', 1035 }); 1036 1037 // ë²í¼ ì¤íì¼: í¸ë² 1038 $('.ui-dialog-buttonpane button').hover( 1039 function () { 1040 // ë§ì°ì¤ ì¬ë¼ê° ë 1041 $(this).css({ 1042 background: '#4789e7', 1043 color: '#fff', 1044 border: '0.5px solid #e6e8ea', 1045 }); 1046 }, 1047 function () { 1048 // ë§ì°ì¤ ëê° ë 1049 $(this).css({ 1050 background: '#f5f5f5', 1051 color: '#222428', 1052 border: '0.5px solid #e6e8ea', 1053 }); 1054 } 1055 ); 1056 1057 // ë«ê¸° ë²í¼ ì¤íì¼ 1058 $('.ui-dialog-titlebar-close').css({ 1059 background: 'none', 1060 border: 'none', 1061 }); 1062 1063 // ì¤ ìì 기 1064 $('.ui-dialog-buttonpane.ui-widget-content').css({ 1065 border: 'none', 1066 }) 1067 1068 // 본문 ìë¨ ì¬ë°± 1069 $('.ui-dialog .ui-dialog-content').css({ 1070 'margin-top': '10px', 1071 }) 1072 }, 1073 close: function () { 1074 $(this).remove(); 1075 } 1076 }); 1077 1078 e.target.value = ''; 1079 return; 1080 } 1081 1082 const reader = new FileReader(); 1083 reader.onload = (event) => { 1084 const text = event.target.result; 1085 const lines = text.split('\n'); 1086 1087 let rawUserAgent = null; 1088 let currentUserAgent = null; 1089 let directive = null; 1090 let rawValue = null; 1091 let value = null; 1092 1093 lines.forEach(line => { 1094 line = line.trim(); 1095 if (!line || line.startsWith('#')) return; 1096 1097 const idx = line.indexOf(':'); 1098 if (line.toLowerCase().startsWith('user-agent:')) { 1099 rawUserAgent = line.slice(idx + 1).trim(); 1100 rawUserAgent = rawUserAgent.indexOf('#') === -1 ? rawUserAgent : rawUserAgent.slice(0, rawUserAgent.indexOf('#')).trim(); // User-agent 주ì(#) ì´í ê° ì ê±° 1101 currentUserAgent = $.robots.escapeHtml(rawUserAgent); 1102 } else if (currentUserAgent && (line.toLowerCase().startsWith('allow:') || line.toLowerCase().startsWith('disallow:'))) { 1103 directive = line.slice(0, idx).trim(); 1104 rawValue = line.slice(idx + 1).trim(); 1105 rawValue = rawValue.indexOf('#') === -1 ? rawValue : rawValue.slice(0, rawValue.indexOf('#')).trim(); // valueì 주ì(#) ì´í ê° ì ê±° 1106 value = $.robots.escapeHtml(rawValue); 1107 1108 // robotsRulesìë ì ì¥ 1109 if (!$.robots.robotsRules[rawUserAgent]) { 1110 $.robots.robotsRules[rawUserAgent] = []; 1111 } 1112 1113 // tr ì¶ê°(íì§ë§ ë¡ëí ë ì¤ë³µê° ì¶ê° ì ì return) 1114 const isDuplicate = $.robots.robotsRules[rawUserAgent].some(rule => 1115 rule.directive.toLowerCase() === directive.toLowerCase() && rule.value === rawValue 1116 ); 1117 if (isDuplicate) return; 1118 1119 $.robots.addItemRow(currentUserAgent, directive, value); 1120 1121 $.robots.robotsRules[rawUserAgent].push({ 1122 directive: directive.charAt(0).toUpperCase() + directive.slice(1), 1123 value: rawValue 1124 }); 1125 } else if (line.toLowerCase().startsWith('sitemap:')) { 1126 const sitemapUrl = line.slice(line.indexOf(':') + 1).trim(); 1127 $.robots.siteMaps.add(sitemapUrl); 1128 } 1129 }) 1130 $.robots.previewText(); 1131 e.target.value = ''; 1132 }; 1133 reader.readAsText(file); 1134 }, 1135 escapeHtml: function (text) { 1136 return text 1137 .replace(/&/g, "&") // & 1138 .replace(/</g, "<") // < 1139 .replace(/>/g, ">") // > 1140 .replace(/"/g, """) // " 1141 .replace(/'/g, "'") // ' 1142 .replace(/\//g, "/") // / 1143 .replace(/`/g, "`") // ` 1144 .replace(/=/g, "="); // = 1145 }, 1146 unescapeHtml: function (text) { 1147 return text 1148 .replace(/&/g, "&") // & 1149 .replace(/</g, "<") // < 1150 .replace(/>/g, ">") // > 1151 .replace(/"/g, "\"") // " 1152 .replace(/'/g, "'") // ' 1153 .replace(///g, "/") // / 1154 .replace(/`/g, "`") // ` 1155 .replace(/=/g, "="); // = 1156 }, 1157 dragAndDrop: function () { 1158 // ì´ë¯¸ sortable ì ì©ë¼ ìì¼ë©´ 먼ì ì ê±° 1159 if ($('.robots-form tbody').data('ui-sortable')) { 1160 console.log('ì´ë¯¸ sortableì´ë¼ destroy ì¤í'); 1161 $('.robots-form tbody').sortable('destroy'); 1162 } 1163 1164 const fixRowHelper = function (event, $row) { 1165 const $cells = $row.children();
1166 const $valueCell = $cells.last(); 1167 const $valueInput = $valueCell.find('input[name="value"]'); 1168 const valueInputWidth = $valueInput.outerWidth(); 1169 1170 $row.css('width', $row.closest('table').outerWidth() + 'px'); 1171 $cells.each(function () { 1172 const $cell = $(this); 1173 $cell.css('width', $cell.outerWidth() + 'px'); 1174 }); 1175 $valueCell.css('grid-template-columns', valueInputWidth + 'px 32px 32px'); 1176 $valueInput.css('width', valueInputWidth + 'px'); 1177 return $row; 1178 }; 1179 1180 $('.robots-form tbody').sortable({ 1181 start: function (event, ui) { 1182 const $draggingRow = ui.item; 1183 const $select = $draggingRow.find('select'); 1184 $select.prop('disabled', false); 1185 const $isConfirmBtnVisible = $draggingRow.find('.robots-form__confirmBtn').is(':visible'); 1186 $isConfirmBtnVisible ? $select.css('color', '') : $select.css('color', '#aeaeae'); 1187 $.robots.refreshDirectiveSelects($select); 1188 }, 1189 stop: function (event, ui) { 1190 const $draggingRow = ui.item; 1191 const $select = $draggingRow.find('select'); 1192 const $isConfirmBtnVisible = $draggingRow.find('.robots-form__confirmBtn').is(':visible'); 1193 if($isConfirmBtnVisible) { 1194 $select.css('color', ''); 1195 } else { 1196 $select.css('color', ''); 1197 $select.prop('disabled', true); 1198 } 1199 $draggingRow.css('width', ''); 1200 $draggingRow.children().css('width', ''); 1201 $draggingRow.children().last().css('grid-template-columns', ''); 1202 $draggingRow.find('input[name="value"]').css('width', ''); 1203 $.robots.refreshDirectiveSelects($select); 1204 }, 1205 handle: '.robots-form__dragHandle', 1206 axis: 'y', 1207 helper: fixRowHelper, 1208 forceHelperSize: true, 1209 forcePlaceholderSize: true, 1210 cursor: 'move', 1211 revert: 100, 1212 placeholder: 'sortable-placeholder', 1213 update: function (event, ui) { 1214 $.robots.previewText(); 1215 $.robots.updateItemIndex(); 1216 } 1217 }); 1218 }, 1219 updateItemIndex: function () { 1220 let index = 1; 1221 $('.robots-form table tbody tr').each(function () { 1222 $(this).find('td').eq(1).find('span').text(index++); 1223 }); 1224 }, 1225 getDaumWebmaster: function () { 1226 const self = this; 1227 $.ajax({ 1228 url: '/template/get_daum_webmaster', 1229 type: 'POST', 1230 data: { 1231 'sid': SID, 1232 }, 1233 success: function (r) { 1234 if(!r || !r['settings']) return; 1235 1236 const settings = JSON.parse(r['settings']); 1237 if(settings['daum_webmaster']) { 1238 const daumWebmaster = settings['daum_webmaster'] 1239 self.daumWebmaster = daumWebmaster; 1240 }; 1241 }, 1242 error: function (xhr, status, error) { 1243 alert(error); 1244 } 1245 }); 1246 }, 1247 previewText: function () { 1248 let content = ''; 1249 this.robotsRules = {}; 1250 // ê° í목ì ìíí´ì robotsRules ê°ì²´ì ë£ê¸° 1251 $('.robots-form table tbody tr').each(function () { 1252 // Confirmë ê²ë§(ì¦, íë©´ì íì¸ë²í¼ ìëê²ë¤ë§) robotRulesì ì½ì 1253 const isConfirmed = $(this).find('td').eq(4).find('.robots-form__confirmBtn').css('display') === 'none'; 1254 if (!isConfirmed) return; 1255 const userAgent = $(this).find('input[name="user-agent"]').val()?.trim(); 1256 const directive = $(this).find('select[name="directive"]').val(); 1257 const value = $(this).find('input[name="value"]').val()?.trim(); 1258 1259 // 모ë ë¤ ì ì±ìì¡ì¼ë©´ ì í¨ì± ê²ì¬ í return 1260 if (!userAgent || !directive || !value) { 1261 if (!userAgent) { 1262 $(this).find('input[name="user-agent"]')[0].reportValidity(); 1263 } 1264 if (!directive) { 1265 $(this).find('select[name="directive"]')[0].reportValidity(); 1266 } 1267 if (!value) { 1268 $(this).find('input[name="value"]')[0].reportValidity(); 1269 } 1270 return; 1271 } 1272 1273 // user-agent í¤ê°ì´ ê°ì²´ì ìë¤ë©´ ìë¡ ìì± 1274 if (!$.robots.robotsRules[userAgent]) { 1275 $.robots.robotsRules[userAgent] = []; 1276 } 1277 1278 // ì´ë¯¸ ì¡´ì¬íëì§ íì¸ 1279 const exists = $.robots.robotsRules[userAgent].some(rule => 1280 rule.directive === directive.charAt(0).toUpperCase() + directive.slice(1) && rule.value === value 1281 ); 1282 1283 if (!exists) { 1284 $.robots.robotsRules[userAgent].push({ 1285 directive: `${directive.charAt(0).toUpperCase() + directive.slice(1)}`, 1286 value: value 1287 }); 1288 } 1289 }) 1290 1291 // ìì±ë robotsRulesì ê°ë¤ì 꺼ë´ì contentì ë£ê¸° 1292 for (const userAgent in this.robotsRules) { 1293 content += `User-agent: ${userAgent}\n`; 1294 const rules = this.robotsRules[userAgent];
1295 rules.forEach(rule => { 1296 content += `${rule.directive}: ${rule.value}\n`; 1297 }); 1298 content += '\n'; 1299 } 1300 1301 // ë§ì§ë§ì¼ë¡ Sitemap ë£ê¸° 1302 this.siteMaps.forEach(siteMap => content += `Sitemap: ${siteMap}\n`); 1303 1304 // ì¶ê°: ë¤ì ì¹ë§ì¤í°ë구 1305 if($.robots.daumWebmaster !== ''){ 1306 content += $.robots.daumWebmaster; 1307 } 1308 1309 // textarea íê·¸ì ê° ë£ê¸° 1310 $('.robots-preview__content').val(content); 1311 1312 // robotsText ë³ìì content ë£ê¸° 1313 $.robots.robotsText = content; 1314 }, 1315 } 1316 1317 $.sitemap = { 1318 sitemapPreviewText: '', 1319 defaultGeneratedSitemap: '', 1320 isFileUploaded: false, 1321 XMLFileContent: '', 1322 init: function () { 1323 $.ajax({ 1324 url: '/user_sitemap/get_sitemap', 1325 type: 'POST', 1326 data: { 1327 'sid': SID, 1328 }, 1329 success: function (r) { 1330 $.sitemap.sitemapPreviewText = r.message; 1331 1332 if(r.source === 'generated') { 1333 $.sitemap.defaultGeneratedSitemap = r.message; 1334 } else if (r.source === 'uploaded') { 1335 $.sitemap.loadDefaultSitemap(); 1336 } 1337 1338 $.sitemap.previewText(); 1339 }, 1340 error: function (xhr, status, error) { 1341 alert('기존 ì¬ì´í¸ë§µ ë°ì´í°ê° ì¡´ì¬íì§ ììµëë¤', error); 1342 } 1343 }); 1344 }, 1345 previewText: function () { 1346 $('.sitemap-preview__content').val($.sitemap.sitemapPreviewText); 1347 }, 1348 loadDefaultSitemap: function () { 1349 $.ajax({ 1350 url: '/user_sitemap/get_default_sitemap', 1351 type: 'POST', 1352 data: { 1353 'sid': SID, 1354 }, 1355 success: function (r) { 1356 $.sitemap.defaultGeneratedSitemap = r.message; 1357 } 1358 }); 1359 }, 1360 loadFile: function (e) { 1361 const file = e.target.files[0]; 1362 if (!file) return; 1363 1364 const reader = new FileReader(); 1365 reader.onload = function(event) { 1366 $.sitemap.XMLFileContent = event.target.result; 1367 1368 // ê²ì¦ ì¤í¨ ì ì´ê¸°í 1369 if (!$.sitemap.validateAll()) { 1370 $.sitemap.XMLFileContent = ''; 1371 $.sitemap.isFileUploaded = false; 1372 $(e.target).val(''); 1373 return; 1374 } 1375 1376 // ê²ì¦ íµê³¼ ì sitemapPreviewTextì ì ì¥ 1377 $.sitemap.sitemapPreviewText = $.sitemap.XMLFileContent; 1378 1379 $.processON(); 1380 $.sitemap.previewText(); 1381 setTimeout(() => { 1382 $.processOFF(); 1383 }, 1000); 1384 $.sitemap.isFileUploaded = true; 1385 alert('íì¼ì´ ì±ê³µì ì¼ë¡ ë¡ëëììµëë¤.\nì ì¥ ë²í¼ì ëë¬ ì ë¡ëíì¸ì.'); 1386 $(e.target).val(''); // íì¼ ë¡ë í input ì´ê¸°í(ì¬ì ë¡ë ê°ë¥íê²) 1387 }; 1388 reader.readAsText(file); 1389 }, 1390 save: function () { 1391 // ê²ì¦ íµê³¼ í ì¸ì½ë© ë° ì ì¡ 1392 const encodedSitemap = btoa(encodeURIComponent($.sitemap.sitemapPreviewText).replace(/%([0-9A-F]{2})/g, function(match, p1) { 1393 return String.fromCharCode(parseInt(p1, 16)); 1394 })); 1395 1396 $.ajax({ 1397 url: '/user_sitemap/save', 1398 type: 'POST', 1399 contentType: 'application/x-www-form-urlencoded; charset=UTF-8', 1400 data: { 1401 'sitemap_text': encodedSitemap, 1402 'is_encoded': '1', // ì¸ì½ë© ì¬ë¶ íì 1403 'user_dir': RPATH, 1404 'sid': SID, 1405 'host': HOST, 1406 }, 1407 success: function (r) { 1408 if(r.success) { 1409 $.robots.close(); 1410 } else { 1411 alert(r.error || 'ì ì¥ì ì¤í¨íìµëë¤.'); 1412 } 1413 }, 1414 error: function (xhr, status, error) { 1415 try { 1416 const response = JSON.parse(xhr.responseText); 1417 alert(response.error || 'ì ì¥ ì¤ ì¤ë¥ê° ë°ìíìµëë¤.'); 1418 } catch(e) { 1419 alert('ì ì¥ ì¤ ì¤ë¥ê° ë°ìíìµëë¤.\n\n' + xhr.responseText); 1420 } 1421 } 1422 }); 1423 }, 1424 reset: function () { 1425 $.sitemap.sitemapPreviewText = $.sitemap.defaultGeneratedSitemap; 1426 $.sitemap.isFileUploaded = false; 1427 $('#sitemap-content input[type="file"]').val(''); 1428 $.sitemap.previewText(); 1429 }, 1430 validateEmpty: function() { 1431 if (!$.sitemap.XMLFileContent || $.sitemap.XMLFileContent.trim() === '') { 1432 alert($.lang[LANG]['editor.sitemap.error.message.empty-content']); 1433 return false;
1434 } 1435 return true; 1436 }, 1437 validateFileSize: function() { 1438 const maxSize = 2 * 1024 * 1024; // 2MB 1439 const textSize = new Blob([$.sitemap.XMLFileContent]).size; 1440 if (textSize > maxSize) { 1441 alert($.lang[LANG]['editor.sitemap.error.message.file-size']); 1442 return false; 1443 } 1444 return true; 1445 }, 1446 validateXMLStructure: function() { 1447 try { 1448 const parser = new DOMParser(); 1449 const xmlDoc = parser.parseFromString($.sitemap.XMLFileContent, 'text/xml'); 1450 1451 // íì± ìë¬ ì²´í¬ 1452 const parseError = xmlDoc.getElementsByTagName('parsererror'); 1453 if (parseError.length > 0) { 1454 alert($.lang[LANG]['editor.sitemap.error.message.invalid-content'] + '\n\n' + parseError[0].textContent); 1455 return false; 1456 } 1457 1458 return xmlDoc; 1459 } catch (e) { 1460 alert('XML ê²ì¦ ì¤ ì¤ë¥ê° ë°ìíìµëë¤.\n\n' + e.message); 1461 return false; 1462 } 1463 }, 1464 validateUrlsetElement: function(xmlDoc) { 1465 const urlset = xmlDoc.getElementsByTagName('urlset')[0]; 1466 if (!urlset) { 1467 alert($.lang[LANG]['editor.sitemap.error.message.url-set']); 1468 return false; 1469 } 1470 1471 // namespace íì¸ 1472 const xmlns = urlset.getAttribute('xmlns'); 1473 if (xmlns !== 'http://www.sitemaps.org/schemas/sitemap/0.9') { 1474 alert($.lang[LANG]['editor.sitemap.error.message.url-namespace'] + '\n\n' + $.lang[LANG]['editor.sitemap.error.message.valid-format'] +':\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'); 1475 return false; 1476 } 1477 1478 return urlset; 1479 }, 1480 validateUrlCount: function(xmlDoc) { 1481 const urls = xmlDoc.getElementsByTagName('url'); 1482 1483 if (urls.length === 0) { 1484 alert($.lang[LANG]['editor.sitemap.error.message.url-required']); 1485 return false; 1486 } 1487 1488 if (urls.length > 50000) { 1489 alert($.lang[LANG]['editor.sitemap.error.message.url-max']); 1490 return false; 1491 } 1492 1493 return urls; 1494 }, 1495 validateUrlFormat: function(urlText, index) { 1496 try { 1497 const urlObj = new URL(urlText); 1498 return urlObj; 1499 } catch (e) { 1500 alert($.lang[LANG]['editor.sitemap.error.message.url-invalid'] + '\n\nURL ' + (index + 1) + ': ' + urlText); 1501 return false; 1502 } 1503 }, 1504 validateDomain: function(urlObj, index, urlText) { 1505 const hostDomain = new URL(HOST).hostname; 1506 1507 if (urlObj.hostname !== hostDomain) { 1508 alert('ë¤ë¥¸ ëë©ì¸ì URLì íì©ëì§ ììµëë¤.\n\níì¬ ëë©ì¸: ' + hostDomain + '\ní¬í¨ë URL: ' + urlText); 1509 return false; 1510 } 1511 return true; 1512 }, 1513 validateLastmod: function(url, index) { 1514 const lastmod = url.getElementsByTagName('lastmod')[0]; 1515 if (lastmod) { 1516 const lastmodValue = lastmod.textContent.trim(); 1517 if (!/^\d{4}-\d{2}-\d{2}/.test(lastmodValue)) { 1518 alert($.lang[LANG]['editor.sitemap.error.message.lastmod'] + '\n\nURL ' + (index + 1) + ': ' + lastmodValue); 1519 return false; 1520 } 1521 } 1522 return true; 1523 }, 1524 validatePriority: function(url, index) { 1525 const priority = url.getElementsByTagName('priority')[0]; 1526 if (priority) { 1527 const priorityValue = parseFloat(priority.textContent); 1528 if (isNaN(priorityValue) || priorityValue < 0.0 || priorityValue > 1.0) { 1529 alert($.lang[LANG]['editor.sitemap.error.message.priority'] + '\n\nURL ' + (index + 1) + ': ' + priorityValue); 1530 return false; 1531 } 1532 } 1533 return true; 1534 }, 1535 validateChangefreq: function(url, index) { 1536 const changefreq = url.getElementsByTagName('changefreq')[0]; 1537 if (changefreq) { 1538 const validFreq = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never']; 1539 const freqValue = changefreq.textContent.trim().toLowerCase(); 1540 if (!validFreq.includes(freqValue)) { 1541 alert($.lang[LANG]['editor.sitemap.error.message.changefreq'] + '\n\n' + $.lang[LANG]['editor.sitemap.error.message.allowed'] + ': ' + validFreq.join(', ') + '\n' + $.lang[LANG]['editor.sitemap.error.message.input'] + ': ' + freqValue); 1542 return false;
1543 } 1544 } 1545 return true; 1546 }, 1547 validateUrls: function(urls) { 1548 for (let i = 0; i < urls.length; i++) { 1549 const url = urls[i]; 1550 const loc = url.getElementsByTagName('loc')[0]; 1551 1552 // loc ìì ì¡´ì¬ íì¸ 1553 if (!loc || !loc.textContent.trim()) { 1554 let msg = $.lang[LANG]['editor.sitemap.error.message.loc']; 1555 msg = msg.replace('{0}', i + 1); 1556 alert(msg); 1557 return false; 1558 } 1559 1560 const urlText = loc.textContent.trim(); 1561 1562 // URL íì ê²ì¦ 1563 const urlObj = this.validateUrlFormat(urlText, i); 1564 if (!urlObj) return false; 1565 1566 // ëë©ì¸ ê²ì¦ 1567 // if (!this.validateDomain(urlObj, i, urlText)) return false; 1568 1569 // lastmod ê²ì¦ 1570 if (!this.validateLastmod(url, i)) return false; 1571 1572 // priority ê²ì¦ 1573 if (!this.validatePriority(url, i)) return false; 1574 1575 // changefreq ê²ì¦ 1576 if (!this.validateChangefreq(url, i)) return false; 1577 } 1578 return true; 1579 }, 1580 validateDangerousPatterns: function() { 1581 const dangerousPatterns = [ 1582 { pattern: /<!ENTITY/i, message: $.lang[LANG]['editor.sitemap.error.message.entity'] }, 1583 { pattern: /<!DOCTYPE[^>]*\[/i, message: $.lang[LANG]['editor.sitemap.error.message.doctype'] }, 1584 { pattern: /<\?php/i, message: $.lang[LANG]['editor.sitemap.error.message.php'] }, 1585 { pattern: /<script/i, message: $.lang[LANG]['editor.sitemap.error.message.script'] }, 1586 { pattern: /javascript:/i, message: $.lang[LANG]['editor.sitemap.error.message.javascript'] } 1587 ]; 1588 1589 for (let check of dangerousPatterns) { 1590 if (check.pattern.test($.sitemap.XMLFileContent)) { 1591 alert(check.message); 1592 return false; 1593 } 1594 } 1595 return true; 1596 }, 1597 validateAll: function() { 1598 if (!this.validateEmpty()) return false; // 1. ë¹ ë´ì© ì²´í¬ 1599 if (!this.validateFileSize()) return false; // 2. íì¼ í¬ê¸° ì²´í¬ 1600 const xmlDoc = this.validateXMLStructure(); // 3. XML 구조 ê²ì¦ 1601 if (!xmlDoc) return false; 1602 if (!this.validateUrlsetElement(xmlDoc)) return false; // 4. urlset ìì ê²ì¦ 1603 const urls = this.validateUrlCount(xmlDoc); // 5. URL ê°ì ì²´í¬ 1604 if (!urls) return false; 1605 if (!this.validateUrls(urls)) return false; // 6. ê° URL ê²ì¦ 1606 if (!this.validateDangerousPatterns()) return false; // 7. ìíí í¨í´ ì²´í¬ 1607 return true; 1608 }, 1609 } 1610 1611})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.