import React from 'react'; import PropTypes from 'prop-types'; import { Modal, ModalHeader, ModalBody, ModalFooter, } from 'reactstrap'; import { debounce } from 'throttle-debounce'; import path from 'path'; import validator from 'validator'; import Preview from './Preview'; import AppContainer from '../../services/AppContainer'; import PageContainer from '../../services/PageContainer'; import SearchTypeahead from '../SearchTypeahead'; import Linker from '../../models/Linker'; import { withUnstatedContainers } from '../UnstatedUtils'; class LinkEditModal extends React.PureComponent { constructor(props) { super(props); this.state = { show: false, isUseRelativePath: false, isUsePermanentLink: false, linkInputValue: '', labelInputValue: '', linkerType: Linker.types.markdownLink, markdown: '', permalink: '', linkText: '', }; this.isApplyPukiwikiLikeLinkerPlugin = window.growiRenderer.preProcessors.some(process => process.constructor.name === 'PukiwikiLikeLinker'); this.show = this.show.bind(this); this.hide = this.hide.bind(this); this.cancel = this.cancel.bind(this); this.handleChangeTypeahead = this.handleChangeTypeahead.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); this.save = this.save.bind(this); this.generateLink = this.generateLink.bind(this); this.renderPreview = this.renderPreview.bind(this); this.getRootPath = this.getRootPath.bind(this); this.generateAndSetPreviewDebounced = debounce(200, this.generateAndSetPreview.bind(this)); this.generateAndSetLinkTextPreviewDebounced = debounce(200, this.generateAndSetLinkTextPreview.bind(this)); } componentDidUpdate(prevProps, prevState) { const { linkInputValue: prevLinkInputValue } = prevState; const { linkInputValue } = this.state; if (linkInputValue !== prevLinkInputValue) { this.generateAndSetPreviewDebounced(linkInputValue); } this.generateAndSetLinkTextPreviewDebounced(); } // 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.parseLinkAndSetState(link, type); this.setState({ show: true, labelInputValue: label, isUsePermanentLink: false, permalink: '', linkerType: type, }); } // parse link, link is ... // case-1. url of this growi's page (ex. 'http://localhost:3000/hoge/fuga') // case-2. absolute path of this growi's page (ex. '/hoge/fuga') // case-3. relative path of this growi's page (ex. '../fuga', 'hoge') // case-4. external link (ex. 'https://growi.org') // case-5. the others (ex. '') parseLinkAndSetState(link, type) { // create url from link, add dummy origin if link is not valid url. // ex-1. link = 'https://growi.org/' -> url = 'https://growi.org/' (case-1,4) // ex-2. link = 'hoge' -> url = 'http://example.com/hoge' (case-2,3,5) const url = new URL(link, 'http://example.com'); const isUrl = url.origin !== 'http://example.com'; let isUseRelativePath = false; let reshapedLink = link; // if case-1, reshapedLink becomes page path reshapedLink = this.convertUrlToPathIfPageUrl(reshapedLink, url); // case-3 if (!isUrl && !reshapedLink.startsWith('/') && reshapedLink !== '') { isUseRelativePath = true; const rootPath = this.getRootPath(type); reshapedLink = path.resolve(rootPath, reshapedLink); } this.setState({ linkInputValue: reshapedLink, isUseRelativePath, }); } // return path name of link if link is this growi page url, else return original link. convertUrlToPathIfPageUrl(link, url) { // when link is this growi's page url, url.origin === window.location.origin and return path name return url.origin === window.location.origin ? decodeURI(url.pathname) : link; } cancel() { this.hide(); } hide() { this.setState({ show: false, }); } toggleIsUseRelativePath() { if (!this.state.linkInputValue.startsWith('/') || this.state.linkerType === Linker.types.growiLink) { return; } // User can't use both relativePath and permalink at the same time this.setState({ isUseRelativePath: !this.state.isUseRelativePath, isUsePermanentLink: false }); } toggleIsUsePamanentLink() { if (this.state.permalink === '' || this.state.linkerType === Linker.types.growiLink) { return; } // User can't use both relativePath and permalink at the same time this.setState({ isUsePermanentLink: !this.state.isUsePermanentLink, isUseRelativePath: false }); } renderPreview() { return (