Skip to content

Commit

Permalink
Merge pull request #117 from speee/ol-type-attribute
Browse files Browse the repository at this point in the history
Support type attribute for <ol> element
  • Loading branch information
Yuki Hattori authored Mar 6, 2020
2 parents 7fdb1cb + e3c40c7 commit 649d437
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 37 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- Support `type` attribute for `<ol>` element ([#117](https://github.com/speee/jsx-slack/pull/117))

### Changed

- Change spaces for indenting lists into unicode spaces that were based on measured width in Slack's font ([#117](https://github.com/speee/jsx-slack/pull/117))

### Fixed

- Prevent over-escaping for link and time formatting ([#118](https://github.com/speee/jsx-slack/issues/118), [#120](https://github.com/speee/jsx-slack/pull/120))
Expand Down
5 changes: 4 additions & 1 deletion demo/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,10 @@ const schema = {
attrs: {},
children: markupHTML.filter(t => t !== 'ul' && t !== 'ol' && t !== 'li'),
},
ol: { attrs: { start: null }, children: ['li'] },
ol: {
attrs: { start: null, type: ['1', 'a', 'A', 'i', 'I'] },
children: ['li'],
},
p: { attrs: {}, children: markupHTML.filter(t => t !== 'p') },
pre: { attrs: {}, children: [] },
s: {
Expand Down
25 changes: 23 additions & 2 deletions docs/html-like-formatting.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ We can simulate the list provided from `<ul>` and `<ol>` tag by using mimicked t
Item C
<ol>
<li>Ordered item 1</li>
<li>Ordered item 2</li>
<li>
Ordered item 2
<ol type="I">
<li>Ordered sub item with type 1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5...</li>
</ol>
</li>
</ol>
</li>
</ul>
Expand All @@ -63,12 +72,23 @@ The above would be replaced to just a plain text like this:
• Item B
  ◦ Sub item 1
  ◦ Sub item 2
▪︎ and more...
   ▪︎ and more...
• Item C
  1. Ordered item 1
  2. Ordered item 2
       I. Ordered sub item with type
       II. 2
      III. 3
     IV. 4
      V. 5...
```

Indents, look like lumpy in a monospace font, will be aligned pretty when rendering to Slack.

[<img src="https://raw.githubusercontent.com/speee/jsx-slack/master/docs/preview-btn.svg?sanitize=true" width="240" />](https://api.slack.com/tools/block-kit-builder?mode=message&blocks=%5B%7B%22type%22%3A%22section%22%2C%22text%22%3A%7B%22type%22%3A%22mrkdwn%22%2C%22text%22%3A%22%E2%80%A2%20Item%20A%5Cn%E2%80%A2%20Item%20B%5Cn%E2%80%87%20%E2%97%A6%20Sub%20item%201%5Cn%E2%80%87%20%E2%97%A6%20Sub%20item%202%5Cn%E2%80%87%20%E2%80%84%E2%80%8A%20%E2%96%AA%EF%B8%8E%20and%20more...%5Cn%E2%80%A2%20Item%20C%5Cn%E2%80%87%201.%20Ordered%20item%201%5Cn%E2%80%87%202.%20Ordered%20item%202%5Cn%E2%80%87%20%E2%80%83%E2%80%8A%20%E2%80%87%E2%80%8AI.%20Ordered%20sub%20item%20with%20type%5Cn%E2%80%87%20%E2%80%83%E2%80%8A%20%E2%80%84%E2%80%8AII.%202%5Cn%E2%80%87%20%E2%80%83%E2%80%8A%20%E2%80%8AIII.%203%5Cn%E2%80%87%20%E2%80%83%E2%80%8A%20IV.%204%5Cn%E2%80%87%20%E2%80%83%E2%80%8A%20%E2%80%85V.%205...%22%2C%22verbatim%22%3Atrue%7D%7D%5D)

`<ol>` tag supports [`start` and `type` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol#Attributes) as same as HTML.

## Links

jsx-slack will not recognize URL-like string as hyperlink unless using [`<Mrkdwn verbatim={false}>`](block-elements.md#mrkdwn). Generally you should use `<a>` tag whenever you want to use a link.
Expand Down Expand Up @@ -128,6 +148,7 @@ An optional fallback text may specify via additional `fallback` attribute. If it
| `<code>code</code>` | `` `code` `` |
| `<pre>{'code\nblock'}</pre>` | ` ```\ncode\nblock\n``` ` |
| `<ul><li>List</li></ul>` | `• List` |
| `<ol><li>Ordered</li></ol>` | `1. Ordered` |

### Links

Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,14 @@
"format": "prettier \"**/*.{css,html,js,json,jsx,md,scss,ts,tsx,yaml,yml}\"",
"format:write": "yarn -s format --write",
"lint": "eslint \"**/*.{js,jsx,ts,tsx}\"",
"measure-font": "node ./tools/measure-font.js",
"prepack": "npm-run-all -p check:* lint test:coverage -p build:* types",
"preversion": "run-p check:* lint test:coverage",
"test": "jest",
"test:coverage": "jest --coverage",
"test:debug": "node --inspect ./node_modules/.bin/jest -i",
"types": "rimraf types && tsc --declaration --emitDeclarationOnly --outDir types",
"version": "node version.js && git add -A CHANGELOG.md"
"version": "node ./tools/version.js && git add -A CHANGELOG.md"
},
"prettier": {
"semi": false,
Expand All @@ -79,6 +80,7 @@
"npm-run-all": "^4.1.5",
"parcel": "^1.12.4",
"prettier": "^1.19.1",
"puppeteer": "^2.1.1",
"rimraf": "^3.0.1",
"ts-jest": "^25.2.0",
"typescript": "^3.7.5"
Expand Down
82 changes: 82 additions & 0 deletions src/data/font-width.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"letters": {
"0": 58,
"1": 58,
"2": 58,
"3": 58,
"4": 58,
"5": 58,
"6": 58,
"7": 58,
"8": 58,
"9": 58,
"a": 49.69999694824219,
"b": 56,
"c": 47.75,
"d": 56,
"e": 52.79998779296875,
"f": 35.04998779296875,
"g": 52,
"h": 55.79998779296875,
"i": 24,
"j": 24,
"k": 50.79998779296875,
"l": 23.599990844726562,
"m": 82.25,
"n": 55.79998779296875,
"o": 56.69999694824219,
"p": 56.04998779296875,
"q": 56,
"r": 36.399993896484375,
"s": 43.29998779296875,
"t": 35.84999084472656,
"u": 55.75,
"v": 51.59999084472656,
"w": 78.55000305175781,
"x": 49.79998779296875,
"y": 51.54998779296875,
"z": 45.19999694824219,
"A": 67.69999694824219,
"B": 64.64999389648438,
"C": 66.80000305175781,
"D": 76.05000305175781,
"E": 57.75,
"F": 56.54998779296875,
"G": 73.05000305175781,
"H": 76.34999084472656,
"I": 28,
"J": 42.25,
"K": 66.25,
"L": 51.34999084472656,
"M": 92.84999084472656,
"N": 76.34999084472656,
"O": 80.05000305175781,
"P": 60.04998779296875,
"Q": 80.05000305175781,
"R": 62.649993896484375,
"S": 54.25,
"T": 59.04998779296875,
"U": 73.55000305175781,
"V": 67.69999694824219,
"W": 103.55000305175781,
"X": 64.89999389648438,
"Y": 62.399993896484375,
"Z": 60.19999694824219,
"-": 37.149993896484375,
".": 23.599990844726562,
"•": 58,
"◦": 40.44999694824219,
"▪": 40.44999694824219
},
"spaces": {
" ": 50,
" ": 75,
" ": 33.29998779296875,
" ": 25,
" ": 16.649993896484375,
" ": 58,
" ": 21.25,
" ": 12.5,
" ": 6.25
}
}
6 changes: 5 additions & 1 deletion src/jsx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,11 @@ export namespace JSXSlack {
img: { alt: string; id?: string; src: string; title?: string }
input: IntrinsicProps<InputProps>
li: {}
ol: { start?: number; children: Children<any> }
ol: {
start?: number
type?: '1' | 'a' | 'A' | 'i' | 'I'
children: Children<any>
}
optgroup: OptgroupProps
option: OptionProps
p: {}
Expand Down
3 changes: 2 additions & 1 deletion src/mrkdwn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import stringifier from './stringifier'

const list = (h, node) => {
const ordered = node.tagName === 'ol'
const orderedType = ordered ? node.properties.type ?? '1' : null
const start = ordered ? Number.parseInt(node.properties.start ?? 1, 10) : null

// Mark implied list item
Expand All @@ -16,7 +17,7 @@ const list = (h, node) => {
return item
})

return h(node, 'list', { ordered, start }, children)
return h(node, 'list', { ordered, orderedType, start }, children)
}

const mrkdwn = (html: string) =>
Expand Down
44 changes: 44 additions & 0 deletions src/mrkdwn/measure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-disable no-param-reassign */
import { letters, spaces } from '../data/font-width.json'

const flippedSpaces = Object.keys(spaces).reduce(
(obj, key) => ({ ...obj, [spaces[key]]: key }),
{} as Record<number, string>
)

const spaceWidth = Object.values(spaces).sort((a, b) => b - a)

const indentCache = new Map<number, string>()
const measureCache = new Map<string, number>()

export function makeIndent(width: number): string {
let indent = indentCache.get(width)

if (indent === undefined) {
indent = ''
let targetWidth = width

spaceWidth.forEach(w => {
const num = Math.floor(targetWidth / w)
if (num > 0) indent += flippedSpaces[w].repeat(num)

targetWidth -= w * num
})

indentCache.set(width, indent)
}

return indent
}

export function measureWidth(bulletStr: string) {
let width = measureCache.get(bulletStr)

if (width === undefined) {
// 25.6 is almost same width with the regular whitespace
width = [...bulletStr].reduce((total, l) => total + (letters[l] ?? 25.6), 0)
measureCache.set(bulletStr, width)
}

return width
}
69 changes: 50 additions & 19 deletions src/mrkdwn/stringifier.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import isPhrasing from 'mdast-util-phrasing'
import { makeIndent, measureWidth } from './measure'
import { escapeEntity } from '../html'
import { JSXSlack } from '../jsx'
import { SpecialLink, detectSpecialLink } from '../utils'
import {
SpecialLink,
detectSpecialLink,
intToAlpha,
intToRoman,
} from '../utils'

type Node = { type: string; [key: string]: any }
type Visitor = (node: Node, parent?: Node) => string
Expand All @@ -23,7 +30,7 @@ export class MrkdwnCompiler {

private codes: string[] = []

private lists: number[] = []
private lists: [number, number[]][] = []

private root: Node

Expand Down Expand Up @@ -79,36 +86,60 @@ export class MrkdwnCompiler {
}
},
list: node => {
this.lists.unshift(Math.floor(node.start - 1) || 0)
this.lists.unshift([Math.floor(node.start - 1) || 0, []])

const rendered = this.block(node)
const digitLength = (this.lists.shift() as number).toString().length
const [, values] = this.lists.shift()!

let markers: Map<number, string>

if (node.ordered) {
return rendered
.replace(
/<<l(\d+)>>/g,
(_, d: string) => `${d.padStart(digitLength, '\u2007')}. `
)
.replace(/<<ls>>/g, `${'\u2007'.repeat(digitLength)} `)
markers = new Map<number, string>(
values.map(v => [
v,
`${(() => {
if (node.orderedType === 'a') return intToAlpha(v)
if (node.orderedType === 'A') return intToAlpha(v).toUpperCase()
if (node.orderedType === 'i') return intToRoman(v)
if (node.orderedType === 'I') return intToRoman(v).toUpperCase()
return v.toString()
})()}.`,
])
)
} else {
const bullet =
bulletListMarkers[
Math.min(this.lists.length, bulletListMarkers.length - 1)
]

markers = new Map<number, string>(values.map(v => [v, bullet]))
}

const marker =
bulletListMarkers[
Math.min(this.lists.length, bulletListMarkers.length - 1)
]
const maxWidth = Math.max(...[...markers.values()].map(measureWidth))

return rendered
.replace(/<<l\d+>>/g, `${marker} `)
.replace(/<<ls>>/g, '\u2007 ')
.replace(/<<l(-?\d+)>>/g, (_, d: string) => {
const marker = markers.get(Number.parseInt(d, 10))!
const markerWidth = measureWidth(marker)

return `${makeIndent(maxWidth - markerWidth)}${marker} `
})
.replace(/<<ls>>/g, `${makeIndent(maxWidth)} `)
},
listItem: node => {
// eslint-disable-next-line no-plusplus
const number = node.data?.implied ? 's' : ++this.lists[0]
let internalNum = 's'

if (!node.data?.implied) {
// eslint-disable-next-line no-plusplus
const num = ++this.lists[0][0]
this.lists[0][1].push(num)

internalNum = num.toString()
}

return this.block(node)
.split('\n')
.map((s, i) => `<<l${i > 0 ? 's' : number}>>${s}`)
.map((s, i) => `<<l${i > 0 ? 's' : internalNum}>>${s}`)
.join('\n')
},
time: node => {
Expand Down
Loading

0 comments on commit 649d437

Please sign in to comment.