Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Add support for definitions with structured content #1689

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions ext/css/display.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
111 changes: 110 additions & 1 deletion ext/data/schemas/dictionary-term-bank-v3-schema.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down Expand Up @@ -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": [
Expand All @@ -67,6 +159,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",
Expand Down
51 changes: 50 additions & 1 deletion ext/js/display/display-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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' ?
Expand All @@ -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`;
Expand Down Expand Up @@ -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;
Expand Down
50 changes: 44 additions & 6 deletions ext/js/language/dictionary-importer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
27 changes: 26 additions & 1 deletion test/data/dictionaries/valid-dictionary1/term_bank_1.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
]
4 changes: 2 additions & 2 deletions test/test-database.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down