Linker.js 4.7 KB

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