|
// ==UserScript== |
|
// @name c4 -- unicode markup |
|
// @author queue |
|
// @namespace https://github.com/queue-/c4 |
|
// @description converts unicode bold/italic/monospace back to real letters and HTML |
|
// |
|
// @version 0.2 |
|
// |
|
// @match http://boards.4chan.org/* |
|
// @match https://boards.4chan.org/* |
|
// @exclude http://boards.4chan.org/f/* |
|
// @exclude https://boards.4chan.org/f/* |
|
// |
|
// ==/UserScript== |
|
|
|
"use strict"; |
|
|
|
var alphanum, bold, italic, bold_italic, monospace, regex, char_regex, CharMap, map, replacer, replace, markup; |
|
|
|
alphanum = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; |
|
bold = ["𝟎", "𝟏", "𝟐", "𝟑", "𝟒", "𝟓", "𝟔", "𝟕", "𝟖", "𝟗", "𝐚", "𝐛", "𝐜", "𝐝", "𝐞", "𝐟", "𝐠", "𝐡", "𝐢", "𝐣", "𝐤", "𝐥", "𝐦", "𝐧", "𝐨", "𝐩", "𝐪", "𝐫", "𝐬", "𝐭", "𝐮", "𝐯", "𝐰", "𝐱", "𝐲", "𝐳", "𝐀", "𝐁", "𝐂", "𝐃", "𝐄", "𝐅", "𝐆", "𝐇", "𝐈", "𝐉", "𝐊", "𝐋", "𝐌", "𝐍", "𝐎", "𝐏", "𝐐", "𝐑", "𝐒", "𝐓", "𝐔", "𝐕", "𝐖", "𝐗", "𝐘", "𝐙"]; |
|
italic = ["𝑎", "𝑏", "𝑐", "𝑑", "𝑒", "𝑓", "𝑔", "ℎ", "𝑖", "𝑗", "𝑘", "𝑙", "𝑚", "𝑛", "𝑜", "𝑝", "𝑞", "𝑟", "𝑠", "𝑡", "𝑢", "𝑣", "𝑤", "𝑥", "𝑦", "𝑧", "𝐴", "𝐵", "𝐶", "𝐷", "𝐸", "𝐹", "𝐺", "𝐻", "𝐼", "𝐽", "𝐾", "𝐿", "𝑀", "𝑁", "𝑂", "𝑃", "𝑄", "𝑅", "𝑆", "𝑇", "𝑈", "𝑉", "𝑊", "𝑋", "𝑌", "𝑍"]; |
|
bold_italic = ["𝒂", "𝒃", "𝒄", "𝒅", "𝒆", "𝒇", "𝒈", "𝒉", "𝒊", "𝒋", "𝒌", "𝒍", "𝒎", "𝒏", "𝒐", "𝒑", "𝒒", "𝒓", "𝒔", "𝒕", "𝒖", "𝒗", "𝒘", "𝒙", "𝒚", "𝒛", "𝑨", "𝑩", "𝑪", "𝑫", "𝑬", "𝑭", "𝑮", "𝑯", "𝑰", "𝑱", "𝑲", "𝑳", "𝑴", "𝑵", "𝑶", "𝑷", "𝑸", "𝑹", "𝑺", "𝑻", "𝑼", "𝑽", "𝑾", "𝑿", "𝒀", "𝒁"]; |
|
monospace = ["𝟶", "𝟷", "𝟸", "𝟹", "𝟺", "𝟻", "𝟼", "𝟽", "𝟾", "𝟿", "𝚊", "𝚋", "𝚌", "𝚍", "𝚎", "𝚏", "𝚐", "𝚑", "𝚒", "𝚓", "𝚔", "𝚕", "𝚖", "𝚗", "𝚘", "𝚙", "𝚚", "𝚛", "𝚜", "𝚝", "𝚞", "𝚟", "𝚠", "𝚡", "𝚢", "𝚣", "𝙰", "𝙱", "𝙲", "𝙳", "𝙴", "𝙵", "𝙶", "𝙷", "𝙸", "𝙹", "𝙺", "𝙻", "𝙼", "𝙽", "𝙾", "𝙿", "𝚀", "𝚁", "𝚂", "𝚃", "𝚄", "𝚅", "𝚆", "𝚇", "𝚈", "𝚉"]; |
|
|
|
// Javascript's unicode support in regex ranges can't actually match the |
|
// extended mathematical alphanumeric symbols that 4chanX uses, as they're |
|
// double-byte characters (except for italic h), so these are instead compiled |
|
// as alternations (a|b|c...). |
|
regex = { |
|
b: new RegExp( '(?:' + bold.join('|') + ')+', 'g' ), |
|
i: new RegExp( '(?:' + italic.join('|') + ')+', 'g' ), |
|
bi: new RegExp( '(?:' + bold_italic.join('|') + ')+', 'g' ), |
|
m: new RegExp( '(?:' + monospace.join('|') + ')+', 'g' ) |
|
}; |
|
|
|
// these match a _single_ (double-byte) unicode character, so |
|
// they can be replaced one by one with their alphanumeric equivalent |
|
// used in replace.* |
|
char_regex = { |
|
b: new RegExp( bold.join('|'), 'g' ), |
|
i: new RegExp( italic.join('|'), 'g' ), |
|
bi: new RegExp( bold_italic.join('|'), 'g' ), |
|
m: new RegExp( monospace.join('|'), 'g' ) |
|
}; |
|
|
|
// constructor for a map from unicode char to alpha char, |
|
// exploiting JS's objects as string -> obj hashmaps |
|
CharMap = function (type) { |
|
for ( var i = 0, len = type.length; i < len; ++i ) { |
|
this[ type[i] ] = alphanum[i]; |
|
} |
|
}; |
|
|
|
// and to avoid dealing with fromCharCode and unicode, these are manual maps |
|
// unicode back to the equivalent ASCII |
|
map = { |
|
b: new CharMap( bold ), |
|
// there aren't italic numbers, but to make the indices line up, we add |
|
// regular alpha numbers to the beginning of italics |
|
i: new CharMap( ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].concat( italic ) ), |
|
bi: new CharMap( ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].concat( bold_italic ) ), |
|
m: new CharMap( monospace ) |
|
}; |
|
|
|
// the 2nd argument to replace() inside {replace} that converts from unicode |
|
// math to a regular alphanum, using map |
|
replacer = { |
|
b: function (c) { return map.b[c]; }, |
|
i: function (c) { return map.i[c]; }, |
|
bi: function (c) { return map.bi[c]; }, |
|
m: function (c) { return map.m[c]; } |
|
} |
|
|
|
// the 2nd argument to replace inside markup() that converts the |
|
// entire string using char_regex and replacer and adds HTML markup |
|
replace = { |
|
b: function (str) { |
|
return '<b>' + str.replace( char_regex.b, replacer.b ) + '</b>'; |
|
}, |
|
i: function (str) { |
|
return '<i>' + str.replace( char_regex.i, replacer.i ) + '</i>'; |
|
}, |
|
bi: function (str) { |
|
return '<i><b>' + str.replace( char_regex.bi, replacer.bi ) + '</b></i>'; |
|
}, |
|
m: function (str) { |
|
return '<code>' + str.replace( char_regex.m, replacer.m ) + '</code>'; |
|
} |
|
}; |
|
|
|
// TODO instead use splitTextNode to avoid messing with event listeners |
|
// attached to quotelinks and shit. |
|
markup = function (comment) { |
|
var nodes, node, text, span; |
|
// need a non-live childNodes array |
|
nodes = Array.prototype.slice.call( comment.childNodes ); |
|
for ( var i = 0, len = nodes.length; i < len; ++i ) { |
|
node = nodes[i]; |
|
// markup quotes and spoilers too |
|
if ( node.nodeName === 'SPAN' ) { |
|
text = node.textContent; |
|
// TODO fix code repetition, it's unsightly |
|
if ( regex.b.test( text ) || regex.i.test( text ) || regex.bi.test( text ) || regex.m.test( text ) ) { |
|
node.innerHTML = node.innerHTML |
|
.replace( regex.b, replace.b ) |
|
.replace( regex.i, replace.i ) |
|
.replace( regex.bi, replace.bi ) |
|
.replace( regex.m, replace.m ); |
|
} |
|
} else if ( node.nodeType === Node.TEXT_NODE ) { |
|
text = node.textContent; |
|
// test to see if node contains unicode text |
|
if ( regex.b.test( text ) || regex.i.test( text ) || regex.bi.test( text ) || regex.m.test( text ) ) { |
|
// replace it with a span containing marked up text. |
|
span = document.createElement( 'span' ); |
|
span.innerHTML = text |
|
.replace( regex.b, replace.b ) |
|
.replace( regex.i, replace.i ) |
|
.replace( regex.bi, replace.bi ) |
|
.replace( regex.m, replace.m ); |
|
comment.replaceChild( span, node ); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
Array.prototype.forEach.call( document.querySelectorAll('.postMessage'), markup ); |
|
|
|
// support for c4 updated threads |
|
window.addEventListener( 'beforeupdate', function (e) { |
|
Array.prototype.forEach.call( e.detail.fragment.querySelectorAll('.postMessage'), markup ); |
|
}); |