getQuoteMarkup.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. const getQuoteMarkup = (range) => {
  2. const metadata = getQuoteMetadata(range)
  3. let markup = convertNodesToMarkup(range.cloneContents().childNodes, [])
  4. let prefix = metadata ? `[quote="${metadata}"]\n` : "[quote]\n"
  5. let suffix = "\n[/quote]\n\n"
  6. const codeBlock = getQuoteCodeBlock(range)
  7. if (codeBlock) {
  8. prefix += codeBlock.syntax ? `[code=${codeBlock.syntax}]\n` : "[code]\n"
  9. suffix = "\n[/code]" + suffix
  10. } else if (isNodeInlineCodeBlock(range)) {
  11. markup = markup.trim()
  12. prefix += "`"
  13. suffix = "`" + suffix
  14. } else {
  15. markup = markup.trim()
  16. }
  17. return prefix + markup + suffix
  18. }
  19. export default getQuoteMarkup
  20. const getQuoteMetadata = (range) => {
  21. const node = range.commonAncestorContainer
  22. if (isNodeElementWithQuoteMetadata(node)) {
  23. return getQuoteMetadataFromNode(node)
  24. }
  25. let p = node.parentNode
  26. while (p) {
  27. if (isNodeElementWithQuoteMetadata(p)) {
  28. return getQuoteMetadataFromNode(p)
  29. }
  30. p = p.parentNode
  31. }
  32. return ""
  33. }
  34. const isNodeElementWithQuoteMetadata = (node) => {
  35. if (node.nodeType !== Node.ELEMENT_NODE) return false
  36. if (node.nodeName === "ARTICLE") return true
  37. if (node.nodeName === "BLOCKQUOTE") {
  38. return node.dataset && node.dataset.block === "quote"
  39. }
  40. return false
  41. }
  42. const getQuoteMetadataFromNode = (element) => {
  43. if (element.dataset) {
  44. return element.dataset.author || null
  45. }
  46. return null
  47. }
  48. const getQuoteCodeBlock = (range) => {
  49. const node = range.commonAncestorContainer
  50. if (isNodeCodeBlock(node)) {
  51. return getNodeCodeBlockMeta(node)
  52. }
  53. let p = node.parentNode
  54. while (p) {
  55. if (isNodeCodeBlock(p)) {
  56. return getNodeCodeBlockMeta(p)
  57. }
  58. p = p.parentNode
  59. }
  60. return null
  61. }
  62. const isNodeCodeBlock = (node) => {
  63. return node.nodeName === "PRE"
  64. }
  65. const isNodeInlineCodeBlock = (range) => {
  66. const node = range.commonAncestorContainer
  67. if (node.nodeName === "CODE") {
  68. return true
  69. }
  70. let p = node.parentNode
  71. while (p) {
  72. if (isNodeElementWithQuoteMetadata(p)) {
  73. return false
  74. }
  75. if (p.nodeName === "CODE") {
  76. return true
  77. }
  78. p = p.parentNode
  79. }
  80. return false
  81. }
  82. const getNodeCodeBlockMeta = (node) => {
  83. if (!node.dataset) {
  84. return { syntax: null }
  85. }
  86. return { syntax: node.dataset.syntax || null }
  87. }
  88. const convertNodesToMarkup = (nodes, stack) => {
  89. let markup = ""
  90. for (let i = 0; i < nodes.length; i++) {
  91. const node = nodes[i]
  92. markup += convertNodeToMarkup(node, stack)
  93. }
  94. return markup
  95. }
  96. const SIMPLE_NODE_MAPPINGS = {
  97. H1: ["\n\n# ", ""],
  98. H2: ["\n\n## ", ""],
  99. H3: ["\n\n### ", ""],
  100. H4: ["\n\n#### ", ""],
  101. H5: ["\n\n##### ", ""],
  102. H6: ["\n\n###### ", ""],
  103. STRONG: ["**", "**"],
  104. EM: ["*", "*"],
  105. DEL: ["~~", "~~"],
  106. B: ["[b]", "[/b]"],
  107. U: ["[u]", "[/u]"],
  108. I: ["[i]", "[/i]"],
  109. SUB: ["[sub]", "[/sub]"],
  110. SUP: ["[sup]", "[/sup]"],
  111. }
  112. const convertNodeToMarkup = (node, stack) => {
  113. const dataset = node.dataset || {}
  114. if (node.nodeType === Node.TEXT_NODE) {
  115. return node.textContent || ""
  116. }
  117. if (node.nodeType === Node.ELEMENT_NODE) {
  118. if (dataset.quote) {
  119. return dataset.quote || ""
  120. }
  121. if (dataset.noquote === "1") return ""
  122. }
  123. if (
  124. node.nodeType === Node.ELEMENT_NODE &&
  125. dataset.quote &&
  126. dataset.quote.trim()
  127. ) {
  128. return ""
  129. }
  130. if (node.nodeName === "HR") {
  131. return "\n\n- - -"
  132. }
  133. if (node.nodeName === "BR") {
  134. return "\n"
  135. }
  136. if (SIMPLE_NODE_MAPPINGS[node.nodeName]) {
  137. const [prefix, suffix] = SIMPLE_NODE_MAPPINGS[node.nodeName]
  138. return (
  139. prefix +
  140. convertNodesToMarkup(node.childNodes, [...stack, node.nodeName]) +
  141. suffix
  142. )
  143. }
  144. if (node.nodeName === "A") {
  145. const href = node.href
  146. const text = convertNodesToMarkup(node.childNodes, [
  147. ...stack,
  148. node.nodeName,
  149. ])
  150. if (text) {
  151. return `[${text}](${href})`
  152. } else {
  153. return `!(${href})`
  154. }
  155. }
  156. if (node.nodeName === "IMG") {
  157. const src = node.src
  158. const alt = node.alt
  159. if (alt) {
  160. return `![${alt}](${src})`
  161. } else {
  162. return `!(${src})`
  163. }
  164. }
  165. if (node.nodeName === "DIV") {
  166. const block = dataset.block.toUpperCase()
  167. if (block && SIMPLE_NODE_MAPPINGS[block]) {
  168. const [prefix, suffix] = SIMPLE_NODE_MAPPINGS[block]
  169. return (
  170. prefix +
  171. convertNodesToMarkup(node.childNodes, [...stack, block]) +
  172. suffix
  173. )
  174. } else {
  175. return convertNodesToMarkup(node.childNodes, stack)
  176. }
  177. }
  178. if (node.nodeName === "BLOCKQUOTE") {
  179. if (dataset.block === "quote") {
  180. const content = convertNodesToMarkup(node.childNodes, [
  181. ...stack,
  182. "QUOTE",
  183. ]).trim()
  184. if (!content) return ""
  185. const metadata = getQuoteMetadataFromNode(node)
  186. let markup = metadata ? `\n\n[quote=${metadata}]\n` : "\n\n[quote]\n"
  187. markup += content
  188. markup += "\n[/quote]"
  189. return markup
  190. }
  191. if (dataset.block === "spoiler") {
  192. const content = convertNodesToMarkup(node.childNodes, [
  193. ...stack,
  194. "SPOILER",
  195. ]).trim()
  196. if (!content) return ""
  197. let markup = "\n\n[spoiler]\n"
  198. markup += content
  199. markup += "\n[/spoiler]"
  200. return markup
  201. }
  202. }
  203. if (node.nodeName === "PRE") {
  204. const syntax = dataset.syntax || null
  205. const code = node.querySelector("code")
  206. const content = code ? code.innerText || "" : ""
  207. if (!content.trim()) return ""
  208. return (
  209. "\n\n[code" + (syntax ? "=" + syntax : "") + "]" + content + "[/code]"
  210. )
  211. }
  212. if (node.nodeName === "CODE") {
  213. return "`" + node.innerText + "`"
  214. }
  215. if (node.nodeName === "P") {
  216. return (
  217. "\n\n" + convertNodesToMarkup(node.childNodes, [...stack, node.nodeName])
  218. )
  219. }
  220. if (node.nodeName === "UL" || node.nodeName === "OL") {
  221. const level = stack.filter((item) => item === "OL" || item === "UL").length
  222. const prefix = level === 0 ? "\n" : ""
  223. return (
  224. prefix + convertNodesToMarkup(node.childNodes, [...stack, node.nodeName])
  225. )
  226. }
  227. if (node.nodeName === "LI") {
  228. let prefix = ""
  229. const level = stack.filter((item) => item === "OL" || item === "UL").length
  230. for (let i = 1; i < level; i++) {
  231. prefix += " "
  232. }
  233. const ordered = stack[stack.length - 1] === "OL"
  234. if (ordered) {
  235. prefix += dataset.index ? dataset.index + ". " : "1. "
  236. } else {
  237. prefix += "- "
  238. }
  239. const content = convertNodesToMarkup(node.childNodes, [
  240. ...stack,
  241. node.nodeName,
  242. ])
  243. if (!content.trim()) return ""
  244. return "\n" + prefix + content
  245. }
  246. if (node.nodeName === "SPAN") {
  247. return convertNodesToMarkup(node.childNodes, stack)
  248. }
  249. return ""
  250. }