import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
import * as toastr from 'toastr';
import io from 'socket.io-client';
import i18nFactory from './i18n';
import loggerFactory from '@alias/logger';
import Xss from '@commons/service/xss';
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 { EditorOptions, PreviewOptions } from './components/PageEditor/OptionsSelector';
import SavePageControls from './components/SavePageControls';
import PageEditorByHackmd from './components/PageEditorByHackmd';
import Page from './components/Page';
import PageListSearch from './components/PageListSearch';
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 SeenUserList from './components/SeenUserList';
import RevisionPath from './components/Page/RevisionPath';
import RevisionUrl from './components/Page/RevisionUrl';
import BookmarkButton from './components/BookmarkButton';
import NewPageNameInput from './components/NewPageNameInput';
import RecentCreated from './components/RecentCreated/RecentCreated';
import CustomCssEditor from './components/Admin/CustomCssEditor';
import CustomScriptEditor from './components/Admin/CustomScriptEditor';
import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
import * as entities from 'entities';
const logger = loggerFactory('growi:app');
if (!window) {
window = {};
}
const userlang = $('body').data('userlang');
const i18n = i18nFactory(userlang);
const socket = io();
// setup xss library
const xss = new Xss();
window.xss = xss;
const mainContent = document.querySelector('#content-main');
let pageId = null;
let pageRevisionId = null;
let pageRevisionCreatedAt = null;
let pageRevisionIdHackmdSynced = null;
let hasDraftOnHackmd = false;
let pageIdOnHackmd = null;
let pagePath;
let pageContent = '';
let markdown = '';
let slackChannels;
if (mainContent !== null) {
pageId = mainContent.getAttribute('data-page-id');
pageRevisionId = mainContent.getAttribute('data-page-revision-id');
pageRevisionCreatedAt = +mainContent.getAttribute('data-page-revision-created');
pageRevisionIdHackmdSynced = mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null;
pageIdOnHackmd = mainContent.getAttribute('data-page-id-on-hackmd') || null;
hasDraftOnHackmd = !!mainContent.getAttribute('data-page-has-draft-on-hackmd');
pagePath = mainContent.attributes['data-path'].value;
slackChannels = mainContent.getAttribute('data-slack-channels') || '';
const rawText = document.getElementById('raw-text-original');
if (rawText) {
pageContent = rawText.innerHTML;
}
markdown = entities.decodeHTML(pageContent);
}
const isLoggedin = document.querySelector('.main-container.nologin') == null;
// FIXME
const crowi = new Crowi({
me: $('body').data('current-username'),
isAdmin: $('body').data('is-admin'),
csrfToken: $('body').data('csrftoken'),
}, window);
window.crowi = crowi;
crowi.setConfig(JSON.parse(document.getElementById('crowi-context-hydrate').textContent || '{}'));
if (isLoggedin) {
crowi.fetchUsers();
}
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
});
window.crowiRenderer = crowiRenderer;
// FIXME
const isEnabledPlugins = $('body').data('plugin-enabled');
if (isEnabledPlugins) {
const crowiPlugin = window.crowiPlugin;
crowiPlugin.installAll(crowi, crowiRenderer);
}
// setup renderer after plugins are installed
crowiRenderer.setup();
// restore draft when the first time to edit
const draft = crowi.findDraft(pagePath);
if (!pageRevisionId && draft != null) {
markdown = draft;
}
/**
* define components
* key: id of element
* value: React Element
*/
const componentMappings = {
'search-top': ,
'search-sidebar': ,
'search-page': ,
'page-list-search': ,
//'revision-history': ,
'seen-user-list': ,
'bookmark-button': ,
'bookmark-button-lg': ,
'page-name-input': ,
};
// additional definitions if data exists
if (pageId) {
componentMappings['page-comments-list'] = ;
componentMappings['page-attachment'] = ;
}
if (pagePath) {
componentMappings['page'] = ;
componentMappings['revision-path'] = ;
componentMappings['revision-url'] = ;
}
let componentInstances = {};
Object.keys(componentMappings).forEach((key) => {
const elem = document.getElementById(key);
if (elem) {
componentInstances[key] = ReactDOM.render(componentMappings[key], elem);
}
});
/**
* save success handler when reloading is not needed
* @param {object} page Page instance
*/
const saveWithShortcutSuccessHandler = function(page) {
const editorMode = crowi.getCrowiForJquery().getCurrentEditorMode();
// show toastr
toastr.success(undefined, 'Saved successfully', {
closeButton: true,
progressBar: true,
newestOnTop: false,
showDuration: '100',
hideDuration: '100',
timeOut: '1200',
extendedTimeOut: '150',
});
pageId = page._id;
pageRevisionId = page.revision._id;
pageRevisionIdHackmdSynced = page.revisionHackmdSynced;
// set page id to SavePageControls
componentInstances.savePageControls.setPageId(pageId);
// Page component
if (componentInstances.page != null) {
componentInstances.page.setMarkdown(page.revision.body);
}
// PageEditor component
if (componentInstances.pageEditor != null) {
const updateEditorValue = (editorMode !== 'builtin');
componentInstances.pageEditor.setMarkdown(page.revision.body, updateEditorValue);
}
// PageEditorByHackmd component
if (componentInstances.pageEditorByHackmd != null) {
// clear state of PageEditorByHackmd
componentInstances.pageEditorByHackmd.clearRevisionStatus(pageRevisionId, pageRevisionIdHackmdSynced);
// reset
if (editorMode !== 'hackmd') {
componentInstances.pageEditorByHackmd.setMarkdown(page.revision.body, false);
componentInstances.pageEditorByHackmd.reset();
}
}
// PageStatusAlert component
const pageStatusAlert = componentInstances.pageStatusAlert;
// clear state of PageStatusAlert
if (componentInstances.pageStatusAlert != null) {
pageStatusAlert.clearRevisionStatus(pageRevisionId, pageRevisionIdHackmdSynced);
}
// hidden input
$('input[name="revision_id"]').val(pageRevisionId);
};
const errorHandler = function(error) {
toastr.error(error.message, 'Error occured', {
closeButton: true,
progressBar: true,
newestOnTop: false,
showDuration: '100',
hideDuration: '100',
timeOut: '3000',
});
};
const saveWithShortcut = function(markdown) {
const editorMode = crowi.getCrowiForJquery().getCurrentEditorMode();
if (editorMode == null) {
// do nothing
return;
}
let revisionId = pageRevisionId;
// get options
const options = componentInstances.savePageControls.getCurrentOptionsToSave();
options.socketClientId = socketClientId;
if (editorMode === 'hackmd') {
// set option to sync
options.isSyncRevisionToHackmd = true;
// use revisionId of PageEditorByHackmd
revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
}
let promise = undefined;
if (pageId == null) {
promise = crowi.createPage(pagePath, markdown, options);
}
else {
promise = crowi.updatePage(pageId, revisionId, markdown, options);
}
promise
.then(saveWithShortcutSuccessHandler)
.catch(errorHandler);
};
const saveWithSubmitButtonSuccessHandler = function() {
crowi.clearDraft(pagePath);
location.href = pagePath;
};
const saveWithSubmitButton = function() {
const editorMode = crowi.getCrowiForJquery().getCurrentEditorMode();
if (editorMode == null) {
// do nothing
return;
}
let revisionId = pageRevisionId;
// get options
const options = componentInstances.savePageControls.getCurrentOptionsToSave();
options.socketClientId = socketClientId;
let promise = undefined;
if (editorMode === 'builtin') {
// get markdown
promise = Promise.resolve(componentInstances.pageEditor.getMarkdown());
}
else {
// get markdown
promise = componentInstances.pageEditorByHackmd.getMarkdown();
// use revisionId of PageEditorByHackmd
revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
// set option to sync
options.isSyncRevisionToHackmd = true;
}
// create or update
if (pageId == null) {
promise = promise.then(markdown => {
return crowi.createPage(pagePath, markdown, options);
});
}
else {
promise = promise.then(markdown => {
return crowi.updatePage(pageId, revisionId, markdown, options);
});
}
promise
.then(saveWithSubmitButtonSuccessHandler)
.catch(errorHandler);
};
// render SavePageControls
let savePageControls = null;
const savePageControlsElem = document.getElementById('save-page-controls');
if (savePageControlsElem) {
const grant = +savePageControlsElem.dataset.grant;
const grantGroupId = savePageControlsElem.dataset.grantGroup;
const grantGroupName = savePageControlsElem.dataset.grantGroupName;
ReactDOM.render(
{
if (savePageControls == null) {
savePageControls = elem.getWrappedInstance();
}
}}
pageId={pageId} pagePath={pagePath} slackChannels={slackChannels}
grant={grant} grantGroupId={grantGroupId} grantGroupName={grantGroupName} />
,
savePageControlsElem
);
componentInstances.savePageControls = savePageControls;
}
// RecentCreated dev GC-939 start
const recentCreatedControlsElem = document.getElementById('user-created-list');
if (recentCreatedControlsElem) {
let limit = crowi.getConfig().recentCreatedLimit;
if (null == limit) {
limit = 10;
}
ReactDOM.render(
, document.getElementById('user-created-list')
);
}
// RecentCreated dev GC-939 end
/*
* HackMD Editor
*/
// render PageEditorWithHackmd
let pageEditorByHackmd = null;
const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
if (pageEditorWithHackmdElem) {
pageEditorByHackmd = ReactDOM.render(
,
pageEditorWithHackmdElem
);
componentInstances.pageEditorByHackmd = pageEditorByHackmd;
}
/*
* PageEditor
*/
let pageEditor = null;
const editorOptions = new EditorOptions(crowi.editorOptions);
const previewOptions = new PreviewOptions(crowi.previewOptions);
// render PageEditor
const pageEditorElem = document.getElementById('page-editor');
if (pageEditorElem) {
pageEditor = ReactDOM.render(
,
pageEditorElem
);
componentInstances.pageEditor = pageEditor;
// set refs for setCaretLine/forceToFocus when tab is changed
crowi.setPageEditor(pageEditor);
}
// render comment form
const writeCommentElem = document.getElementById('page-comment-write');
if (writeCommentElem) {
const pageCommentsElem = componentInstances['page-comments-list'];
const postCompleteHandler = (comment) => {
if (pageCommentsElem != null) {
pageCommentsElem.retrieveData();
}
};
ReactDOM.render(
,
writeCommentElem);
}
// render OptionsSelector
const pageEditorOptionsSelectorElem = document.getElementById('page-editor-options-selector');
if (pageEditorOptionsSelectorElem) {
ReactDOM.render(
{ // set onChange event handler
// set options
pageEditor.setEditorOptions(newEditorOptions);
pageEditor.setPreviewOptions(newPreviewOptions);
// save
crowi.saveEditorOptions(newEditorOptions);
crowi.savePreviewOptions(newPreviewOptions);
}} />,
pageEditorOptionsSelectorElem
);
}
// render PageStatusAlert
let pageStatusAlert = null;
const pageStatusAlertElem = document.getElementById('page-status-alert');
if (pageStatusAlertElem) {
ReactDOM.render(
{
if (pageStatusAlert == null) {
pageStatusAlert = elem.getWrappedInstance();
}
}}
revisionId={pageRevisionId} revisionIdHackmdSynced={pageRevisionIdHackmdSynced} hasDraftOnHackmd={hasDraftOnHackmd} />
,
pageStatusAlertElem
);
componentInstances.pageStatusAlert = pageStatusAlert;
}
// render for admin
const customCssEditorElem = document.getElementById('custom-css-editor');
if (customCssEditorElem != null) {
// get input[type=hidden] element
const customCssInputElem = document.getElementById('inputCustomCss');
ReactDOM.render(
,
customCssEditorElem
);
}
const customScriptEditorElem = document.getElementById('custom-script-editor');
if (customScriptEditorElem != null) {
// get input[type=hidden] element
const customScriptInputElem = document.getElementById('inputCustomScript');
ReactDOM.render(
,
customScriptEditorElem
);
}
const customHeaderEditorElem = document.getElementById('custom-header-editor');
if (customHeaderEditorElem != null) {
// get input[type=hidden] element
const customHeaderInputElem = document.getElementById('inputCustomHeader');
ReactDOM.render(
,
customHeaderEditorElem
);
}
// notification from websocket
function updatePageStatusAlert(page, user) {
const pageStatusAlert = componentInstances.pageStatusAlert;
if (pageStatusAlert != null) {
const revisionId = page.revision._id;
const revisionIdHackmdSynced = page.revisionHackmdSynced;
pageStatusAlert.setRevisionId(revisionId, revisionIdHackmdSynced);
pageStatusAlert.setLastUpdateUsername(user.name);
}
}
socket.on('page:create', function(data) {
// skip if triggered myself
if (data.socketClientId != null && data.socketClientId === socketClientId) {
return;
}
logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
// update PageStatusAlert
if (data.page.path == pagePath) {
updatePageStatusAlert(data.page, data.user);
}
});
socket.on('page:update', function(data) {
// skip if triggered myself
if (data.socketClientId != null && data.socketClientId === socketClientId) {
return;
}
logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
if (data.page.path == pagePath) {
// update PageStatusAlert
updatePageStatusAlert(data.page, data.user);
// update PageEditorByHackmd
const pageEditorByHackmd = componentInstances.pageEditorByHackmd;
if (pageEditorByHackmd != null) {
const page = data.page;
pageEditorByHackmd.setRevisionId(page.revision._id, page.revisionHackmdSynced);
pageEditorByHackmd.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
}
}
});
socket.on('page:delete', function(data) {
// skip if triggered myself
if (data.socketClientId != null && data.socketClientId === socketClientId) {
return;
}
logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
// update PageStatusAlert
if (data.page.path == pagePath) {
updatePageStatusAlert(data.page, data.user);
}
});
socket.on('page:editingWithHackmd', function(data) {
// skip if triggered myself
if (data.socketClientId != null && data.socketClientId === socketClientId) {
return;
}
logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
if (data.page.path == pagePath) {
// update PageStatusAlert
const pageStatusAlert = componentInstances.pageStatusAlert;
if (pageStatusAlert != null) {
pageStatusAlert.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
}
// update PageEditorByHackmd
const pageEditorByHackmd = componentInstances.pageEditorByHackmd;
if (pageEditorByHackmd != null) {
pageEditorByHackmd.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
}
}
});
// うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
$('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
ReactDOM.render(, document.getElementById('revision-history'));
});