diff --git a/.gitignore b/.gitignore index 71584e76b..28fabc00b 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 2ad2936fb..6ee07c2c6 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 7114db3b6..e113adf8a 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 73bd26fbb..71af39236 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 a265a9235..890f225d3 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. @@ -348,25 +348,23 @@ */ "maxAge": "${MAX_AGE:21600}", // 60 * 60 * 6 = 6 hours - /* - * 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. - */ - "abiword": "${ABIWORD: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. + * 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. */ "soffice": "${SOFFICE:null}", /* - * Allow import of file types other than the supported ones: + * 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"). + */ + "docxExport": "${DOCX_EXPORT:true}", + + /* * txt, doc, docx, rtf, odt, html & htm */ "allowUnknownFileEnds": "${ALLOW_UNKNOWN_FILE_ENDS:true}", diff --git a/settings.json.template b/settings.json.template index 9f0bc55a0..4ee63fe9b 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. @@ -335,25 +335,23 @@ */ "maxAge": 21600, // 60 * 60 * 6 = 6 hours - /* - * 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. - */ - "abiword": 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. + * 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. */ "soffice": null, /* - * Allow import of file types other than the supported ones: + * 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"). + */ + "docxExport": true, + + /* * txt, doc, docx, rtf, odt, html & htm */ "allowUnknownFileEnds": true, diff --git a/src/locales/en.json b/src/locales/en.json index 51e07f302..4e23daaa5 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 e1294171a..8a03bd898 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 e569c12fa..393c76f23 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 eb8faa74b..27df3ffa4 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 d3d40664a..c2ded2a80 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 fd17497ed..000000000 --- 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 2d8e6a3a6..700b4403f 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 ba4d9e19f..587468228 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 d25c3d4e1..56fec21f6 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 3bfe0722e..25f2cab7d 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 8b8575c81..de16213df 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 f2b8cfc14..690c293cb 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 d8425d895..0465e20b5 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 1c6071f28..926d16c03 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 1e3fb5b02..ec8c6536b 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 9fef0fa19..ca2c5f64d 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 e11541943..6ce5cb560 100644 --- a/ui/pad.html +++ b/ui/pad.html @@ -402,7 +402,6 @@

-