diff options
| -rw-r--r-- | ext/bg/search.html | 90 | ||||
| -rw-r--r-- | ext/fg/float.html | 92 | ||||
| -rw-r--r-- | ext/mixed/css/display-dark.css | 59 | ||||
| -rw-r--r-- | ext/mixed/css/display-default.css | 57 | ||||
| -rw-r--r-- | ext/mixed/css/display.css | 359 | ||||
| -rw-r--r-- | ext/mixed/js/display-generator.js | 332 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 87 | 
7 files changed, 928 insertions, 148 deletions
| diff --git a/ext/bg/search.html b/ext/bg/search.html index 409243dd..5d9babea 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -54,9 +54,98 @@              <hr> +            <div id="navigation-header" class="navigation-header" hidden><div class="navigation-header-actions"> +                <button class="action-button action-previous"><img src="/mixed/img/source-term.svg" class="icon-image" title="Source term (Alt + B)" alt></button> +                <button class="action-button action-next"><img src="/mixed/img/source-term.svg" class="icon-image" title="Next term (Alt + F)" alt></button> +            </div></div><div class="navigation-header-spacer"></div> +              <div id="content"></div> + +            <div id="no-results" hidden> +                <div class="entry"> +                    <p>No results found.</p> +                </div> +            </div>          </div> +        <template id="term-entry-template"><div class="entry" data-type="term"> +            <div class="entry-header1"> +                <div class="entry-header2"> +                    <div class="entry-header3"> +                        <div class="actions"> +                            <button class="action-button action-view-note pending disabled"><img src="/mixed/img/view-note.svg" class="icon-image" title="View added note (Alt + V)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="term-kanji"><img src="/mixed/img/add-term-kanji.svg" class="icon-image" title="Add expression (Alt + E)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="term-kana"><img src="/mixed/img/add-term-kana.svg" class="icon-image" title="Add reading (Alt + R)" alt></button> +                            <button class="action-button action-play-audio"><img src="/mixed/img/play-audio.svg" class="icon-image" title="Play audio (Alt + P)" alt></button> +                            <span class="action-button action-current-indicator"><img src="/mixed/img/entry-current.svg" class="icon-image current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt></span> +                        </div> +                        <div class="term-expression-list"></div> +                    </div> +                    <div class="term-reasons"></div> +                </div> +                <div class="frequencies"></div> +            </div> +            <div class="term-definition-container"><ol class="term-definition-list"></ol></div> +            <pre class="debug-info"></pre> +        </div></template> +        <template id="term-expression-template"><div class="term-expression"><span class="term-expression-text"></span><div class="term-expression-details"> +            <button class="action-button action-play-audio"><img src="/mixed/img/play-audio.svg" class="icon-image" title="Play audio" alt></button> +            <div class="tags tag-list"></div> +            <div class="frequencies tag-list"></div> +        </div></div></template> +        <template id="term-definition-item-template"><li class="term-definition-item"> +            <div class="term-definition-tag-list tag-list"></div> +            <div class="term-definition-only-list"></div> +            <ul class="term-glossary-list"></ul> +        </li></template> +        <template id="term-definition-only-template"><span class="term-definition-only"></span></template> +        <template id="term-glossary-item-template"><li class="term-glossary-item"><span class="term-glossary"></span></li></template> +        <template id="term-reason-template"><span class="term-reason"></span></template> + +        <template id="kanji-entry-template"><div class="entry" data-type="kanji"> +            <div class="entry-header1"> +                <div class="entry-header2"> +                    <div class="entry-header3"> +                        <div class="actions"> +                            <button class="action-button action-view-note pending disabled"><img src="/mixed/img/view-note.svg" class="icon-image" title="View added note (Alt + V)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="kanji"><img src="/mixed/img/add-term-kanji.svg" class="icon-image" title="Add Kanji (Alt + K)" alt></button> +                            <span class="action-button action-current-indicator"><img src="/mixed/img/entry-current.svg" class="icon-image current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt></span> +                        </div> +                        <div class="kanji-glyph"></div> +                    </div> +                </div> +                <div class="frequencies"></div> +            </div> +            <div class="tags tag-list"></div> +            <table class="kanji-glyph-data"><tbody> +                <tr> +                    <th>Glossary</th> +                    <th>Readings</th> +                    <th>Statistics</th> +                </tr> +                <tr> +                    <td class="kanji-glossary-container"><ol class="kanji-glossary-list"></ol></td> +                    <td class="kanji-readings"><dl class="kanji-readings-chinese"></dl><dl class="kanji-readings-japanese"></dl></td> +                    <td class="kanji-statistics"></td> +                </tr> +                <tr><th colspan="3">Classifications</th></tr> +                <tr><td colspan="3" class="kanji-classifications"></td></tr> +                <tr><th colspan="3">Codepoints</th></tr> +                <tr><td colspan="3" class="kanji-codepoints"></td></tr> +                <tr><th colspan="3">Dictionary Indices</th></tr> +                <tr><td colspan="3" class="kanji-dictionary-indices"></td></tr> +            </tbody></table> +            <pre class="debug-info"></pre> +        </div></template> +        <template id="kanji-info-table-template"><table class="kanji-info-table"><tbody class="kanji-info-table-body"></tbody></table></template> +        <template id="kanji-info-table-item-template"><tr class="kanji-info-table-item"><th class="kanji-info-table-item-header"></th><td class="kanji-info-table-item-value"></td></tr></template> +        <template id="kanji-info-table-empty-template"><tr class="kanji-info-table-item kanji-info-table-item-empty"><td class="kanji-info-table-item-value-empty">No data found</td></tr class="kanji-info-table-item"></template> +        <template id="kanji-glossary-item-template"><li class="kanji-glossary-item"><span class="kanji-glossary"></span></li></template> +        <template id="kanji-reading-template"><dd class="kanji-reading"></dd></template> + +        <template id="tag-template"><span class="tag"></span></template> +        <template id="tag-frequency-template"><span class="tag" data-category="frequency"><span class="term-frequency-dictionary-name"></span><span class="term-frequency-separator"></span><span class="term-frequency-value"></span></template> +          <script src="/mixed/lib/handlebars.min.js"></script>          <script src="/mixed/lib/wanakana.min.js"></script> @@ -72,6 +161,7 @@          <script src="/mixed/js/audio.js"></script>          <script src="/mixed/js/display-context.js"></script>          <script src="/mixed/js/display.js"></script> +        <script src="/mixed/js/display-generator.js"></script>          <script src="/mixed/js/japanese.js"></script>          <script src="/mixed/js/scroll.js"></script>          <script src="/mixed/js/text-scanner.js"></script> diff --git a/ext/fg/float.html b/ext/fg/float.html index 886e5e8b..f5f56853 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -19,10 +19,21 @@              <img src="/mixed/img/spinner.gif">          </div> +        <div id="navigation-header" class="navigation-header" hidden><div class="navigation-header-actions"> +            <button class="action-button action-previous"><img src="/mixed/img/source-term.svg" class="icon-image" title="Source term (Alt + B)" alt></button> +            <button class="action-button action-next"><img src="/mixed/img/source-term.svg" class="icon-image" title="Next term (Alt + F)" alt></button> +        </div></div><div class="navigation-header-spacer"></div> +          <div id="definitions"></div> +        <div id="no-results" hidden> +            <div class="entry"> +                <p>No results found.</p> +            </div> +        </div> +          <div id="error-orphaned"> -            <div class="container-fluid"> +            <div class="entry">                  <h1>Yomichan Updated!</h1>                  <p>                      The Yomichan extension has been updated to a new version! In order to continue @@ -31,6 +42,84 @@              </div>          </div> +        <template id="term-entry-template"><div class="entry" data-type="term"> +            <div class="entry-header1"> +                <div class="entry-header2"> +                    <div class="entry-header3"> +                        <div class="actions"> +                            <button class="action-button action-view-note pending disabled"><img src="/mixed/img/view-note.svg" class="icon-image" title="View added note (Alt + V)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="term-kanji"><img src="/mixed/img/add-term-kanji.svg" class="icon-image" title="Add expression (Alt + E)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="term-kana"><img src="/mixed/img/add-term-kana.svg" class="icon-image" title="Add reading (Alt + R)" alt></button> +                            <button class="action-button action-play-audio"><img src="/mixed/img/play-audio.svg" class="icon-image" title="Play audio (Alt + P)" alt></button> +                            <span class="action-button action-current-indicator"><img src="/mixed/img/entry-current.svg" class="icon-image current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt></span> +                        </div> +                        <div class="term-expression-list"></div> +                    </div> +                    <div class="term-reasons"></div> +                </div> +                <div class="frequencies"></div> +            </div> +            <div class="term-definition-container"><ol class="term-definition-list"></ol></div> +            <pre class="debug-info"></pre> +        </div></template> +        <template id="term-expression-template"><div class="term-expression"><span class="term-expression-text"></span><div class="term-expression-details"> +            <button class="action-button action-play-audio"><img src="/mixed/img/play-audio.svg" class="icon-image" title="Play audio" alt></button> +            <div class="tags tag-list"></div> +            <div class="frequencies tag-list"></div> +        </div></div></template> +        <template id="term-definition-item-template"><li class="term-definition-item"> +            <div class="term-definition-tag-list tag-list"></div> +            <div class="term-definition-only-list"></div> +            <ul class="term-glossary-list"></ul> +        </li></template> +        <template id="term-definition-only-template"><span class="term-definition-only"></span></template> +        <template id="term-glossary-item-template"><li class="term-glossary-item"><span class="term-glossary"></span></li></template> +        <template id="term-reason-template"><span class="term-reason"></span></template> + +        <template id="kanji-entry-template"><div class="entry" data-type="kanji"> +            <div class="entry-header1"> +                <div class="entry-header2"> +                    <div class="entry-header3"> +                        <div class="actions"> +                            <button class="action-button action-view-note pending disabled"><img src="/mixed/img/view-note.svg" class="icon-image" title="View added note (Alt + V)" alt></button> +                            <button class="action-button action-add-note pending disabled" data-mode="kanji"><img src="/mixed/img/add-term-kanji.svg" class="icon-image" title="Add Kanji (Alt + K)" alt></button> +                            <span class="action-button action-current-indicator"><img src="/mixed/img/entry-current.svg" class="icon-image current" title="Current entry (Alt + Up/Down/Home/End/PgUp/PgDn)" alt></span> +                        </div> +                        <div class="kanji-glyph"></div> +                    </div> +                </div> +                <div class="frequencies"></div> +            </div> +            <div class="tags tag-list"></div> +            <table class="kanji-glyph-data"><tbody> +                <tr> +                    <th>Glossary</th> +                    <th>Readings</th> +                    <th>Statistics</th> +                </tr> +                <tr> +                    <td class="kanji-glossary-container"><ol class="kanji-glossary-list"></ol></td> +                    <td class="kanji-readings"><dl class="kanji-readings-chinese"></dl><dl class="kanji-readings-japanese"></dl></td> +                    <td class="kanji-statistics"></td> +                </tr> +                <tr><th colspan="3">Classifications</th></tr> +                <tr><td colspan="3" class="kanji-classifications"></td></tr> +                <tr><th colspan="3">Codepoints</th></tr> +                <tr><td colspan="3" class="kanji-codepoints"></td></tr> +                <tr><th colspan="3">Dictionary Indices</th></tr> +                <tr><td colspan="3" class="kanji-dictionary-indices"></td></tr> +            </tbody></table> +            <pre class="debug-info"></pre> +        </div></template> +        <template id="kanji-info-table-template"><table class="kanji-info-table"><tbody class="kanji-info-table-body"></tbody></table></template> +        <template id="kanji-info-table-item-template"><tr class="kanji-info-table-item"><th class="kanji-info-table-item-header"></th><td class="kanji-info-table-item-value"></td></tr></template> +        <template id="kanji-info-table-empty-template"><tr class="kanji-info-table-item kanji-info-table-item-empty"><td class="kanji-info-table-item-value-empty">No data found</td></tr class="kanji-info-table-item"></template> +        <template id="kanji-glossary-item-template"><li class="kanji-glossary-item"><span class="kanji-glossary"></span></li></template> +        <template id="kanji-reading-template"><dd class="kanji-reading"></dd></template> + +        <template id="tag-template"><span class="tag"></span></template> +        <template id="tag-frequency-template"><span class="tag" data-category="frequency"><span class="term-frequency-dictionary-name"></span><span class="term-frequency-separator"></span><span class="term-frequency-value"></span></template> +          <script src="/mixed/js/core.js"></script>          <script src="/mixed/js/dom.js"></script>          <script src="/mixed/js/api.js"></script> @@ -40,6 +129,7 @@          <script src="/mixed/js/audio.js"></script>          <script src="/mixed/js/display-context.js"></script>          <script src="/mixed/js/display.js"></script> +        <script src="/mixed/js/display-generator.js"></script>          <script src="/mixed/js/scroll.js"></script>          <script src="/fg/js/float.js"></script> diff --git a/ext/mixed/css/display-dark.css b/ext/mixed/css/display-dark.css index e26c72aa..088fc741 100644 --- a/ext/mixed/css/display-dark.css +++ b/ext/mixed/css/display-dark.css @@ -19,36 +19,53 @@  body { background-color: #1e1e1e; color: #d4d4d4; } -hr { border-top-color: #2f2f2f; } - -.tag-default      { background-color: #69696e; } -.tag-name         { background-color: #489148; } -.tag-expression   { background-color: #b07f39; } -.tag-popular      { background-color: #025caa; } -.tag-frequent     { background-color: #4490a7; } -.tag-archaism     { background-color: #b04340; } -.tag-dictionary   { background-color: #9057ad; } -.tag-frequency    { background-color: #489148; } -.tag-partOfSpeech { background-color: #565656; } - -.reasons       { color: #888888; } -.glossary li   { color: #888888; } -.glossary-item { color: #d4d4d4; } -.label         { color: #e1e1e1; } - -.expression .kanji-link { +.navigation-header { +    background-color: #1e1e1e; +    border-bottom-color: #2f2f2f; +} + +.entry+.entry { border-top-color: #2f2f2f; } + +.kanji-glyph-data>tbody>tr>* { border-top-color: #3f3f3f; } + +.tag { color: #e1e1e1; } +.tag[data-category=default]      { background-color: #69696e; } +.tag[data-category=name]         { background-color: #489148; } +.tag[data-category=expression]   { background-color: #b07f39; } +.tag[data-category=popular]      { background-color: #025caa; } +.tag[data-category=frequent]     { background-color: #4490a7; } +.tag[data-category=archaism]     { background-color: #b04340; } +.tag[data-category=dictionary]   { background-color: #9057ad; } +.tag[data-category=frequency]    { background-color: #489148; } +.tag[data-category=partOfSpeech] { background-color: #565656; } + +.term-reasons { color: #888888; } + +.term-expression>.term-expression-text .kanji-link {      border-bottom-color: #888888; -    color: #CCCCCC; +    color: #cccccc;  } -.expression-popular, .expression-popular .kanji-link { +.term-expression[data-frequency=popular]>.term-expression-text, +.term-expression[data-frequency=popular]>.term-expression-text .kanji-link {      color: #0275d8;  } -.expression-rare, .expression-rare .kanji-link { +.term-expression[data-frequency=rare]>.term-expression-text, +.term-expression[data-frequency=rare]>.term-expression-text .kanji-link {      color: #666666;  } +.term-definition-container, +.kanji-glossary-container { +    color: #888888; +} + +.term-glossary, +.kanji-glossary { +    color: #d4d4d4; +} +  .icon-checkbox:checked + label {      /* invert colors */      background-color: #d4d4d4; diff --git a/ext/mixed/css/display-default.css b/ext/mixed/css/display-default.css index ac237e79..69141c9d 100644 --- a/ext/mixed/css/display-default.css +++ b/ext/mixed/css/display-default.css @@ -19,36 +19,53 @@  body { background-color: #ffffff; color: #333333; } -hr { border-top-color: #eeeeee; } - -.tag-default      { background-color: #8a8a91; } -.tag-name         { background-color: #5cb85c; } -.tag-expression   { background-color: #f0ad4e; } -.tag-popular      { background-color: #0275d8; } -.tag-frequent     { background-color: #5bc0de; } -.tag-archaism     { background-color: #d9534f; } -.tag-dictionary   { background-color: #aa66cc; } -.tag-frequency    { background-color: #5cb85c; } -.tag-partOfSpeech { background-color: #565656; } - -.reasons       { color: #777777; } -.glossary li   { color: #777777; } -.glossary-item { color: #000000; } -.label         { color: #ffffff; } - -.expression .kanji-link { +.navigation-header { +    background-color: #ffffff; +    border-bottom-color: #eeeeee; +} + +.entry+.entry { border-top-color: #eeeeee; } + +.kanji-glyph-data>tbody>tr>* { border-top-color: #dddddd; } + +.tag { color: #ffffff; } +.tag[data-category=default]      { background-color: #8a8a91; } +.tag[data-category=name]         { background-color: #5cb85c; } +.tag[data-category=expression]   { background-color: #f0ad4e; } +.tag[data-category=popular]      { background-color: #0275d8; } +.tag[data-category=frequent]     { background-color: #5bc0de; } +.tag[data-category=archaism]     { background-color: #d9534f; } +.tag[data-category=dictionary]   { background-color: #aa66cc; } +.tag[data-category=frequency]    { background-color: #5cb85c; } +.tag[data-category=partOfSpeech] { background-color: #565656; } + +.term-reasons { color: #777777; } + +.term-expression>.term-expression-text .kanji-link {      border-bottom-color: #777777;      color: #333333;  } -.expression-popular, .expression-popular .kanji-link { +.term-expression[data-frequency=popular]>.term-expression-text, +.term-expression[data-frequency=popular]>.term-expression-text .kanji-link {      color: #0275d8;  } -.expression-rare, .expression-rare .kanji-link { +.term-expression[data-frequency=rare]>.term-expression-text, +.term-expression[data-frequency=rare]>.term-expression-text .kanji-link {      color: #999999;  } +.term-definition-container, +.kanji-glossary-container { +    color: #777777; +} + +.term-glossary, +.kanji-glossary { +    color: #000000; +} +  .icon-checkbox:checked + label {      /* invert colors */      background-color: #333333; diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 7a00bccb..ff42771a 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -38,30 +38,28 @@ html:root[data-yomichan-page=float]:not([data-yomichan-theme]) body {  body {      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;      font-size: 14px; -    line-height: 1.42857143; +    line-height: 1.42857143; /* 14px => 20px */      margin: 0;      border: 0;      padding: 0;  } -hr { -    padding: 0px; -    margin: 0px; -    border: 0; -    border-top-width: 1px; -    border-top-style: solid; -} -  ol, ul {      margin-top: 0; -    margin-bottom: 10px; +    margin-bottom: 0.72em;  }  #spinner { -    bottom: 5px;      display: none;      position: fixed; -    right: 5px; +    z-index: 1; +    right: 0.36em; +    bottom: 0.36em; +} + +#spinner>img { +    width: 2.28571428em; /* 14px => 32px */ +    height: 2.28571428em; /* 14px => 32px */  }  #error-orphaned { @@ -76,10 +74,38 @@ ol, ul {   * Navigation   */ -.term-navigation { +.navigation-header { +    top: 0; +    left: 0; +    width: 100%; +    height: 2.1em; +    box-sizing: border-box; +    padding: 0.25em 0.5em; +    border-bottom-width: 0.07142857em; /* 14px => 1px */ +    border-bottom-style: solid; +} + +html:root[data-yomichan-page=float] .navigation-header {      position: fixed; -    top: 0px; -    right: 0px; +} + +.navigation-header:not([hidden])~.navigation-header-spacer { +    height: 2.1em; +} + +.navigation-header-actions { +    display: flex; +} + +.navigation-header:not([data-has-previous=true]) .navigation-header-actions .action-previous>img, +.navigation-header:not([data-has-next=true]) .navigation-header-actions .action-next>img { +    opacity: 0.25; +    -webkit-filter: grayscale(100%); +    filter: grayscale(100%); +} + +.action-next>img { +    transform: scaleX(-1);  } @@ -91,20 +117,20 @@ ol, ul {      display: none;  } -.icon-checkbox + label { +.icon-checkbox+label {      cursor: pointer; -    font-size: 22px; -    padding: 2px; +    font-size: 1.6em; +    padding: 0.1em;      user-select: none;  }  #query-parser { -    margin-top: 10px; -    font-size: 24px; +    margin-top: 0.5em; +    font-size: 2em;  }  .query-parser-term { -    margin-right: 5px; +    margin-right: 0.2em;  }  html:root[data-yomichan-page=search] body { @@ -116,15 +142,14 @@ html:root[data-yomichan-page=search] body {   * Entries   */ -.entry, .note { -    padding-top: 20px; -    padding-bottom: 10px; +.entry { +    padding-top: 0.72em; +    padding-bottom: 0.72em;  } -html:root[data-yomichan-page=float] .entry, -html:root[data-yomichan-page=float] .note { -    padding-left: 10px; -    padding-right: 10px; +html:root[data-yomichan-page=float] .entry { +    padding-left: 0.72em; +    padding-right: 0.72em;  }  .actions .disabled { @@ -143,8 +168,9 @@ html:root[data-yomichan-page=float] .note {  }  .actions { -    display: block; +    display: flex;      float: right; +    margin: -0.25em;  }  .actions:after { @@ -153,111 +179,286 @@ html:root[data-yomichan-page=float] .note {      display: block;  } -.expression { +.action-button {      display: inline-block; -    font-size: 24px; +    border: 0; +    margin: 0; +    padding: 0.25em; +    background: transparent; +} + +button.action-button { +    cursor: pointer;  } -.expression .kanji-link { -    border-bottom-width: 1px; +.icon-image { +    width: 1.14285714em; /* 14px => 16px */ +    height: 1.14285714em; /* 14px => 16px */ +    display: block; +} + +.term-expression .kanji-link { +    border-bottom-width: 0.03571428em; /* 28px => 1px */      border-bottom-style: dashed;      text-decoration: none;  } -.expression .peek-wrapper { -    font-size: 14px; +.entry:not(.entry-current) .current { +    display: none; +} + +.tag { +    display: inline; +    padding: 0.2em 0.6em 0.3em; +    font-size: 75%; +    font-weight: 700; +    line-height: 1; +    text-align: center;      white-space: nowrap; +    vertical-align: baseline; +    border-radius: 0.25em; +} + +.tag-list>.tag+.tag { +    margin-left: 0.375em; +} + +.entry-header2, +.entry-header3 { +    display: inline; +} + +.term-frequency-separator::before { +    content: ":"; +} + +.entry+.entry { +    border-top-width: 0.07142857em; /* 14px => 1px */ +    border-top-style: solid; +} + +.entry[data-type=term][data-multi-expression=true] .actions>.action-play-audio { +    display: none; +} + +.term-reasons { +    display: inline-block; +} + +.term-reasons>.term-reason+.term-reason:before { +    content: " \00AB  "; /* The two spaces is not a typo */ +    display: inline; +} + +.term-expression-list { +    display: inline-block; +} + +.term-expression { +    display: inline-block; +} + +.term-expression-text { +    display: inline-block; +    font-size: 2em; +} + +.term-expression-details { +    display: inline; +} + +.term-expression-details>.action-play-audio { +    display: none; +} + +.term-expression-details>.tags { +    display: inline; +} + +.term-expression-details>.frequencies { +    display: none; +} + +.term-expression-list>.term-expression:not(:last-of-type):after { +    font-size: 2em; +    content: "\3001"; +} + +.term-expression-list[data-multi=true] .term-expression-details { +    visibility: hidden; +} + +.term-expression-list[data-multi=true] .term-expression-details {      display: inline-block;      position: relative; -    width: 0px; -    height: 0px; +    width: 0; +    height: 0;      visibility: hidden;  } -.expression .peek-wrapper .action-play-audio { +.term-expression-list[data-multi=true] .term-expression:hover .term-expression-details { +    visibility: visible; +} + +.term-expression-list[data-multi=true] .term-expression-details>.action-play-audio {      position: absolute; -    left: 0px; -    bottom: 10px; +    left: 0; +    bottom: 0.5em;  } -.expression .peek-wrapper .tags { +.term-expression-list[data-multi=true] .term-expression-details>.action-play-audio { +    display: block; +} + +.term-expression-list[data-multi=true] .term-expression-details>.tags { +    display: block;      position: absolute; -    left: 0px; -    bottom: -10px; +    left: 0; +    bottom: -0.5em; +    white-space: nowrap;  } -.expression .peek-wrapper .frequencies { +.term-expression-list[data-multi=true] .term-expression-details>.frequencies { +    display: block;      position: absolute; -    left: 0px; -    bottom: -30px; +    left: 0; +    bottom: -1.9em; +    white-space: nowrap;  } -.expression:hover .peek-wrapper { -    visibility: visible; +.term-definition-list { +    margin: 0; +    padding: 0; +    list-style-type: none;  } -.reasons { -    display: inline-block; +.term-definition-list:not([data-count="0"]):not([data-count="1"]) { +    padding-left: 1.4em; +    list-style-type: decimal;  } -.compact-info { -    display: inline-block; +.term-glossary-list { +    margin: 0; +    padding: 0; +    list-style-type: none;  } -.glossary ol, .glossary ul { +.term-glossary-list:not([data-count="0"]):not([data-count="1"]) {      padding-left: 1.4em; +    list-style-type: circle; +} + +.term-definition-only-list[data-count="0"] { +    display: none; +} + +.term-definition-only-list:before { +    content: "("; +} + +.term-definition-only-list:after { +    content: " only)"; +} + +.term-definition-only+.term-definition-only:before { +    content: ", "; +} + +.debug-info { +    display: none; +} + +:root[data-debug=true] .debug-info { +    display: block; +} + +:root[data-anki-enabled=false] .action-view-note, +:root[data-anki-enabled=false] .action-add-note { +    display: none; +} + +:root[data-audio-enabled=false] .action-play-audio { +    display: none;  } -.glossary ul.compact-glossary { +:root[data-compact-glossaries=true] .term-definition-tag-list, +:root[data-compact-glossaries=true] .term-definition-only-list:not([data-count="0"]) { +    display: inline-block; +} + +:root[data-compact-glossaries=true] .term-glossary-list {      display: inline;      list-style: none; -    padding-left: 0px; +    padding-left: 0;  } -.glossary .compact-glossary li { +:root[data-compact-glossaries=true] .term-glossary-list>li {      display: inline;  } -.glossary .compact-glossary li:not(:first-child):before { +:root[data-compact-glossaries=true] .term-glossary-list>li:not(:first-child):before {      content: " | ";  } -div.glossary-item.compact-glossary { -    display: inline; -} +/* + * Kanji + */ -.glyph { +.kanji-glyph {      font-family: kanji-stroke-orders; -    font-size: 120px; -    line-height: 120px; +    font-size: 8.5em; +    line-height: 1;      padding: 0.01em;      vertical-align: top;  } -.glyph-data { -    margin-top: 10px; +.kanji-glyph-data { +    margin-top: 0.75em; +    border-spacing: 0; +    border-collapse: collapse; +} + +.kanji-glyph-data>tbody>tr>* { +    border-top-width: 0.07142857em; /* 14px => 1px */ +    border-top-style: solid; +    text-align: left; +    vertical-align: top; +    padding: 0.36em; +    margin: 0;  } -.info-output { +.kanji-info-table {      width: 100%;  } -.info-output td { +.kanji-info-table>tbody>tr>th, +.kanji-info-table>tbody>tr>td { +    text-align: left; +    vertical-align: top; +    padding: 0; +    margin: 0; +} + +.kanji-info-table>tbody>tr>td {      text-align: right;  } -.entry:not(.entry-current) .current { -    display: none; +.kanji-glyph-data dl { +    margin-top: 0; +    margin-bottom: 1.4em;  } -.label { -    display: inline; -    padding: 0.2em 0.6em 0.3em; -    font-size: 75%; -    font-weight: 700; -    line-height: 1; -    text-align: center; -    white-space: nowrap; -    vertical-align: baseline; -    border-radius: 0.25em; +.kanji-glyph-data dd { +    margin-left: 0; +} + +.kanji-glossary-list { +    margin: 0; +    padding: 0; +    list-style-type: none; +} + +.kanji-glossary-list:not([data-count="0"]):not([data-count="1"]) { +    padding-left: 1.4em; +    list-style-type: decimal;  } diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js new file mode 100644 index 00000000..37be5041 --- /dev/null +++ b/ext/mixed/js/display-generator.js @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2019-2020  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + + +class DisplayGenerator { +    constructor() { +        this._termEntryTemplate = document.querySelector('#term-entry-template'); +        this._termExpressionTemplate = document.querySelector('#term-expression-template'); +        this._termDefinitionItemTemplate = document.querySelector('#term-definition-item-template'); +        this._termDefinitionOnlyTemplate = document.querySelector('#term-definition-only-template'); +        this._termGlossaryItemTemplate = document.querySelector('#term-glossary-item-template'); +        this._termReasonTemplate = document.querySelector('#term-reason-template'); + +        this._kanjiEntryTemplate = document.querySelector('#kanji-entry-template'); +        this._kanjiInfoTableTemplate = document.querySelector('#kanji-info-table-template'); +        this._kanjiInfoTableItemTemplate = document.querySelector('#kanji-info-table-item-template'); +        this._kanjiInfoTableEmptyTemplate = document.querySelector('#kanji-info-table-empty-template'); +        this._kanjiGlossaryItemTemplate = document.querySelector('#kanji-glossary-item-template'); +        this._kanjiReadingTemplate = document.querySelector('#kanji-reading-template'); + +        this._tagTemplate = document.querySelector('#tag-template'); +        this._tagFrequencyTemplate = document.querySelector('#tag-frequency-template'); +    } + +    createTermEntry(details) { +        const node = DisplayGenerator._instantiateTemplate(this._termEntryTemplate); + +        const expressionsContainer = node.querySelector('.term-expression-list'); +        const reasonsContainer = node.querySelector('.reasons'); +        const frequenciesContainer = node.querySelector('.frequencies'); +        const definitionsContainer = node.querySelector('.term-definition-list'); +        const debugInfoContainer = node.querySelector('.debug-info'); + +        const multiExpression = Array.isArray(details.expressions); +        const multiDefinition = Array.isArray(details.definitions); + +        node.dataset.multiExpression = `${multiExpression}`; +        node.dataset.multiDefinition = `${multiDefinition}`; + +        DisplayGenerator._appendMultiple(expressionsContainer, this.createTermExpression.bind(this), details.expressions, [details]); +        DisplayGenerator._appendMultiple(reasonsContainer, this.createTermReason.bind(this), details.reasons); +        DisplayGenerator._appendMultiple(frequenciesContainer, this.createFrequencyTag.bind(this), details.frequencies); +        DisplayGenerator._appendMultiple(definitionsContainer, this.createTermDefinitionItem.bind(this), details.definitions, [details]); + +        if (debugInfoContainer !== null) { +            debugInfoContainer.textContent = JSON.stringify(details, null, 4); +        } + +        return node; +    } + +    createTermExpression(details) { +        const node = DisplayGenerator._instantiateTemplate(this._termExpressionTemplate); + +        const expressionContainer = node.querySelector('.term-expression-text'); +        const tagContainer = node.querySelector('.tags'); +        const frequencyContainer = node.querySelector('.frequencies'); + +        if (details.termFrequency) { +            node.dataset.frequency = details.termFrequency; +        } + +        if (expressionContainer !== null) { +            const segments = [{text: details.expression, furigana: details.reading}]; // TODO : Use proper furigana segmentation +            DisplayGenerator._appendFurigana(expressionContainer, segments, this._appendKanjiLinks.bind(this)); +        } + +        DisplayGenerator._appendMultiple(tagContainer, this.createTag.bind(this), details.termTags); +        DisplayGenerator._appendMultiple(frequencyContainer, this.createFrequencyTag.bind(this), details.frequencies); + +        return node; +    } + +    createTermReason(reason) { +        const node = DisplayGenerator._instantiateTemplate(this._termReasonTemplate); +        node.textContent = reason; +        node.dataset.reason = reason; +        return node; +    } + +    createTermDefinitionItem(details) { +        const node = DisplayGenerator._instantiateTemplate(this._termDefinitionItemTemplate); + +        const tagListContainer = node.querySelector('.term-definition-tag-list'); +        const onlyListContainer = node.querySelector('.term-definition-only-list'); +        const glossaryContainer = node.querySelector('.term-glossary-list'); + +        node.dataset.dictionary = details.dictionary; + +        DisplayGenerator._appendMultiple(tagListContainer, this.createTag.bind(this), details.definitionTags); +        DisplayGenerator._appendMultiple(onlyListContainer, this.createTermOnly.bind(this), details.only); +        DisplayGenerator._appendMultiple(glossaryContainer, this.createTermGlossaryItem.bind(this), details.glossary); + +        return node; +    } + +    createTermGlossaryItem(glossary) { +        const node = DisplayGenerator._instantiateTemplate(this._termGlossaryItemTemplate); +        const container = node.querySelector('.term-glossary'); +        if (container !== null) { +            DisplayGenerator._appendMultilineText(container, glossary); +        } +        return node; +    } + +    createTermOnly(only) { +        const node = DisplayGenerator._instantiateTemplate(this._termDefinitionOnlyTemplate); +        node.dataset.only = only; +        node.textContent = only; +        return node; +    } + +    createKanjiLink(character) { +        const node = document.createElement('a'); +        node.href = '#'; +        node.className = 'kanji-link'; +        node.textContent = character; +        return node; +    } + +    createKanjiEntry(details) { +        const node = DisplayGenerator._instantiateTemplate(this._kanjiEntryTemplate); + +        const glyphContainer = node.querySelector('.kanji-glyph'); +        const frequenciesContainer = node.querySelector('.frequencies'); +        const tagContainer = node.querySelector('.tags'); +        const glossaryContainer = node.querySelector('.kanji-glossary-list'); +        const chineseReadingsContainer = node.querySelector('.kanji-readings-chinese'); +        const japaneseReadingsContainer = node.querySelector('.kanji-readings-japanese'); +        const statisticsContainer = node.querySelector('.kanji-statistics'); +        const classificationsContainer = node.querySelector('.kanji-classifications'); +        const codepointsContainer = node.querySelector('.kanji-codepoints'); +        const dictionaryIndicesContainer = node.querySelector('.kanji-dictionary-indices'); +        const debugInfoContainer = node.querySelector('.debug-info'); + +        if (glyphContainer !== null) { +            glyphContainer.textContent = details.character; +        } + +        DisplayGenerator._appendMultiple(frequenciesContainer, this.createFrequencyTag.bind(this), details.frequencies); +        DisplayGenerator._appendMultiple(tagContainer, this.createTag.bind(this), details.tags); +        DisplayGenerator._appendMultiple(glossaryContainer, this.createKanjiGlossaryItem.bind(this), details.glossary); +        DisplayGenerator._appendMultiple(chineseReadingsContainer, this.createKanjiReading.bind(this), details.onyomi); +        DisplayGenerator._appendMultiple(japaneseReadingsContainer, this.createKanjiReading.bind(this), details.kunyomi); + +        if (statisticsContainer !== null) { +            statisticsContainer.appendChild(this.createKanjiInfoTable(details.stats.misc)); +        } +        if (classificationsContainer !== null) { +            classificationsContainer.appendChild(this.createKanjiInfoTable(details.stats.class)); +        } +        if (codepointsContainer !== null) { +            codepointsContainer.appendChild(this.createKanjiInfoTable(details.stats.code)); +        } +        if (dictionaryIndicesContainer !== null) { +            dictionaryIndicesContainer.appendChild(this.createKanjiInfoTable(details.stats.index)); +        } + +        if (debugInfoContainer !== null) { +            debugInfoContainer.textContent = JSON.stringify(details, null, 4); +        } + +        return node; +    } + +    createKanjiGlossaryItem(glossary) { +        const node = DisplayGenerator._instantiateTemplate(this._kanjiGlossaryItemTemplate); +        const container = node.querySelector('.kanji-glossary'); +        if (container !== null) { +            DisplayGenerator._appendMultilineText(container, glossary); +        } +        return node; +    } + +    createKanjiReading(reading) { +        const node = DisplayGenerator._instantiateTemplate(this._kanjiReadingTemplate); +        node.textContent = reading; +        return node; +    } + +    createKanjiInfoTable(details) { +        const node = DisplayGenerator._instantiateTemplate(this._kanjiInfoTableTemplate); + +        const container = node.querySelector('.kanji-info-table-body'); + +        if (container !== null) { +            const count = DisplayGenerator._appendMultiple(container, this.createKanjiInfoTableItem.bind(this), details); +            if (count === 0) { +                const n = this.createKanjiInfoTableItemEmpty(); +                container.appendChild(n); +            } +        } + +        return node; +    } + +    createKanjiInfoTableItem(details) { +        const node = DisplayGenerator._instantiateTemplate(this._kanjiInfoTableItemTemplate); +        const nameNode = node.querySelector('.kanji-info-table-item-header'); +        const valueNode = node.querySelector('.kanji-info-table-item-value'); +        if (nameNode !== null) { +            nameNode.textContent = details.notes || details.name; +        } +        if (valueNode !== null) { +            valueNode.textContent = details.value; +        } +        return node; +    } + +    createKanjiInfoTableItemEmpty() { +        return DisplayGenerator._instantiateTemplate(this._kanjiInfoTableEmptyTemplate); +    } + +    createTag(details) { +        const node = DisplayGenerator._instantiateTemplate(this._tagTemplate); + +        node.title = details.notes; +        node.textContent = details.name; +        node.dataset.category = details.category; + +        return node; +    } + +    createFrequencyTag(details) { +        const node = DisplayGenerator._instantiateTemplate(this._tagFrequencyTemplate); + +        let n = node.querySelector('.term-frequency-dictionary-name'); +        if (n !== null) { +            n.textContent = details.dictionary; +        } + +        n = node.querySelector('.term-frequency-value'); +        if (n !== null) { +            n.textContent = `${details.frequency}`; +        } + +        node.dataset.dictionary = details.dictionary; +        node.dataset.frequency = details.frequency; + +        return node; +    } + +    _appendKanjiLinks(container, text) { +        let part = ''; +        for (const c of text) { +            if (DisplayGenerator._isCharacterKanji(c)) { +                if (part.length > 0) { +                    container.appendChild(document.createTextNode(part)); +                    part = ''; +                } + +                const link = this.createKanjiLink(c); +                container.appendChild(link); +            } else { +                part += c; +            } +        } +        if (part.length > 0) { +            container.appendChild(document.createTextNode(part)); +        } +    } + +    static _isCharacterKanji(c) { +        const code = c.charCodeAt(0); +        return ( +            code >= 0x4e00 && code < 0x9fb0 || +            code >= 0x3400 && code < 0x4dc0 +        ); +    } + +    static _appendMultiple(container, createItem, detailsArray, fallback=[]) { +        if (container === null) { return 0; } + +        const isArray = Array.isArray(detailsArray); +        if (!isArray) { detailsArray = fallback; } + +        container.dataset.multi = `${isArray}`; +        container.dataset.count = `${detailsArray.length}`; + +        for (const details of detailsArray) { +            const item = createItem(details); +            if (item === null) { continue; } +            container.appendChild(item); +        } + +        return detailsArray.length; +    } + +    static _appendFurigana(container, segments, addText) { +        for (const {text, furigana} of segments) { +            if (furigana) { +                const ruby = document.createElement('ruby'); +                const rt = document.createElement('rt'); +                addText(ruby, text); +                ruby.appendChild(rt); +                rt.appendChild(document.createTextNode(furigana)); +                container.appendChild(ruby); +            } else { +                addText(container, text); +            } +        } +    } + +    static _appendMultilineText(container, text) { +        const parts = text.split('\n'); +        container.appendChild(document.createTextNode(parts[0])); +        for (let i = 1, ii = parts.length; i < ii; ++i) { +            container.appendChild(document.createElement('br')); +            container.appendChild(document.createTextNode(parts[i])); +        } +    } + +    static _instantiateTemplate(template) { +        const content = document.importNode(template.content, true); +        return content.firstChild; +    } +} diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e756f948..f61e76b5 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -37,6 +37,7 @@ class Display {          this.eventListenersActive = false;          this.clickScanPrevent = false; +        this.displayGenerator = new DisplayGenerator();          this.windowScroll = new WindowScroll();          this.setInteractive(true); @@ -240,11 +241,20 @@ class Display {      async updateOptions(options) {          this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); +        this.updateDocumentOptions(this.options);          this.updateTheme(this.options.general.popupTheme);          this.setCustomCss(this.options.general.customPopupCss);          audioPrepareTextToSpeech(this.options);      } +    updateDocumentOptions(options) { +        const data = document.documentElement.dataset; +        data.ankiEnabled = `${options.anki.enable}`; +        data.audioEnabled = `${options.audio.enable}`; +        data.compactGlossaries = `${options.general.compactGlossaries}`; +        data.debug = `${options.general.debugInfo}`; +    } +      updateTheme(themeName) {          document.documentElement.dataset.yomichanTheme = themeName; @@ -277,6 +287,9 @@ class Display {          if (interactive) {              Display.addEventListener(this.persistentEventListeners, document, 'keydown', this.onKeyDown.bind(this), false);              Display.addEventListener(this.persistentEventListeners, document, 'wheel', this.onWheel.bind(this), {passive: false}); +            Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-previous'), 'click', this.onSourceTermView.bind(this)); +            Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-next'), 'click', this.onNextTermView.bind(this)); +            Display.addEventListener(this.persistentEventListeners, document.querySelector('.navigation-header'), 'wheel', this.onHistoryWheel.bind(this), {passive: false});          } else {              Display.clearEventListeners(this.persistentEventListeners);          } @@ -293,9 +306,6 @@ class Display {              this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this));              this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this));              this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); -            this.addEventListeners('.source-term', 'click', this.onSourceTermView.bind(this)); -            this.addEventListeners('.next-term', 'click', this.onNextTermView.bind(this)); -            this.addEventListeners('.term-navigation', 'wheel', this.onHistoryWheel.bind(this), {passive: false});              if (this.options.scanning.enablePopupSearch) {                  this.addEventListeners('.glossary-item', 'mouseup', this.onGlossaryMouseUp.bind(this));                  this.addEventListeners('.glossary-item', 'mousedown', this.onGlossaryMouseDown.bind(this)); @@ -347,25 +357,25 @@ class Display {              }              const sequence = ++this.sequence; -            const params = { -                definitions, -                source: !!this.context.previous, -                next: !!this.context.next, -                addable: options.anki.enable, -                grouped: options.general.resultOutputMode === 'group', -                merged: options.general.resultOutputMode === 'merge', -                playback: options.audio.enabled, -                compactGlossaries: options.general.compactGlossaries, -                debug: options.general.debugInfo -            };              for (const definition of definitions) {                  definition.cloze = Display.clozeBuild(context.sentence, definition.source);                  definition.url = context.url;              } -            const content = await apiTemplateRender('terms.html', params); -            this.container.innerHTML = content; +            this.updateNavigation(this.context.previous, this.context.next); +            this.setNoContentVisible(definitions.length === 0); + +            const fragment = document.createDocumentFragment(); +            for (const definition of definitions) { +                fragment.appendChild(this.displayGenerator.createTermEntry(definition)); +            } + +            await Promise.resolve(); // Delay to help avoid forced reflow warnings in Chrome + +            this.container.textContent = ''; +            this.container.appendChild(fragment); +              const {index, scroll, disableScroll} = context;              if (!disableScroll) {                  this.entryScrollIntoView(index || 0, scroll); @@ -391,8 +401,6 @@ class Display {          if (!this.isInitialized()) { return; }          try { -            const options = this.options; -              this.setEventListenersActive(false);              if (context.focus !== false) { @@ -408,21 +416,25 @@ class Display {              }              const sequence = ++this.sequence; -            const params = { -                definitions, -                source: !!this.context.previous, -                next: !!this.context.next, -                addable: options.anki.enable, -                debug: options.general.debugInfo -            };              for (const definition of definitions) {                  definition.cloze = Display.clozeBuild(context.sentence, definition.character);                  definition.url = context.url;              } -            const content = await apiTemplateRender('kanji.html', params); -            this.container.innerHTML = content; +            this.updateNavigation(this.context.previous, this.context.next); +            this.setNoContentVisible(definitions.length === 0); + +            const fragment = document.createDocumentFragment(); +            for (const definition of definitions) { +                fragment.appendChild(this.displayGenerator.createKanjiEntry(definition)); +            } + +            await Promise.resolve(); // Delay to help avoid forced reflow warnings in Chrome + +            this.container.textContent = ''; +            this.container.appendChild(fragment); +              const {index, scroll} = context;              this.entryScrollIntoView(index || 0, scroll); @@ -445,6 +457,26 @@ class Display {          if (errorOrphaned !== null) {              errorOrphaned.style.setProperty('display', 'block', 'important');          } + +        this.updateNavigation(null, null); +        this.setNoContentVisible(false); +    } + +    setNoContentVisible(visible) { +        const noResults = document.querySelector('#no-results'); + +        if (noResults !== null) { +            noResults.hidden = !visible; +        } +    } + +    updateNavigation(previous, next) { +        const navigation = document.querySelector('#navigation-header'); +        if (navigation !== null) { +            navigation.hidden = !(previous || next); +            navigation.dataset.hasPrevious = `${!!previous}`; +            navigation.dataset.hasNext = `${!!next}`; +        }      }      autoPlayAudio() { @@ -733,6 +765,7 @@ class Display {      }      static addEventListener(eventListeners, object, type, listener, options) { +        if (object === null) { return; }          object.addEventListener(type, listener, options);          eventListeners.push([object, type, listener, options]);      } |