From 37335e35b04c046d67a677a8bca2824436895dab Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Sat, 8 Jul 2017 01:34:30 +0800 Subject: [PATCH] Markdown copy improvements (#546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Skip selections across elements * Drop `g-emoji` elements * Drop tasks from lists * Drop autolinks around images * Keep if it has width, height or align * Unwrap commit/issue autolinks * Unwrap some shortened links * Wrap orphaned
  • s in their original parent This allows partial list selections. Without a parent,
  • s will be rendered inline elements by to-markdown * Add support for
      lists that start after 1 * Keep the original number of orphaned
    1. s in numbered lists * It should only prevent the original copy if it succeeds * Add tests ⚡️ * Update to-markdown@3.1.0 Include ol[start] support --- package.json | 3 +- src/libs/copy-markdown.js | 65 ++++++++++++++++++++++-- test/copy-markdown.js | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 test/copy-markdown.js diff --git a/package.json b/package.json index 95b77f9..1ff7ed7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "linkify-urls": "^1.3.0", "select-dom": "^4.1.0", "shorten-repo-url": "^1.1.0", - "to-markdown": "^3.0.4", + "to-markdown": "^3.1.0", "to-semver": "^1.1.0", "webext-dynamic-content-scripts": "^2.0.1", "webext-options-sync": "^0.11.0" @@ -35,6 +35,7 @@ "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", "babel-plugin-transform-react-jsx": "^6.24.1", "chrome-webstore-upload-cli": "^1.0.0", + "common-tags": "^1.4.0", "dot-json": "^1.0.3", "npm-run-all": "^4.0.2", "webext": "^1.9.1-with-submit.1", diff --git a/src/libs/copy-markdown.js b/src/libs/copy-markdown.js index 6034535..df95349 100644 --- a/src/libs/copy-markdown.js +++ b/src/libs/copy-markdown.js @@ -1,22 +1,77 @@ import toMarkdown from 'to-markdown'; import copyToClipboard from 'copy-text-to-clipboard'; +const unwrapContent = content => content; +const unshortenRegex = /^https:[/][/](www[.])?|[/]$/g; + +const converters = [ + // Drop unnecessary elements + // is GH's emoji wrapper + // input and .handle appear in "- [ ] lists", let's not copy tasks + { + filter: node => node.matches('g-emoji,.handle,input.task-list-item-checkbox'), + replacement: unwrapContent + }, + + // Unwrap commit/issue autolinks + { + filter: node => node.matches('.commit-link,.issue-link') || // GH autolinks + (node.href && node.href.replace(unshortenRegex, '') === node.textContent), // Some of bfred-it/shorten-repo-url + replacement: (content, element) => element.href + }, + + // Unwrap images + { + filter: node => node.tagName === 'A' && // It's a link + node.childNodes.length === 1 && // It has one child + node.firstChild.tagName === 'IMG' && // Its child is an image + node.firstChild.src === node.href, // It links to its own image + replacement: unwrapContent + }, + + // Keep if it's customized + { + filter: node => node.matches('img[width],img[height],img[align]'), + replacement: (content, element) => element.outerHTML + } +]; + +export const getSmarterMarkdown = html => toMarkdown(html, { + converters, + gfm: true +}); + export default event => { const selection = window.getSelection(); const range = selection.getRangeAt(0); const container = range.commonAncestorContainer; const containerEl = container.closest ? container : container.parentNode; - if (containerEl.closest('pre')) { + // Exclude pure code selections and selections across markdown elements: + // https://github.com/sindresorhus/refined-github/issues/522#issuecomment-311271274 + if (containerEl.closest('pre') || containerEl.querySelector('.markdown-body')) { return; } - event.stopImmediatePropagation(); - event.preventDefault(); - const holder = document.createElement('div'); holder.append(range.cloneContents()); - const markdown = toMarkdown(holder.innerHTML, {gfm: true}); + + // Wrap orphaned
    2. s in their original parent + // And keep the their original number + if (holder.firstChild.tagName === 'LI') { + const list = document.createElement(containerEl.tagName); + try { + const originalLi = range.startContainer.parentNode.closest('li'); + list.start = containerEl.start + [...containerEl.children].indexOf(originalLi); + } catch (err) {} + list.append(...holder.childNodes); + holder.appendChild(list); + } + + const markdown = getSmarterMarkdown(holder.innerHTML); copyToClipboard(markdown); + + event.stopImmediatePropagation(); + event.preventDefault(); }; diff --git a/test/copy-markdown.js b/test/copy-markdown.js new file mode 100644 index 0000000..6f9f44a --- /dev/null +++ b/test/copy-markdown.js @@ -0,0 +1,101 @@ +import test from 'ava'; +import {stripIndent} from 'common-tags'; +import {getSmarterMarkdown} from '../src/libs/copy-markdown'; + +test('base markdown', t => { + t.is( + getSmarterMarkdown('this is markdown'), + '[this](url) is **markdown**' + ); +}); + +test('drop ', t => { + t.is( + getSmarterMarkdown('🔥'), + '🔥' + ); +}); + +test('drop tasks from lists', t => { + t.is( + getSmarterMarkdown(stripIndent` +
        +
      • try me out
      • +
      • test across lines
      • +
      + `), + stripIndent` + * try me out + * test across lines + ` + ); +}); + +test('drop autolinks around images', t => { + t.is( + getSmarterMarkdown(stripIndent` + + `), + stripIndent` + ![](https://camo.githubusercontent.com/7a0ef30dc39981585543e0bbd816392a52dddd8a/687474703a2f2f692e696d6775722e636f6d2f4b6361644c36472e706e67) + ` + ); +}); + +test('keep img tags if they have width, height or align', t => { + t.is( + getSmarterMarkdown(stripIndent` + copy + `), + stripIndent` + copy + ` + ); +}); + +test('drop autolinks from issue links and commit links', t => { + t.is( + getSmarterMarkdown(stripIndent` + #522 + `), + 'https://github.com/sindresorhus/refined-github/issues/522' + ); + t.is( + getSmarterMarkdown(stripIndent` + 833d598 + `), + 'https://github.com/sindresorhus/refined-github/commit/833d5984fffb18a44b83d965b397f82e0ff3085e' + ); +}); + +test('drop autolinks around some shortened links', t => { + t.is( + getSmarterMarkdown(stripIndent` +

      npmjs.com

      +

      twitter.com/bfred_it

      +

      github.com

      + `), + stripIndent` + https://www.npmjs.com/ + + https://twitter.com/bfred_it + + https://github.com/ + ` + ); +}); + +test('wrap orphaned li in their original parent', t => { + t.is( + getSmarterMarkdown(stripIndent` +
        +
      1. big lists
      2. +
      3. deserve big numbers
      4. +
      + `), + stripIndent` + 99. big lists + 100. deserve big numbers + ` + ); +});