Skip to content

Commit

Permalink
Support Markdown import (#2186)
Browse files Browse the repository at this point in the history
Co-authored-by: Raine Revere <raine@cybersemics.org>
  • Loading branch information
finkef and raineorshine committed Aug 3, 2024
1 parent e8fa57c commit db59f8b
Show file tree
Hide file tree
Showing 9 changed files with 816 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"immer": "^10.1.1",
"ipfs-http-client": "^43.0.1",
"lodash": "^4.17.21",
"marked": "^13.0.2",
"moize": "^6.1.6",
"murmurhash3js": "^3.0.1",
"nanoid": "^5.0.7",
Expand Down
34 changes: 34 additions & 0 deletions src/actions/__tests__/importText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1453,3 +1453,37 @@ it('import plaintext with embedded <li> mixed with text', () => {
- y
- d`)
})

it('import markdown', () => {
const text = `
# H1
a
## H2
b
### H3
c
#### H4
d
##### H5
e
###### H6
f
`

const stateNew = importText(initialState(), { text })
const exported = exportContext(stateNew, [HOME_TOKEN], 'text/plain')

expect(exported).toBe(`- ${HOME_TOKEN}
- H1
- a
- H2
- b
- H3
- c
- H4
- d
- H5
- e
- H6
- f`)
})
4 changes: 3 additions & 1 deletion src/actions/importText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ import head from '../util/head'
import htmlToJson from '../util/htmlToJson'
import importJSON from '../util/importJSON'
import initialState from '../util/initialState'
import { isMarkdown } from '../util/isMarkdown'
import isRoot from '../util/isRoot'
import { markdownToText } from '../util/markdownToText'
import parentOf from '../util/parentOf'
import reducerFlow from '../util/reducerFlow'
import roamJsonToBlocks from '../util/roamJsonToBlocks'
Expand Down Expand Up @@ -91,7 +93,7 @@ const importText = (

path = path || HOME_PATH
const simplePath = simplifyPath(state, path)
const convertedText = isRoam ? text : textToHtml(text)
const convertedText = isRoam ? text : isMarkdown(text) ? textToHtml(markdownToText(text)) : textToHtml(text)
const numLines = (convertedText.match(REGEX_LIST_ITEM) || []).length
const thoughtId = head(path)
const destThought = getThoughtById(state, thoughtId)
Expand Down
6 changes: 5 additions & 1 deletion src/components/Editable/useOnPaste.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import rootedParentOf from '../../selectors/rootedParentOf'
import store from '../../stores/app'
import equalPath from '../../util/equalPath'
import isHTML from '../../util/isHTML'
import { isMarkdown } from '../../util/isMarkdown'
import strip from '../../util/strip'
import timestamp from '../../util/timestamp'

Expand Down Expand Up @@ -68,9 +69,12 @@ const useOnPaste = ({
// Is this an adequate check if the thought is multiline, or do we need to use textToHtml like in importText?
const multiline = plainText.trim().includes('\n') || htmlText?.match(/<(li|p|br)[\s>]/)

// Check if the text is markdown, if so, prefer importText over importFiles
const markdown = isMarkdown(text)

// Resumable imports (via importFiles) import thoughts one at a time and can be resumed if the page is refreshed or there is another interruption. They have a progress bar and they allow duplicates pending descendants to be loaded and merged.
// Non-resumable imports (via importText), in contrast, are atomic, fast, and preserve the browser selection. Due to the lack of support for duplicates pending descendants, they are only used for single line imports.
if (!multiline) {
if (!multiline || markdown) {
dispatch(
importText({
// use caret position to correctly track the last navigated point for caret
Expand Down
47 changes: 47 additions & 0 deletions src/util/__tests__/isMarkdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { isMarkdown } from '../isMarkdown'

describe('isMarkdown', () => {
it('should identify markdown with headings', () => {
const markdown = `
plain text before heading
# Heading 1
`
expect(isMarkdown(markdown)).toBe(true)
})

it('should identify markdown with links', () => {
const markdown = `
This is a [link](https://example.com)
`
expect(isMarkdown(markdown)).toBe(true)
})

it('should identify markdown with images', () => {
const markdown = `
- List item 1
- List item 2
- List item 3
![Alt text](https://example.com/image.jpg)
`
expect(isMarkdown(markdown)).toBe(true)
})

it('should not identify plain text as markdown', () => {
const plainText = `
This is just plain text.
It has no markdown elements.
`
expect(isMarkdown(plainText)).toBe(false)
})

it('should not identify HTML as markdown', () => {
const html = `
<h1>This is HTML</h1>
<p>This is a paragraph with a <a href="https://example.com">link</a>.</p>
<img src="https://example.com/image.jpg" alt="Image">
`
expect(isMarkdown(html)).toBe(false)
})
})
Loading

0 comments on commit db59f8b

Please sign in to comment.