A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

129 lines
3.7 KiB

  1. import { MarkdownSerializer } from "prosemirror-markdown";
  2. function backticksFor(node, side) {
  3. const ticks = /`+/g;
  4. let m;
  5. let len = 0;
  6. if (node.isText)
  7. while ((m = ticks.exec(node.text))) len = Math.max(len, m[0].length);
  8. let result = len > 0 && side > 0 ? " `" : "`";
  9. for (let i = 0; i < len; i++) result += "`";
  10. if (len > 0 && side < 0) result += " ";
  11. return result;
  12. }
  13. function isPlainURL(link, parent, index, side) {
  14. if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) return false;
  15. const content = parent.child(index + (side < 0 ? -1 : 0));
  16. if (
  17. !content.isText ||
  18. content.text != link.attrs.href ||
  19. content.marks[content.marks.length - 1] != link
  20. )
  21. return false;
  22. if (index == (side < 0 ? 1 : parent.childCount - 1)) return true;
  23. const next = parent.child(index + (side < 0 ? -2 : 1));
  24. return !link.isInSet(next.marks);
  25. }
  26. export const writeFreelyMarkdownSerializer = new MarkdownSerializer(
  27. {
  28. readmore(state, node) {
  29. state.write("<!--more-->\n");
  30. state.closeBlock(node);
  31. },
  32. blockquote(state, node) {
  33. state.wrapBlock("> ", null, node, () => state.renderContent(node));
  34. },
  35. code_block(state, node) {
  36. state.write(`\`\`\`${node.attrs.params || ""}\n`);
  37. state.text(node.textContent, false);
  38. state.ensureNewLine();
  39. state.write("```");
  40. state.closeBlock(node);
  41. },
  42. heading(state, node) {
  43. state.write(`${state.repeat("#", node.attrs.level)} `);
  44. state.renderInline(node);
  45. state.closeBlock(node);
  46. },
  47. horizontal_rule: function horizontal_rule(state, node) {
  48. state.write(node.attrs.markup || "---");
  49. state.closeBlock(node);
  50. },
  51. bullet_list(state, node) {
  52. node.attrs.tight = true;
  53. state.renderList(node, " ", () => `${node.attrs.bullet || "*"} `);
  54. },
  55. ordered_list(state, node) {
  56. const start = node.attrs.order || 1;
  57. const maxW = String(start + node.childCount - 1).length;
  58. const space = state.repeat(" ", maxW + 2);
  59. state.renderList(node, space, (i) => {
  60. const nStr = String(start + i);
  61. return `${state.repeat(" ", maxW - nStr.length) + nStr}. `;
  62. });
  63. },
  64. list_item(state, node) {
  65. state.renderContent(node);
  66. },
  67. paragraph(state, node) {
  68. state.renderInline(node);
  69. state.closeBlock(node);
  70. },
  71. image(state, node) {
  72. state.write(
  73. `![${state.esc(node.attrs.alt || "")}](${state.esc(node.attrs.src)}${
  74. node.attrs.title ? ` ${state.quote(node.attrs.title)}` : ""
  75. })`
  76. );
  77. },
  78. hard_break(state, node, parent, index) {
  79. for (let i = index + 1; i < parent.childCount; i += 1)
  80. if (parent.child(i).type !== node.type) {
  81. state.write("\\\n");
  82. return;
  83. }
  84. },
  85. text(state, node) {
  86. state.text(node.text || "");
  87. },
  88. },
  89. {
  90. em: {
  91. open: "_",
  92. close: "_",
  93. mixable: true,
  94. expelEnclosingWhitespace: true,
  95. },
  96. strong: {
  97. open: "**",
  98. close: "**",
  99. mixable: true,
  100. expelEnclosingWhitespace: true,
  101. },
  102. link: {
  103. open(_state, mark, parent, index) {
  104. return isPlainURL(mark, parent, index, 1) ? "<" : "[";
  105. },
  106. close(state, mark, parent, index) {
  107. return isPlainURL(mark, parent, index, -1)
  108. ? ">"
  109. : `](${state.esc(mark.attrs.href)}${
  110. mark.attrs.title ? ` ${state.quote(mark.attrs.title)}` : ""
  111. })`;
  112. },
  113. },
  114. code: {
  115. open(_state, _mark, parent, index) {
  116. return backticksFor(parent.child(index), -1);
  117. },
  118. close(_state, _mark, parent, index) {
  119. return backticksFor(parent.child(index - 1), 1);
  120. },
  121. escape: false,
  122. },
  123. }
  124. );