git ssb

1+

Daan Patchwork / patchwork



Tree: 455af00799601cb57c6ea54edad5e85c516b799f

Files: 455af00799601cb57c6ea54edad5e85c516b799f / lib / depject / message / html / markdown.js

4566 bytesRaw
1const renderer = require('ssb-markdown')
2const h = require('mutant/h')
3const ref = require('ssb-ref')
4const nest = require('depnest')
5const htmlEscape = require('html-escape')
6const watch = require('mutant/watch')
7const querystring = require('querystring')
8const nodeEmoji = require('node-emoji')
9
10exports.needs = nest({
11 'blob.sync.url': 'first',
12 'blob.obs.has': 'first'
13})
14
15exports.gives = nest('message.html.markdown')
16
17exports.create = function (api) {
18 return nest('message.html.markdown', markdown)
19
20 function markdown (content, { classList = null } = {}) {
21 if (typeof content === 'string') { content = { text: content } }
22 const mentions = {}
23 const typeLookup = {}
24 const emojiMentions = {}
25 if (Array.isArray(content.mentions)) {
26 content.mentions.forEach(function (link) {
27 if (link && link.link && link.type) {
28 typeLookup[link.link] = link.type
29 }
30 if (link && link.name && link.link) {
31 if (link.emoji) {
32 // handle custom emoji
33 emojiMentions[link.name] = link.link
34 } else {
35 // handle old-style patchwork v2 mentions (deprecated)
36 mentions['@' + link.name] = link.link
37 }
38 }
39 })
40 }
41
42 return h('Markdown', {
43 classList,
44 hooks: [
45 LoadingBlobHook(api.blob.obs.has),
46 LargeEmojiHook()
47 ],
48 innerHTML: renderer.block(content.text, {
49 emoji: (emoji) => {
50 if (emojiMentions[emoji]) {
51 return renderEmoji(emoji, api.blob.sync.url(emojiMentions[emoji]))
52 } else {
53 // https://github.com/omnidan/node-emoji/issues/76
54 const emojiCharacter = nodeEmoji.get(emoji).replace(/:/g, '')
55 return `<span class="Emoji">${emojiCharacter}</span>`
56 }
57 },
58 toUrl: (id) => {
59 const link = ref.parseLink(id)
60 if (link && ref.isBlob(link.link)) {
61 const url = api.blob.sync.url(link.link)
62 const query = {}
63 if (link.query && link.query.unbox) query.unbox = link.query.unbox
64 if (typeLookup[link.link]) query.contentType = typeLookup[link.link]
65 return url + '?' + querystring.stringify(query)
66 } else if (link || id.startsWith('#') || id.startsWith('?')) {
67 return id
68 } else if (mentions[id]) {
69 // handle old-style patchwork v2 mentions (deprecated)
70 return mentions[id]
71 }
72 return false
73 },
74 imageLink: (id) => id
75 })
76 })
77 }
78
79 function renderEmoji (emoji, url) {
80 if (!url) return ':' + emoji + ':'
81
82 return `
83 <img
84 src="${htmlEscape(url)}"
85 alt=":${htmlEscape(emoji)}:"
86 title=":${htmlEscape(emoji)}:"
87 class="emoji"
88 >
89 `
90 }
91}
92
93function LoadingBlobHook (hasBlob) {
94 return function (element) {
95 const releases = []
96 element.querySelectorAll('img').forEach(img => {
97 const id = ref.extract(img.src)
98 if (id) {
99 releases.push(watch(hasBlob(id), has => {
100 if (has === false) {
101 img.classList.add('-pending')
102 } else {
103 img.classList.remove('-pending')
104 }
105 }))
106 }
107 })
108 return function () {
109 while (releases.length) {
110 releases.pop()()
111 }
112 }
113 }
114}
115
116function LargeEmojiHook () {
117 return function (element) {
118 // if a line has only emoji, we want them to be LARGE
119 //
120 // first select for all emoji as the first child of a paragraph
121 element.querySelectorAll('p > .Emoji:nth-child(1)').forEach(firstEmojiElement => {
122 // then collect all emoji siblings.
123 // if a sibling is not an emoji or an empty text node, early return.
124 const emojiElements = []
125 let nextNode = firstEmojiElement
126
127 // before we start, check that matched emoji element's previous sibling is empty text.
128 if (firstEmojiElement.previousSibling && firstEmojiElement.previousSibling.textContent.trim() !== '') return
129
130 while (nextNode !== null) {
131 switch (nextNode.nodeType) {
132 case document.ELEMENT_NODE:
133 if (nextNode.className !== 'Emoji') return
134 emojiElements.push(nextNode)
135 break
136 case document.TEXT_NODE:
137 if (nextNode.textContent.trim() !== '') return
138 break
139 }
140 nextNode = nextNode.nextSibling
141 }
142
143 // set all emoji children to be LARGE
144 emojiElements.forEach(emojiElement => {
145 emojiElement.classList.add('-large')
146 })
147 })
148 }
149}
150

Built with git-ssb-web