Merge pull request #385 from toasted-nutbread/pitch-accents
Pitch accents
This commit is contained in:
commit
f439d12718
@ -87,6 +87,8 @@
|
||||
"stringReverse": "readonly",
|
||||
"promiseTimeout": "readonly",
|
||||
"parseUrl": "readonly",
|
||||
"areSetsEqual": "readonly",
|
||||
"getSetIntersection": "readonly",
|
||||
"EventDispatcher": "readonly",
|
||||
"EventListenerCollection": "readonly",
|
||||
"EXTENSION_IS_BROWSER_EDGE": "readonly"
|
||||
|
@ -13,13 +13,71 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["freq"],
|
||||
"description": "Type of data. \"freq\" corresponds to frequency information."
|
||||
"enum": ["freq", "pitch"],
|
||||
"description": "Type of data. \"freq\" corresponds to frequency information; \"pitch\" corresponds to pitch information."
|
||||
},
|
||||
{
|
||||
"type": ["string", "number"],
|
||||
"description": "Data for the term/expression."
|
||||
}
|
||||
],
|
||||
"oneOf": [
|
||||
{
|
||||
"items": [
|
||||
{},
|
||||
{"enum": ["freq"]},
|
||||
{
|
||||
"type": ["string", "number"],
|
||||
"description": "Frequency information for the term or expression."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
{},
|
||||
{"enum": ["pitch"]},
|
||||
{
|
||||
"type": ["object"],
|
||||
"description": "Pitch accent information for the term or expression.",
|
||||
"required": [
|
||||
"reading",
|
||||
"pitches"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"reading": {
|
||||
"type": "string",
|
||||
"description": "Reading for the term or expression."
|
||||
},
|
||||
"pitches": {
|
||||
"type": "array",
|
||||
"description": "List of different pitch accent information for the term and reading combination.",
|
||||
"additionalItems": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"position"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"position": {
|
||||
"type": "integer",
|
||||
"description": "Mora position of the pitch accent downstep. A value of 0 indicates that the word does not have a downstep (heiban).",
|
||||
"minimum": 0
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"description": "List of tags for this pitch accent.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Tag for this pitch accent. This typically corresponds to a certain type of part of speech."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -105,7 +105,10 @@
|
||||
"customPopupCss",
|
||||
"customPopupOuterCss",
|
||||
"enableWanakana",
|
||||
"enableClipboardMonitor"
|
||||
"enableClipboardMonitor",
|
||||
"showPitchAccentDownstepNotation",
|
||||
"showPitchAccentPositionNotation",
|
||||
"showPitchAccentGraph"
|
||||
],
|
||||
"properties": {
|
||||
"enable": {
|
||||
@ -227,6 +230,18 @@
|
||||
"enableClipboardMonitor": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"showPitchAccentDownstepNotation": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"showPitchAccentPositionNotation": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"showPitchAccentGraph": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -137,30 +137,6 @@ function dictTermsGroup(definitions, dictionaries) {
|
||||
return dictTermsSort(results);
|
||||
}
|
||||
|
||||
function dictAreSetsEqual(set1, set2) {
|
||||
if (set1.size !== set2.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const value of set1) {
|
||||
if (!set2.has(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function dictGetSetIntersection(set1, set2) {
|
||||
const result = [];
|
||||
for (const value of set1) {
|
||||
if (set2.has(value)) {
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function dictTermsMergeBySequence(definitions, mainDictionary) {
|
||||
const sequencedDefinitions = new Map();
|
||||
const nonSequencedDefinitions = [];
|
||||
@ -281,11 +257,11 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices
|
||||
const only = [];
|
||||
const expressionSet = definition.expression;
|
||||
const readingSet = definition.reading;
|
||||
if (!dictAreSetsEqual(expressionSet, resultExpressionSet)) {
|
||||
only.push(...dictGetSetIntersection(expressionSet, resultExpressionSet));
|
||||
if (!areSetsEqual(expressionSet, resultExpressionSet)) {
|
||||
only.push(...getSetIntersection(expressionSet, resultExpressionSet));
|
||||
}
|
||||
if (!dictAreSetsEqual(readingSet, resultReadingSet)) {
|
||||
only.push(...dictGetSetIntersection(readingSet, resultReadingSet));
|
||||
if (!areSetsEqual(readingSet, resultReadingSet)) {
|
||||
only.push(...getSetIntersection(readingSet, resultReadingSet));
|
||||
}
|
||||
definition.only = only;
|
||||
}
|
||||
|
@ -124,7 +124,10 @@ function profileOptionsCreateDefaults() {
|
||||
customPopupCss: '',
|
||||
customPopupOuterCss: '',
|
||||
enableWanakana: true,
|
||||
enableClipboardMonitor: false
|
||||
enableClipboardMonitor: false,
|
||||
showPitchAccentDownstepNotation: true,
|
||||
showPitchAccentPositionNotation: true,
|
||||
showPitchAccentGraph: false
|
||||
},
|
||||
|
||||
audio: {
|
||||
|
@ -84,6 +84,9 @@ async function formRead(options) {
|
||||
options.general.popupScalingFactor = parseFloat($('#popup-scaling-factor').val());
|
||||
options.general.popupScaleRelativeToPageZoom = $('#popup-scale-relative-to-page-zoom').prop('checked');
|
||||
options.general.popupScaleRelativeToVisualViewport = $('#popup-scale-relative-to-visual-viewport').prop('checked');
|
||||
options.general.showPitchAccentDownstepNotation = $('#show-pitch-accent-downstep-notation').prop('checked');
|
||||
options.general.showPitchAccentPositionNotation = $('#show-pitch-accent-position-notation').prop('checked');
|
||||
options.general.showPitchAccentGraph = $('#show-pitch-accent-graph').prop('checked');
|
||||
options.general.popupTheme = $('#popup-theme').val();
|
||||
options.general.popupOuterTheme = $('#popup-outer-theme').val();
|
||||
options.general.customPopupCss = $('#custom-popup-css').val();
|
||||
@ -161,6 +164,9 @@ async function formWrite(options) {
|
||||
$('#popup-scaling-factor').val(options.general.popupScalingFactor);
|
||||
$('#popup-scale-relative-to-page-zoom').prop('checked', options.general.popupScaleRelativeToPageZoom);
|
||||
$('#popup-scale-relative-to-visual-viewport').prop('checked', options.general.popupScaleRelativeToVisualViewport);
|
||||
$('#show-pitch-accent-downstep-notation').prop('checked', options.general.showPitchAccentDownstepNotation);
|
||||
$('#show-pitch-accent-position-notation').prop('checked', options.general.showPitchAccentPositionNotation);
|
||||
$('#show-pitch-accent-graph').prop('checked', options.general.showPitchAccentGraph);
|
||||
$('#popup-theme').val(options.general.popupTheme);
|
||||
$('#popup-outer-theme').val(options.general.popupOuterTheme);
|
||||
$('#custom-popup-css').val(options.general.customPopupCss);
|
||||
|
@ -490,6 +490,7 @@ class Translator {
|
||||
|
||||
// New data
|
||||
term.frequencies = [];
|
||||
term.pitches = [];
|
||||
}
|
||||
|
||||
const metas = await this.database.findTermMetaBulk(expressionsUnique, dictionaries);
|
||||
@ -500,6 +501,13 @@ class Translator {
|
||||
term.frequencies.push({expression, frequency: data, dictionary});
|
||||
}
|
||||
break;
|
||||
case 'pitch':
|
||||
for (const term of termsUnique[index]) {
|
||||
const pitchData = await this.getPitchData(expression, data, dictionary, term);
|
||||
if (pitchData === null) { continue; }
|
||||
term.pitches.push(pitchData);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -583,6 +591,20 @@ class Translator {
|
||||
return tagMetaList;
|
||||
}
|
||||
|
||||
async getPitchData(expression, data, dictionary, term) {
|
||||
const reading = data.reading;
|
||||
const termReading = term.reading || expression;
|
||||
if (reading !== termReading) { return null; }
|
||||
|
||||
const pitches = [];
|
||||
for (let {position, tags} of data.pitches) {
|
||||
tags = Array.isArray(tags) ? await this.getTagMetaList(tags, dictionary) : [];
|
||||
pitches.push({position, tags});
|
||||
}
|
||||
|
||||
return {reading, pitches, dictionary};
|
||||
}
|
||||
|
||||
static createExpression(expression, reading, termTags=null, termFrequency=null) {
|
||||
const furiganaSegments = jp.distributeFurigana(expression, reading);
|
||||
return {
|
||||
|
@ -162,6 +162,18 @@
|
||||
<label><input type="checkbox" id="popup-scale-relative-to-visual-viewport"> Change popup size relative to page viewport</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox options-advanced">
|
||||
<label><input type="checkbox" id="show-pitch-accent-downstep-notation"> Show downstep notation for pitch accents</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox options-position">
|
||||
<label><input type="checkbox" id="show-pitch-accent-position-notation"> Show position notation for pitch accents</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox options-advanced">
|
||||
<label><input type="checkbox" id="show-pitch-accent-graph"> Show graph for pitch accents</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox options-advanced">
|
||||
<label><input type="checkbox" id="show-debug-info"> Show debug information</label>
|
||||
</div>
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
body { background-color: #1e1e1e; color: #d4d4d4; }
|
||||
|
||||
h2 { border-bottom-color: #2f2f2f; }
|
||||
|
||||
.navigation-header {
|
||||
background-color: #1e1e1e;
|
||||
border-bottom-color: #2f2f2f;
|
||||
@ -39,6 +41,7 @@ body { background-color: #1e1e1e; color: #d4d4d4; }
|
||||
.tag[data-category=frequency] { background-color: #489148; }
|
||||
.tag[data-category=partOfSpeech] { background-color: #565656; }
|
||||
.tag[data-category=search] { background-color: #69696e; }
|
||||
.tag[data-category=pitch-accent-dictionary] { background-color: #6640be; }
|
||||
|
||||
.term-reasons { color: #888888; }
|
||||
|
||||
@ -57,12 +60,15 @@ body { background-color: #1e1e1e; color: #d4d4d4; }
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.term-definition-container,
|
||||
.kanji-glossary-container {
|
||||
.term-definition-list,
|
||||
.term-pitch-accent-group-list,
|
||||
.term-pitch-accent-disambiguation-list,
|
||||
.kanji-glossary-list {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.term-glossary,
|
||||
.term-pitch-accent,
|
||||
.kanji-glossary {
|
||||
color: #d4d4d4;
|
||||
}
|
||||
@ -72,3 +78,20 @@ body { background-color: #1e1e1e; color: #d4d4d4; }
|
||||
background-color: #d4d4d4;
|
||||
color: #1e1e1e;
|
||||
}
|
||||
|
||||
.term-pitch-accent-container { border-bottom-color: #2f2f2f; }
|
||||
|
||||
.term-pitch-accent-character:before { border-color: #ffffff; }
|
||||
|
||||
.term-pitch-accent-graph-line,
|
||||
.term-pitch-accent-graph-line-tail,
|
||||
#term-pitch-accent-graph-dot,
|
||||
#term-pitch-accent-graph-dot-downstep,
|
||||
#term-pitch-accent-graph-triangle {
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
#term-pitch-accent-graph-dot,
|
||||
#term-pitch-accent-graph-dot-downstep>circle:last-of-type {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
body { background-color: #ffffff; color: #333333; }
|
||||
|
||||
h2 { border-bottom-color: #eeeeee; }
|
||||
|
||||
.navigation-header {
|
||||
background-color: #ffffff;
|
||||
border-bottom-color: #eeeeee;
|
||||
@ -39,6 +41,7 @@ body { background-color: #ffffff; color: #333333; }
|
||||
.tag[data-category=frequency] { background-color: #5cb85c; }
|
||||
.tag[data-category=partOfSpeech] { background-color: #565656; }
|
||||
.tag[data-category=search] { background-color: #8a8a91; }
|
||||
.tag[data-category=pitch-accent-dictionary] { background-color: #6640be; }
|
||||
|
||||
.term-reasons { color: #777777; }
|
||||
|
||||
@ -57,12 +60,15 @@ body { background-color: #ffffff; color: #333333; }
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.term-definition-container,
|
||||
.kanji-glossary-container {
|
||||
.term-definition-list,
|
||||
.term-pitch-accent-group-list,
|
||||
.term-pitch-accent-disambiguation-list,
|
||||
.kanji-glossary-list {
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
.term-glossary,
|
||||
.term-pitch-accent,
|
||||
.kanji-glossary {
|
||||
color: #000000;
|
||||
}
|
||||
@ -72,3 +78,20 @@ body { background-color: #ffffff; color: #333333; }
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.term-pitch-accent-container { border-bottom-color: #eeeeee; }
|
||||
|
||||
.term-pitch-accent-character:before { border-color: #000000; }
|
||||
|
||||
.term-pitch-accent-graph-line,
|
||||
.term-pitch-accent-graph-line-tail,
|
||||
#term-pitch-accent-graph-dot,
|
||||
#term-pitch-accent-graph-dot-downstep,
|
||||
#term-pitch-accent-graph-triangle {
|
||||
stroke: #000000;
|
||||
}
|
||||
|
||||
#term-pitch-accent-graph-dot,
|
||||
#term-pitch-accent-graph-dot-downstep>circle:last-of-type {
|
||||
fill: #000000;
|
||||
}
|
||||
|
@ -65,6 +65,14 @@ ol, ul {
|
||||
height: 2.28571428em; /* 14px => 32px */
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.25em;
|
||||
font-weight: normal;
|
||||
margin: 0.25em 0 0;
|
||||
border-bottom-width: 0.05714285714285714em; /* 14px * 1.25em => 1px */
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
/*
|
||||
* Navigation
|
||||
*/
|
||||
@ -302,6 +310,7 @@ button.action-button {
|
||||
width: 0;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.term-expression-list[data-multi=true] .term-expression:hover .term-expression-details {
|
||||
@ -422,6 +431,187 @@ button.action-button {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.term-entry-body[data-section-count="0"] .term-entry-body-section-header,
|
||||
.term-entry-body[data-section-count="1"] .term-entry-body-section-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pitch accent styles
|
||||
*/
|
||||
|
||||
.entry[data-pitch-accent-count='0'] .term-pitch-accent-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.term-pitch-accent-container {
|
||||
border-bottom-width: 0.05714285714285714em; /* 14px * 1.25em => 1px */
|
||||
border-bottom-style: solid;
|
||||
padding-bottom: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.term-pitch-accent-group-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.term-pitch-accent-group-list:not([data-count="0"]):not([data-count="1"]) {
|
||||
padding-left: 1.4em;
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.term-pitch-accent-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.term-pitch-accent-list:not([data-count="0"]):not([data-count="1"]) {
|
||||
padding-left: 1.4em;
|
||||
list-style-type: circle;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.term-pitch-accent {
|
||||
display: inline;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.term-pitch-accent-list:not([data-count="0"]):not([data-count="1"])>.term-pitch-accent {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
.term-pitch-accent-group-tag-list {
|
||||
margin-right: 0.375em;
|
||||
}
|
||||
|
||||
.term-pitch-accent-disambiguation-list {
|
||||
padding-right: 0.25em;
|
||||
}
|
||||
|
||||
.term-pitch-accent-disambiguation-list:before {
|
||||
content: "(";
|
||||
}
|
||||
|
||||
.term-pitch-accent-disambiguation-list:after {
|
||||
content: " only)";
|
||||
}
|
||||
|
||||
.term-pitch-accent-disambiguation+.term-pitch-accent-disambiguation:before {
|
||||
content: ", ";
|
||||
}
|
||||
|
||||
.term-pitch-accent-disambiguation-list[data-count="0"],
|
||||
:root[data-show-pitch-accent-downstep-notation=true] .term-pitch-accent-disambiguation-list[data-expression-count="0"],
|
||||
:root[data-show-pitch-accent-downstep-notation=true] .term-pitch-accent-disambiguation[data-type=reading] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.term-pitch-accent-tag-list:not([data-count="0"]) {
|
||||
margin-right: 0.375em;
|
||||
}
|
||||
|
||||
.term-special-tags>.pitches {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.term-pitch-accent-character {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.term-pitch-accent-character[data-pitch='high']:before {
|
||||
content: "";
|
||||
display: block;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0.1em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0;
|
||||
border-top-width: 0.1em;
|
||||
border-top-style: solid;
|
||||
}
|
||||
.term-pitch-accent-character[data-pitch='high'][data-pitch-next='low']:before {
|
||||
right: -0.1em;
|
||||
height: 0.4em;
|
||||
border-right-width: 0.1em;
|
||||
border-right-style: solid;
|
||||
}
|
||||
.term-pitch-accent-character[data-pitch='high'][data-pitch-next='low'] {
|
||||
padding-right: 0.1em;
|
||||
margin-right: 0.1em;
|
||||
}
|
||||
|
||||
.term-pitch-accent-position:before {
|
||||
content: " [";
|
||||
}
|
||||
.term-pitch-accent-position:after {
|
||||
content: "]";
|
||||
}
|
||||
|
||||
.term-pitch-accent-details {
|
||||
display: inline-block;
|
||||
height: 0;
|
||||
padding: 0 0.25em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
:root[data-show-pitch-accent-downstep-notation=false] .term-pitch-accent-characters {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root[data-show-pitch-accent-position-notation=false] .term-pitch-accent-position {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:root[data-show-pitch-accent-graph=false] .term-pitch-accent-details {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pitch accent graph styles
|
||||
*/
|
||||
|
||||
.term-pitch-accent-graph {
|
||||
display: block;
|
||||
height: 1.5em;
|
||||
transform: translateY(-0.875em);
|
||||
}
|
||||
.term-pitch-accent-graph-line,
|
||||
.term-pitch-accent-graph-line-tail {
|
||||
fill: none;
|
||||
stroke: #000000;
|
||||
stroke-width: 5;
|
||||
}
|
||||
.term-pitch-accent-graph-line-tail {
|
||||
stroke-dasharray: 5 5;
|
||||
}
|
||||
#term-pitch-accent-graph-dot {
|
||||
fill: #000000;
|
||||
stroke: #000000;
|
||||
stroke-width: 5;
|
||||
}
|
||||
#term-pitch-accent-graph-dot-downstep {
|
||||
fill: none;
|
||||
stroke: #000000;
|
||||
stroke-width: 5;
|
||||
}
|
||||
#term-pitch-accent-graph-dot-downstep>circle:last-of-type {
|
||||
fill: #000000;
|
||||
}
|
||||
#term-pitch-accent-graph-triangle {
|
||||
fill: none;
|
||||
stroke: #000000;
|
||||
stroke-width: 5;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Kanji
|
||||
|
@ -17,7 +17,10 @@
|
||||
</div>
|
||||
<div class="term-special-tags"><div class="frequencies tag-list"></div></div>
|
||||
</div>
|
||||
<div class="term-definition-container"><ol class="term-definition-list"></ol></div>
|
||||
<div class="term-entry-body">
|
||||
<div class="term-entry-body-section term-pitch-accent-container"><ol class="term-entry-body-section-content term-pitch-accent-group-list"></ol></div>
|
||||
<div class="term-entry-body-section term-definition-container"><ol class="term-entry-body-section-content term-definition-list"></ol></div>
|
||||
</div>
|
||||
<pre class="debug-info"></pre>
|
||||
</div></template>
|
||||
<template id="term-expression-template"><div class="term-expression"><span class="term-expression-text source-text"></span><div class="term-expression-details">
|
||||
@ -34,6 +37,18 @@
|
||||
<template id="term-glossary-item-template"><li class="term-glossary-item"><span class="term-glossary-separator"> </span><span class="term-glossary"></span></li></template>
|
||||
<template id="term-reason-template"><span class="term-reason"></span><span class="term-reason-separator"> </span></template>
|
||||
|
||||
<template id="term-pitch-accent-static-template"><svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<defs>
|
||||
<g id="term-pitch-accent-graph-dot"><circle cx="0" cy="0" r="15" /></g>
|
||||
<g id="term-pitch-accent-graph-dot-downstep"><circle cx="0" cy="0" r="15" /><circle cx="0" cy="0" r="5" /></g>
|
||||
<g id="term-pitch-accent-graph-triangle"><path d="M0 13 L15 -13 L-15 -13 Z" /></g>
|
||||
</defs>
|
||||
</svg></template>
|
||||
<template id="term-pitch-accent-group-template"><li class="term-pitch-accent-group"><span class="term-pitch-accent-group-tag-list tag-list"></span><ul class="term-pitch-accent-list"></ul></li></template>
|
||||
<template id="term-pitch-accent-disambiguation-template"><span class="term-pitch-accent-disambiguation"></span></template>
|
||||
<template id="term-pitch-accent-template"><li class="term-pitch-accent"><span class="term-pitch-accent-tag-list tag-list"></span><span class="term-pitch-accent-disambiguation-list"></span><span class="term-pitch-accent-characters"></span><span class="term-pitch-accent-position"></span><span class="term-pitch-accent-details"><svg class="term-pitch-accent-graph" xmlns="http://www.w3.org/2000/svg"><path class="term-pitch-accent-graph-line" /><path class="term-pitch-accent-graph-line-tail" /></svg></span></li></template>
|
||||
<template id="term-pitch-accent-character-template"><span class="term-pitch-accent-character"><span class="term-pitch-accent-character-inner"></span></span></template>
|
||||
|
||||
<template id="kanji-entry-template"><div class="entry" data-type="kanji">
|
||||
<div class="entry-header1">
|
||||
<div class="entry-header2">
|
||||
|
@ -132,6 +132,30 @@ function parseUrl(url) {
|
||||
return {baseUrl, queryParams};
|
||||
}
|
||||
|
||||
function areSetsEqual(set1, set2) {
|
||||
if (set1.size !== set2.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const value of set1) {
|
||||
if (!set2.has(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function getSetIntersection(set1, set2) {
|
||||
const result = [];
|
||||
for (const value of set1) {
|
||||
if (set2.has(value)) {
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Async utilities
|
||||
|
@ -25,6 +25,7 @@
|
||||
class DisplayGenerator {
|
||||
constructor() {
|
||||
this._templateHandler = null;
|
||||
this._termPitchAccentStaticTemplateIsSetup = false;
|
||||
}
|
||||
|
||||
async prepare() {
|
||||
@ -37,17 +38,33 @@ class DisplayGenerator {
|
||||
|
||||
const expressionsContainer = node.querySelector('.term-expression-list');
|
||||
const reasonsContainer = node.querySelector('.term-reasons');
|
||||
const pitchesContainer = node.querySelector('.term-pitch-accent-group-list');
|
||||
const frequenciesContainer = node.querySelector('.frequencies');
|
||||
const definitionsContainer = node.querySelector('.term-definition-list');
|
||||
const debugInfoContainer = node.querySelector('.debug-info');
|
||||
const bodyContainer = node.querySelector('.term-entry-body');
|
||||
|
||||
const pitches = DisplayGenerator._getPitchInfos(details);
|
||||
const pitchCount = pitches.reduce((i, v) => i + v[1].length, 0);
|
||||
|
||||
const expressionMulti = Array.isArray(details.expressions);
|
||||
const definitionMulti = Array.isArray(details.definitions);
|
||||
const expressionCount = expressionMulti ? details.expressions.length : 1;
|
||||
const definitionCount = definitionMulti ? details.definitions.length : 1;
|
||||
const uniqueExpressionCount = Array.isArray(details.expression) ? new Set(details.expression).size : 1;
|
||||
|
||||
node.dataset.expressionMulti = `${expressionMulti}`;
|
||||
node.dataset.definitionMulti = `${definitionMulti}`;
|
||||
node.dataset.expressionCount = `${expressionMulti ? details.expressions.length : 1}`;
|
||||
node.dataset.definitionCount = `${definitionMulti ? details.definitions.length : 1}`;
|
||||
node.dataset.expressionCount = `${expressionCount}`;
|
||||
node.dataset.definitionCount = `${definitionCount}`;
|
||||
node.dataset.uniqueExpressionCount = `${uniqueExpressionCount}`;
|
||||
node.dataset.pitchAccentDictionaryCount = `${pitches.length}`;
|
||||
node.dataset.pitchAccentCount = `${pitchCount}`;
|
||||
|
||||
bodyContainer.dataset.sectionCount = `${
|
||||
(definitionCount > 0 ? 1 : 0) +
|
||||
(pitches.length > 0 ? 1 : 0)
|
||||
}`;
|
||||
|
||||
const termTags = details.termTags;
|
||||
let expressions = details.expressions;
|
||||
@ -56,6 +73,7 @@ class DisplayGenerator {
|
||||
DisplayGenerator._appendMultiple(expressionsContainer, this.createTermExpression.bind(this), expressions, [[details, termTags]]);
|
||||
DisplayGenerator._appendMultiple(reasonsContainer, this.createTermReason.bind(this), details.reasons);
|
||||
DisplayGenerator._appendMultiple(frequenciesContainer, this.createFrequencyTag.bind(this), details.frequencies);
|
||||
DisplayGenerator._appendMultiple(pitchesContainer, this.createPitches.bind(this), pitches);
|
||||
DisplayGenerator._appendMultiple(definitionsContainer, this.createTermDefinitionItem.bind(this), details.definitions, [details]);
|
||||
|
||||
if (debugInfoContainer !== null) {
|
||||
@ -262,6 +280,133 @@ class DisplayGenerator {
|
||||
return node;
|
||||
}
|
||||
|
||||
createPitches(details) {
|
||||
if (!this._termPitchAccentStaticTemplateIsSetup) {
|
||||
this._termPitchAccentStaticTemplateIsSetup = true;
|
||||
const t = this._templateHandler.instantiate('term-pitch-accent-static');
|
||||
document.head.appendChild(t);
|
||||
}
|
||||
|
||||
const [dictionary, dictionaryPitches] = details;
|
||||
|
||||
const node = this._templateHandler.instantiate('term-pitch-accent-group');
|
||||
node.dataset.dictionary = dictionary;
|
||||
node.dataset.pitchesMulti = 'true';
|
||||
node.dataset.pitchesCount = `${dictionaryPitches.length}`;
|
||||
|
||||
const tag = this.createTag({notes: '', name: dictionary, category: 'pitch-accent-dictionary'});
|
||||
node.querySelector('.term-pitch-accent-group-tag-list').appendChild(tag);
|
||||
|
||||
const n = node.querySelector('.term-pitch-accent-list');
|
||||
DisplayGenerator._appendMultiple(n, this.createPitch.bind(this), dictionaryPitches);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
createPitch(details) {
|
||||
const {reading, position, tags, exclusiveExpressions, exclusiveReadings} = details;
|
||||
const morae = jp.getKanaMorae(reading);
|
||||
|
||||
const node = this._templateHandler.instantiate('term-pitch-accent');
|
||||
|
||||
node.dataset.pitchAccentPosition = `${position}`;
|
||||
node.dataset.tagCount = `${tags.length}`;
|
||||
|
||||
let n = node.querySelector('.term-pitch-accent-position');
|
||||
n.textContent = `${position}`;
|
||||
|
||||
n = node.querySelector('.term-pitch-accent-tag-list');
|
||||
DisplayGenerator._appendMultiple(n, this.createTag.bind(this), tags);
|
||||
|
||||
n = node.querySelector('.term-pitch-accent-disambiguation-list');
|
||||
this.createPitchAccentDisambiguations(n, exclusiveExpressions, exclusiveReadings);
|
||||
|
||||
n = node.querySelector('.term-pitch-accent-characters');
|
||||
for (let i = 0, ii = morae.length; i < ii; ++i) {
|
||||
const mora = morae[i];
|
||||
const highPitch = jp.isMoraPitchHigh(i, position);
|
||||
const highPitchNext = jp.isMoraPitchHigh(i + 1, position);
|
||||
|
||||
const n1 = this._templateHandler.instantiate('term-pitch-accent-character');
|
||||
const n2 = n1.querySelector('.term-pitch-accent-character-inner');
|
||||
|
||||
n1.dataset.position = `${i}`;
|
||||
n1.dataset.pitch = highPitch ? 'high' : 'low';
|
||||
n1.dataset.pitchNext = highPitchNext ? 'high' : 'low';
|
||||
n2.textContent = mora;
|
||||
|
||||
n.appendChild(n1);
|
||||
}
|
||||
|
||||
if (morae.length > 0) {
|
||||
this.populatePitchGraph(node.querySelector('.term-pitch-accent-graph'), position, morae);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
createPitchAccentDisambiguations(container, exclusiveExpressions, exclusiveReadings) {
|
||||
const templateName = 'term-pitch-accent-disambiguation';
|
||||
for (const exclusiveExpression of exclusiveExpressions) {
|
||||
const node = this._templateHandler.instantiate(templateName);
|
||||
node.dataset.type = 'expression';
|
||||
node.textContent = exclusiveExpression;
|
||||
container.appendChild(node);
|
||||
}
|
||||
|
||||
for (const exclusiveReading of exclusiveReadings) {
|
||||
const node = this._templateHandler.instantiate(templateName);
|
||||
node.dataset.type = 'reading';
|
||||
node.textContent = exclusiveReading;
|
||||
container.appendChild(node);
|
||||
}
|
||||
|
||||
container.dataset.multi = 'true';
|
||||
container.dataset.count = `${exclusiveExpressions.length + exclusiveReadings.length}`;
|
||||
container.dataset.expressionCount = `${exclusiveExpressions.length}`;
|
||||
container.dataset.readingCount = `${exclusiveReadings.length}`;
|
||||
}
|
||||
|
||||
populatePitchGraph(svg, position, morae) {
|
||||
const svgns = svg.getAttribute('xmlns');
|
||||
const ii = morae.length;
|
||||
svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`);
|
||||
|
||||
const pathPoints = [];
|
||||
for (let i = 0; i < ii; ++i) {
|
||||
const highPitch = jp.isMoraPitchHigh(i, position);
|
||||
const highPitchNext = jp.isMoraPitchHigh(i + 1, position);
|
||||
const graphic = (highPitch && !highPitchNext ? '#term-pitch-accent-graph-dot-downstep' : '#term-pitch-accent-graph-dot');
|
||||
const x = `${i * 50 + 25}`;
|
||||
const y = highPitch ? '25' : '75';
|
||||
const use = document.createElementNS(svgns, 'use');
|
||||
use.setAttribute('href', graphic);
|
||||
use.setAttribute('x', x);
|
||||
use.setAttribute('y', y);
|
||||
svg.appendChild(use);
|
||||
pathPoints.push(`${x} ${y}`);
|
||||
}
|
||||
|
||||
let path = svg.querySelector('.term-pitch-accent-graph-line');
|
||||
path.setAttribute('d', `M${pathPoints.join(' L')}`);
|
||||
|
||||
pathPoints.splice(0, ii - 1);
|
||||
{
|
||||
const highPitch = jp.isMoraPitchHigh(ii, position);
|
||||
const x = `${ii * 50 + 25}`;
|
||||
const y = highPitch ? '25' : '75';
|
||||
const use = document.createElementNS(svgns, 'use');
|
||||
use.setAttribute('href', '#term-pitch-accent-graph-triangle');
|
||||
use.setAttribute('x', x);
|
||||
use.setAttribute('y', y);
|
||||
svg.appendChild(use);
|
||||
pathPoints.push(`${x} ${y}`);
|
||||
}
|
||||
|
||||
path = svg.querySelector('.term-pitch-accent-graph-line-tail');
|
||||
path.setAttribute('d', `M${pathPoints.join(' L')}`);
|
||||
}
|
||||
|
||||
createFrequencyTag(details) {
|
||||
const node = this._templateHandler.instantiate('tag-frequency');
|
||||
|
||||
@ -301,22 +446,28 @@ class DisplayGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
static _appendMultiple(container, createItem, detailsArray, fallback=[]) {
|
||||
static _appendMultiple(container, createItem, detailsIterable, fallback=[]) {
|
||||
if (container === null) { return 0; }
|
||||
|
||||
const isArray = Array.isArray(detailsArray);
|
||||
if (!isArray) { detailsArray = fallback; }
|
||||
const multi = (
|
||||
detailsIterable !== null &&
|
||||
typeof detailsIterable === 'object' &&
|
||||
typeof detailsIterable[Symbol.iterator] !== 'undefined'
|
||||
);
|
||||
if (!multi) { detailsIterable = fallback; }
|
||||
|
||||
container.dataset.multi = `${isArray}`;
|
||||
container.dataset.count = `${detailsArray.length}`;
|
||||
|
||||
for (const details of detailsArray) {
|
||||
let count = 0;
|
||||
for (const details of detailsIterable) {
|
||||
const item = createItem(details);
|
||||
if (item === null) { continue; }
|
||||
container.appendChild(item);
|
||||
++count;
|
||||
}
|
||||
|
||||
return detailsArray.length;
|
||||
container.dataset.multi = `${multi}`;
|
||||
container.dataset.count = `${count}`;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static _appendFurigana(container, segments, addText) {
|
||||
@ -342,4 +493,79 @@ class DisplayGenerator {
|
||||
container.appendChild(document.createTextNode(parts[i]));
|
||||
}
|
||||
}
|
||||
|
||||
static _getPitchInfos(definition) {
|
||||
const results = new Map();
|
||||
|
||||
const allExpressions = new Set();
|
||||
const allReadings = new Set();
|
||||
const expressions = definition.expressions;
|
||||
const sources = Array.isArray(expressions) ? expressions : [definition];
|
||||
for (const {pitches: expressionPitches, expression} of sources) {
|
||||
allExpressions.add(expression);
|
||||
for (const {reading, pitches, dictionary} of expressionPitches) {
|
||||
allReadings.add(reading);
|
||||
let dictionaryResults = results.get(dictionary);
|
||||
if (typeof dictionaryResults === 'undefined') {
|
||||
dictionaryResults = [];
|
||||
results.set(dictionary, dictionaryResults);
|
||||
}
|
||||
|
||||
for (const {position, tags} of pitches) {
|
||||
let pitchInfo = DisplayGenerator._findExistingPitchInfo(reading, position, tags, dictionaryResults);
|
||||
if (pitchInfo === null) {
|
||||
pitchInfo = {expressions: new Set(), reading, position, tags};
|
||||
dictionaryResults.push(pitchInfo);
|
||||
}
|
||||
pitchInfo.expressions.add(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const dictionaryResults of results.values()) {
|
||||
for (const result of dictionaryResults) {
|
||||
const exclusiveExpressions = [];
|
||||
const exclusiveReadings = [];
|
||||
const resultExpressions = result.expressions;
|
||||
if (!areSetsEqual(resultExpressions, allExpressions)) {
|
||||
exclusiveExpressions.push(...getSetIntersection(resultExpressions, allExpressions));
|
||||
}
|
||||
if (allReadings.size > 1) {
|
||||
exclusiveReadings.push(result.reading);
|
||||
}
|
||||
result.exclusiveExpressions = exclusiveExpressions;
|
||||
result.exclusiveReadings = exclusiveReadings;
|
||||
}
|
||||
}
|
||||
|
||||
return [...results.entries()];
|
||||
}
|
||||
|
||||
static _findExistingPitchInfo(reading, position, tags, pitchInfoList) {
|
||||
for (const pitchInfo of pitchInfoList) {
|
||||
if (
|
||||
pitchInfo.reading === reading &&
|
||||
pitchInfo.position === position &&
|
||||
DisplayGenerator._areTagListsEqual(pitchInfo.tags, tags)
|
||||
) {
|
||||
return pitchInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static _areTagListsEqual(tagList1, tagList2) {
|
||||
const ii = tagList1.length;
|
||||
if (tagList2.length !== ii) { return false; }
|
||||
|
||||
for (let i = 0; i < ii; ++i) {
|
||||
const tag1 = tagList1[i];
|
||||
const tag2 = tagList2[i];
|
||||
if (tag1.name !== tag2.name || tag1.dictionary !== tag2.dictionary) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -385,6 +385,9 @@ class Display {
|
||||
data.audioEnabled = `${options.audio.enabled}`;
|
||||
data.compactGlossaries = `${options.general.compactGlossaries}`;
|
||||
data.enableSearchTags = `${options.scanning.enableSearchTags}`;
|
||||
data.showPitchAccentDownstepNotation = `${options.general.showPitchAccentDownstepNotation}`;
|
||||
data.showPitchAccentPositionNotation = `${options.general.showPitchAccentPositionNotation}`;
|
||||
data.showPitchAccentGraph = `${options.general.showPitchAccentGraph}`;
|
||||
data.debug = `${options.general.debugInfo}`;
|
||||
}
|
||||
|
||||
|
@ -64,6 +64,8 @@ const jp = (() => {
|
||||
[0xffe0, 0xffee] // Currency markers
|
||||
];
|
||||
|
||||
const SMALL_KANA_SET = new Set(Array.from('ぁぃぅぇぉゃゅょゎァィゥェォャュョヮ'));
|
||||
|
||||
|
||||
// Character code testing functions
|
||||
|
||||
@ -112,6 +114,26 @@ const jp = (() => {
|
||||
}
|
||||
|
||||
|
||||
// Mora functions
|
||||
|
||||
function isMoraPitchHigh(moraIndex, pitchAccentPosition) {
|
||||
return pitchAccentPosition === 0 ? (moraIndex > 0) : (moraIndex < pitchAccentPosition);
|
||||
}
|
||||
|
||||
function getKanaMorae(text) {
|
||||
const morae = [];
|
||||
let i;
|
||||
for (const c of text) {
|
||||
if (SMALL_KANA_SET.has(c) && (i = morae.length) > 0) {
|
||||
morae[i - 1] += c;
|
||||
} else {
|
||||
morae.push(c);
|
||||
}
|
||||
}
|
||||
return morae;
|
||||
}
|
||||
|
||||
|
||||
// Exports
|
||||
|
||||
return {
|
||||
@ -119,6 +141,8 @@ const jp = (() => {
|
||||
isCodePointKana,
|
||||
isCodePointJapanese,
|
||||
isStringEntirelyKana,
|
||||
isStringPartiallyJapanese
|
||||
isStringPartiallyJapanese,
|
||||
isMoraPitchHigh,
|
||||
getKanaMorae
|
||||
};
|
||||
})();
|
||||
|
4
test/data/dictionaries/valid-dictionary1/tag_bank_3.json
Normal file
4
test/data/dictionaries/valid-dictionary1/tag_bank_3.json
Normal file
@ -0,0 +1,4 @@
|
||||
[
|
||||
["ptag1", "pcategory1", 0, "ptag1 notes", 0],
|
||||
["ptag2", "pcategory2", 0, "ptag2 notes", 0]
|
||||
]
|
@ -1,5 +1,39 @@
|
||||
[
|
||||
["打", "freq", 1],
|
||||
["打つ", "freq", 2],
|
||||
["打ち込む", "freq", 3]
|
||||
["打ち込む", "freq", 3],
|
||||
[
|
||||
"打ち込む",
|
||||
"pitch",
|
||||
{
|
||||
"reading": "うちこむ",
|
||||
"pitches": [
|
||||
{"position": 0},
|
||||
{"position": 3}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"打ち込む",
|
||||
"pitch",
|
||||
{
|
||||
"reading": "ぶちこむ",
|
||||
"pitches": [
|
||||
{"position": 0},
|
||||
{"position": 3}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"お手前",
|
||||
"pitch",
|
||||
{
|
||||
"reading": "おてまえ",
|
||||
"pitches": [
|
||||
{"position": 2, "tags": ["ptag1"]},
|
||||
{"position": 2, "tags": ["ptag2"]},
|
||||
{"position": 0, "tags": ["ptag2"]}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
@ -231,8 +231,8 @@ async function testDatabase1() {
|
||||
true
|
||||
);
|
||||
vm.assert.deepStrictEqual(counts, {
|
||||
counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}],
|
||||
total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 3, tagMeta: 12}
|
||||
counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}],
|
||||
total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}
|
||||
});
|
||||
|
||||
// Test find* functions
|
||||
@ -648,9 +648,10 @@ async function testFindTermMetaBulk1(database, titles) {
|
||||
}
|
||||
],
|
||||
expectedResults: {
|
||||
total: 1,
|
||||
total: 3,
|
||||
modes: [
|
||||
['freq', 1]
|
||||
['freq', 1],
|
||||
['pitch', 2]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -392,6 +392,59 @@ function testDistributeFuriganaInflected() {
|
||||
}
|
||||
}
|
||||
|
||||
function testIsMoraPitchHigh() {
|
||||
const data = [
|
||||
[[0, 0], false],
|
||||
[[1, 0], true],
|
||||
[[2, 0], true],
|
||||
[[3, 0], true],
|
||||
|
||||
[[0, 1], true],
|
||||
[[1, 1], false],
|
||||
[[2, 1], false],
|
||||
[[3, 1], false],
|
||||
|
||||
[[0, 2], true],
|
||||
[[1, 2], true],
|
||||
[[2, 2], false],
|
||||
[[3, 2], false],
|
||||
|
||||
[[0, 3], true],
|
||||
[[1, 3], true],
|
||||
[[2, 3], true],
|
||||
[[3, 3], false],
|
||||
|
||||
[[0, 4], true],
|
||||
[[1, 4], true],
|
||||
[[2, 4], true],
|
||||
[[3, 4], true]
|
||||
];
|
||||
|
||||
for (const [[moraIndex, pitchAccentPosition], expected] of data) {
|
||||
const actual = jp.isMoraPitchHigh(moraIndex, pitchAccentPosition);
|
||||
assert.strictEqual(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
function testGetKanaMorae() {
|
||||
const data = [
|
||||
['かこ', ['か', 'こ']],
|
||||
['かっこ', ['か', 'っ', 'こ']],
|
||||
['カコ', ['カ', 'コ']],
|
||||
['カッコ', ['カ', 'ッ', 'コ']],
|
||||
['コート', ['コ', 'ー', 'ト']],
|
||||
['ちゃんと', ['ちゃ', 'ん', 'と']],
|
||||
['とうきょう', ['と', 'う', 'きょ', 'う']],
|
||||
['ぎゅう', ['ぎゅ', 'う']],
|
||||
['ディスコ', ['ディ', 'ス', 'コ']]
|
||||
];
|
||||
|
||||
for (const [text, expected] of data) {
|
||||
const actual = jp.getKanaMorae(text);
|
||||
vm.assert.deepStrictEqual(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
testIsCodePointKanji();
|
||||
@ -408,6 +461,8 @@ function main() {
|
||||
testConvertAlphabeticToKana();
|
||||
testDistributeFurigana();
|
||||
testDistributeFuriganaInflected();
|
||||
testIsMoraPitchHigh();
|
||||
testGetKanaMorae();
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user