فهرست منبع

Merge branch 'feat/enable-update-link-with-link-editor-modal' into feat/replace-perma-link

# Conflicts:
#	src/client/js/components/PageEditor/LinkEditModal.jsx
yusuketk 5 سال پیش
والد
کامیت
db38f03a32

+ 1 - 1
src/client/js/components/PageEditor/CodeMirrorEditor.jsx

@@ -667,7 +667,7 @@ export default class CodeMirrorEditor extends AbstractEditor {
   }
 
   showLinkEditHandler() {
-    this.linkEditModal.current.show(mlu.getSelectedTextInEditor(this.getCodeMirror()));
+    this.linkEditModal.current.show(mlu.getMarkdownLink(this.getCodeMirror()));
   }
 
   showHandsonTableHandler() {

+ 55 - 23
src/client/js/components/PageEditor/LinkEditModal.jsx

@@ -15,7 +15,8 @@ import Preview from './Preview';
 import AppContainer from '../../services/AppContainer';
 import PageContainer from '../../services/PageContainer';
 
-import PagePathAutoComplete from '../PagePathAutoComplete';
+import SearchTypeahead from '../SearchTypeahead';
+import Linker from '../../models/Linker';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 
@@ -28,9 +29,9 @@ class LinkEditModal extends React.PureComponent {
       show: false,
       isUseRelativePath: false,
       isUsePermanentLink: false,
-      linkInputValue: '/',
+      linkInputValue: '',
       labelInputValue: '',
-      linkerType: 'mdLink',
+      linkerType: Linker.types.markdownLink,
       markdown: '',
       permalink: '',
       isEnablePermanentLink: false,
@@ -43,6 +44,7 @@ class LinkEditModal extends React.PureComponent {
     this.cancel = this.cancel.bind(this);
     this.handleChangeLinkInput = this.handleChangeLinkInput.bind(this);
     this.handleChangeLabelInput = this.handleChangeLabelInput.bind(this);
+    this.handleChangeLinkInput = this.handleChangeLinkInput.bind(this);
     this.handleSelecteLinkerType = this.handleSelecteLinkerType.bind(this);
     this.toggleIsUseRelativePath = this.toggleIsUseRelativePath.bind(this);
     this.toggleIsUsePamanentLink = this.toggleIsUsePamanentLink.bind(this);
@@ -60,8 +62,23 @@ class LinkEditModal extends React.PureComponent {
     }
   }
 
-  show(defaultLabelInputValue = '') {
-    this.setState({ show: true, labelInputValue: defaultLabelInputValue });
+  // defaultMarkdownLink is an instance of Linker
+  show(defaultMarkdownLink = null) {
+    // if defaultMarkdownLink is null, set default value in inputs.
+    const { label = '', link = '' } = defaultMarkdownLink;
+    let { type = Linker.types.markdownLink } = defaultMarkdownLink;
+
+    // if type of defaultMarkdownLink is pukiwikiLink when pukiwikiLikeLinker plugin is disable, change type(not change label and link)
+    if (type === Linker.types.pukiwikiLink && !this.isApplyPukiwikiLikeLinkerPlugin) {
+      type = Linker.types.markdownLink;
+    }
+
+    this.setState({
+      show: true,
+      labelInputValue: label,
+      linkInputValue: link,
+      linkerType: type,
+    });
   }
 
   cancel() {
@@ -75,7 +92,7 @@ class LinkEditModal extends React.PureComponent {
   }
 
   toggleIsUseRelativePath() {
-    if (this.state.linkerType === 'growiLink') {
+    if (this.state.linkerType === Linker.types.growiLink) {
       return;
     }
 
@@ -102,16 +119,29 @@ class LinkEditModal extends React.PureComponent {
     );
   }
 
-  handleChangeLinkInput(inputChangeValue) {
-    this.setState({ linkInputValue: inputChangeValue, isEnablePermanentLink: false, isUsePermanentLink: false });
+  async setMarkdown(path) {
+    let markdown = '';
+    try {
+      await this.props.appContainer.apiGet('/pages.get', { path }).then((res) => {
+        markdown = res.page.revision.body;
+      });
+    }
+    catch (err) {
+      markdown = `<div class="alert alert-warning" role="alert"><strong>${err.message}</strong></div>`;
+    }
+    this.setState({ markdown });
   }
 
   handleChangeLabelInput(label) {
     this.setState({ labelInputValue: label });
   }
 
+  handleChangeLinkInput(link) {
+    this.setState({ linkInputValue: link });
+  }
+
   handleSelecteLinkerType(linkerType) {
-    if (this.state.isUseRelativePath && linkerType === 'growiLink') {
+    if (this.state.isUseRelativePath && linkerType === Linker.types.growiLink) {
       this.toggleIsUseRelativePath();
     }
     this.setState({ linkerType });
@@ -143,7 +173,6 @@ class LinkEditModal extends React.PureComponent {
     this.setState({ markdown, permalink, isEnablePermanentLink });
   }
 
-
   generateLink() {
     const { pageContainer } = this.props;
     const {
@@ -163,13 +192,13 @@ class LinkEditModal extends React.PureComponent {
       reshapedLink = this.state.permalink;
     }
 
-    if (linkerType === 'pukiwikiLink') {
+    if (linkerType === Linker.types.pukiwikiLink) {
       return `[[${labelInputValue}>${reshapedLink}]]`;
     }
-    if (linkerType === 'growiLink') {
+    if (linkerType === Linker.types.growiLink) {
       return `[${reshapedLink}]`;
     }
-    if (linkerType === 'mdLink') {
+    if (linkerType === Linker.types.markdownLink) {
       return `[${labelInputValue}](${reshapedLink})`;
     }
   }
@@ -188,9 +217,12 @@ class LinkEditModal extends React.PureComponent {
                 <div className="form-gorup my-3">
                   <label htmlFor="linkInput">Link</label>
                   <div className="input-group">
-                    <PagePathAutoComplete
+                    <SearchTypeahead
+                      onChange={this.handleChangeTypeahead}
                       onInputChange={this.handleChangeLinkInput}
-                      onSubmit={this.getPageWithLinkInputValue}
+                      inputName="link"
+                      placeholder="Input page path or URL"
+                      keywordOnInit={this.state.linkInputValue}
                     />
                   </div>
                 </div>
@@ -206,16 +238,16 @@ class LinkEditModal extends React.PureComponent {
                     <div className="form-group btn-group d-flex" role="group" aria-label="type">
                       <button
                         type="button"
-                        name="mdLink"
-                        className={`btn btn-outline-secondary w-100 ${this.state.linkerType === 'mdLink' && 'active'}`}
+                        name={Linker.types.markdownLink}
+                        className={`btn btn-outline-secondary w-100 ${this.state.linkerType === Linker.types.markdownLink && 'active'}`}
                         onClick={e => this.handleSelecteLinkerType(e.target.name)}
                       >
                         Markdown
                       </button>
                       <button
                         type="button"
-                        name="growiLink"
-                        className={`btn btn-outline-secondary w-100 ${this.state.linkerType === 'growiLink' && 'active'}`}
+                        name={Linker.types.growiLink}
+                        className={`btn btn-outline-secondary w-100 ${this.state.linkerType === Linker.types.growiLink && 'active'}`}
                         onClick={e => this.handleSelecteLinkerType(e.target.name)}
                       >
                         Growi Original
@@ -223,8 +255,8 @@ class LinkEditModal extends React.PureComponent {
                       {this.isApplyPukiwikiLikeLinkerPlugin && (
                         <button
                           type="button"
-                          name="pukiwikiLink"
-                          className={`btn btn-outline-secondary w-100 ${this.state.linkerType === 'pukiwikiLink' && 'active'}`}
+                          name={Linker.types.pukiwikiLink}
+                          className={`btn btn-outline-secondary w-100 ${this.state.linkerType === Linker.types.pukiwikiLink && 'active'}`}
                           onClick={e => this.handleSelecteLinkerType(e.target.name)}
                         >
                           Pukiwiki
@@ -240,7 +272,7 @@ class LinkEditModal extends React.PureComponent {
                         id="label"
                         value={this.state.labelInputValue}
                         onChange={e => this.handleChangeLabelInput(e.target.value)}
-                        disabled={this.state.linkerType === 'growiLink'}
+                        disabled={this.state.linkerType === Linker.types.growiLink}
                       />
                     </div>
                     <div className="form-inline">
@@ -250,7 +282,7 @@ class LinkEditModal extends React.PureComponent {
                           id="relativePath"
                           type="checkbox"
                           checked={this.state.isUseRelativePath}
-                          disabled={this.state.linkerType === 'growiLink'}
+                          disabled={this.state.linkerType === Linker.types.growiLink}
                         />
                         <label className="custom-control-label" htmlFor="relativePath" onClick={this.toggleIsUseRelativePath}>
                           Use relative path

+ 17 - 9
src/client/js/components/PageEditor/MarkdownLinkUtil.js

@@ -1,25 +1,33 @@
+import Linker from '../../models/Linker';
+
 /**
  * Utility for markdown link
  */
 class MarkdownLinkUtil {
 
   constructor() {
-    // https://regex101.com/r/1UuWBJ/8
-    this.linePartOfLink = /(\[+(.*)+\]){1}(\(+(.*)+\)){1}/;
+    this.getMarkdownLink = this.getMarkdownLink.bind(this);
     this.isInLink = this.isInLink.bind(this);
+    this.replaceFocusedMarkdownLinkWithEditor = this.replaceFocusedMarkdownLinkWithEditor.bind(this);
   }
 
-  getSelectedTextInEditor(editor) {
-    return editor.getDoc().getSelection();
-  }
-
-  replaceFocusedMarkdownLinkWithEditor(editor, link) {
-    editor.getDoc().replaceSelection(link);
+  // return an instance of Linker from cursor position or selected text.
+  getMarkdownLink(editor) {
+    if (!this.isInLink(editor)) {
+      return Linker.fromMarkdownString(editor.getDoc().getSelection());
+    }
+    const curPos = editor.getCursor();
+    return Linker.fromLineWithIndex(editor.getDoc().getLine(curPos.line), curPos.ch);
   }
 
   isInLink(editor) {
     const curPos = editor.getCursor();
-    return this.linePartOfLink.test(editor.getDoc().getLine(curPos.line));
+    const { beginningOfLink, endOfLink } = Linker.getBeginningAndEndIndexOfLink(editor.getDoc().getLine(curPos.line), curPos.ch);
+    return beginningOfLink >= 0 && endOfLink >= 0;
+  }
+
+  replaceFocusedMarkdownLinkWithEditor(editor) {
+    // GW-3023
   }
 
 }

+ 112 - 0
src/client/js/models/Linker.js

@@ -0,0 +1,112 @@
+export default class Linker {
+
+  constructor(type, label, link) {
+    this.type = type;
+    this.label = label;
+    this.link = link;
+    // TODO GW-3074 相対パスを利用しているかの情報も持つようにする
+  }
+
+  static types = {
+    markdownLink: 'mdLink',
+    growiLink: 'growiLink',
+    pukiwikiLink: 'pukiwikiLink',
+  }
+
+  // create an instance of Linker from string
+  static fromMarkdownString(str) {
+    // if str doesn't mean a linker, create a link whose label is str
+    let label = str;
+    let link = '';
+    let type = this.types.markdownLink;
+
+    // pukiwiki
+    // https://regex101.com/r/2fNmUN/1
+    if (str.match(/^\[\[.*\]\]$/)) {
+      type = this.types.pukiwikiLink;
+      const value = str.slice(2, -2);
+      const indexOfSplit = value.lastIndexOf('>');
+      if (indexOfSplit < 0) {
+        label = value;
+        link = value;
+      }
+      else {
+        label = value.slice(0, indexOfSplit);
+        link = value.slice(indexOfSplit + 1);
+      }
+    }
+    // growi
+    // https://regex101.com/r/DJfkYf/1
+    else if (str.match(/^\[\/.*\]$/)) {
+      type = this.types.growiLink;
+      const value = str.slice(1, -1);
+      label = value;
+      link = value;
+    }
+    // markdown
+    // https://regex101.com/r/DZCKP3/1
+    else if (str.match(/^\[.*\]\(.*\)$/)) {
+      type = this.types.markdownLink;
+      const value = str.slice(1, -1);
+      const indexOfSplit = value.lastIndexOf('](');
+      label = value.slice(0, indexOfSplit);
+      link = value.slice(indexOfSplit + 2);
+    }
+
+    return new Linker(type, label, link);
+  }
+
+  // create an instance of Linker from text with index
+  static fromLineWithIndex(line, index) {
+    const { beginningOfLink, endOfLink } = this.getBeginningAndEndIndexOfLink(line, index);
+    // if index is in a link, extract it from line
+    let linkStr = '';
+    if (beginningOfLink >= 0 && endOfLink >= 0) {
+      linkStr = line.substring(beginningOfLink, endOfLink);
+    }
+    return this.fromMarkdownString(linkStr);
+  }
+
+  // return beginning and end indexies of link
+  // if index is not in a link, return { beginningOfLink: -1, endOfLink: -1 }
+  static getBeginningAndEndIndexOfLink(line, index) {
+    let beginningOfLink;
+    let endOfLink;
+
+    // pukiwiki link ('[[link]]')
+    [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[[', ']]');
+
+    // if index is not in a pukiwiki link
+    // growi link ('[/link]')
+    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
+      [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[/', ']');
+    }
+
+    // and if index is not in a growi link
+    // markdown link ('[label](link)')
+    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
+      [beginningOfLink, endOfLink] = this.getBeginningAndEndIndexWithPrefixAndSuffix(line, index, '[', ')', '](');
+    }
+
+    // and if index is not in a markdown link
+    // return { beginningOfLink: -1, endOfLink: -1 }
+    if (beginningOfLink < 0 || endOfLink < 0 || beginningOfLink > index || endOfLink < index) {
+      [beginningOfLink, endOfLink] = [-1, -1];
+    }
+
+    return { beginningOfLink, endOfLink };
+  }
+
+  // return begin and end indexies as array only when index is between prefix and suffix and link contains containText.
+  static getBeginningAndEndIndexWithPrefixAndSuffix(line, index, prefix, suffix, containText = '') {
+    const beginningIndex = line.lastIndexOf(prefix, index);
+    const IndexOfContainText = line.indexOf(containText, beginningIndex + prefix.length);
+    const endIndex = line.indexOf(suffix, IndexOfContainText + containText.length);
+
+    if (beginningIndex < 0 || IndexOfContainText < 0 || endIndex < 0) {
+      return [-1, -1];
+    }
+    return [beginningIndex, endIndex + suffix.length];
+  }
+
+}