From 5756885fa9340f087a168d40c8d0d10a3a8fe4d5 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 25 Jun 2021 17:24:29 -0400 Subject: [PATCH] Structured content updates (#1753) * Update schema * Update content generation * Update styles * Update test data * Update style names --- ext/css/display.css | 26 +++++ ext/css/material.css | 2 + .../dictionary-term-bank-v3-schema.json | 107 +++++++++++++++++- ext/js/display/display-generator.js | 71 ++++++++++-- .../valid-dictionary1/term_bank_1.json | 73 ++++++++++++ 5 files changed, 270 insertions(+), 9 deletions(-) diff --git a/ext/css/display.css b/ext/css/display.css index 45658f19..e1efb223 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -1726,6 +1726,32 @@ button.definition-item-expansion-button:focus:focus-visible+.definition-item-con } +/* Structured content glossary styles */ +.gloss-sc-table-container { + display: block; +} +.gloss-sc-table { + table-layout: auto; + border-collapse: collapse; + border-spacing: 0; +} +.gloss-sc-tbody { + background-color: transparent; +} +.gloss-sc-thead, +.gloss-sc-tfoot, +.gloss-sc-th { + font-weight: bold; + background-color: var(--background-color-dark1); +} +.gloss-sc-th, +.gloss-sc-td { + border: calc(1em / var(--font-size-no-units)) solid var(--text-color-light2); + padding: 0.25em; + vertical-align: top; +} + + /* Kanji */ .kanji-glyph-container { display: block; diff --git a/ext/css/material.css b/ext/css/material.css index dd135065..b19b0b0f 100644 --- a/ext/css/material.css +++ b/ext/css/material.css @@ -69,6 +69,7 @@ --text-color-light4: #888888; --background-color: #f8f9fa; --background-color-light: #ffffff; + --background-color-dark1: #eeeeee; --shadow-color: rgba(0, 0, 0, 0.185); --shadow-color-off: rgba(0, 0, 0, 0); @@ -128,6 +129,7 @@ --text-color-light4: #777777; --background-color: #1e1e1e; --background-color-light: #0a0a0a; + --background-color-dark1: #333333; --shadow-color: rgba(255, 255, 255, 0.185); --shadow-color-off: rgba(255, 255, 255, 0); diff --git a/ext/data/schemas/dictionary-term-bank-v3-schema.json b/ext/data/schemas/dictionary-term-bank-v3-schema.json index fd3f3844..af4494ff 100644 --- a/ext/data/schemas/dictionary-term-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-term-bank-v3-schema.json @@ -17,6 +17,20 @@ { "type": "object", "oneOf": [ + { + "type": "object", + "description": "Empty tags.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["br"] + } + } + }, { "type": "object", "description": "Generic container tags.", @@ -27,13 +41,61 @@ "properties": { "tag": { "type": "string", - "enum": ["ruby", "rt", "rp"] + "enum": ["ruby", "rt", "rp", "table", "thead", "tbody", "tfoot", "tr"] }, "content": { "$ref": "#/definitions/structuredContent" } } }, + { + "type": "object", + "description": "Table tags.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["td", "th"] + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "colSpan": { + "type": "integer", + "minimum": 1 + }, + "rowSpan": { + "type": "integer", + "minimum": 1 + }, + "style": { + "$ref": "#/definitions/structuredContentStyle" + } + } + }, + { + "type": "object", + "description": "Container tags supporting configurable styles.", + "required": [ + "tag" + ], + "additionalProperties": false, + "properties": { + "tag": { + "type": "string", + "enum": ["span", "div"] + }, + "content": { + "$ref": "#/definitions/structuredContent" + }, + "style": { + "$ref": "#/definitions/structuredContentStyle" + } + } + }, { "type": "object", "description": "Image tag.", @@ -112,6 +174,49 @@ ] } ] + }, + "structuredContentStyle": { + "type": "object", + "additionalProperties": false, + "properties": { + "fontStyle": { + "type": "string", + "enum": ["normal", "italic"], + "default": "normal" + }, + "fontWeight": { + "type": "string", + "enum": ["normal", "bold"], + "default": "normal" + }, + "fontSize": { + "type": "string", + "enum": ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"], + "default": "medium" + }, + "textDecorationLine": { + "oneOf": [ + { + "type": "string", + "enum": ["none", "underline", "overline", "line-through"], + "default": "none" + }, + { + "type": "array", + "items": { + "type": "string", + "enum": ["underline", "overline", "line-through"], + "default": "none" + } + } + ] + }, + "verticalAlign": { + "type": "string", + "enum": ["baseline", "sub", "super", "text-top", "text-bottom", "middle", "top", "bottom"], + "default": "baseline" + } + } } }, "type": "array", diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index 6a5b26f1..7de65f78 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -442,23 +442,78 @@ class DisplayGenerator { } const {tag} = content; switch (tag) { + case 'br': + return this._createStructuredContentElement(tag, content, dictionary, 'simple', false, false); 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; - } + return this._createStructuredContentElement(tag, content, dictionary, 'simple', true, false); + case 'table': + return this._createStructuredContentTableElement(tag, content, dictionary); + case 'thead': + case 'tbody': + case 'tfoot': + case 'tr': + return this._createStructuredContentElement(tag, content, dictionary, 'table', true, false); + case 'th': + case 'td': + return this._createStructuredContentElement(tag, content, dictionary, 'table-cell', true, true); + case 'div': + case 'span': + return this._createStructuredContentElement(tag, content, dictionary, 'simple', true, true); case 'img': return this._createDefinitionImage(content, dictionary); } return null; } + _createStructuredContentTableElement(tag, content, dictionary) { + const container = document.createElement('div'); + container.classList = 'gloss-sc-table-container'; + const table = this._createStructuredContentElement(tag, content, dictionary, 'table', true, false); + container.appendChild(table); + return container; + } + + _createStructuredContentElement(tag, content, dictionary, type, hasChildren, hasStyle) { + const node = document.createElement(tag); + node.className = `gloss-sc-${tag}`; + switch (type) { + case 'table-cell': + { + const {colSpan, rowSpan} = content; + if (typeof colSpan === 'number') { node.colSpan = colSpan; } + if (typeof rowSpan === 'number') { node.rowSpan = rowSpan; } + } + break; + } + if (hasStyle) { + const {style} = content; + if (typeof style === 'object' && style !== null) { + this._setStructuredContentElementStyle(node, style); + } + } + if (hasChildren) { + const child = this._createStructuredContent(content.content, dictionary); + if (child !== null) { node.appendChild(child); } + } + return node; + } + + _setStructuredContentElementStyle(node, contentStyle) { + const {style} = node; + const {fontStyle, fontWeight, fontSize, textDecorationLine, verticalAlign} = contentStyle; + if (typeof fontStyle === 'string') { style.fontStyle = fontStyle; } + if (typeof fontWeight === 'string') { style.fontWeight = fontWeight; } + if (typeof fontSize === 'string') { style.fontSize = fontSize; } + if (typeof verticalAlign === 'string') { style.verticalAlign = verticalAlign; } + if (typeof textDecorationLine === 'string') { + style.textDecoration = textDecorationLine; + } else if (Array.isArray(textDecorationLine)) { + style.textDecoration = textDecorationLine.join(' '); + } + } + _createTermDisambiguation(disambiguation) { const node = this._templates.instantiate('definition-disambiguation'); node.dataset.term = disambiguation; diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json index 1ba70c26..c58a17bf 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -59,6 +59,79 @@ "莢 ", {"tag": "img", "path": "character.gif", "width": 1, "height": 1, "imageRendering": "crisp-edges", "background": false, "appearance": "monochrome", "collapsible": false, "collapsed": false, "sizeUnits": "em"}, "莢\n" + ]}, + {"type": "structured-content", "content": [ + {"tag": "div", "style": {"fontStyle": "normal"}, "content": "fontStyle:normal"}, + {"tag": "div", "style": {"fontStyle": "italic"}, "content": "fontStyle:italic"}, + {"tag": "div", "style": {"fontWeight": "normal"}, "content": "fontWeight:normal"}, + {"tag": "div", "style": {"fontWeight": "bold"}, "content": "fontWeight:bold"}, + {"tag": "div", "style": {"fontSize": "xx-small"}, "content": "fontSize:xx-small"}, + {"tag": "div", "style": {"fontSize": "x-small"}, "content": "fontSize:x-small"}, + {"tag": "div", "style": {"fontSize": "small"}, "content": "fontSize:small"}, + {"tag": "div", "style": {"fontSize": "medium"}, "content": "fontSize:medium"}, + {"tag": "div", "style": {"fontSize": "large"}, "content": "fontSize:large"}, + {"tag": "div", "style": {"fontSize": "x-large"}, "content": "fontSize:x-large"}, + {"tag": "div", "style": {"fontSize": "xx-large"}, "content": "fontSize:xx-large"}, + {"tag": "div", "style": {"fontSize": "xxx-large"}, "content": "fontSize:xxx-large"}, + {"tag": "div", "style": {"textDecorationLine": "none"}, "content": "textDecorationLine:none "}, + {"tag": "div", "style": {"textDecorationLine": "underline"}, "content": "textDecorationLine:underline "}, + {"tag": "div", "style": {"textDecorationLine": "overline"}, "content": "textDecorationLine:overline "}, + {"tag": "div", "style": {"textDecorationLine": "line-through"}, "content": "textDecorationLine:line-through "}, + {"tag": "div", "style": {"textDecorationLine": ["underline", "overline", "line-through"]}, "content": "textDecorationLine:[underline,overline,line-through] "}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "baseline"}, "content": "verticalAlign:baseline "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "sub"}, "content": "verticalAlign:sub "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "super"}, "content": "verticalAlign:super "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "text-top"}, "content": "verticalAlign:text-top "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "text-bottom"}, "content": "verticalAlign:text-bottom "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "middle"}, "content": "verticalAlign:middle "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "top"}, "content": "verticalAlign:top "}]}, + {"tag": "div", "content": ["baseline ", {"tag": "span", "style": {"verticalAlign": "bottom"}, "content": "verticalAlign:bottom "}]} + ]}, + {"type": "structured-content", "content": [ + "br", + {"tag": "br"}, + "br" + ]}, + {"type": "structured-content", "content": [ + {"tag": "table", "content": [ + {"tag": "thead", "content": [ + {"tag": "tr", "content": [ + {"tag": "th", "content": "Header 1"}, + {"tag": "th", "content": "Header 2"}, + {"tag": "th", "content": "Header 3"}, + {"tag": "th", "content": "Header 4"} + ]} + ]}, + {"tag": "tbody", "content": [ + {"tag": "tr", "content": [ + {"tag": "td", "content": "Cell A1"}, + {"tag": "td", "content": "Cell B1"}, + {"tag": "td", "content": "Cell C1"}, + {"tag": "td", "content": "Cell D1"} + ]}, + {"tag": "tr", "content": [ + {"tag": "td", "content": "Cell A2"}, + {"tag": "td", "content": "Cell B2"}, + {"tag": "td", "content": "Cell C2:D2", "colSpan": 2} + ]}, + {"tag": "tr", "content": [ + {"tag": "td", "content": "Cell A3"}, + {"tag": "td", "content": "Cell B3\nCell B4", "rowSpan": 2}, + {"tag": "td", "content": "Cell C3:D3\nCell C4:D4", "rowSpan": 2, "colSpan": 2} + ]}, + {"tag": "tr", "content": [ + {"tag": "td", "content": "Cell A4"} + ]} + ]}, + {"tag": "tfoot", "content": [ + {"tag": "tr", "content": [ + {"tag": "th", "content": "Footer 1"}, + {"tag": "th", "content": "Footer 2"}, + {"tag": "th", "content": "Footer 3"}, + {"tag": "th", "content": "Footer 4"} + ]} + ]} + ]} ]} ], 9, "P E1"