Repository: speee/jsx-slack
Pull request #8 (2019-03-11 08:16:26)

Right-aligned number in ordered list
For better compatibility with HTML rendering, I updated turndown parser to render right-aligned numbers in ordered list.

```jsx
<Block>
  <Section>
    <ol start={9}>
      <li>Ordered list</li>
      <li>Right-aligned numbers<br />...and indent</li>
    </ol>
  </Section>
</Block>
```

|HTML|Previous jsx-slack|Updated|
|:---:|:---:|:---:|
|<img width="228" alt="HTML" src="https://user-images.githubusercontent.com/3993388/54109065-f1548080-4420-11e9-98fe-359a91a8a83b.png">|<img width="197" alt="Previous jsx-slack" src="https://user-images.githubusercontent.com/3993388/54109076-f9acbb80-4420-11e9-814e-5376d00a5f86.png">|<img width="195" alt="Updated" src="https://user-images.githubusercontent.com/3993388/54109086-00d3c980-4421-11e9-9453-033aa8e9bee6.png">|

We will know improved layout by looking `9. Ordered list`.


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8412bd7..b9abb66 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## [Unreleased]
 
+### Changed
+
+- Right-aligned number in ordered list ([#8](https://github.com/speee/jsx-slack/pull/8))
+
 ## v0.2.0 - 2019-03-07
 
 ### Added
diff --git a/src/turndown.ts b/src/turndown.ts
index 30b8730..72de114 100644
--- a/src/turndown.ts
+++ b/src/turndown.ts
@@ -17,6 +17,8 @@ const applyMarkup = (
   })
 
 const preSymbol = Symbol('pre')
+const olIdSymbol = Symbol('olId')
+const olDigitsSymbol = Symbol('olDigits')
 
 const turndownService = () => {
   const td = new TurndownService({
@@ -31,6 +33,8 @@ const turndownService = () => {
     strongDelimiter: JSXSlack.exactMode() ? '\u200b*\u200b' : '*',
   })
 
+  let olUniqueId = 0
+
   const rules = {
     heading: null,
     horizontalRule: null,
@@ -83,25 +87,38 @@ const turndownService = () => {
     },
     listItem: {
       filter: 'li',
-      replacement: (s: string, node: HTMLElement, { bulletListMarker }) => {
+      replacement: (s: string, node: HTMLElement, opts) => {
         const content = s
           .replace(/^\n+/, '') // remove leading newlines
           .replace(/\n+$/, '\n') // replace trailing newlines with just a single one
 
-        let prefix = `${bulletListMarker} `
+        let prefix = `${opts.bulletListMarker} `
         let indent = `\u2007 ` // Figure space + space
 
         const parent = node.parentNode
         if (parent && parent.nodeName === 'OL') {
-          const start = (parent as HTMLOListElement).getAttribute('start')
-          const index = Array.prototype.indexOf.call(parent.children, node)
-          const number = (start ? Number(start) + index : index + 1).toString()
-
-          prefix = `${number}. `
-          indent = ''
-
-          for (let i = 0; i < number.length; i += 1) indent += '\u2007'
-          indent += '  '
+          const ol = parent as HTMLOListElement
+
+          // Get the number of order
+          const start = ol.getAttribute('start') || 1
+          const index = Array.prototype.indexOf.call(
+            Array.prototype.filter.call(
+              parent.children,
+              (n: HTMLElement) => n.nodeName === 'LI'
+            ),
+            node
+          )
+          const number = Number(start) + index
+
+          // eslint-disable-next-line no-plusplus
+          ol[olIdSymbol] = ol[olIdSymbol] || ++olUniqueId
+          ol[olDigitsSymbol] = Math.max(
+            ol[olDigitsSymbol] || 0,
+            number.toString().length
+          )
+
+          prefix = `<<ol-number:${ol[olIdSymbol]}:${number}>> `
+          indent = `<<ol-indent:${ol[olIdSymbol]}>> `
         }
 
         return `${prefix}${content.replace(/\n/gm, `\n${indent}`)}${
@@ -109,6 +126,21 @@ const turndownService = () => {
         }`
       },
     },
+    orderedList: {
+      filter: 'ol',
+      replacement: (s: string, ol: HTMLOListElement) => {
+        const uniqId = ol[olIdSymbol]
+        const numMatcher = new RegExp(`<<ol-number:${uniqId}:(\\d+)>>`, 'g')
+        const indentMatcher = new RegExp(`<<ol-indent:${uniqId}>>`, 'g')
+
+        return s
+          .replace(
+            numMatcher,
+            (_, num: string) => `${num.padStart(ol[olDigitsSymbol], '\u2007')}.`
+          )
+          .replace(indentMatcher, `${'\u2007'.repeat(ol[olDigitsSymbol])} `)
+      },
+    },
     mrkdwnLink: {
       filter: (node: HTMLElement, { linkStyle }) =>
         linkStyle === 'mrkdwn' &&


Test patch:
diff --git a/test/html.tsx b/test/html.tsx
index d7869e6..7835bf9 100644
--- a/test/html.tsx
+++ b/test/html.tsx
@@ -506,21 +506,60 @@ describe('HTML parser for mrkdwn', () => {
             </li>
           </ol>
         )
-      ).toBe('9. Change\n10. Start\n\u2007\u2007  number')
+      ).toBe('\u20079. Change\n10. Start\n\u2007\u2007  number') // aligned number
     })
 
-    // TODO: Support nested list
-    it.skip('allows nested list', () => {
+    // TODO: Support sub list
+    it.skip('allows sub list', () => {
       expect(
         html(
           <ul>
             <li>test</li>
             <ul>
-              <li>nesting</li>
+              <li>sub-list with direct nesting</li>
             </ul>
+            <li>
+              <ul>
+                <li>sub-list</li>
+                <li>
+                  and
+                  <ul>
+                    <li>sub-sub-list</li>
+                  </ul>
+                </li>
+              </ul>
+            </li>
           </ul>
         )
-      ).toBe('• test\n\u2007 • nesting')
+      ).toBe(
+        '• test\n\u2007 • sub-list with direct nesting\n• • sub-list\n\u2007 • and\n\u2007 \u2007 • sub-sub-list'
+      )
+    })
+
+    it.skip('allows sub ordered list', () => {
+      expect(
+        html(
+          <ol start={2}>
+            <li>test</li>
+            <ol>
+              <li>sub-list with direct nesting</li>
+            </ol>
+            <li>
+              <ol>
+                <li>sub-list</li>
+                <li>
+                  and
+                  <ul>
+                    <li>sub-sub-list</li>
+                  </ul>
+                </li>
+              </ol>
+            </li>
+          </ol>
+        )
+      ).toBe(
+        '2. test\n\u2007 1. sub-list with direct nesting\n3. 1. sub-list\n\u2007 2, and\n\u2007 \u2007 • sub-sub-list'
+      )
     })
 
     it('does not allow unsupported block components', () => {