From b0e5f7e33566890fa3cf61831b5e8b2a788650c6 Mon Sep 17 00:00:00 2001 From: Artem Tarasov Date: Wed, 16 Feb 2022 12:41:12 +0300 Subject: [PATCH] Web: Doceditor: added Editor.js --- web/ASC.Web.Editor/src/Editor.js | 664 +++++++++++++++++++++++++++++++ 1 file changed, 664 insertions(+) create mode 100644 web/ASC.Web.Editor/src/Editor.js diff --git a/web/ASC.Web.Editor/src/Editor.js b/web/ASC.Web.Editor/src/Editor.js new file mode 100644 index 0000000000..1ba50a7378 --- /dev/null +++ b/web/ASC.Web.Editor/src/Editor.js @@ -0,0 +1,664 @@ +import React, { useState, useEffect } from "react"; +import { isMobile } from "react-device-detect"; +import FilesFilter from "@appserver/common/api/files/filter"; +import combineUrl from "@appserver/common/utils/combineUrl"; +import { AppServerConfig } from "@appserver/common/constants"; +import { homepage } from "../package.json"; +import throttle from "lodash/throttle"; +import Loader from "@appserver/components/loader"; + +import { + restoreDocumentsVersion, + markAsFavorite, + removeFromFavorite, + getEditDiff, + getEditHistory, + updateFile, +} from "@appserver/common/api/files"; + +import DynamicComponent from "../components/dynamic"; + +const LoaderComponent = ( + +); + +// TODO: i18n + +const onSDKInfo = (event) => { + console.log( + "ONLYOFFICE Document Editor is opened in mode " + event.data.mode + ); +}; + +const onSDKRequestEditRights = async () => { + console.log("ONLYOFFICE Document Editor requests editing rights"); + // const index = url.indexOf("&action=view"); + + // if (index) { + // let convertUrl = url.substring(0, index); + + // if (canConvert(fileInfo.fileExst)) { + // convertUrl = await convertDocumentUrl(); + // } + + // history.pushState({}, null, convertUrl); + // document.location.reload(); + // } +}; + +const onSDKWarning = (event) => { + console.log( + "ONLYOFFICE Document Editor reports a warning: code " + + event.data.warningCode + + ", description " + + event.data.warningDescription + ); +}; + +const onSDKError = (event) => { + console.log( + "ONLYOFFICE Document Editor reports an error: code " + + event.data.errorCode + + ", description " + + event.data.errorDescription + ); +}; + +const initDesktop = (cfg) => { + const encryptionKeys = cfg?.editorConfig?.encryptionKeys; + + regDesktop( + user, + !!encryptionKeys, + encryptionKeys, + (keys) => { + setEncryptionKeys(keys); + }, + true, + (callback) => { + getEncryptionAccess(fileId) + .then((keys) => { + var data = { + keys, + }; + + callback(data); + }) + .catch((error) => { + console.log(error); + // toastr.error( + // typeof error === "string" ? error : error.message, + // null, + // 0, + // true + // ); + }); + }, + i18n.t + ); +}; + +const text = "text"; +const presentation = "presentation"; +const insertImageAction = "imageFileType"; +let documentIsReady = false; // move to state? +let docSaved = null; // move to state? +let docTitle = null; +let docEditor; + +// const SharingDialog = +// typeof window === "undefined" ? null : dynamic(() => loadComponent()); + +export default function Editor({ + fileInfo, + docApiUrl, + config, + personal, + successAuth, + isSharingAccess, + user, + url, + doc, + fileId, + actionLink, + error, + needLoader, +}) { + const [titleSelectorFolder, setTitleSelectorFolder] = useState(""); + const [urlSelectorFolder, setUrlSelectorFolder] = useState(""); + const [extension, setExtension] = useState(); + const [isFolderDialogVisible, setIsFolderDialogVisible] = useState(false); + const [typeInsertImageAction, setTypeInsertImageAction] = useState(); + const [filesType, setFilesType] = useState(""); + const [isFileDialogVisible, setIsFileDialogVisible] = useState(false); // ?? + const [isVisible, setIsVisible] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + const [documentTitle, setNewDocumentTitle] = useState("Loading..."); + const [faviconHref, setFaviconHref] = useState("/favicon.ico"); // try without state + + useEffect(() => { + console.log("useEffect error catch"); + + if (error) { + error?.unAuthorized && + error?.redirectPath && + (window.location.href = error?.redirectPath); + } + }, []); + + useEffect(() => { + console.log("useEffect config", config, docApiUrl); + if (config) { + console.log("useEffect meta change", config); + loadScript(docApiUrl, "scripDocServiceAddress", () => onLoad()); + setFavicon(config?.documentType); + setDocumentTitle(config?.document?.title); + } + }, []); + + const loadScript = (url, id, onLoad, onError) => { + try { + const script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.setAttribute("id", id); + + if (onLoad) script.onload = onLoad; + if (onError) script.onerror = onError; + + script.src = url; + script.async = true; + + document.body.appendChild(script); + } catch (e) { + console.error(e); + } + }; + // const { t } = useTranslation("Editor"); + + const getDefaultFileName = (format) => { + switch (format) { + case "docx": + return 't("NewDocument")'; + case "xlsx": + return 't("NewSpreadsheet")'; + case "pptx": + return 't("NewPresentation")'; + case "docxf": + return 't("NewMasterForm")'; + default: + return 't("NewFolder")'; + } + }; + + const setFavicon = (documentType) => { + const favicon = document.getElementById("favicon"); + if (!favicon) return; + let icon = null; + switch (documentType) { + case "text": + icon = "text.ico"; + break; + case "presentation": + icon = "presentation.ico"; + break; + case "spreadsheet": + icon = "spreadsheet.ico"; + break; + default: + break; + } + if (icon) setFaviconHref(`${homepage}/images/${icon}`); + }; + + const throttledChangeTitle = throttle(() => changeTitle(), 500); + + const onSDKRequestHistoryClose = () => { + document.location.reload(); + }; + + const onSDKRequestEditRights = async () => { + console.log("ONLYOFFICE Document Editor requests editing rights"); + const index = url.indexOf("&action=view"); + + if (index) { + let convertUrl = url.substring(0, index); + + // if (canConvert(fileInfo.fileExst)) { + // convertUrl = await convertDocumentUrl(); + // } // TODO: need move can canConvert from docsevicestore + + history.pushState({}, null, convertUrl); + document.location.reload(); + } + }; + + const onMakeActionLink = (event) => { + const actionData = event.data; + + const link = generateLink(actionData); + + const urlFormation = !actionLink ? url : url.split("&anchor=")[0]; + + const linkFormation = `${urlFormation}&anchor=${link}`; + + docEditor.setActionLink(linkFormation); + }; + + const generateLink = (actionData) => { + return encodeURIComponent(JSON.stringify(actionData)); + }; + + const onSDKRequestRename = (event) => { + const title = event.data; + updateFile(fileInfo.id, title); + }; + + const onSDKRequestSharingSettings = () => { + setIsVisible(true); + }; + + const onSDKRequestRestore = async (event) => { + const restoreVersion = event.data.version; + try { + const updateVersions = await restoreDocumentsVersion( + fileId, + restoreVersion, + doc + ); + const historyLength = updateVersions.length; + docEditor.refreshHistory({ + currentVersion: getCurrentDocumentVersion( + updateVersions, + historyLength + ), + history: getDocumentHistory(updateVersions, historyLength), + }); + } catch (e) { + docEditor.refreshHistory({ + error: `${e}`, //TODO: maybe need to display something else. + }); + } + }; + + const onSDKRequestCompareFile = () => { + setFilesType(compareFilesAction); + setIsFileDialogVisible(true); + }; + + const onSDKRequestMailMergeRecipients = () => { + setFilesType(mailMergeAction); + setIsFileDialogVisible(true); + }; + + const onSDKRequestInsertImage = (event) => { + setTypeInsertImageAction(event.data); + setFilesType(insertImageAction); + setIsFileDialogVisible(true); + }; + + const getDocumentHistory = (fileHistory, historyLength) => { + let result = []; + + for (let i = 0; i < historyLength; i++) { + const changes = fileHistory[i].changes; + const serverVersion = fileHistory[i].serverVersion; + const version = fileHistory[i].version; + const versionGroup = fileHistory[i].versionGroup; + + let obj = { + ...(changes.length !== 0 && { changes }), + created: `${new Date(fileHistory[i].created).toLocaleString( + config.editorConfig.lang + )}`, + ...(serverVersion && { serverVersion }), + key: fileHistory[i].key, + user: { + id: fileHistory[i].user.id, + name: fileHistory[i].user.name, + }, + version, + versionGroup, + }; + + result.push(obj); + } + return result; + }; // +++ + + const getCurrentDocumentVersion = (fileHistory, historyLength) => { + return url.indexOf("&version=") !== -1 + ? +url.split("&version=")[1] + : fileHistory[historyLength - 1].version; + }; // +++ + + const onSDKRequestHistory = async () => { + try { + const fileHistory = await getEditHistory(fileId, doc); + const historyLength = fileHistory.length; + + docEditor.refreshHistory({ + currentVersion: getCurrentDocumentVersion(fileHistory, historyLength), + history: getDocumentHistory(fileHistory, historyLength), + }); + } catch (e) { + docEditor.refreshHistory({ + error: `${e}`, //TODO: maybe need to display something else. + }); + } + }; // +++ + + const onSDKRequestHistoryData = async (event) => { + const version = event.data; + + try { + const versionDifference = await getEditDiff(fileId, version, doc); + const changesUrl = versionDifference.changesUrl; + const previous = versionDifference.previous; + const token = versionDifference.token; + + docEditor.setHistoryData({ + ...(changesUrl && { changesUrl }), + key: versionDifference.key, + fileType: versionDifference.fileType, + ...(previous && { + previous: { + fileType: previous.fileType, + key: previous.key, + url: previous.url, + }, + }), + ...(token && { token }), + url: versionDifference.url, + version, + }); + } catch (e) { + docEditor.setHistoryData({ + error: `${e}`, //TODO: maybe need to display something else. + version, + }); + } + }; // +++ + + const loadUsersRightsList = () => { + console.log("loadUsersRightsList", SharingDialog.getSharingSettings); + // SharingDialog?.getSharingSettings(fileId).then((sharingSettings) => { + // console.log("sharingSettings", sharingSettings); + // docEditor.setSharingSettings({ + // sharingSettings, + // }); + // }); + //TODO: + }; + + const onDocumentReady = () => { + documentIsReady = true; + + if (isSharingAccess) { + loadUsersRightsList(); + } + }; + + const updateFavorite = (favorite) => { + docEditor.setFavorite(favorite); + }; //+++ + + const onMetaChange = (event) => { + const newTitle = event.data.title; + const favorite = event.data.favorite; + + if (newTitle && newTitle !== docTitle) { + setDocumentTitle(newTitle); + docTitle = newTitle; + } + + if (!newTitle) { + const onlyNumbers = new RegExp("^[0-9]+$"); + const isFileWithoutProvider = onlyNumbers.test(fileId); + + const convertFileId = isFileWithoutProvider ? +fileId : fileId; + + favorite + ? markAsFavorite([convertFileId]) + .then(() => updateFavorite(favorite)) + .catch((error) => console.log("error", error)) + : removeFromFavorite([convertFileId]) + .then(() => updateFavorite(favorite)) + .catch((error) => console.log("error", error)); + } + }; // +++ + + const setDocumentTitle = (subTitle = null) => { + //const { isAuthenticated, settingsStore, product: currentModule } = auth; + //const { organizationName } = settingsStore; + const organizationName = "ONLYOFFICE"; //TODO: Replace to API variant + const moduleTitle = "Documents"; //TODO: Replace to API variant + + let title; + if (subTitle) { + if (successAuth && moduleTitle) { + title = subTitle + " - " + moduleTitle; + } else { + title = subTitle + " - " + organizationName; + } + } else if (moduleTitle && organizationName) { + title = moduleTitle + " - " + organizationName; + } else { + title = organizationName; + } + console.log("setDocumentTitle", title); + document.title = title; + setNewDocumentTitle(title); + }; //+++ + + const changeTitle = () => { + docSaved ? setDocumentTitle(docTitle) : setDocumentTitle(`*${docTitle}`); + }; // +++ + + const onDocumentStateChange = (event) => { + if (!documentIsReady) return; + + docSaved = !event.data; + throttledChangeTitle(); + }; //+++ + + const onSDKRequestSaveAs = (event) => { + setTitleSelectorFolder(event.data.title); + setUrlSelectorFolder(event.data.url); + setExtension(event.data.title.split(".").pop()); + setIsFolderDialogVisible(true); + }; // +++ + + const onSDKAppReady = () => { + console.log("ONLYOFFICE Document Editor is ready"); + + const index = url.indexOf("#message/"); + if (index > -1) { + const splitUrl = url.split("#message/"); + const message = decodeURIComponent(splitUrl[1]).replaceAll("+", " "); + history.pushState({}, null, url.substring(0, index)); + docEditor.showMessage(message); + } + + // const tempElm = document.getElementById("loader"); + // if (tempElm) { + // tempElm.outerHTML = ""; + // } not need to ssr + }; // +++ + + const onLoad = () => { + try { + if (!window.DocsAPI) throw new Error("DocsAPI is not defined"); + + console.log("Editor config: ", config); + + if (isMobile) { + config.type = "mobile"; + } + + let goBack; + const url = window.location.href; + + if (fileInfo) { + const filterObj = FilesFilter.getDefault(); + filterObj.folder = fileInfo.folderId; + const urlFilter = filterObj.toUrlParams(); + + const filesUrl = url.substring(0, url.indexOf("/doceditor")); + + goBack = { + blank: true, + requestClose: false, + text: 't("FileLocation")', + url: `${combineUrl(filesUrl, `/filter?${urlFilter}`)}`, + }; + } + + config.editorConfig.customization = { + ...config.editorConfig.customization, + goback: goBack, + }; + + if (personal && !fileInfo) { + //TODO: add conditions for SaaS + config.document.info.favorite = null; + } + + //let url = window.location.href; + if (url.indexOf("anchor") !== -1) { + const splitUrl = url.split("anchor="); + const decodeURI = decodeURIComponent(splitUrl[1]); + const obj = JSON.parse(decodeURI); + + config.editorConfig.actionLink = { + action: obj.action, + }; + } + + if (successAuth) { + const documentType = config.documentType; + const fileExt = + documentType === text + ? "docx" + : documentType === presentation + ? "pptx" + : "xlsx"; + + const defaultFileName = getDefaultFileName(fileExt); + + if (!user.isVisitor) + config.editorConfig.createUrl = combineUrl( + window.location.origin, + AppServerConfig.proxyURL, + "products/files/", + `/httphandlers/filehandler.ashx?action=create&doctype=text&title=${encodeURIComponent( + defaultFileName + )}` + ); + } + + let onRequestSharingSettings, + onRequestRename, + onRequestSaveAs, + onRequestInsertImage, + onRequestMailMergeRecipients, + onRequestCompareFile, + onRequestRestore; + + if (isSharingAccess) { + onRequestSharingSettings = onSDKRequestSharingSettings; // +++ + } + + if (fileInfo && fileInfo.canEdit) { + onRequestRename = onSDKRequestRename; // +++ + } + + if (successAuth) { + onRequestSaveAs = onSDKRequestSaveAs; //+++ + onRequestInsertImage = onSDKRequestInsertImage; // +++ + onRequestMailMergeRecipients = onSDKRequestMailMergeRecipients; // +++ + onRequestCompareFile = onSDKRequestCompareFile; // +++ + } + + if (!!config.document.permissions.changeHistory) { + onRequestRestore = onSDKRequestRestore; // +++ + } + const events = { + events: { + onAppReady: onSDKAppReady, // +++ + onDocumentStateChange: onDocumentStateChange, // +++ + onMetaChange: onMetaChange, // +++ + onDocumentReady: onDocumentReady, // ++- + onInfo: onSDKInfo, // +++ + onWarning: onSDKWarning, // +++ + onError: onSDKError, // +++ + onRequestSharingSettings, // +++ + onRequestRename, // +++ + onMakeActionLink: onMakeActionLink, // +++ + onRequestInsertImage, //+++ + onRequestSaveAs, // +++ + onRequestMailMergeRecipients, // +++ + onRequestCompareFile, // +++ + onRequestEditRights: onSDKRequestEditRights, // // TODO: need move can canConvert from docsevicestore + onRequestHistory: onSDKRequestHistory, // +++ + onRequestHistoryClose: onSDKRequestHistoryClose, // +++ + onRequestHistoryData: onSDKRequestHistoryData, // +++ + onRequestRestore, // +++ + }, + }; + + const newConfig = Object.assign(config, events); + + docEditor = window.DocsAPI.DocEditor("editor", newConfig); + console.log("docEditor", docEditor); + setIsLoaded(true); + } catch (error) { + console.log(error, "init error"); + //toastr.error(error.message, null, 0, true); + } + }; + const onCancel = () => { + setIsVisible(false); + }; + + //console.log(SharingDialog); + + return ( + <> + {/* + {documentTitle} + + */} +
+ {needLoader ? ( + LoaderComponent + ) : ( + <> +
+ {!isLoaded && LoaderComponent} + + )} +
+ {isVisible && ( + + )} + + ); +}