diff --git a/.gitignore b/.gitignore index 71584e76bb5..28fabc00b96 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ out/ .nyc_output .idea /package-lock.json -/src/bin/abiword.exe /src/bin/convertSettings.json /src/bin/etherpad-1.deb /src/bin/node.exe diff --git a/Dockerfile b/Dockerfile index 2ad2936fb0d..6ee07c2c644 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,15 +62,7 @@ ARG ETHERPAD_LOCAL_PLUGINS= # ETHERPAD_GITHUB_PLUGINS="ether/ep_plugin" ARG ETHERPAD_GITHUB_PLUGINS= -# Control whether abiword will be installed, enabling exports to DOC/PDF/ODT formats. -# By default, it is not installed. -# If given any value, abiword will be installed. -# -# EXAMPLE: -# INSTALL_ABIWORD=true -ARG INSTALL_ABIWORD= - -# Control whether libreoffice will be installed, enabling exports to DOC/PDF/ODT formats. +# Control whether libreoffice will be installed, enabling exports to DOC/DOCX/PDF/ODT formats. # By default, it is not installed. # If given any value, libreoffice will be installed. # @@ -110,7 +102,6 @@ RUN \ ca-certificates \ curl \ git \ - ${INSTALL_ABIWORD:+abiword abiword-plugin-command} \ ${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} && \ rm -rf /var/cache/apk/* diff --git a/doc/docker.adoc b/doc/docker.adoc index 7114db3b6c9..e113adf8a5b 100644 --- a/doc/docker.adoc +++ b/doc/docker.adoc @@ -62,24 +62,11 @@ The variable value has to be a space separated, double quoted list of plugin nam Some plugins will need personalized settings. Just refer to the previous section, and include them in your custom `settings.json.docker`. -==== Rebuilding including export functionality for DOC/PDF/ODT +==== Rebuilding including export functionality for DOC/DOCX/PDF/ODT -If you want to be able to export your pads to DOC/PDF/ODT files, you can install -either Abiword or Libreoffice via setting a build variable. - -===== Via Abiword - -For installing Abiword, set the `INSTALL_ABIWORD` build variable to any value. - -Also, you will need to configure the path to the abiword executable -via setting the `abiword` property in `/settings.json.docker` to -`/usr/bin/abiword` or via setting the environment variable `ABIWORD` to -`/usr/bin/abiword`. - -===== Via Libreoffice - -For installing Libreoffice instead, set the `INSTALL_SOFFICE` build variable -to any value. +If you want to be able to export your pads to DOC/DOCX/PDF/ODT files, you can +install Libreoffice via setting the `INSTALL_SOFFICE` build variable to any +value. Also, you will need to configure the path to the libreoffice executable via setting the `soffice` property in `/settings.json.docker` to @@ -464,12 +451,8 @@ For the editor container, you can also make it full width by adding `full-width- | How long may clients use served javascript code (in seconds)? Not setting this may cause problems during deployment. Set to 0 to disable caching. | `21600` (6 hours) -| `ABIWORD` -| Absolute path to the Abiword executable. Abiword is needed to get advanced import/export features of pads. Setting it to null disables Abiword and will only allow plain text and HTML import/exports. -| `null` - | `SOFFICE` -| This is the absolute path to the soffice executable. LibreOffice can be used in lieu of Abiword to export pads. Setting it to null disables LibreOffice exporting. +| Absolute path to the soffice (LibreOffice) executable. Needed for advanced import/export of pads (docx, pdf, odt). Setting it to null disables LibreOffice and will only allow plain text and HTML import/exports. | `null` | `ALLOW_UNKNOWN_FILE_ENDS` diff --git a/doc/docker.md b/doc/docker.md index 73bd26fbbe4..71af392360f 100644 --- a/doc/docker.md +++ b/doc/docker.md @@ -29,24 +29,11 @@ The variable value has to be a space separated, double quoted list of plugin nam Some plugins will need personalized settings. Just refer to the previous section, and include them in your custom `settings.json.docker`. -### Rebuilding including export functionality for DOC/PDF/ODT +### Rebuilding including export functionality for DOC/DOCX/PDF/ODT -If you want to be able to export your pads to DOC/PDF/ODT files, you can install -either Abiword or Libreoffice via setting a build variable. - -#### Via Abiword - -For installing Abiword, set the `INSTALL_ABIWORD` build variable to any value. - -Also, you will need to configure the path to the abiword executable -via setting the `abiword` property in `/settings.json.docker` to -`/usr/bin/abiword` or via setting the environment variable `ABIWORD` to -`/usr/bin/abiword`. - -#### Via Libreoffice - -For installing Libreoffice instead, set the `INSTALL_SOFFICE` build variable -to any value. +If you want to be able to export your pads to DOC/DOCX/PDF/ODT files, you can +install Libreoffice via setting the `INSTALL_SOFFICE` build variable to any +value. Also, you will need to configure the path to the libreoffice executable via setting the `soffice` property in `/settings.json.docker` to @@ -202,8 +189,7 @@ For the editor container, you can also make it full width by adding `full-width- | `EDIT_ONLY` | Users may edit pads but not create new ones. Pad creation is only via the API. This applies both to group pads and regular pads. | `false` | | `MINIFY` | If true, all css & js will be minified before sending to the client. This will improve the loading performance massively, but makes it difficult to debug the javascript/css | `true` | | `MAX_AGE` | How long may clients use served javascript code (in seconds)? Not setting this may cause problems during deployment. Set to 0 to disable caching. | `21600` (6 hours) | -| `ABIWORD` | Absolute path to the Abiword executable. Abiword is needed to get advanced import/export features of pads. Setting it to null disables Abiword and will only allow plain text and HTML import/exports. | `null` | -| `SOFFICE` | This is the absolute path to the soffice executable. LibreOffice can be used in lieu of Abiword to export pads. Setting it to null disables LibreOffice exporting. | `null` | +| `SOFFICE` | Absolute path to the soffice (LibreOffice) executable. Needed for advanced import/export of pads (docx, pdf, odt). Setting it to null disables LibreOffice and will only allow plain text and HTML import/exports. | `null` | | `ALLOW_UNKNOWN_FILE_ENDS` | Allow import of file types other than the supported ones: txt, doc, docx, rtf, odt, html & htm | `true` | | `REQUIRE_AUTHENTICATION` | This setting is used if you require authentication of all users. Note: "/admin" always requires authentication. | `false` | | `REQUIRE_AUTHORIZATION` | Require authorization by a module, or a user with is_admin set, see below. | `false` | diff --git a/settings.json.docker b/settings.json.docker index a265a9235cf..890f225d30a 100644 --- a/settings.json.docker +++ b/settings.json.docker @@ -91,7 +91,7 @@ * "password": "${PASSW:}" // if PASSW is not defined would result in password === '' * * If you want to use an empty value (null) as default value for a variable, - * simply do not set it, without putting any colons: "${ABIWORD}". + * simply do not set it, without putting any colons: "${SOFFICE}". * * 3) if you want to use newlines in the default value of a string parameter, * use "\n" as usual. @@ -349,24 +349,22 @@ "maxAge": "${MAX_AGE:21600}", // 60 * 60 * 6 = 6 hours /* - * Absolute path to the Abiword executable. + * This is the absolute path to the soffice executable. * - * Abiword is needed to get advanced import/export features of pads. Setting - * it to null disables Abiword and will only allow plain text and HTML - * import/exports. + * LibreOffice is used for advanced import/export of pads (docx, pdf, odt). + * Setting it to null disables LibreOffice and will only allow plain text + * and HTML import/exports. */ - "abiword": "${ABIWORD:null}", + "soffice": "${SOFFICE:null}", /* - * This is the absolute path to the soffice executable. - * - * LibreOffice can be used in lieu of Abiword to export pads. - * Setting it to null disables LibreOffice exporting. + * When true (the default), the "Microsoft Word" export button downloads a .docx file via + * LibreOffice (requires "soffice" to be set). Set to false to revert to legacy .doc output + * (which also requires "soffice"). */ - "soffice": "${SOFFICE:null}", + "docxExport": "${DOCX_EXPORT:true}", /* - * Allow import of file types other than the supported ones: * txt, doc, docx, rtf, odt, html & htm */ "allowUnknownFileEnds": "${ALLOW_UNKNOWN_FILE_ENDS:true}", diff --git a/settings.json.template b/settings.json.template index 9f0bc55a00e..4ee63fe9bb1 100644 --- a/settings.json.template +++ b/settings.json.template @@ -82,7 +82,7 @@ * "password": "${PASSW:}" // if PASSW is not defined would result in password === '' * * If you want to use an empty value (null) as default value for a variable, - * simply do not set it, without putting any colons: "${ABIWORD}". + * simply do not set it, without putting any colons: "${SOFFICE}". * * 3) if you want to use newlines in the default value of a string parameter, * use "\n" as usual. @@ -336,24 +336,22 @@ "maxAge": 21600, // 60 * 60 * 6 = 6 hours /* - * Absolute path to the Abiword executable. + * This is the absolute path to the soffice executable. * - * Abiword is needed to get advanced import/export features of pads. Setting - * it to null disables Abiword and will only allow plain text and HTML - * import/exports. + * LibreOffice is used for advanced import/export of pads (docx, pdf, odt). + * Setting it to null disables LibreOffice and will only allow plain text + * and HTML import/exports. */ - "abiword": null, + "soffice": null, /* - * This is the absolute path to the soffice executable. - * - * LibreOffice can be used in lieu of Abiword to export pads. - * Setting it to null disables LibreOffice exporting. + * When true (the default), the "Microsoft Word" export button downloads a .docx file via + * LibreOffice (requires "soffice" to be set). Set to false to revert to legacy .doc output + * (which also requires "soffice"). */ - "soffice": null, + "docxExport": true, /* - * Allow import of file types other than the supported ones: * txt, doc, docx, rtf, odt, html & htm */ "allowUnknownFileEnds": true, diff --git a/src/locales/en.json b/src/locales/en.json index 51e07f3021a..4e23daaa5ec 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -108,7 +108,7 @@ "pad.importExport.exportword": "Microsoft Word", "pad.importExport.exportpdf": "PDF", "pad.importExport.exportopen": "ODF (Open Document Format)", - "pad.importExport.abiword.innerHTML": "You only can import from plain text or HTML formats. For more advanced import features please install AbiWord or LibreOffice.", + "pad.importExport.noConverter.innerHTML": "You can only import from plain text or HTML formats. For more advanced import features, please install LibreOffice.", "pad.modals.connected": "Connected.", "pad.modals.reconnecting": "Reconnecting to your pad…", diff --git a/src/node/handler/ExportHandler.ts b/src/node/handler/ExportHandler.ts index e1294171ab9..8a03bd898c5 100644 --- a/src/node/handler/ExportHandler.ts +++ b/src/node/handler/ExportHandler.ts @@ -95,7 +95,7 @@ exports.doExport = async (req: any, res: any, padId: string, readOnlyId: string, // ensure html can be collected by the garbage collector html = null; - // send the convert job to the converter (abiword, libreoffice, ..) + // send the convert job to the converter (libreoffice) const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`; // Allow plugins to overwrite the convert in export process @@ -103,10 +103,7 @@ exports.doExport = async (req: any, res: any, padId: string, readOnlyId: string, if (result.length > 0) { // console.log("export handled by plugin", destFile); } else { - const converter = - settings.soffice != null ? require('../utils/LibreOffice') - : settings.abiword != null ? require('../utils/Abiword') - : null; + const converter = require('../utils/LibreOffice'); await converter.convertFile(srcFile, destFile, type); } diff --git a/src/node/handler/ImportHandler.ts b/src/node/handler/ImportHandler.ts index e569c12fa15..393c76f2377 100644 --- a/src/node/handler/ImportHandler.ts +++ b/src/node/handler/ImportHandler.ts @@ -59,11 +59,6 @@ const rm = async (path: string) => { let converter:any = null; let exportExtension = 'htm'; -// load abiword only if it is enabled and if soffice is disabled -if (settings.abiword != null && settings.soffice == null) { - converter = require('../utils/Abiword'); -} - // load soffice only if it is enabled if (settings.soffice != null) { converter = require('../utils/LibreOffice'); @@ -81,7 +76,7 @@ const tmpDirectory = os.tmpdir(); */ const doImport = async (req:any, res:any, padId:string, authorId:string) => { // pipe to a file - // convert file to html via abiword or soffice + // convert file to html via soffice // set html in the pad const randNum = Math.floor(Math.random() * 0xFFFFFFFF); diff --git a/src/node/handler/PadMessageHandler.ts b/src/node/handler/PadMessageHandler.ts index eb8faa74bb9..27df3ffa4d2 100644 --- a/src/node/handler/PadMessageHandler.ts +++ b/src/node/handler/PadMessageHandler.ts @@ -32,7 +32,6 @@ import padutils from '../../static/js/pad_utils'; import readOnlyManager from '../db/ReadOnlyManager'; import settings, { exportAvailable, - abiwordAvailable, sofficeAvailable } from '../utils/Settings'; const securityManager = require('../db/SecurityManager'); @@ -1043,9 +1042,9 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => { serverTimestamp: Date.now(), sessionRefreshInterval: settings.cookie.sessionRefreshInterval, userId: sessionInfo.author, - abiwordAvailable: abiwordAvailable(), sofficeAvailable: sofficeAvailable(), exportAvailable: exportAvailable(), + docxExport: settings.docxExport, plugins: { plugins: plugins.plugins, parts: plugins.parts, diff --git a/src/node/hooks/express/importexport.ts b/src/node/hooks/express/importexport.ts index d3d40664afd..c2ded2a80d8 100644 --- a/src/node/hooks/express/importexport.ts +++ b/src/node/hooks/express/importexport.ts @@ -28,22 +28,22 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio args.app.use('/p/:pad{/:rev}/export/:type', limiter); args.app.get('/p/:pad{/:rev}/export/:type', (req:any, res:any, next:Function) => { (async () => { - const types = ['pdf', 'doc', 'txt', 'html', 'odt', 'etherpad']; + const types = ['pdf', 'doc', 'docx', 'txt', 'html', 'odt', 'etherpad']; // send a 404 if we don't support this filetype if (types.indexOf(req.params.type) === -1) { return next(); } - // if abiword is disabled, and this is a format we only support with abiword, output a message + // if soffice is disabled, and this is a format we only support with soffice, output a message if (exportAvailable() === 'no' && - ['odt', 'pdf', 'doc'].indexOf(req.params.type) !== -1) { + ['odt', 'pdf', 'doc', 'docx'].indexOf(req.params.type) !== -1) { console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` + ' There is no converter configured'); // ACHTUNG: do not include req.params.type in res.send() because there is // no HTML escaping and it would lead to an XSS - res.send('This export is not enabled at this Etherpad instance. Set the path to Abiword' + - ' or soffice (LibreOffice) in settings.json to enable this feature'); + res.send('This export is not enabled at this Etherpad instance. Set the path to soffice ' + + '(LibreOffice) in settings.json to enable this feature'); return; } diff --git a/src/node/utils/Abiword.ts b/src/node/utils/Abiword.ts deleted file mode 100644 index fd17497ed12..00000000000 --- a/src/node/utils/Abiword.ts +++ /dev/null @@ -1,93 +0,0 @@ -'use strict'; -/** - * Controls the communication with the Abiword application - */ - -/* - * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {ChildProcess} from "node:child_process"; -import {AsyncQueueTask} from "../types/AsyncQueueTask"; - -const spawn = require('child_process').spawn; -const async = require('async'); -import settings from './Settings'; -const os = require('os'); - -// on windows we have to spawn a process for each convertion, -// cause the plugin abicommand doesn't exist on this platform -if (os.type().indexOf('Windows') > -1) { - exports.convertFile = async (srcFile: string, destFile: string, type: string) => { - const abiword = spawn(settings.abiword, [`--to=${destFile}`, srcFile]); - let stdoutBuffer = ''; - abiword.stdout.on('data', (data: string) => { stdoutBuffer += data.toString(); }); - abiword.stderr.on('data', (data: string) => { stdoutBuffer += data.toString(); }); - await new Promise((resolve, reject) => { - abiword.on('exit', (code: number) => { - if (code !== 0) return reject(new Error(`Abiword died with exit code ${code}`)); - if (stdoutBuffer !== '') { - console.log(stdoutBuffer); - } - resolve(); - }); - }); - }; - // on unix operating systems, we can start abiword with abicommand and - // communicate with it via stdin/stdout - // thats much faster, about factor 10 -} else { - let abiword: ChildProcess; - let stdoutCallback: Function|null = null; - const spawnAbiword = () => { - abiword = spawn(settings.abiword, ['--plugin', 'AbiCommand']); - let stdoutBuffer = ''; - let firstPrompt = true; - abiword.stderr!.on('data', (data) => { stdoutBuffer += data.toString(); }); - abiword.on('exit', (code) => { - spawnAbiword(); - if (stdoutCallback != null) { - stdoutCallback(new Error(`Abiword died with exit code ${code}`)); - stdoutCallback = null; - } - }); - abiword.stdout!.on('data', (data) => { - stdoutBuffer += data.toString(); - // we're searching for the prompt, cause this means everything we need is in the buffer - if (stdoutBuffer.search('AbiWord:>') !== -1) { - const err = stdoutBuffer.search('OK') !== -1 ? null : new Error(stdoutBuffer); - stdoutBuffer = ''; - if (stdoutCallback != null && !firstPrompt) { - stdoutCallback(err); - stdoutCallback = null; - } - firstPrompt = false; - } - }); - }; - spawnAbiword(); - - const queue = async.queue((task: AsyncQueueTask, callback:Function) => { - abiword.stdin!.write(`convert ${task.srcFile} ${task.destFile} ${task.type}\n`); - stdoutCallback = (err: string) => { - if (err != null) console.error('Abiword File failed to convert', err); - callback(err); - }; - }, 1); - - exports.convertFile = async (srcFile: string, destFile: string, type: string) => { - await queue.pushAsync({srcFile, destFile, type}); - }; -} diff --git a/src/node/utils/ExportHtml.ts b/src/node/utils/ExportHtml.ts index 2d8e6a3a695..700b4403fdc 100644 --- a/src/node/utils/ExportHtml.ts +++ b/src/node/utils/ExportHtml.ts @@ -254,8 +254,7 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string let s = taker.take(chars); - // removes the characters with the code 12. Don't know where they come - // from but they break the abiword parser and are completly useless + // form feed (0x0C) is a legacy control char with no meaning in HTML s = s.replace(String.fromCharCode(12), ''); assem.append(_encodeWhitespace(Security.escapeHTML(s))); diff --git a/src/node/utils/ExportTxt.ts b/src/node/utils/ExportTxt.ts index ba4d9e19f00..58746822817 100644 --- a/src/node/utils/ExportTxt.ts +++ b/src/node/utils/ExportTxt.ts @@ -159,8 +159,7 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => { const s = taker.take(chars); - // removes the characters with the code 12. Don't know where they come - // from but they break the abiword parser and are completly useless + // form feed (0x0C) stripping — left commented historically // s = s.replace(String.fromCharCode(12), ""); // remove * from s, it's just not needed on a blank line.. This stops diff --git a/src/node/utils/Settings.ts b/src/node/utils/Settings.ts index d25c3d4e1a3..56fec21f6f9 100644 --- a/src/node/utils/Settings.ts +++ b/src/node/utils/Settings.ts @@ -237,8 +237,8 @@ export type SettingsType = { editOnly: boolean, maxAge: number, minify: boolean, - abiword: string | null, soffice: string | null, + docxExport: boolean, allowUnknownFileEnds: boolean, loglevel: string, logLayoutType: string, @@ -475,14 +475,15 @@ const settings: SettingsType = { * A flag that shows if minification is enabled or not */ minify: true, - /** - * The path of the abiword executable - */ - abiword: null, /** * The path of the libreoffice executable */ soffice: null, + /** + * When true, the "Microsoft Word" export button downloads a .docx file (requires soffice). + * Set to false to revert to legacy .doc output. + */ + docxExport: true, /** * Should we support none natively supported file types on import? */ @@ -684,15 +685,6 @@ if (typeof module !== 'undefined' && module.exports) { settings.dbSettings = {filename: path.join(settings.root, 'var/rusty.db')}; // END OF SETTINGS -// checks if abiword is avaiable -export const abiwordAvailable = () => { - if (settings.abiword != null) { - return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; - } else { - return 'no'; - } -}; - export const sofficeAvailable = () => { if (settings.soffice != null) { return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes'; @@ -701,19 +693,7 @@ export const sofficeAvailable = () => { } }; -export const exportAvailable = () => { - const abiword = abiwordAvailable(); - const soffice = sofficeAvailable(); - - if (abiword === 'no' && soffice === 'no') { - return 'no'; - } else if ((abiword === 'withoutPDF' && soffice === 'no') || - (abiword === 'no' && soffice === 'withoutPDF')) { - return 'withoutPDF'; - } else { - return 'yes'; - } -}; +export const exportAvailable = () => sofficeAvailable(); // Return etherpad version from package.json @@ -767,7 +747,7 @@ const storeSettings = (settingsObj: any) => { * no coercition for "null" values. * * If the user wants a variable to be null by default, he'll have to use the - * short syntax "${ABIWORD}", and not "${ABIWORD:null}": the latter would result + * short syntax "${SOFFICE}", and not "${SOFFICE:null}": the latter would result * in the literal string "null", instead. */ const coerceValue = (stringValue: string) => { @@ -962,6 +942,15 @@ export const reloadSettings = () => { storeSettings(settingsParsed); storeSettings(credentials); + // Emit a clear migration warning when the deprecated abiword setting is detected. + if (settingsParsed && (settingsParsed as any).abiword != null) { + logger.warn( + 'The "abiword" setting is no longer supported and has been ignored. ' + + 'Abiword import/export support has been removed. ' + + 'Please install LibreOffice and set "soffice" to its executable path instead.' + ); + } + // Init logging config settings.logconfig = defaultLogConfig( settings.loglevel ? settings.loglevel : defaultLogLevel, @@ -1015,20 +1004,6 @@ export const reloadSettings = () => { logger.info(`Using skin "${settings.skinName}" in dir: ${skinPath}`); } - if (settings.abiword) { - // Check abiword actually exists - fs.exists(settings.abiword, (exists: boolean) => { - if (!exists) { - const abiwordError = 'Abiword does not exist at this path, check your settings file.'; - if (!settings.suppressErrorsInPadText) { - settings.defaultPadText += `\nError: ${abiwordError}${suppressDisableMsg}`; - } - logger.error(`${abiwordError} File location: ${settings.abiword}`); - settings.abiword = null; - } - }); - } - if (settings.soffice) { fs.exists(settings.soffice, (exists: boolean) => { if (!exists) { diff --git a/src/static/css/pad/popup_import_export.css b/src/static/css/pad/popup_import_export.css index 3bfe0722e75..25f2cab7df7 100644 --- a/src/static/css/pad/popup_import_export.css +++ b/src/static/css/pad/popup_import_export.css @@ -11,16 +11,10 @@ /* hidden element */ #importstatusball, -#importmessagesuccess, -#importmessageabiword { +#importmessagesuccess { display: none; } -#importmessageabiword { - color: #900; - font-size: small; -} - #importsubmitinput { margin-top: 10px; } \ No newline at end of file diff --git a/src/static/js/pad_impexp.ts b/src/static/js/pad_impexp.ts index 8b8575c819d..de16213dff5 100644 --- a/src/static/js/pad_impexp.ts +++ b/src/static/js/pad_impexp.ts @@ -144,23 +144,23 @@ const padimpexp = (() => { $('#exportetherpada').attr('href', `${padRootPath}/export/etherpad`); $('#exportplaina').attr('href', `${padRootPath}/export/txt`); - // hide stuff thats not avaible if abiword/soffice is disabled + // hide stuff thats not avaible if soffice is disabled + const wordFormat = clientVars.docxExport ? 'docx' : 'doc'; if (clientVars.exportAvailable === 'no') { $('#exportworda').remove(); $('#exportpdfa').remove(); $('#exportopena').remove(); - - $('#importmessageabiword').show(); + $('#importmessagenoconverter').prop('hidden', false); } else if (clientVars.exportAvailable === 'withoutPDF') { $('#exportpdfa').remove(); - $('#exportworda').attr('href', `${padRootPath}/export/doc`); + $('#exportworda').attr('href', `${padRootPath}/export/${wordFormat}`); $('#exportopena').attr('href', `${padRootPath}/export/odt`); $('#importexport').css({height: '142px'}); $('#importexportline').css({height: '142px'}); } else { - $('#exportworda').attr('href', `${padRootPath}/export/doc`); + $('#exportworda').attr('href', `${padRootPath}/export/${wordFormat}`); $('#exportpdfa').attr('href', `${padRootPath}/export/pdf`); $('#exportopena').attr('href', `${padRootPath}/export/odt`); } diff --git a/src/static/js/types/SocketIOMessage.ts b/src/static/js/types/SocketIOMessage.ts index f2b8cfc14fa..690c293cba8 100644 --- a/src/static/js/types/SocketIOMessage.ts +++ b/src/static/js/types/SocketIOMessage.ts @@ -80,13 +80,13 @@ export type ClientVarPayload = { skinName: string skinVariants: string, exportAvailable: string + docxExport: boolean savedRevisions: PadRevision[], initialRevisionList: number[], padShortcutEnabled: MapArrayType, initialTitle: string, opts: {} numConnectedUsers: number - abiwordAvailable: string sofficeAvailable: string plugins: { plugins: MapArrayType diff --git a/src/static/skins/colibris/src/components/import-export.css b/src/static/skins/colibris/src/components/import-export.css index d8425d89529..0465e20b55c 100644 --- a/src/static/skins/colibris/src/components/import-export.css +++ b/src/static/skins/colibris/src/components/import-export.css @@ -1,15 +1,3 @@ -#importmessageabiword { - font-style: italic; - color: #64d29b; - color: var(--primary-color); -} -#importmessageabiword > a { - font-weight: bold; - text-decoration: underline; - color: #64d29b; - color: var(--primary-color); -} - #importmessagefail { margin-top: 10px; } diff --git a/src/templates/pad.html b/src/templates/pad.html index 1c6071f28ba..926d16c0305 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -192,7 +192,7 @@

<% e.begin_block("importColumn"); %>

-

+
diff --git a/src/tests/backend/specs/api/importexportGetPost.ts b/src/tests/backend/specs/api/importexportGetPost.ts index 1e3fb5b026e..ec8c6536be6 100644 --- a/src/tests/backend/specs/api/importexportGetPost.ts +++ b/src/tests/backend/specs/api/importexportGetPost.ts @@ -199,17 +199,16 @@ describe(__filename, function () { } }); - describe('Import/Export tests requiring AbiWord/LibreOffice', function () { + describe('Import/Export tests requiring LibreOffice', function () { before(async function () { - if ((!settings.abiword || settings.abiword.indexOf('/') === -1) && - (!settings.soffice || settings.soffice.indexOf('/') === -1)) { + if (!settings.soffice || settings.soffice.indexOf('/') === -1) { this.skip(); } }); // For some reason word import does not work in testing.. // TODO: fix support for .doc files.. - it('Tries to import .doc that uses soffice or abiword', async function () { + it('Tries to import .doc that uses soffice', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'}) @@ -230,7 +229,15 @@ describe(__filename, function () { .expect((res:any) => assert(res.body.length >= 9000)); }); - it('Tries to import .docx that uses soffice or abiword', async function () { + it('exports DOCX', async function () { + await agent.get(`/p/${testPadId}/export/docx`) + .set("authorization", await common.generateJWTToken()) + .buffer(true).parse(superagent.parse['application/octet-stream']) + .expect(200) + .expect((res:any) => assert(res.body.length >= 4000)); + }); + + it('Tries to import .docx that uses soffice', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', wordXDoc, { @@ -255,7 +262,15 @@ describe(__filename, function () { .expect((res:any) => assert(res.body.length >= 9100)); }); - it('Tries to import .pdf that uses soffice or abiword', async function () { + it('exports DOCX from imported DOCX', async function () { + await agent.get(`/p/${testPadId}/export/docx`) + .set("authorization", await common.generateJWTToken()) + .buffer(true).parse(superagent.parse['application/octet-stream']) + .expect(200) + .expect((res:any) => assert(res.body.length >= 4000)); + }); + + it('Tries to import .pdf that uses soffice', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'}) @@ -276,7 +291,7 @@ describe(__filename, function () { .expect((res:any) => assert(res.body.length >= 1000)); }); - it('Tries to import .odt that uses soffice or abiword', async function () { + it('Tries to import .odt that uses soffice', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'}) @@ -296,7 +311,7 @@ describe(__filename, function () { .expect(200) .expect((res:any) => assert(res.body.length >= 7000)); }); - }); // End of AbiWord/LibreOffice tests. + }); // End of LibreOffice tests. it('Tries to import .etherpad', async function () { this.timeout(3000); diff --git a/src/tests/settings.json b/src/tests/settings.json index 9fef0fa1998..ca2c5f64d2c 100644 --- a/src/tests/settings.json +++ b/src/tests/settings.json @@ -1 +1 @@ -{"title":"Etherpad","favicon":null,"skinName":"colibris","skinVariants":"super-light-toolbar super-light-editor light-background","ip":"0.0.0.0","port":9001,"showSettingsInAdminPage":true,"dbType":"dirty","dbSettings":{"filename":"var/dirty.db"},"defaultPadText":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n","padOptions":{"noColors":false,"showControls":true,"showChat":true,"showLineNumbers":true,"useMonospaceFont":false,"userName":null,"userColor":null,"rtl":false,"alwaysShowChat":false,"chatAndUsers":false,"lang":null},"padShortcutEnabled":{"altF9":true,"altC":true,"cmdShift2":true,"delete":true,"return":true,"esc":true,"cmdS":true,"tab":true,"cmdZ":true,"cmdY":true,"cmdI":true,"cmdB":true,"cmdU":true,"cmd5":true,"cmdShiftL":true,"cmdShiftN":true,"cmdShift1":true,"cmdShiftC":true,"cmdH":true,"ctrlHome":true,"pageUp":true,"pageDown":true},"suppressErrorsInPadText":false,"requireSession":false,"editOnly":false,"minify":true,"maxAge":21600,"abiword":null,"soffice":null,"allowUnknownFileEnds":true,"requireAuthentication":false,"requireAuthorization":false,"trustProxy":false,"cookie":{"keyRotationInterval":86400000,"sameSite":"Lax","sessionLifetime":864000000,"sessionRefreshInterval":86400000},"disableIPlogging":false,"automaticReconnectionTimeout":0,"scrollWhenFocusLineIsOutOfViewport":{"percentage":{"editionAboveViewport":0,"editionBelowViewport":0},"duration":0,"scrollWhenCaretIsInTheLastLineOfViewport":false,"percentageToScrollWhenUserPressesArrowUp":0},"users":{"admin":{"password":"changeme1","is_admin":true},"user":{"password":"changeme1","is_admin":false}},"socketTransportProtocols":["websocket","polling"],"socketIo":{"maxHttpBufferSize":1000000},"loadTest":false,"dumpOnUncleanExit":false,"importExportRateLimiting":{"windowMs":90000,"max":10},"importMaxFileSize":52428800,"commitRateLimiting":{"duration":1,"points":10},"exposeVersion":false,"loglevel":"INFO","customLocaleStrings":{},"enableAdminUITests":true,"lowerCasePadIds":false,"sso":{"issuer":"${SSO_ISSUER:http://localhost:9001}","clients":[{"client_id":"${ADMIN_CLIENT:admin_client}","client_secret":"${ADMIN_SECRET:admin}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${ADMIN_REDIRECT:http://localhost:9001/admin/}","https://oauth.pstmn.io/v1/callback"]},{"client_id":"${USER_CLIENT:user_client}","client_secret":"${USER_SECRET:user}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${USER_REDIRECT:http://localhost:9001/}"]}]}} \ No newline at end of file +{"title":"Etherpad","favicon":null,"skinName":"colibris","skinVariants":"super-light-toolbar super-light-editor light-background","ip":"0.0.0.0","port":9001,"showSettingsInAdminPage":true,"dbType":"dirty","dbSettings":{"filename":"var/dirty.db"},"defaultPadText":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n","padOptions":{"noColors":false,"showControls":true,"showChat":true,"showLineNumbers":true,"useMonospaceFont":false,"userName":null,"userColor":null,"rtl":false,"alwaysShowChat":false,"chatAndUsers":false,"lang":null},"padShortcutEnabled":{"altF9":true,"altC":true,"cmdShift2":true,"delete":true,"return":true,"esc":true,"cmdS":true,"tab":true,"cmdZ":true,"cmdY":true,"cmdI":true,"cmdB":true,"cmdU":true,"cmd5":true,"cmdShiftL":true,"cmdShiftN":true,"cmdShift1":true,"cmdShiftC":true,"cmdH":true,"ctrlHome":true,"pageUp":true,"pageDown":true},"suppressErrorsInPadText":false,"requireSession":false,"editOnly":false,"minify":true,"maxAge":21600,"soffice":null,"allowUnknownFileEnds":true,"requireAuthentication":false,"requireAuthorization":false,"trustProxy":false,"cookie":{"keyRotationInterval":86400000,"sameSite":"Lax","sessionLifetime":864000000,"sessionRefreshInterval":86400000},"disableIPlogging":false,"automaticReconnectionTimeout":0,"scrollWhenFocusLineIsOutOfViewport":{"percentage":{"editionAboveViewport":0,"editionBelowViewport":0},"duration":0,"scrollWhenCaretIsInTheLastLineOfViewport":false,"percentageToScrollWhenUserPressesArrowUp":0},"users":{"admin":{"password":"changeme1","is_admin":true},"user":{"password":"changeme1","is_admin":false}},"socketTransportProtocols":["websocket","polling"],"socketIo":{"maxHttpBufferSize":1000000},"loadTest":false,"dumpOnUncleanExit":false,"importExportRateLimiting":{"windowMs":90000,"max":10},"importMaxFileSize":52428800,"commitRateLimiting":{"duration":1,"points":10},"exposeVersion":false,"loglevel":"INFO","customLocaleStrings":{},"enableAdminUITests":true,"lowerCasePadIds":false,"sso":{"issuer":"${SSO_ISSUER:http://localhost:9001}","clients":[{"client_id":"${ADMIN_CLIENT:admin_client}","client_secret":"${ADMIN_SECRET:admin}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${ADMIN_REDIRECT:http://localhost:9001/admin/}","https://oauth.pstmn.io/v1/callback"]},{"client_id":"${USER_CLIENT:user_client}","client_secret":"${USER_SECRET:user}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${USER_REDIRECT:http://localhost:9001/}"]}]}} \ No newline at end of file diff --git a/ui/pad.html b/ui/pad.html index e1154194372..6ce5cb56039 100644 --- a/ui/pad.html +++ b/ui/pad.html @@ -402,7 +402,6 @@

-