Yuki Takei 6 лет назад
Родитель
Сommit
973be3e6bc

+ 8 - 0
packages/growi-plugin-attachment-refs/src/client-entry.js

@@ -1,2 +1,10 @@
+import PreRenderInterceptor from './client/js/util/Interceptor/PreRenderInterceptor';
+import PostRenderInterceptor from './client/js/util/Interceptor/PostRenderInterceptor';
+
 export default (appContainer) => {
+  // add interceptors
+  appContainer.interceptorManager.addInterceptors([
+    new PreRenderInterceptor(),
+    new PostRenderInterceptor(appContainer),
+  ]);
 };

+ 103 - 0
packages/growi-plugin-attachment-refs/src/client/js/components/AttachmentList.jsx

@@ -0,0 +1,103 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import * as url from 'url';
+
+import { pathUtils } from 'growi-commons';
+import RefsContext from '../util/RefsContext';
+import CacheHelper from '../util/CacheHelper';
+
+
+export default class AttachmentList extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      isLoading: true,
+      isError: false,
+      errorMessage: '',
+    };
+  }
+
+  // eslint-disable-next-line react/no-deprecated
+  componentWillMount() {
+    const pluginContext = this.props.refContext || this.props.refsContext;
+
+    // get state object cache
+    const stateCache = CacheHelper.getStateCache(pluginContext);
+
+    // check cache exists
+    if (stateCache != null) {
+      this.setState({
+        isLoading: false,
+        isError: stateCache.isError,
+        errorMessage: stateCache.errorMessage,
+      });
+      return; // go to render()
+    }
+
+    pluginContext.parse();
+
+    // // add slash ensure not to forward match to another page
+    // // ex: '/Java/' not to match to '/JavaScript'
+    // const pagePath = pathUtils.addTrailingSlash(pluginContext.pagePath);
+
+    // this.props.appContainer.apiGet('/plugins/lsx', { pagePath, options: pluginContext.options })
+    //   .then((res) => {
+    //     if (res.ok) {
+    //       const nodeTree = this.generatePageNodeTree(pagePath, res.pages);
+    //       this.setState({ nodeTree });
+    //     }
+    //     else {
+    //       return Promise.reject(res.error);
+    //     }
+    //   })
+    //   .catch((error) => {
+    //     this.setState({ isError: true, errorMessage: error.message });
+    //   })
+    //   // finally
+    //   .then(() => {
+    //     this.setState({ isLoading: false });
+
+    //     // store to sessionStorage
+    //     CacheHelper.cacheState(pluginContext, this.state);
+    //   });
+  }
+
+  // renderContents() {
+  //   const lsxContext = this.props.lsxContext;
+
+  //   if (this.state.isLoading) {
+  //     return (
+  //       <div className="text-muted">
+  //         <i className="fa fa-spinner fa-pulse mr-1"></i>
+  //         <span className="lsx-blink">{lsxContext.tagExpression}</span>
+  //       </div>
+  //     );
+  //   }
+  //   if (this.state.isError) {
+  //     return (
+  //       <div className="text-warning">
+  //         <i className="fa fa-exclamation-triangle fa-fw"></i>
+  //         {lsxContext.tagExpression} (-&gt; <small>{this.state.errorMessage}</small>)
+  //       </div>
+  //     );
+  //   }
+  //   // render tree
+
+  //   return <LsxListView nodeTree={this.state.nodeTree} lsxContext={this.props.lsxContext} />;
+
+  // }
+
+  render() {
+    return <div className="refs-attachment-list">AttachmentList</div>;
+  }
+
+}
+
+AttachmentList.propTypes = {
+  appContainer: PropTypes.object.isRequired,
+  refContext: PropTypes.instanceOf(PropTypes.instanceOf(Object)),
+  refsContext: PropTypes.instanceOf(PropTypes.instanceOf(RefsContext)),
+};

+ 65 - 0
packages/growi-plugin-attachment-refs/src/client/js/util/CacheHelper.js

@@ -0,0 +1,65 @@
+import { LocalStorageManager } from 'growi-commons';
+
+const REFS_STATE_CACHE_NS = 'refs-state-cache';
+
+export default class CacheHelper {
+
+  /**
+   * generate cache key for storing to storage
+   *
+   * @param {TagContext} tagContext
+   */
+  static generateCacheKey(tagContext) {
+    // return `${lsxContext.fromPagePath}__${lsxContext.args}`;
+    return `${tagContext.method}__${tagContext.args}`;
+  }
+
+  /**
+   *
+   *
+   * @static
+   * @param {TagContext} tagContext
+   * @returns
+   */
+  static getStateCache(tagContext) {
+    const localStorageManager = LocalStorageManager.getInstance();
+
+    const key = CacheHelper.generateCacheKey(tagContext);
+    const stateCache = localStorageManager.retrieveFromSessionStorage(REFS_STATE_CACHE_NS, key);
+
+    // if (stateCache != null && stateCache.nodeTree != null) {
+    //   // instanciate PageNode
+    //   stateCache.nodeTree = stateCache.nodeTree.map((obj) => {
+    //     return PageNode.instanciateFrom(obj);
+    //   });
+    // }
+
+    return stateCache;
+  }
+
+  /**
+   * store state object of React Component with specified key
+   *
+   * @static
+   * @param {TagContext} tagContext
+   * @param {object} state state object of React Component
+   */
+  static cacheState(tagContext, state) {
+    const localStorageManager = LocalStorageManager.getInstance();
+    const key = CacheHelper.generateCacheKey(tagContext);
+    localStorageManager.saveToSessionStorage(REFS_STATE_CACHE_NS, key, state);
+  }
+
+  /**
+   * clear all state caches
+   *
+   * @static
+   *
+   * @memberOf LsxCacheHelper
+   */
+  static clearAllStateCaches() {
+    const localStorageManager = LocalStorageManager.getInstance();
+    localStorageManager.saveToSessionStorage(REFS_STATE_CACHE_NS, {});
+  }
+
+}

+ 62 - 0
packages/growi-plugin-attachment-refs/src/client/js/util/Interceptor/PostRenderInterceptor.js

@@ -0,0 +1,62 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import { BasicInterceptor } from 'growi-commons';
+
+import RefsContext from '../RefsContext';
+import AttachmentList from '../../components/AttachmentList';
+
+/**
+ * The interceptor for refs
+ *
+ *  render React DOM
+ */
+export default class PostRenderInterceptor extends BasicInterceptor {
+
+  constructor(appContainer) {
+    super();
+    this.appContainer = appContainer;
+  }
+
+  /**
+   * @inheritdoc
+   */
+  isInterceptWhen(contextName) {
+    return (
+      contextName === 'postRenderHtml'
+      || contextName === 'postRenderPreviewHtml'
+    );
+  }
+
+  /**
+   * @inheritdoc
+   */
+  process(contextName, ...args) {
+    const context = Object.assign(args[0]); // clone
+
+    // forEach keys of tagContextMap
+    Object.keys(context.tagContextMap).forEach((domId) => {
+      const elem = document.getElementById(domId);
+
+      if (elem) {
+        // get TagContext instance from context
+        const tagContext = context.tagContextMap[domId] || {};
+        // create RefsContext instance
+        const refsContext = new RefsContext(tagContext);
+        refsContext.fromPagePath = context.currentPagePath;
+
+        this.renderReactDOM(refsContext, elem);
+      }
+    });
+
+    return Promise.resolve();
+  }
+
+  renderReactDOM(refsContext, elem) {
+    ReactDOM.render(
+      <AttachmentList appContainer={this.appContainer} refsContext={refsContext} />,
+      elem,
+    );
+  }
+
+}

+ 54 - 0
packages/growi-plugin-attachment-refs/src/client/js/util/Interceptor/PreRenderInterceptor.js

@@ -0,0 +1,54 @@
+import { customTagUtils, BasicInterceptor } from 'growi-commons';
+
+import CacheHelper from '../CacheHelper';
+
+/**
+ * The interceptor for refs
+ *
+ *  replace refs tag to a React target element
+ */
+export default class PreRenderInterceptor extends BasicInterceptor {
+
+  /**
+   * @inheritdoc
+   */
+  isInterceptWhen(contextName) {
+    return (
+      contextName === 'preRenderHtml'
+      || contextName === 'preRenderPreviewHtml'
+    );
+  }
+
+  /**
+   * @inheritdoc
+   */
+  async process(contextName, ...args) {
+    const context = Object.assign(args[0]); // clone
+    const parsedHTML = context.parsedHTML;
+    this.initializeCache(contextName);
+
+    context.lsxContextMap = {};
+
+    const tagPattern = /ref|refs|refimg|refsimg/;
+    const result = customTagUtils.findTagAndReplace(tagPattern, parsedHTML);
+
+    context.parsedHTML = result.html;
+    context.tagContextMap = result.tagContextMap;
+
+    return context;
+  }
+
+  /**
+   * initialize cache
+   *  when contextName is 'preRenderHtml'         -> clear cache
+   *  when contextName is 'preRenderPreviewHtml'  -> doesn't clear cache
+   *
+   * @param {string} contextName
+   */
+  initializeCache(contextName) {
+    if (contextName === 'preRenderHtml') {
+      CacheHelper.clearAllStateCaches();
+    }
+  }
+
+}

+ 64 - 0
packages/growi-plugin-attachment-refs/src/client/js/util/RefsContext.js

@@ -0,0 +1,64 @@
+import * as url from 'url';
+
+import { customTagUtils, pathUtils } from 'growi-commons';
+
+const { TagContext, ArgsParser, OptionParser } = customTagUtils;
+
+/**
+ * Context Object class for $refs() and $refsimg()
+ */
+export default class RefsContext extends TagContext {
+
+  /**
+   * @param {object|TagContext|RefsContext} initArgs
+   */
+  constructor(initArgs) {
+    super(initArgs);
+
+    this.fromPagePath = null;
+
+    // initialized after parse()
+    this.isParsed = null;
+    this.pagePath = null;
+    this.options = {};
+  }
+
+  parse() {
+    if (this.isParsed) {
+      return;
+    }
+
+    const parsedResult = ArgsParser.parse(this.args);
+    this.options = parsedResult.options;
+
+    // determine specifiedPath
+    // order:
+    //   1: refs(prefix=..., ...)
+    //   2: refs(firstArgs, ...)
+    //   3: fromPagePath
+    const specifiedPath = this.options.prefix
+        || ((parsedResult.firstArgsValue === true) ? parsedResult.firstArgsKey : undefined)
+        || this.fromPagePath;
+
+    // resolve pagePath
+    //   when `fromPagePath`=/hoge and `specifiedPath`=./fuga,
+    //        `pagePath` to be /hoge/fuga
+    //   when `fromPagePath`=/hoge and `specifiedPath`=/fuga,
+    //        `pagePath` to be /fuga
+    //   when `fromPagePath`=/hoge and `specifiedPath`=undefined,
+    //        `pagePath` to be /hoge
+    this.pagePath = (specifiedPath !== undefined)
+      ? decodeURIComponent(url.resolve(pathUtils.addTrailingSlash(this.fromPagePath), specifiedPath))
+      : this.fromPagePath;
+
+    this.isParsed = true;
+  }
+
+  getOptDepth() {
+    if (this.options.depth === undefined) {
+      return undefined;
+    }
+    return OptionParser.parseRange(this.options.depth);
+  }
+
+}

+ 0 - 0
packages/growi-plugin-attachment-refs/src/client/js/util/preprocessor/attachment-refs.js