Skip to content

Commit

Permalink
feat: add <wbr> tags to word boundaries
Browse files Browse the repository at this point in the history
  • Loading branch information
yufeih committed Mar 17, 2023
1 parent 86c61f7 commit 6a0c891
Show file tree
Hide file tree
Showing 12 changed files with 7,203 additions and 2,457 deletions.
4 changes: 4 additions & 0 deletions .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ runs:
shell: bash
working-directory: templates

- run: npm test
shell: bash
working-directory: templates

- run: npm run build
shell: bash
working-directory: templates
Expand Down
15 changes: 15 additions & 0 deletions templates/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', {
tsconfig: {
allowJs: true
}
}]
},
transformIgnorePatterns: [
'<rootDir>/node_modules/(?!lit-html)'
]
}
2 changes: 2 additions & 0 deletions templates/modern/src/docfx.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ h3,
h4,
h5,
h6,
.xref,
.text-break {
white-space: nowrap;
word-wrap: break-word;
word-break: break-word;
}
Expand Down
4 changes: 0 additions & 4 deletions templates/modern/src/docfx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

import 'bootstrap'
import { highlight } from './highlight'
import { breakText } from './helper'
import { renderMarkdown } from './markdown'
import { enableSearch } from './search'
import { renderToc } from './toc'
Expand Down Expand Up @@ -35,8 +34,5 @@ document.addEventListener('DOMContentLoaded', function() {
.then(([navbar, toc]) => renderBreadcrumb([...navbar, ...toc]))

renderInThisArticle()

breakText()

window.docfx.ready = true
})
11 changes: 11 additions & 0 deletions templates/modern/src/helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { breakWord } from './helper'

test('break-text', () => {
expect(breakWord('Other APIs')).toEqual(['Other APIs'])
expect(breakWord('System.CodeDom')).toEqual(['System.', 'Code', 'Dom'])
expect(breakWord('System.Collections.Dictionary<string, object>')).toEqual(['System.', 'Collections.', 'Dictionary<', 'string, object>'])
expect(breakWord('https://github.com/dotnet/docfx')).toEqual(['https://github.', 'com/', 'dotnet/', 'docfx'])
})
44 changes: 31 additions & 13 deletions templates/modern/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { html, TemplateResult } from 'lit-html'

/**
* Get the value of an HTML meta tag.
*/
export function meta(name: string): string {
return (document.querySelector(`meta[name="${name}"]`) as HTMLMetaElement)?.content
}

/**
* Add <wbr> into long word.
* @param {String} text - The word to break. It should be in plain text without HTML tags.
*/
function breakPlainText(text) {
if (!text) return text
return text.replace(/([a-z])([A-Z])|(\.)(\w)/g, '$1$3<wbr>$2$4')
export function breakWord(text: string): string[] {
const regex = /([a-z0-9])([A-Z]+[a-z])|([a-zA-Z0-9][./<>\-_])/g
const result = []
let start = 0
while (true) {
const match = regex.exec(text)
if (!match) {
break
}
const index = match.index + (match[1] || match[3]).length
result.push(text.slice(start, index))
start = index
}
if (start < text.length) {
result.push(text.slice(start))
}
return result
}

/**
* Add <wbr> into long word.
*/
function breakWord(e: Element) {
if (!e.innerHTML.match(/(<\w*)((\s\/>)|(.*<\/\w*>))/g)) {
e.innerHTML = breakPlainText(e.innerHTML)
}
}

export function breakText() {
document.querySelectorAll('.xref').forEach(e => e.classList.add('text-break'))
document.querySelectorAll('.text-break').forEach(e => breakWord(e))
export function breakWordLit(text: string): TemplateResult {
const result = []
breakWord(text).forEach(word => {
if (result.length > 0) {
result.push(html`<wbr>`)
}
result.push(html`${word}`)
})
return html`${result}`
}
21 changes: 20 additions & 1 deletion templates/modern/src/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { meta } from './helper'
import { breakWord, meta } from './helper'
import AnchorJs from 'anchor-js'
import { html, render } from 'lit-html'

/**
* Initialize markdown rendering.
*/
export function renderMarkdown() {
renderWordBreaks()
renderTables()
renderAlerts()
renderLinks()
Expand All @@ -18,6 +19,24 @@ export function renderMarkdown() {
renderClickableImage()
}

/**
* Add <wbr> to break long text.
*/
function renderWordBreaks() {
document.querySelectorAll<HTMLElement>('article h1,h2,h3,h4,h5,h6,.xref,.text-break').forEach(e => {
if (e.innerHTML === e.innerText) {
const children: (string | Node)[] = []
for (const text of breakWord(e.innerText)) {
if (children.length > 0) {
children.push(document.createElement('wbr'))
}
children.push(text)
}
e.replaceChildren(...children)
}
})
}

/**
* Make images in articles clickable by wrapping the image in an anchor tag.
* The image is clickable only if its size is larger than 200x200 and it is not already been wrapped in an anchor tag.
Expand Down
13 changes: 7 additions & 6 deletions templates/modern/src/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

import { render, html, TemplateResult } from 'lit-html'
import { meta } from './helper'
import { breakWordLit, meta } from './helper'
import { themePicker } from './theme'
import { TocNode } from './toc'

Expand Down Expand Up @@ -35,7 +35,8 @@ export async function renderNavbar(): Promise<NavItem[]> {
navItems.map(item => {
const current = (item === activeItem ? 'page' : false)
const active = (item === activeItem ? 'active' : null)
return html`<li class='nav-item'><a class='nav-link ${active}' aria-current=${current} href=${item.href}>${item.name}</a></li>`
return html`
<li class='nav-item'><a class='nav-link ${active}' aria-current=${current} href=${item.href}>${breakWordLit(item.name)}</a></li>`
})
}</ul>`

Expand Down Expand Up @@ -73,7 +74,7 @@ export function renderBreadcrumb(breadcrumb: (NavItem | TocNode)[]) {
render(
html`
<ol class="breadcrumb">
${breadcrumb.map(i => html`<li class="breadcrumb-item"><a href="${i.href}">${i.name}</a></li>`)}
${breadcrumb.map(i => html`<li class="breadcrumb-item"><a href="${i.href}">${breakWordLit(i.name)}</a></li>`)}
</ol>`,
container)
}
Expand All @@ -91,7 +92,7 @@ function inThisArticleForConceptual() {
if (headings.length > 0) {
return html`
<h5 class="border-bottom">In this article</h5>
<ul>${Array.from(headings).map(h => html`<li><a class="link-secondary" href="#${h.id}">${h.innerText}</a></li>`)}</ul>`
<ul>${Array.from(headings).map(h => html`<li><a class="link-secondary" href="#${h.id}">${breakWordLit(h.innerText)}</a></li>`)}</ul>`
}
}

Expand All @@ -104,8 +105,8 @@ function inThisArticleForManagedReference(): TemplateResult {
<h5 class="border-bottom">In this article</h5>
<ul>${headings.map(h => {
return h.tagName === 'H3'
? html`<li><h6>${h.innerText}</h6></li>`
: html`<li><a class="link-secondary" href="#${h.id}">${h.innerText}</a></li>`
? html`<li><h6>${breakWordLit(h.innerText)}</h6></li>`
: html`<li><a class="link-secondary" href="#${h.id}">${breakWordLit(h.innerText)}</a></li>`
})}</ul>`
}
}
Expand Down
6 changes: 3 additions & 3 deletions templates/modern/src/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { html, render } from 'lit-html'
import { classMap } from 'lit-html/directives/class-map.js'
import { meta } from './helper'
import { breakWordLit, meta } from './helper'

export type TocNode = {
name: string
Expand Down Expand Up @@ -77,8 +77,8 @@ export async function renderToc(): Promise<TocNode[]> {
<li class=${classMap({ expanded })}>
${isLeaf ? null : html`<span class='expand-stub' @click=${toggleExpand}></span>`}
${href
? html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href=${href}>${name}</a>`
: html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href='#' @click=${toggleExpand}>${name}</a>`}
? html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href=${href}>${breakWordLit(name)}</a>`
: html`<a class='${classMap({ 'nav-link': !activeNodes.includes(node) })}' href='#' @click=${toggleExpand}>${breakWordLit(name)}</a>`}
${isLeaf ? null : html`<ul>${renderTocNodes(items)}</ul>`}
</li>`
Expand Down
Loading

0 comments on commit 6a0c891

Please sign in to comment.