// ==UserScript==
// @name 유아렐갤 말투 교정기
// @namespace http://tampermonkey.net/
// @version 1.1
// @description 모바일 페이지 지원
// @author urlgall
// @match */mini/board/lists/?id=sandboxurl*
// @match */mini/board/view/?id=sandboxurl*
// @match */mini/sandboxurl*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const CHO = [
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
];
const JUNG = [
'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ',
'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ',
'ㅣ'
];
const JONG = [
'', 'ㄱ', 'ㄲ', 'ㄳ', 'ㄴ', 'ㄵ', 'ㄶ', 'ㄷ', 'ㄹ', 'ㄺ',
'ㄻ', 'ㄼ', 'ㄽ', 'ㄾ', 'ㄿ', 'ㅀ', 'ㅁ', 'ㅂ', 'ㅄ', 'ㅅ',
'ㅆ', 'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
];
const H_START = 0xAC00;
const J_COUNT = 21;
const L_COUNT = 28;
function fixText(t) {
const urlR = /(https?:\/\/[^\s<>]+)/g;
const tokens = [];
let lI = 0;
t.replace(urlR, (match, offset) => {
const nUT = t.substring(lI, offset);
if (nUT) tokens.push({ v: nUT });
tokens.push({ v: match, isU: true });
lI = offset + match.length;
return match;
});
if (lI < t.length) tokens.push({ v: t.substring(lI) });
let o = '';
tokens.forEach(token => {
if (token.isU) {
o += token.v;
} else {
const cleanedT = token.v.replace(/[.,]/g, '');
o += a(cleanedT);
}
});
return o;
}
function a(t) {
let o = '';
let c = -1;
let j = -1;
const getCI = (char) => CHO.indexOf(char);
const getJI = (char) => JUNG.indexOf(char);
const getLI = (char) => JONG.indexOf(char);
const compose = (c, j, l = 0) => {
if (c === -1 || j === -1) return '';
const code = H_START + (c * J_COUNT * L_COUNT) + (j * L_COUNT) + l;
return String.fromCharCode(code);
};
for (let i = 0; i < t.length; i++) {
const char = t[i];
const ci = getCI(char);
const ji = getJI(char);
const isJamo = (ci !== -1 || ji !== -1);
if (!isJamo) {
if (c !== -1 && j !== -1) {
o += compose(c, j);
} else if (c !== -1) {
o += CHO[c];
}
o += char;
c = -1;
j = -1;
continue;
}
if (c === -1 && ci !== -1) {
c = ci;
} else if (c !== -1 && ji !== -1) {
j = ji;
const nc = (i + 1 < t.length) ? t[i + 1] : '';
const nli = getLI(nc);
const anc = (i + 2 < t.length) ? t[i + 2] : '';
const anisj = getJI(anc) !== -1;
if (nli > 0) {
if (!anisj) {
o += compose(c, j, nli);
i++;
c = -1;
j = -1;
} else {
o += compose(c, j);
c = -1;
j = -1;
}
} else {
o += compose(c, j);
c = -1;
j = -1;
}
} else {
if (c !== -1 && j !== -1) {
o += compose(c, j);
c = -1; j = -1;
} else if (c !== -1) {
o += CHO[c];
c = -1;
}
if (ci !== -1) {
c = ci;
} else {
o += char;
}
}
}
if (c !== -1 && j !== -1) {
o += compose(c, j);
} else if (c !== -1) {
o += CHO[c];
}
return o;
}
function fixTitles() {
const titles = document.querySelectorAll('td.gall_tit a, span.title_subject, span.subjectin, span.tit');
titles.forEach(el => {
const origT = el.textContent;
const fixedT = fixText(origT);
const isA = el.tagName === 'A';
if (origT !== fixedT) {
if (isA) {
const keepC = Array.from(el.children);
const trimT = fixedT.trimStart();
const newTn = document.createTextNode(trimT);
el.innerHTML = '';
keepC.forEach(child => el.appendChild(child));
el.appendChild(newTn);
} else {
el.textContent = fixedT.trim();
}
}
});
}
function fixContents() {
const writes = document.querySelectorAll('div.write_div, div.thum-txtin');
writes.forEach(div => {
fixNodes(div);
});
const comments = document.querySelectorAll('p.usertxt, p.txt');
comments.forEach(p => {
fixNodes(p);
});
}
function fixNodes(el) {
const w = document.createTreeWalker(
el,
NodeFilter.SHOW_TEXT,
null,
false
);
let n;
const updates = [];
while (n = w.nextNode()) {
if (n.parentNode && n.parentNode.nodeName !== 'SCRIPT' && n.parentNode.nodeName !== 'STYLE') {
if (!n.parentNode.classList || (!n.parentNode.classList.contains('korean-fixer-wrapper'))) {
updates.push(n);
}
}
}
updates.forEach(tn => {
const origT = tn.nodeValue;
if (!origT.trim()) return;
const fixedT = fixText(origT);
if (origT !== fixedT) {
const parent = tn.parentNode;
if (!parent) return;
const newTn = document.createTextNode(fixedT);
const span = document.createElement('span');
span.classList.add('korean-fixer-wrapper');
span.appendChild(newTn);
parent.replaceChild(span, tn);
} else {
tn.nodeValue = origT.trimStart();
}
});
}
fixTitles();
fixContents();
const observer = new MutationObserver((m) => {
let update = false;
m.forEach(mutation => {
if (mutation.type === 'childList') {
Array.from(mutation.addedNodes).forEach(n => {
if (n.nodeType === 1) {
if (n.querySelector('td.gall_tit a') || n.querySelector('span.title_subject') || n.querySelector('span.subjectin') || n.querySelector('span.tit') || n.querySelector('div.write_div') || n.querySelector('div.thum-txtin') || n.querySelector('p.usertxt') || n.querySelector('p.txt')) {
update = true;
}
}
});
}
});
if (update) {
fixTitles();
fixContents();
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();