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
This commit is contained in:
parent
ae89a8f2ad
commit
eddd028864
@ -1643,6 +1643,19 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con
|
|||||||
white-space: pre-line;
|
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,
|
.gloss-image-link[data-collapsed=true] .gloss-image-container,
|
||||||
:root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] .gloss-image-container {
|
:root[data-glossary-layout-mode=compact] .gloss-image-link[data-collapsible=true] .gloss-image-container {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -1,5 +1,97 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$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",
|
"type": "array",
|
||||||
"description": "Data file containing term and expression information.",
|
"description": "Data file containing term and expression information.",
|
||||||
"additionalItems": {
|
"additionalItems": {
|
||||||
@ -46,7 +138,7 @@
|
|||||||
"type": {
|
"type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The type of the data for this definition.",
|
"description": "The type of the data for this definition.",
|
||||||
"enum": ["text", "image"]
|
"enum": ["text", "image", "structured-content"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
@ -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": [
|
"required": [
|
||||||
"type",
|
"type",
|
||||||
|
@ -295,6 +295,8 @@ class DisplayGenerator {
|
|||||||
switch (entry.type) {
|
switch (entry.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return this._createTermDefinitionEntryImage(entry, dictionary);
|
return this._createTermDefinitionEntryImage(entry, dictionary);
|
||||||
|
case 'structured-content':
|
||||||
|
return this._createTermDefinitionEntryStructuredContent(entry.content, dictionary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,8 +329,18 @@ class DisplayGenerator {
|
|||||||
return node;
|
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) {
|
_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 = (
|
const usedWidth = (
|
||||||
typeof preferredWidth === 'number' ?
|
typeof preferredWidth === 'number' ?
|
||||||
@ -349,6 +361,9 @@ class DisplayGenerator {
|
|||||||
node.dataset.hasAspectRatio = 'true';
|
node.dataset.hasAspectRatio = 'true';
|
||||||
node.dataset.collapsed = typeof collapsed === 'boolean' ? `${collapsed}` : 'false';
|
node.dataset.collapsed = typeof collapsed === 'boolean' ? `${collapsed}` : 'false';
|
||||||
node.dataset.collapsible = typeof collapsible === 'boolean' ? `${collapsible}` : 'true';
|
node.dataset.collapsible = typeof collapsible === 'boolean' ? `${collapsible}` : 'true';
|
||||||
|
if (typeof verticalAlign === 'string') {
|
||||||
|
node.dataset.verticalAlign = verticalAlign;
|
||||||
|
}
|
||||||
|
|
||||||
const imageContainer = node.querySelector('.gloss-image-container');
|
const imageContainer = node.querySelector('.gloss-image-container');
|
||||||
imageContainer.style.width = `${usedWidth}em`;
|
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) {
|
_createTermDisambiguation(disambiguation) {
|
||||||
const node = this._templates.instantiate('definition-disambiguation');
|
const node = this._templates.instantiate('definition-disambiguation');
|
||||||
node.dataset.term = disambiguation;
|
node.dataset.term = disambiguation;
|
||||||
|
@ -300,20 +300,58 @@ class DictionaryImporter {
|
|||||||
return data.text;
|
return data.text;
|
||||||
case 'image':
|
case 'image':
|
||||||
return await this._formatDictionaryTermGlossaryImage(data, context, entry);
|
return await this._formatDictionaryTermGlossaryImage(data, context, entry);
|
||||||
|
case 'structured-content':
|
||||||
|
return await this._formatStructuredContent(data, context, entry);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled data type: ${data.type}`);
|
throw new Error(`Unhandled data type: ${data.type}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _formatDictionaryTermGlossaryImage(data, context, entry) {
|
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 {path, width: preferredWidth, height: preferredHeight, title, description, pixelated, collapsed, collapsible} = data;
|
||||||
const {width, height} = await this._getImageMedia(path, context, entry);
|
const {width, height} = await this._getImageMedia(path, context, entry);
|
||||||
const newData = {
|
const newData = Object.assign({}, attributes, {path, width, height});
|
||||||
type: 'image',
|
|
||||||
path,
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
};
|
|
||||||
if (typeof preferredWidth === 'number') { newData.preferredWidth = preferredWidth; }
|
if (typeof preferredWidth === 'number') { newData.preferredWidth = preferredWidth; }
|
||||||
if (typeof preferredHeight === 'number') { newData.preferredHeight = preferredHeight; }
|
if (typeof preferredHeight === 'number') { newData.preferredHeight = preferredHeight; }
|
||||||
if (typeof title === 'string') { newData.title = title; }
|
if (typeof title === 'string') { newData.title = title; }
|
||||||
|
@ -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"],
|
["画像", "がぞう", "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"],
|
["読む", "よむ", "vt", "v5", 100, ["to read"], 6, "P E1"],
|
||||||
["強み", "つよみ", "n", "n", 90, ["strong point"], 7, "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"
|
||||||
|
]
|
||||||
]
|
]
|
@ -162,8 +162,8 @@ async function testDatabase1() {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
vm.assert.deepStrictEqual(counts, {
|
vm.assert.deepStrictEqual(counts, {
|
||||||
counts: [{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: 14, termMeta: 12, tagMeta: 15, media: 1}
|
total: {kanji: 2, kanjiMeta: 2, terms: 15, termMeta: 12, tagMeta: 15, media: 1}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test find* functions
|
// Test find* functions
|
||||||
|
Loading…
Reference in New Issue
Block a user