Skip to content

Instantly share code, notes, and snippets.

@sounisi5011
Last active December 13, 2022 07:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sounisi5011/bd4a431e31e73ff7286fbd09a154f57a to your computer and use it in GitHub Desktop.
Save sounisi5011/bd4a431e31e73ff7286fbd09a154f57a to your computer and use it in GitHub Desktop.
Show Unicode Code Point List
(function(global) {
'use strict';
var ATTR = 'ref';
/**
* @param {*} origCallback
* @param {!function} addCallback
* @return {!function}
*/
function overridePropCallback(origCallback, addCallback) {
if (typeof origCallback === 'function') {
return function() {
var args = Array.prototype.slice.call(arguments);
origCallback.apply(null, args);
addCallback.apply(null, args);
};
} else {
return addCallback;
}
}
/**
* Actionsの各関数の引数にgetRef関数を追加する
* @param {!Object} origActions
* @param {function} getRef
* @return {!Object}
*/
function addActionsRefsParam(origActions, getRef) {
var newActions = {};
for (var key in origActions) {
var origAction = origActions[key];
if (typeof origAction === 'function') {
newActions[key] = (function(origAction) {
return function(data) {
var result = origAction(data);
if (typeof result === 'function') {
return function(actionInState, actionInActions) {
return result(actionInState, actionInActions, getRef);
};
}
return result;
};
})(origAction);
} else {
newActions[key] = addActionsRefsParam(origAction, getRef);
}
}
return newActions;
}
/**
* hyperappにref/refsを追加する
* @param {{h: !function, app: !function}} hyperapp
* @return {{h: !function, app: !function}}
*/
function hyperappRefs(hyperapp) {
var origH = hyperapp.h;
var origApp = hyperapp.app;
return {
h: function h(name, attributes) {
var args = [ name ];
/*
* 第二引数が配列ではないオブジェクトの場合に、属性として設定する
*/
if (typeof attributes === 'object' && !Array.isArray(attributes)) {
args.push(attributes);
} else {
args.push({}, attributes);
}
/*
* 第三引数以降を設定する
*/
for (var i = 2; i < arguments.length; i++) {
args.push(arguments[i]);
}
/*
* 元のh関数を実行する
*/
return origH.apply(null, args);
},
app: function app(state, actions, view, container) {
/**
* @type {Object<string, ?Element>}
*/
var globalRefs = {};
/**
* @param {string} refName
* @return {!Element|null}
*/
function getRef(refName) {
return globalRefs[refName] || null;
}
/*
* Actionsの第三引数にgetRef関数を追加
*/
var overridedActions = addActionsRefsParam(actions, getRef);
var overrideView = function(viewInState, viewInActions) {
/*
* 元のview関数を実行し、仮想DOMを取得する
*/
var node = view(viewInState, viewInActions, getRef);
/*
* 生成された仮想DOMのref属性を、oncreateとondestroyへ上書きする
*/
(function defRefUpdater(node) {
if (node && typeof node === 'object') {
var attributes = node.attributes;
if (attributes) {
var refName = attributes[ATTR];
if (refName && typeof refName === 'string') {
delete attributes[ATTR];
attributes.oncreate = overridePropCallback(attributes.oncreate, function(elem) {
globalRefs[refName] = elem;
});
attributes.ondestroy = overridePropCallback(attributes.ondestroy, function(elem) {
if (globalRefs[refName] === elem) {
globalRefs[refName] = null;
}
});
}
}
node.children.forEach(defRefUpdater);
}
})(node);
/*
* 変更を加えた仮想DOMを返す
*/
return node;
};
/*
* 元のapp関数を実行する
*/
return origApp(state, overridedActions, overrideView, container);
}
};
}
global.hyperappRefs = hyperappRefs;
})(Function('return this')());
<!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<meta name=format-detection content="telephone=no,email=no,address=no">
<title>Show Unicode Code Point List</title>
<link rel=stylesheet href="main.css">
<h1>Show Unicode Code Point List</h1>
<main id=main><p>JavaScript has been disabled. Please enable JavaScript.</main>
<footer>
<h2>Gist</h2>
<p><a href="https://gist.github.com/sounisi5011/bd4a431e31e73ff7286fbd09a154f57a">gist.github.com<wbr>/sounisi5011<wbr>/bd4a431e31e73ff7286fbd09a154f57a</a>
</footer>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es5,String.prototype.padStart&amp;flags=always,gated" crossorigin=anonymous></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/hyperapp/1.2.0/hyperapp.js" integrity="sha256-nGvSTSqpX5gJdp7Jv5+4FCU1IbclUByINJqDQIbfwNc=" crossorigin=anonymous></script>
<script src="https://unpkg.com/grapheme-splitter@1.0.4/index.js" integrity="sha256-ApHozg+22P2OAAiCbUVy12oWNZxACKDhlKOdnZHK7JU=" crossorigin=anonymous></script>
<script src="hyperappRefs.js"></script>
<script src="main.js"></script>
.input-area,
.check-button {
width: 100%;
}
.input-area {
min-height: 5em;
resize: vertical;
}
.table-wrap {
overflow-x: scroll;
}
.table-wrap table {
text-align: center;
}
.table-wrap .full-string {
text-align: left;
}
.table-wrap tr + .full-string td {
padding-top: 3em;
}
.char {
padding: .1em .4em;
border: solid 1px #ccc;
border-radius: 3px;
white-space: nowrap;
}
.char[data-char-type~="ascii-printable"] {
background-color: #dff7ff;
}
(function(global) {
'use strict';
var document = global.document;
var location = global.location;
var hyperapp = global.hyperappRefs(global.hyperapp);
var h = hyperapp.h;
var app = hyperapp.app;
var splitter = new global.GraphemeSplitter();
/**
* @param {number} code
* @return {string}
*/
function unicodeCode2str(code) {
return 'U+' + code.toString(16).toUpperCase().padStart(4, '0');
}
/**
* @param {Array<string>} graphemes
* @return {Array<Array<string>>}
* @see https://en.wikipedia.org/wiki/Newline#Unicode
*/
function graphemes2LineBreakList(graphemes) {
/** @type {Array<Array<string>>} */
var out = [];
/** @type {number} */
var start = 0;
for (var index = 0; index < graphemes.length; index++) {
/** @type {string} */
var graphemeChar = graphemes[index];
/** @type {number} */
var nextIndex = index + 1;
if (nextIndex === graphemes.length || /^(?:\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])$/.test(graphemeChar)) {
out.push(graphemes.slice(start, nextIndex));
start = nextIndex;
}
}
return out;
}
/**
* @param {string} str
* @return {{code: Array<number>, char: Array<string>, data: Array<{code: number, char: string}>}}
* @see https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt#%E4%BA%92%E6%8F%9B%E6%80%A7
*/
function getCodePointList(str) {
/** @type {Array<string>} */
var charList = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]?|[^\uD800-\uDFFF]|./g) || [];
/** @type {Array<{code: number, char: string}>} */
var dataList = charList.map(function(/** @type {string} */ char) {
return {
/** @type {number} */
code: (
(char.length === 2) ?
((char.charCodeAt(0) - 0xD800) * 0x400 + char.charCodeAt(1) - 0xDC00 + 0x10000) :
char.charCodeAt(0)
),
/** @type {string} */
char: char
};
});
return {
/** @type {Array<number>} */
code: dataList.map(function(/** @type {{code: number, char: string}} */ data) {
return data.code;
}),
/** @type {Array<string>} */
char: charList,
/** @type {Array<{code: number, char: string}>} */
data: dataList
};
}
/**
* @param {string} char
* @return {Array<string>}
*/
function getCharTypeValue(char) {
var out = [];
if (/^[\u0020-\u007E]$/.test(char)) {
out.push('ascii-printable');
}
return out;
}
/**
* @param {string} [fragment=location.hash]
* @return {string}
*/
function getUrlFragmentValue(fragment) {
if (typeof fragment !== 'string') {
fragment = location.hash;
}
if (fragment.charAt(0) === '#') {
fragment = fragment.substr(1);
}
return decodeURIComponent(fragment);
}
/**
* @param {string} value
* @param {boolean} [force=false]
*/
function setUrlFragmentValue(value, force) {
if (force || getUrlFragmentValue() !== value) {
location.hash = '#' + (
global.encodeURIComponent(value)
.replace(/[!\'()*]/g, function(char) {
return '%' + char.charCodeAt(0).toString(16).toUpperCase();
})
);
}
}
/**
* @param {string} value
*/
var updateTitleValue = (function(defaultTitle) {
return function(value) {
document.title = (
value !== '' ?
(defaultTitle + ' #' + value) :
defaultTitle
);
};
})(document.title);
var state = {
/** @type {string} */
value: ''
};
var actions = {
updateValue: function(value) {
return function(state) {
if (state.value !== value) {
return { value: value };
}
};
},
inputChange: function(inputValue) {
return function(state, actions) {
setUrlFragmentValue(inputValue);
actions.updateValue(inputValue);
};
},
fragmentChange: function(fragmentValue) {
return function(state, actions, refs) {
var value = getUrlFragmentValue(fragmentValue);
var inputElem = refs('input');
if (inputElem && inputElem.value !== value) {
inputElem.value = value;
}
updateTitleValue(value);
actions.updateValue(value);
};
}
};
var view = function(state, actions, refs) {
var inputListener = function(e) {
return actions.inputChange(e.target.value);
};
/** @type {Array<string>} */
var graphemes = splitter.splitGraphemes(state.value);
/** @type {Array<Array<string>>} */
var graphemesLineList = graphemes2LineBreakList(graphemes);
return h('div', [
h('textarea', {
class: 'input-area',
oninput: inputListener,
onchange: inputListener,
onblur: inputListener,
onkeyup: inputListener,
onmouseup: inputListener,
onpaste: inputListener,
oncreate: function(elem) {
elem.value = state.value;
},
ref: 'input'
}),
h('br'),
h('input', {
type: 'button',
value: 'Check!',
class: 'check-button',
onclick: function(e) {
if (refs('input')) {
actions.inputChange(refs('input').value);
}
}
}),
h('div', { class: 'table-wrap' }, [
h('table', [
graphemesLineList.map(function(/** @type {Array<string>} */ graphemes) {
/** @type {string} */
var fullStr = graphemes.join('');
/** @type {Array<{code: number, char: string, graphemeCharLength: number}>} */
var charCodeList = graphemes.reduce(function(array, /** @type {string} */ graphemeChar) {
/** @type {Array<{code: number, char: string}>} */
var dataList = getCodePointList(graphemeChar).data;
/** @type {number} */
var graphemeCharLength = dataList.length;
return array.concat(dataList.map(function(/** @type {{code: number, char: string}} */ data) {
data.graphemeCharLength = graphemeCharLength;
return data;
}));
}, []);
return (
(0 < graphemes.length) ?
[
h('tr', { class: 'full-string' }, [
h('td', { colSpan: fullStr.length }, [
h('span', { class: 'char' }, fullStr)
])
]),
h('tr', graphemes.map(function(/** @type {string} */ graphemeChar) {
return h('td', { colSpan: graphemeChar.length }, [
h('span', { class: 'char', 'data-char-type': getCharTypeValue(graphemeChar).join(' ') }, graphemeChar)
]);
})),
h('tr', charCodeList.map(function(/** @type {{code: number, char: string, graphemeCharLength: number}} */ data) {
/** @type {string} */
var char = data.char;
/** @type {number} */
var graphemeCharLength = data.graphemeCharLength;
return h('td', { colSpan: char.length }, (
(1 < graphemeCharLength) ?
h('span', { class: 'char', 'data-char-type': getCharTypeValue(char).join(' ') }, char) :
null
));
})),
h('tr', charCodeList.map(function(/** @type {{code: number, char: string, graphemeCharLength: number}} */ data) {
/** @type {string} */
var char = data.char;
/** @type {number} */
var code = data.code;
return h('td', { colSpan: char.length }, [
h('span', { class: 'char', 'data-char-type': getCharTypeValue(char).join(' ') }, unicodeCode2str(code))
]);
}))
] :
null
);
})
])
])
]);
};
var main = app(state, actions, view, document.getElementById('main'));
global.onhashchange = function() {
main.fragmentChange(location.hash);
};
main.fragmentChange(location.hash);
})(Function('return this')());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment