aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-05-19 18:24:50 -0400
committerGitHub <noreply@github.com>2021-05-19 18:24:50 -0400
commiteddd0288643f08d2a2c85f73575bc7ee1c157539 (patch)
treebbcc7dd1279f4954324eccc34d6bf718ff1899aa
parentae89a8f2ad6274f77afbc0c8c202c0fbc0dc8757 (diff)
Add support for definitions with structured content (#1689)
* Add structured content to schema * Add support for generating custom content * Update importer * Update test data * Add verticalAlign property
-rw-r--r--ext/css/display.css13
-rw-r--r--ext/data/schemas/dictionary-term-bank-v3-schema.json111
-rw-r--r--ext/js/display/display-generator.js51
-rw-r--r--ext/js/language/dictionary-importer.js50
-rw-r--r--test/data/dictionaries/valid-dictionary1/term_bank_1.json27
-rw-r--r--test/test-database.js4
6 files changed, 245 insertions, 11 deletions
diff --git a/ext/css/display.css b/ext/css/display.css
index 529e4249..b8f67ff9 100644
--- a/ext/css/display.css
+++ b/ext/css/display.css
@@ -1643,6 +1643,19 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con
white-space: pre-line;
}
+.gloss-image-link[data-vertical-align=baseline] { vertical-align: baseline; }
+.gloss-image-link[data-vertical-align=sub] { vertical-align: sub; }
+.gloss-image-link[data-vertical-align=super] { vertical-align: super; }
+.gloss-image-link[data-vertical-align=text-top] { vertical-align: top; }
+.gloss-image-link[data-vertical-align=text-bottom] { vertical-align: bottom; }
+.gloss-image-link[data-vertical-align=middle] { vertical-align: middle; }
+.gloss-image-link[data-vertical-align=top] { vertical-align: top; }
+.gloss-image-link[data-vertical-align=bottom] { vertical-align: bottom; }
+.gloss-image-link[data-collapsed=true],
+:root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] {
+ vertical-align: baseline;
+}
+
.gloss-image-link[data-collapsed=true] .gloss-image-container,
:root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] .gloss-image-container {
display: none;
diff --git a/ext/data/schemas/dictionary-term-bank-v3-schema.json b/ext/data/schemas/dictionary-term-bank-v3-schema.json
index 2289dfd6..0ab01edb 100644
--- a/ext/data/schemas/dictionary-term-bank-v3-schema.json
+++ b/ext/data/schemas/dictionary-term-bank-v3-schema.json
@@ -1,5 +1,97 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
+ "definitions": {
+ "structuredContent": {
+ "oneOf": [
+ {
+ "type": "string",
+ "description": "Represents a text node."
+ },
+ {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/structuredContent",
+ "description": "An array of child content."
+ }
+ },
+ {
+ "type": "object",
+ "oneOf": [
+ {
+ "type": "object",
+ "description": "Generic container tags.",
+ "required": [
+ "tag"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "tag": {
+ "type": "string",
+ "enum": ["ruby", "rt", "rp"]
+ },
+ "content": {
+ "$ref": "#/definitions/structuredContent"
+ }
+ }
+ },
+ {
+ "type": "object",
+ "description": "Image tag.",
+ "required": [
+ "tag",
+ "path"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "tag": {
+ "type": "string",
+ "const": "img"
+ },
+ "path": {
+ "type": "string",
+ "description": "Path to the image file in the archive."
+ },
+ "width": {
+ "type": "integer",
+ "description": "Preferred width of the image.",
+ "minimum": 1
+ },
+ "height": {
+ "type": "integer",
+ "description": "Preferred width of the image.",
+ "minimum": 1
+ },
+ "title": {
+ "type": "string",
+ "description": "Hover text for the image."
+ },
+ "pixelated": {
+ "type": "boolean",
+ "description": "Whether or not the image should appear pixelated at sizes larger than the image's native resolution.",
+ "default": false
+ },
+ "collapsed": {
+ "type": "boolean",
+ "description": "Whether or not the image is collapsed by default.",
+ "default": false
+ },
+ "collapsible": {
+ "type": "boolean",
+ "description": "Whether or not the image can be collapsed.",
+ "default": false
+ },
+ "verticalAlign": {
+ "type": "string",
+ "description": "The vertical alignment of the image.",
+ "enum": ["baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom"]
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ },
"type": "array",
"description": "Data file containing term and expression information.",
"additionalItems": {
@@ -46,7 +138,7 @@
"type": {
"type": "string",
"description": "The type of the data for this definition.",
- "enum": ["text", "image"]
+ "enum": ["text", "image", "structured-content"]
}
},
"oneOf": [
@@ -70,6 +162,23 @@
{
"required": [
"type",
+ "content"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["structured-content"]
+ },
+ "content": {
+ "$ref": "#/definitions/structuredContent",
+ "description": "Single definition for the term/expression using a structured content object."
+ }
+ }
+ },
+ {
+ "required": [
+ "type",
"path"
],
"additionalProperties": false,
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index 299d730b..2131d805 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -295,6 +295,8 @@ class DisplayGenerator {
switch (entry.type) {
case 'image':
return this._createTermDefinitionEntryImage(entry, dictionary);
+ case 'structured-content':
+ return this._createTermDefinitionEntryStructuredContent(entry.content, dictionary);
}
}
@@ -327,8 +329,18 @@ class DisplayGenerator {
return node;
}
+ _createTermDefinitionEntryStructuredContent(content, dictionary) {
+ const node = this._templates.instantiate('gloss-item');
+ const child = this._createStructuredContent(content, dictionary);
+ if (child !== null) {
+ const contentContainer = node.querySelector('.gloss-content');
+ contentContainer.appendChild(child);
+ }
+ return node;
+ }
+
_createDefinitionImage(data, dictionary) {
- const {path, width, height, preferredWidth, preferredHeight, title, pixelated, collapsed, collapsible} = data;
+ const {path, width, height, preferredWidth, preferredHeight, title, pixelated, collapsed, collapsible, verticalAlign} = data;
const usedWidth = (
typeof preferredWidth === 'number' ?
@@ -349,6 +361,9 @@ class DisplayGenerator {
node.dataset.hasAspectRatio = 'true';
node.dataset.collapsed = typeof collapsed === 'boolean' ? `${collapsed}` : 'false';
node.dataset.collapsible = typeof collapsible === 'boolean' ? `${collapsible}` : 'true';
+ if (typeof verticalAlign === 'string') {
+ node.dataset.verticalAlign = verticalAlign;
+ }
const imageContainer = node.querySelector('.gloss-image-container');
imageContainer.style.width = `${usedWidth}em`;
@@ -386,6 +401,40 @@ class DisplayGenerator {
}
}
+ _createStructuredContent(content, dictionary) {
+ if (typeof content === 'string') {
+ return document.createTextNode(content);
+ }
+ if (!(typeof content === 'object' && content !== null)) {
+ return null;
+ }
+ if (Array.isArray(content)) {
+ const fragment = document.createDocumentFragment();
+ for (const item of content) {
+ const child = this._createStructuredContent(item, dictionary);
+ if (child !== null) { fragment.appendChild(child); }
+ }
+ return fragment;
+ }
+ const {tag} = content;
+ switch (tag) {
+ case 'ruby':
+ case 'rt':
+ case 'rp':
+ {
+ const node = document.createElement(tag);
+ const child = this._createStructuredContent(content.content, dictionary);
+ if (child !== null) {
+ node.appendChild(child);
+ }
+ return node;
+ }
+ case 'img':
+ return this._createDefinitionImage(content, dictionary);
+ }
+ return null;
+ }
+
_createTermDisambiguation(disambiguation) {
const node = this._templates.instantiate('definition-disambiguation');
node.dataset.term = disambiguation;
diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js
index 051375a0..4d885a74 100644
--- a/ext/js/language/dictionary-importer.js
+++ b/ext/js/language/dictionary-importer.js
@@ -300,20 +300,58 @@ class DictionaryImporter {
return data.text;
case 'image':
return await this._formatDictionaryTermGlossaryImage(data, context, entry);
+ case 'structured-content':
+ return await this._formatStructuredContent(data, context, entry);
default:
throw new Error(`Unhandled data type: ${data.type}`);
}
}
async _formatDictionaryTermGlossaryImage(data, context, entry) {
+ return await this._createImageData(data, context, entry, {type: 'image'});
+ }
+
+ async _formatStructuredContent(data, context, entry) {
+ const content = await this._prepareStructuredContent(data.content, context, entry);
+ return {
+ type: 'structured-content',
+ content
+ };
+ }
+
+ async _prepareStructuredContent(content, context, entry) {
+ if (typeof content === 'string' || !(typeof content === 'object' && content !== null)) {
+ return content;
+ }
+ if (Array.isArray(content)) {
+ for (let i = 0, ii = content.length; i < ii; ++i) {
+ content[i] = await this._prepareStructuredContent(content[i], context, entry);
+ }
+ return content;
+ }
+ const {tag} = content;
+ switch (tag) {
+ case 'img':
+ return await this._prepareStructuredContentImage(content, context, entry);
+ }
+ const childContent = content.content;
+ if (typeof childContent !== 'undefined') {
+ content.content = await this._prepareStructuredContent(childContent, context, entry);
+ }
+ return content;
+ }
+
+ async _prepareStructuredContentImage(content, context, entry) {
+ const {verticalAlign} = content;
+ const result = await this._createImageData(content, context, entry, {tag: 'img'});
+ if (typeof verticalAlign === 'string') { result.verticalAlign = verticalAlign; }
+ return result;
+ }
+
+ async _createImageData(data, context, entry, attributes) {
const {path, width: preferredWidth, height: preferredHeight, title, description, pixelated, collapsed, collapsible} = data;
const {width, height} = await this._getImageMedia(path, context, entry);
- const newData = {
- type: 'image',
- path,
- width,
- height
- };
+ const newData = Object.assign({}, attributes, {path, width, height});
if (typeof preferredWidth === 'number') { newData.preferredWidth = preferredWidth; }
if (typeof preferredHeight === 'number') { newData.preferredHeight = preferredHeight; }
if (typeof title === 'string') { newData.title = title; }
diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
index 5094dd0f..a2c9a216 100644
--- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json
+++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
@@ -12,5 +12,30 @@
["画像", "がぞう", "n", "n", 1, ["gazou definition 1", {"type": "image", "path": "image.gif", "width": 350, "height": 350, "description": "gazou definition 2", "pixelated": true}], 5, "P E1"],
["読む", "よむ", "vt", "v5", 100, ["to read"], 6, "P E1"],
["強み", "つよみ", "n", "n", 90, ["strong point"], 7, "P E1"],
- ["テキスト", "テキスト", "n", "n", 1, ["text definition 1", {"type": "text", "text": "text definition 2"}], 8, "P E1"]
+ ["テキスト", "テキスト", "n", "n", 1, ["text definition 1", {"type": "text", "text": "text definition 2"}], 8, "P E1"],
+ [
+ "内容", "ないよう", "n", "n", 35,
+ [
+ "naiyou definition 1",
+ {"type": "structured-content", "content": "naiyou definition 2"},
+ {"type": "structured-content", "content": ["naiyou definition 3"]},
+ {"type": "structured-content", "content": {"tag": "img", "path": "image.gif", "width": 35, "height": 35, "pixelated": true}},
+ {"type": "structured-content", "content": [
+ "naiyou definition 5: ",
+ {"tag": "img", "path": "image.gif", "width": 35, "height": 35, "pixelated": true, "collapsible": false},
+ "\nmore content 1: ",
+ {"tag": "img", "path": "image.gif", "width": 35, "height": 35, "pixelated": true, "collapsible": true},
+ "\nmore content 2: ",
+ {"tag": "img", "path": "image.gif", "width": 35, "height": 35, "pixelated": true, "collapsible": false, "collapsed": false, "verticalAlign": "middle"},
+ " and ",
+ {"tag": "img", "path": "image.gif", "width": 35, "height": 35, "pixelated": true, "collapsible": false, "collapsed": true}
+ ]},
+ {"type": "structured-content", "content": [
+ "naiyou definition 6: ",
+ {"tag": "ruby", "content": ["内", {"tag": "rp", "content": "("}, {"tag": "rt", "content": "ない"}, {"tag": "rp", "content": ")"}]},
+ {"tag": "ruby", "content": ["容", {"tag": "rp", "content": "("}, {"tag": "rt", "content": "よう"}, {"tag": "rp", "content": ")"}]}
+ ]}
+ ],
+ 9, "P E1"
+ ]
] \ No newline at end of file
diff --git a/test/test-database.js b/test/test-database.js
index da4cc6d3..e68a39ba 100644
--- a/test/test-database.js
+++ b/test/test-database.js
@@ -162,8 +162,8 @@ async function testDatabase1() {
true
);
vm.assert.deepStrictEqual(counts, {
- counts: [{kanji: 2, kanjiMeta: 2, terms: 14, termMeta: 12, tagMeta: 15, media: 1}],
- total: {kanji: 2, kanjiMeta: 2, terms: 14, termMeta: 12, tagMeta: 15, media: 1}
+ counts: [{kanji: 2, kanjiMeta: 2, terms: 15, termMeta: 12, tagMeta: 15, media: 1}],
+ total: {kanji: 2, kanjiMeta: 2, terms: 15, termMeta: 12, tagMeta: 15, media: 1}
});
// Test find* functions