Bläddra i källkod

apply to src/client/js

Yuki Takei 7 år sedan
förälder
incheckning
2a836fbe7c

+ 125 - 87
src/client/js/app.js

@@ -1,45 +1,50 @@
+/* eslint-disable max-len */
+
 import React from 'react';
 import ReactDOM from 'react-dom';
 import { I18nextProvider } from 'react-i18next';
 import * as toastr from 'toastr';
 
-import i18nFactory from './i18n';
-
 import loggerFactory from '@alias/logger';
 import Xss from '@commons/service/xss';
+import * as entities from 'entities';
+import i18nFactory from './i18n';
+
 
 import Crowi from './util/Crowi';
 // import CrowiRenderer from './util/CrowiRenderer';
 import GrowiRenderer from './util/GrowiRenderer';
 
-import HeaderSearchBox  from './components/HeaderSearchBox';
-import SearchPage       from './components/SearchPage';
-import PageEditor       from './components/PageEditor';
-import OptionsSelector  from './components/PageEditor/OptionsSelector';
+import HeaderSearchBox from './components/HeaderSearchBox';
+import SearchPage from './components/SearchPage';
+import PageEditor from './components/PageEditor';
+// eslint-disable-next-line import/no-duplicates
+import OptionsSelector from './components/PageEditor/OptionsSelector';
+// eslint-disable-next-line import/no-duplicates
 import { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector';
 import SavePageControls from './components/SavePageControls';
 import PageEditorByHackmd from './components/PageEditorByHackmd';
-import Page             from './components/Page';
-import PageHistory      from './components/PageHistory';
-import PageComments     from './components/PageComments';
+import Page from './components/Page';
+import PageHistory from './components/PageHistory';
+import PageComments from './components/PageComments';
 import CommentForm from './components/PageComment/CommentForm';
-import PageAttachment   from './components/PageAttachment';
-import PageStatusAlert  from './components/PageStatusAlert';
-import RevisionPath     from './components/Page/RevisionPath';
-import PageTagForm      from './components/PageTagForm';
-import RevisionUrl      from './components/Page/RevisionUrl';
-import BookmarkButton   from './components/BookmarkButton';
-import LikeButton       from './components/LikeButton';
+import PageAttachment from './components/PageAttachment';
+import PageStatusAlert from './components/PageStatusAlert';
+import RevisionPath from './components/Page/RevisionPath';
+// TODO GC-1430 activate
+// import PageTagForm from './components/PageTagForm';
+import RevisionUrl from './components/Page/RevisionUrl';
+import BookmarkButton from './components/BookmarkButton';
+import LikeButton from './components/LikeButton';
 import PagePathAutoComplete from './components/PagePathAutoComplete';
 import RecentCreated from './components/RecentCreated/RecentCreated';
-import UserPictureList  from './components/Common/UserPictureList';
+import UserPictureList from './components/Common/UserPictureList';
 
-import CustomCssEditor  from './components/Admin/CustomCssEditor';
+import CustomCssEditor from './components/Admin/CustomCssEditor';
 import CustomScriptEditor from './components/Admin/CustomScriptEditor';
 import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
 
-import * as entities from 'entities';
 
 const logger = loggerFactory('growi:app');
 
@@ -65,8 +70,9 @@ let pagePath;
 let pageContent = '';
 let markdown = '';
 let slackChannels;
-let currentPageTags = '';
-let newPageTags = '';
+// TODO GC-1430 activate
+// const currentPageTags = '';
+// let newPageTags = '';
 if (mainContent !== null) {
   pageId = mainContent.getAttribute('data-page-id') || null;
   pageRevisionId = mainContent.getAttribute('data-page-revision-id');
@@ -100,8 +106,8 @@ const socketClientId = crowi.getSocketClientId();
 
 const crowiRenderer = new GrowiRenderer(crowi, null, {
   mode: 'page',
-  isAutoSetup: false,                                     // manually setup because plugins may configure it
-  renderToc: crowi.getCrowiForJquery().renderTocContent,  // function for rendering Table Of Contents
+  isAutoSetup: false, // manually setup because plugins may configure it
+  renderToc: crowi.getCrowiForJquery().renderTocContent, // function for rendering Table Of Contents
 });
 window.crowiRenderer = crowiRenderer;
 
@@ -116,14 +122,15 @@ if (isEnabledPlugins) {
  * get new tags from page tag form
  * @param {String} tags new tags [TODO] String -> Array
  */
-const getNewPageTags = function(tags) {
-  newPageTags = tags;
-};
+// TODO GC-1430 activate
+// const getNewPageTags = function(tags) {
+//   newPageTags = tags;
+// };
 
 /**
  * component store
  */
-let componentInstances = {};
+const componentInstances = {};
 
 /**
  * save success handler when reloading is not needed
@@ -198,7 +205,8 @@ const saveWithShortcut = function(markdown) {
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
-  options.pageTags = newPageTags;
+  // TODO GC-1430 activate
+  // options.pageTags = newPageTags;
 
   if (editorMode === 'hackmd') {
     // set option to sync
@@ -207,7 +215,7 @@ const saveWithShortcut = function(markdown) {
     revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
   }
 
-  let promise = undefined;
+  let promise;
   if (pageId == null) {
     promise = crowi.createPage(pagePath, markdown, options);
   }
@@ -222,7 +230,7 @@ const saveWithShortcut = function(markdown) {
 
 const saveWithSubmitButtonSuccessHandler = function() {
   crowi.clearDraft(pagePath);
-  location.href = pagePath;
+  window.location.href = pagePath;
 };
 
 const saveWithSubmitButton = function(submitOpts) {
@@ -236,12 +244,13 @@ const saveWithSubmitButton = function(submitOpts) {
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
-  options.pageTags = newPageTags;
+  // TODO GC-1430 activate
+  // options.pageTags = newPageTags;
 
   // set 'submitOpts.overwriteScopesOfDescendants' to options
   options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
 
-  let promise = undefined;
+  let promise;
   if (editorMode === 'hackmd') {
     // get markdown
     promise = componentInstances.pageEditorByHackmd.getMarkdown();
@@ -256,12 +265,12 @@ const saveWithSubmitButton = function(submitOpts) {
   }
   // create or update
   if (pageId == null) {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.createPage(pagePath, markdown, options);
     });
   }
   else {
-    promise = promise.then(markdown => {
+    promise = promise.then((markdown) => {
       return crowi.updatePage(pageId, revisionId, markdown, options);
     });
   }
@@ -290,11 +299,11 @@ const componentMappings = {
   'search-sidebar': <HeaderSearchBox crowi={crowi} />,
   'search-page': <I18nextProvider i18n={i18n}><SearchPage crowi={crowi} crowiRenderer={crowiRenderer} /></I18nextProvider>,
 
-  //'revision-history': <PageHistory pageId={pageId} />,
+  // 'revision-history': <PageHistory pageId={pageId} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
   'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
 
-  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash={true} />,
+  'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash />,
   'rename-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
   'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
 
@@ -305,7 +314,7 @@ if (pageId) {
   componentMappings['page-attachment'] = <PageAttachment pageId={pageId} markdown={markdown} crowi={crowi} />;
 }
 if (pagePath) {
-  componentMappings['page'] = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} showHeadEditButton={true} onSaveWithShortcut={saveWithShortcut} />;
+  componentMappings.page = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} showHeadEditButton onSaveWithShortcut={saveWithShortcut} />;
   componentMappings['revision-path'] = <RevisionPath pagePath={pagePath} crowi={crowi} />;
   // componentMappings['page-tag'] = <PageTagForm pageTags={currentPageTags} submitTags={getNewPageTags} />; [pagetag]
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
@@ -319,8 +328,8 @@ Object.keys(componentMappings).forEach((key) => {
 });
 
 // set page if exists
-if (componentInstances['page'] != null) {
-  crowi.setPage(componentInstances['page']);
+if (componentInstances.page != null) {
+  crowi.setPage(componentInstances.page);
 }
 
 // render LikeButton
@@ -329,7 +338,7 @@ if (likeButtonElem) {
   const isLiked = likeButtonElem.dataset.liked === 'true';
   ReactDOM.render(
     <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
-    likeButtonElem
+    likeButtonElem,
   );
 }
 
@@ -340,7 +349,7 @@ if (seenUserListElem) {
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    seenUserListElem
+    seenUserListElem,
   );
 }
 // render UserPictureList for liker-list
@@ -350,7 +359,7 @@ if (likerListElem) {
   const userIds = userIdsStr.split(',');
   ReactDOM.render(
     <UserPictureList crowi={crowi} userIds={userIds} />,
-    likerListElem
+    likerListElem,
   );
 }
 
@@ -363,16 +372,23 @@ if (savePageControlsElem) {
   const grantGroupName = savePageControlsElem.dataset.grantGroupName;
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <SavePageControls crowi={crowi} onSubmit={saveWithSubmitButton}
-          ref={(elem) => {
+      <SavePageControls
+        crowi={crowi}
+        onSubmit={saveWithSubmitButton}
+        ref={(elem) => {
             if (savePageControls == null) {
               savePageControls = elem.getWrappedInstance();
             }
           }}
-          pageId={pageId} pagePath={pagePath} slackChannels={slackChannels}
-          grant={grant} grantGroupId={grantGroupId} grantGroupName={grantGroupName} />
+        pageId={pageId}
+        pagePath={pagePath}
+        slackChannels={slackChannels}
+        grant={grant}
+        grantGroupId={grantGroupId}
+        grantGroupName={grantGroupName}
+      />
     </I18nextProvider>,
-    savePageControlsElem
+    savePageControlsElem,
   );
   componentInstances.savePageControls = savePageControls;
 }
@@ -380,13 +396,13 @@ if (savePageControlsElem) {
 const recentCreatedControlsElem = document.getElementById('user-created-list');
 if (recentCreatedControlsElem) {
   let limit = crowi.getConfig().recentCreatedLimit;
-  if (null == limit) {
+  if (limit == null) {
     limit = 10;
   }
   ReactDOM.render(
-    <RecentCreated  crowi={crowi} pageId={pageId} limit={limit} >
+    <RecentCreated crowi={crowi} pageId={pageId} limit={limit}>
 
-    </RecentCreated>, document.getElementById('user-created-list')
+    </RecentCreated>, document.getElementById('user-created-list'),
   );
 }
 
@@ -398,12 +414,17 @@ let pageEditorByHackmd = null;
 const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
 if (pageEditorWithHackmdElem) {
   pageEditorByHackmd = ReactDOM.render(
-    <PageEditorByHackmd crowi={crowi}
-        pageId={pageId} revisionId={pageRevisionId}
-        pageIdOnHackmd={pageIdOnHackmd} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd}
-        markdown={markdown}
-        onSaveWithShortcut={saveWithShortcut} />,
-    pageEditorWithHackmdElem
+    <PageEditorByHackmd
+      crowi={crowi}
+      pageId={pageId}
+      revisionId={pageRevisionId}
+      pageIdOnHackmd={pageIdOnHackmd}
+      revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
+      hasDraftOnHackmd={hasDraftOnHackmd}
+      markdown={markdown}
+      onSaveWithShortcut={saveWithShortcut}
+    />,
+    pageEditorWithHackmdElem,
   );
   componentInstances.pageEditorByHackmd = pageEditorByHackmd;
 }
@@ -419,12 +440,18 @@ const previewOptions = new PreviewOptions(crowi.previewOptions);
 const pageEditorElem = document.getElementById('page-editor');
 if (pageEditorElem) {
   pageEditor = ReactDOM.render(
-    <PageEditor crowi={crowi} crowiRenderer={crowiRenderer}
-        pageId={pageId} revisionId={pageRevisionId} pagePath={pagePath}
-        markdown={markdown}
-        editorOptions={editorOptions} previewOptions={previewOptions}
-        onSaveWithShortcut={saveWithShortcut} />,
-    pageEditorElem
+    <PageEditor
+      crowi={crowi}
+      crowiRenderer={crowiRenderer}
+      pageId={pageId}
+      revisionId={pageRevisionId}
+      pagePath={pagePath}
+      markdown={markdown}
+      editorOptions={editorOptions}
+      previewOptions={previewOptions}
+      onSaveWithShortcut={saveWithShortcut}
+    />,
+    pageEditorElem,
   );
   componentInstances.pageEditor = pageEditor;
   // set refs for setCaretLine/forceToFocus when tab is changed
@@ -441,15 +468,18 @@ if (writeCommentElem) {
     }
   };
   ReactDOM.render(
-    <CommentForm crowi={crowi}
+    <CommentForm
+      crowi={crowi}
       crowiOriginRenderer={crowiRenderer}
       pageId={pageId}
       pagePath={pagePath}
       revisionId={pageRevisionId}
       onPostComplete={postCompleteHandler}
       editorOptions={editorOptions}
-      slackChannels = {slackChannels}/>,
-    writeCommentElem);
+      slackChannels={slackChannels}
+    />,
+    writeCommentElem,
+  );
 }
 
 // render OptionsSelector
@@ -457,8 +487,10 @@ const pageEditorOptionsSelectorElem = document.getElementById('page-editor-optio
 if (pageEditorOptionsSelectorElem) {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <OptionsSelector crowi={crowi}
-        editorOptions={editorOptions} previewOptions={previewOptions}
+      <OptionsSelector
+        crowi={crowi}
+        editorOptions={editorOptions}
+        previewOptions={previewOptions}
         onChange={(newEditorOptions, newPreviewOptions) => { // set onChange event handler
           // set options
           pageEditor.setEditorOptions(newEditorOptions);
@@ -466,9 +498,10 @@ if (pageEditorOptionsSelectorElem) {
           // save
           crowi.saveEditorOptions(newEditorOptions);
           crowi.savePreviewOptions(newPreviewOptions);
-        }} />
+        }}
+      />
     </I18nextProvider>,
-    pageEditorOptionsSelectorElem
+    pageEditorOptionsSelectorElem,
   );
 }
 
@@ -478,15 +511,19 @@ const pageStatusAlertElem = document.getElementById('page-status-alert');
 if (pageStatusAlertElem) {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <PageStatusAlert crowi={crowi}
-          ref={(elem) => {
+      <PageStatusAlert
+        crowi={crowi}
+        ref={(elem) => {
             if (pageStatusAlert == null) {
               pageStatusAlert = elem.getWrappedInstance();
             }
           }}
-          revisionId={pageRevisionId} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd} />
+        revisionId={pageRevisionId}
+        revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
+        hasDraftOnHackmd={hasDraftOnHackmd}
+      />
     </I18nextProvider>,
-    pageStatusAlertElem
+    pageStatusAlertElem,
   );
   componentInstances.pageStatusAlert = pageStatusAlert;
 }
@@ -499,7 +536,7 @@ if (customCssEditorElem != null) {
 
   ReactDOM.render(
     <CustomCssEditor inputElem={customCssInputElem} />,
-    customCssEditorElem
+    customCssEditorElem,
   );
 }
 const customScriptEditorElem = document.getElementById('custom-script-editor');
@@ -509,7 +546,7 @@ if (customScriptEditorElem != null) {
 
   ReactDOM.render(
     <CustomScriptEditor inputElem={customScriptInputElem} />,
-    customScriptEditorElem
+    customScriptEditorElem,
   );
 }
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
@@ -519,14 +556,14 @@ if (customHeaderEditorElem != null) {
 
   ReactDOM.render(
     <CustomHeaderEditor inputElem={customHeaderInputElem} />,
-    customHeaderEditorElem
+    customHeaderEditorElem,
   );
 }
 const adminRebuildSearchElem = document.getElementById('admin-rebuild-search');
 if (adminRebuildSearchElem != null) {
   ReactDOM.render(
     <AdminRebuildSearch crowi={crowi} />,
-    adminRebuildSearchElem
+    adminRebuildSearchElem,
   );
 }
 
@@ -540,7 +577,7 @@ function updatePageStatusAlert(page, user) {
     pageStatusAlert.setLastUpdateUsername(user.name);
   }
 }
-socket.on('page:create', function(data) {
+socket.on('page:create', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -549,11 +586,11 @@ socket.on('page:create', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
 
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
   }
 });
-socket.on('page:update', function(data) {
+socket.on('page:update', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -561,7 +598,7 @@ socket.on('page:update', function(data) {
 
   logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     updatePageStatusAlert(data.page, data.user);
     // update PageEditorByHackmd
@@ -573,7 +610,7 @@ socket.on('page:update', function(data) {
     }
   }
 });
-socket.on('page:delete', function(data) {
+socket.on('page:delete', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -582,11 +619,11 @@ socket.on('page:delete', function(data) {
   logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
 
   // update PageStatusAlert
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     updatePageStatusAlert(data.page, data.user);
   }
 });
-socket.on('page:editingWithHackmd', function(data) {
+socket.on('page:editingWithHackmd', (data) => {
   // skip if triggered myself
   if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
@@ -594,7 +631,7 @@ socket.on('page:editingWithHackmd', function(data) {
 
   logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
 
-  if (data.page.path == pagePath) {
+  if (data.page.path === pagePath) {
     // update PageStatusAlert
     const pageStatusAlert = componentInstances.pageStatusAlert;
     if (pageStatusAlert != null) {
@@ -609,9 +646,10 @@ socket.on('page:editingWithHackmd', function(data) {
 });
 
 // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
-$('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
+$('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
       <PageHistory pageId={pageId} crowi={crowi} />
-    </I18nextProvider>, document.getElementById('revision-history'));
+    </I18nextProvider>, document.getElementById('revision-history'),
+  );
 });

+ 20 - 19
src/client/js/hackmd-agent.js

@@ -16,7 +16,7 @@ import { debounce } from 'throttle-debounce';
 
 /* eslint-disable no-console  */
 
-const allowedOrigin = '{{origin}}';         // will be replaced by swig
+const allowedOrigin = '{{origin}}'; // will be replaced by swig
 
 
 /**
@@ -47,14 +47,14 @@ function setValueToCodemirrorOnInit(newValue) {
     setValueToCodemirror(newValue);
     return;
   }
-  else {
-    const intervalId = setInterval(() => {
-      if (window.cmClient != null) {
-        clearInterval(intervalId);
-        setValueToCodemirror(newValue);
-      }
-    }, 250);
-  }
+
+  const intervalId = setInterval(() => {
+    if (window.cmClient != null) {
+      clearInterval(intervalId);
+      setValueToCodemirror(newValue);
+    }
+  }, 250);
+
 }
 
 /**
@@ -86,7 +86,7 @@ function addEventListenersToCodemirror() {
     return;
   }
 
-  //// change event
+  // == change event
   editor.on('change', (cm, change) => {
     if (change.origin === 'ignoreHistory') {
       // do nothing because this operation triggered by other user
@@ -95,7 +95,7 @@ function addEventListenersToCodemirror() {
     debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
   });
 
-  //// save event
+  // == save event
   // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
   codemirror.commands.save = function(cm) {
     postParentToSaveWithShortcut(cm.doc.getValue());
@@ -117,14 +117,16 @@ function connectToParentWithPenpal() {
       },
       setValueOnInit(newValue) {
         setValueToCodemirrorOnInit(newValue);
-      }
-    }
-  });
-  connection.promise.then(parent => {
-    window.growi = parent;
-  }).catch(err => {
-    console.log(err);
+      },
+    },
   });
+  connection.promise
+    .then((parent) => {
+      window.growi = parent;
+    })
+    .catch((err) => {
+      console.log(err);
+    });
 }
 
 /**
@@ -148,4 +150,3 @@ function connectToParentWithPenpal() {
 
   console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
 }());
-

+ 1 - 1
src/client/js/hackmd-styles.js

@@ -12,7 +12,7 @@
 
 /* eslint-disable no-console  */
 
-const styles = '{{styles}}';         // will be replaced by swig
+const styles = '{{styles}}'; // will be replaced by swig
 
 /**
  * Insert link tag to load style file

+ 2 - 2
src/client/js/i18n.js

@@ -39,7 +39,7 @@ export default (userlang) => {
         withRef: true,
         bindI18n: 'languageChanged loaded',
         bindStore: 'added removed',
-        nsMode: 'default'
-      }
+        nsMode: 'default',
+      },
     });
 };

+ 1 - 1
src/client/js/installer.js

@@ -19,6 +19,6 @@ if (installerFormElem) {
     <I18nextProvider i18n={i18n}>
       <InstallerForm userName={userName} name={name} email={email} csrf={csrf} />
     </I18nextProvider>,
-    installerFormElem
+    installerFormElem,
   );
 }

+ 1 - 1
src/client/js/plugin.js

@@ -39,4 +39,4 @@ export default class CrowiPlugin {
 
 }
 
-window.crowiPlugin = new CrowiPlugin();     // FIXME
+window.crowiPlugin = new CrowiPlugin(); // FIXME