Linker.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { pagePathUtils } from '@growi/core';
  2. const { encodeSpaces } = pagePathUtils;
  3. export default class Linker {
  4. constructor(
  5. type = Linker.types.markdownLink,
  6. label = '',
  7. link = '',
  8. ) {
  9. this.type = type;
  10. this.label = label;
  11. this.link = link;
  12. if (type === Linker.types.markdownLink) {
  13. this.initWhenMarkdownLink();
  14. }
  15. this.generateMarkdownText = this.generateMarkdownText.bind(this);
  16. }
  17. static types = {
  18. markdownLink: 'mdLink',
  19. growiLink: 'growiLink',
  20. pukiwikiLink: 'pukiwikiLink',
  21. }
  22. static patterns = {
  23. pukiwikiLinkWithLabel: /^\[\[(?<label>.+)>(?<link>.+)\]\]$/, // https://regex101.com/r/2fNmUN/2
  24. pukiwikiLinkWithoutLabel: /^\[\[(?<label>.+)\]\]$/, // https://regex101.com/r/S7w5Xu/1
  25. growiLink: /^\[(?<label>\/.+)\]$/, // https://regex101.com/r/DJfkYf/3
  26. markdownLink: /^\[(?<label>.*)\]\((?<link>.*)\)$/, // https://regex101.com/r/DZCKP3/2
  27. }
  28. initWhenMarkdownLink() {
  29. // fill label with link if empty
  30. if (this.label === '') {
  31. this.label = this.link;
  32. }
  33. // encode spaces
  34. this.link = encodeSpaces(this.link);
  35. }
  36. generateMarkdownText() {
  37. if (this.type === Linker.types.pukiwikiLink) {
  38. if (this.label === '') return `[[${this.link}]]`;
  39. return `[[${this.label}>${this.link}]]`;
  40. }
  41. if (this.type === Linker.types.growiLink) {
  42. return `[${this.link}]`;
  43. }
  44. if (this.type === Linker.types.markdownLink) {
  45. return `[${this.label}](${this.link})`;
  46. }
  47. }
  48. // create an instance of Linker from string
  49. static fromMarkdownString(str) {
  50. // if str doesn't mean a linker, create a link whose label is str
  51. let label = str;
  52. let link = '';
  53. let type = this.types.markdownLink;
  54. // pukiwiki with separator ">".
  55. if (str.match(this.patterns.pukiwikiLinkWithLabel)) {
  56. type = this.types.pukiwikiLink;
  57. ({ label, link } = str.match(this.patterns.pukiwikiLinkWithLabel).groups);
  58. }
  59. // pukiwiki without separator ">".
  60. else if (str.match(this.patterns.pukiwikiLinkWithoutLabel)) {
  61. type = this.types.pukiwikiLink;
  62. ({ label } = str.match(this.patterns.pukiwikiLinkWithoutLabel).groups);
  63. link = label;
  64. }
  65. // markdown
  66. else if (str.match(this.patterns.markdownLink)) {
  67. type = this.types.markdownLink;
  68. ({ label, link } = str.match(this.patterns.markdownLink).groups);
  69. }
  70. // growi
  71. else if (str.match(this.patterns.growiLink)) {
  72. type = this.types.growiLink;
  73. ({ label } = str.match(this.patterns.growiLink).groups);
  74. link = label;
  75. }
  76. return new Linker(
  77. type,
  78. label,
  79. link,
  80. );
  81. }
  82. // create an instance of Linker from text with index
  83. static fromLineWithIndex(line, index) {
  84. const { beginningOfLink, endOfLink } = this.getBeginningAndEndIndexOfLink(line, index);
  85. // if index is in a link, extract it from line
  86. let linkStr = '';
  87. if (beginningOfLink >= 0 && endOfLink >= 0) {
  88. linkStr = line.substring(beginningOfLink, endOfLink);
  89. }
  90. return this.fromMarkdownString(linkStr);
  91. }
  92. // return beginning and end indexies of link
  93. // if index is not in a link, return { beginningOfLink: -1, endOfLink: -1 }
  94. static getBeginningAndEndIndexOfLink(line, index) {
  95. let beginningOfLink;
  96. let endOfLink;
  97. // pukiwiki link ('[[link]]')
  98. [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[[', ']]');
  99. // markdown link ('[label](link)')
  100. if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
  101. [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[', ')', '](');
  102. }
  103. // growi link ('[/link]')
  104. if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
  105. [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[/', ']');
  106. }
  107. // return { beginningOfLink: -1, endOfLink: -1 }
  108. if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
  109. [beginningOfLink, endOfLink] = [-1, -1];
  110. }
  111. return { beginningOfLink, endOfLink };
  112. }
  113. // return begin and end indexies as array only when index is between prefix and suffix and link contains containText.
  114. static getBeginningAndEndIndexWithPrefixAndSuffix(line, index, prefix, suffix, containText = '') {
  115. const beginningIndex = line.lastIndexOf(prefix, index);
  116. const IndexOfContainText = line.indexOf(containText, beginningIndex + prefix.length);
  117. const endIndex = line.indexOf(suffix, IndexOfContainText + containText.length);
  118. if (beginningIndex < 0 || IndexOfContainText < 0 || endIndex < 0) {
  119. return [-1, -1];
  120. }
  121. return [beginningIndex, endIndex + suffix.length];
  122. }
  123. }