Просмотр исходного кода

refs 120355: make plugin attachment reffs runnable

Futa Arai 3 лет назад
Родитель
Сommit
6f261df922

+ 4 - 0
packages/core/src/plugin/index.ts

@@ -1,2 +1,6 @@
 export * from './interfaces/option-parser';
 export * from './util/option-parser';
+export * from './service/tag-cache-manager';
+export * from './util/custom-tag-utils';
+export * from './util/args-parser';
+export * from './model/tag-context';

+ 12 - 0
packages/core/src/plugin/model/tag-context.js

@@ -0,0 +1,12 @@
+/**
+ * Context class for custom-tag-utils#findTagAndReplace
+ */
+export class TagContext {
+
+  constructor(initArgs = {}) {
+    this.tagExpression = initArgs.tagExpression || null;
+    this.method = initArgs.method || null;
+    this.args = initArgs.args || null;
+  }
+
+}

+ 69 - 0
packages/core/src/plugin/service/tag-cache-manager.js

@@ -0,0 +1,69 @@
+import { LocalStorageManager } from '../../service/localstorage-manager';
+
+/**
+ * Service Class for caching React state and TagContext
+ */
+export class TagCacheManager {
+
+  /**
+   * @callback generateCacheKey
+   * @param {TagContext} tagContext - TagContext instance
+   * @returns {string} Cache key from TagContext
+   *
+   */
+
+  /**
+   * Constructor
+   * @param {string} cacheNs Used as LocalStorageManager namespace
+   * @param {generateCacheKey} generateCacheKey
+   */
+  constructor(cacheNs, generateCacheKey) {
+    if (cacheNs == null) {
+      throw new Error('args \'cacheNs\' is required.');
+    }
+    if (generateCacheKey == null) {
+      throw new Error('args \'generateCacheKey\' is required.');
+    }
+    if (typeof generateCacheKey !== 'function') {
+      throw new Error('args \'generateCacheKey\' should be function.');
+    }
+
+    this.cacheNs = cacheNs;
+    this.generateCacheKey = generateCacheKey;
+  }
+
+  /**
+   * Retrieve state cache object from local storage
+   * @param {TagContext} tagContext
+   * @returns {object} a cache object that correspont to the specified `tagContext`
+   */
+  getStateCache(tagContext) {
+    const localStorageManager = LocalStorageManager.getInstance();
+
+    const key = this.generateCacheKey(tagContext);
+    const stateCache = localStorageManager.retrieveFromSessionStorage(this.cacheNs, key);
+
+    return stateCache;
+  }
+
+  /**
+   * store state object of React Component with specified key
+   *
+   * @param {TagContext} tagContext
+   * @param {object} state state object of React Component
+   */
+  cacheState(tagContext, state) {
+    const localStorageManager = LocalStorageManager.getInstance();
+    const key = this.generateCacheKey(tagContext);
+    localStorageManager.saveToSessionStorage(this.cacheNs, key, state);
+  }
+
+  /**
+   * clear all state caches
+   */
+  clearAllStateCaches() {
+    const localStorageManager = LocalStorageManager.getInstance();
+    localStorageManager.clearAllStateCaches(this.cacheNs);
+  }
+
+}

+ 57 - 0
packages/core/src/plugin/util/args-parser.js

@@ -0,0 +1,57 @@
+/**
+ * Arguments parser for custom tag
+ */
+export class ArgsParser {
+
+  /**
+   * @typedef ParseArgsResult
+   * @property {string} firstArgsKey - key of the first argument
+   * @property {string} firstArgsValue - value of the first argument
+   * @property {object} options - key of the first argument
+   */
+
+  /**
+   * parse plugin argument strings
+   *
+   * @static
+   * @param {string} str
+   * @returns {ParseArgsResult}
+   */
+  static parse(str) {
+    let firstArgsKey = null;
+    let firstArgsValue = null;
+    const options = {};
+
+    if (str != null && str.length > 0) {
+      const splittedArgs = str.split(',');
+
+      splittedArgs.forEach((rawArg, index) => {
+        const arg = rawArg.trim();
+
+        // parse string like 'key1=value1, key2=value2, ...'
+        // see https://regex101.com/r/pYHcOM/1
+        const match = arg.match(/([^=]+)=?(.+)?/);
+
+        if (match == null) {
+          return;
+        }
+
+        const key = match[1];
+        const value = match[2] || true;
+        options[key] = value;
+
+        if (index === 0) {
+          firstArgsKey = key;
+          firstArgsValue = value;
+        }
+      });
+    }
+
+    return {
+      firstArgsKey,
+      firstArgsValue,
+      options,
+    };
+  }
+
+}

+ 66 - 0
packages/core/src/plugin/util/custom-tag-utils.js

@@ -0,0 +1,66 @@
+import { TagContext } from '../model/tag-context';
+
+/**
+ * @private
+ *
+ * create random strings
+ * @see http://qiita.com/ryounagaoka/items/4736c225bdd86a74d59c
+ *
+ * @param {number} length
+ * @return {string} random strings
+ */
+function createRandomStr(length) {
+  const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
+  let generated = '';
+  for (let i = 0; i < length; i++) {
+    generated += bag[Math.floor(Math.random() * bag.length)];
+  }
+  return generated;
+}
+
+/**
+ * @typedef FindTagAndReplaceResult
+ * @property {string} html - HTML string
+ * @property {Object} tagContextMap - Object.<string, [TagContext]{@link ../model/tag-context.html#TagContext}>
+ *
+ * @memberof customTagUtils
+ */
+/**
+ * @param {RegExp} tagPattern
+ * @param {string} html
+ * @param {function} replace replace function
+ * @return {FindTagAndReplaceResult}
+ *
+ * @memberof customTagUtils
+ */
+export function findTagAndReplace(tagPattern, html, replace) {
+  let replacedHtml = html;
+  const tagContextMap = {};
+
+  if (tagPattern == null || html == null) {
+    return { html: replacedHtml, tagContextMap };
+  }
+
+  // see: https://regex101.com/r/NQq3s9/9
+  const pattern = new RegExp(`\\$(${tagPattern.source})\\((.*?)\\)(?=[<\\[\\s\\$])|\\$(${tagPattern.source})\\((.*)\\)(?![<\\[\\s\\$])`, 'g');
+
+  replacedHtml = html.replace(pattern, (all, group1, group2, group3, group4) => {
+    const tagExpression = all;
+    const method = (group1 || group3).trim();
+    const args = (group2 || group4 || '').trim();
+
+    // create contexts
+    const tagContext = new TagContext({ tagExpression, method, args });
+
+    if (replace != null) {
+      return replace(tagContext);
+    }
+
+    // replace with empty dom
+    const domId = `${method}-${createRandomStr(8)}`;
+    tagContextMap[domId] = tagContext;
+    return `<div id="${domId}"></div>`;
+  });
+
+  return { html: replacedHtml, tagContextMap };
+}

+ 2 - 1
packages/plugin-attachment-refs/package.json

@@ -30,7 +30,8 @@
     "http-errors": "^2.0.0",
     "react-images": "~1.0.0",
     "react-motion": "^0.5.2",
-    "universal-bunyan": "^0.9.2"
+    "universal-bunyan": "^0.9.2",
+    "@growi/core": "^6.1.0-RC.0"
   },
   "devDependencies": {
     "eslint-plugin-regex": "^1.8.0",

+ 2 - 2
packages/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPreRenderInterceptor.js

@@ -1,4 +1,4 @@
-import { customTagUtils, BasicInterceptor } from '@growi/core';
+import { findTagAndReplace, BasicInterceptor } from '@growi/core';
 
 import TagCacheManagerFactory from '../TagCacheManagerFactory';
 
@@ -35,7 +35,7 @@ export default class RefsPreRenderInterceptor extends BasicInterceptor {
     this.initializeCache(contextName);
 
     const tagPattern = /ref|refs|refimg|refsimg|gallery/;
-    const result = customTagUtils.findTagAndReplace(tagPattern, parsedHTML);
+    const result = findTagAndReplace(tagPattern, parsedHTML);
 
     context.parsedHTML = result.html;
     context.tagContextMap = result.tagContextMap;

+ 3 - 3
packages/plugin-attachment-refs/src/client/js/util/RefsContext.js

@@ -1,8 +1,8 @@
 import * as url from 'url';
 
-import { customTagUtils, pathUtils } from '@growi/core';
-
-const { TagContext, ArgsParser, OptionParser } = customTagUtils;
+import {
+  TagContext, ArgsParser, OptionParser, pathUtils,
+} from '@growi/core';
 
 const GRID_DEFAULT_TRACK_WIDTH = 64;
 const GRID_AVAILABLE_OPTIONS_LIST = [

+ 1 - 3
packages/plugin-attachment-refs/tsconfig.json

@@ -6,9 +6,7 @@
     "baseUrl": ".",
     "paths": {
       "~/*": ["./src/*"]
-      // "@growi/*": ["../*/src"]
-    },
-    "rootDir": "./src"
+    }
   },
   "include": [
     "src"

+ 13 - 0
turbo.json

@@ -19,6 +19,11 @@
       "cache": false
     },
 
+    "@growi/plugin-attachment-refs#build": {
+      "dependsOn": ["@growi/core#build"],
+      "outputs": ["dist/**"],
+      "outputMode": "new-only"
+    },
     "@growi/ui#build": {
       "dependsOn": ["@growi/core#build"],
       "outputs": ["dist/**"],
@@ -58,6 +63,11 @@
       "outputMode": "new-only"
     },
 
+    "@growi/plugin-attachment-refs#dev": {
+      "dependsOn": ["@growi/core#dev", "@growi/ui#dev"],
+      "outputs": ["dist/**"],
+      "outputMode": "new-only"
+    },
     "@growi/remark-lsx#dev": {
       "dependsOn": ["@growi/core#dev", "@growi/remark-growi-directive#dev", "@growi/ui#dev"],
       "outputs": ["dist/**"],
@@ -122,6 +132,9 @@
       "persistent": true
     },
 
+    "@growi/plugin-attachment-refs#lint": {
+      "dependsOn": ["@growi/core#lint"]
+    },
     "@growi/ui#lint": {
       "dependsOn": ["@growi/core#dev"]
     },