mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-17 16:47:05 +00:00
feat!: replace Abiword with LibreOffice and add DOCX export (#7539)
* feat!: replace Abiword with LibreOffice and add DOCX export (#4805)
The Abiword converter is dropped. Abiword's DOCX export is weak and the
project is niche on modern platforms; LibreOffice (soffice) is the
common deployment path and now serves as the sole converter backend.
DOCX is added as an export format and becomes the new target for the
"Microsoft Word" UI button. The /export/doc URL still works for legacy
API consumers.
BREAKING CHANGE: The 'abiword' setting, the INSTALL_ABIWORD Dockerfile
build arg, the abiwordAvailable clientVar, and the
#importmessageabiword UI element (with locale key
pad.importExport.abiword.innerHTML) are removed. Deployments relying on
Abiword must configure 'soffice' instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: add docxExport feature flag and abiword deprecation WARN
- Add `docxExport: true` setting to opt out of DOCX (use legacy DOC)
- Pass `docxExport` to client via clientVars
- Use `docxExport` flag in pad_impexp.ts for Word button format
- Emit a specific WARN when deprecated `abiword` config is detected
- Update settings.json.template and settings.json.docker with docxExport
- Add docxExport to ClientVarPayload type in SocketIOMessage.ts
Agent-Logs-Url: https://github.com/ether/etherpad/sessions/9afc5291-73b2-4b66-b028-feed39e7056f
Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>
* refactor: extract wordFormat variable and improve docxExport comment
Agent-Logs-Url: https://github.com/ether/etherpad/sessions/9afc5291-73b2-4b66-b028-feed39e7056f
Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>
* fix: restore import-limitation message when no converter is configured
The abiword removal dropped both the #importmessageabiword DOM element
and its locale key, but Copilot's refactor still expected the show()
call to surface a message when exportAvailable === 'no'. Result: users
with no soffice binary got silent failure instead of an explanation.
Add #importmessagenoconverter back with updated, LibreOffice-focused
copy (new locale key pad.importExport.noConverter.innerHTML) and flip
the hidden prop when the client knows no converter is available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* i18n: inline English fallback for noConverter import message
The original abiword message existed in ~70 locale files and was
removed from all of them by this PR. The replacement key was only
added to en.json, so non-English users had an empty div until
translators localize. Follow the project's usual pad.html pattern
(e.g. line 146's "Font type:") and include the English text inside
the div as the fallback content; html10n replaces it when a
translation is available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "i18n: inline English fallback for noConverter import message"
This reverts commit f336f24d. Follow the project convention: add the
new locale key to en.json only and let translations catch up via the
translation system, rather than putting inline fallback in the template.
* i18n: leave non-English locale files untouched
The PR had removed pad.importExport.abiword.innerHTML from ~82 locale
files alongside its removal from en.json. The replacement message uses
a new key (pad.importExport.noConverter.innerHTML) in en.json only, so
churning every localisation file for a key that is no longer referenced
produces useless translation diffs. Restore every non-en locale file to
its pre-PR state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JohnMcLear <220864+JohnMcLear@users.noreply.github.com>
This commit is contained in:
parent
66f49bb808
commit
7ec581afca
23 changed files with 94 additions and 272 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -18,7 +18,6 @@ out/
|
||||||
.nyc_output
|
.nyc_output
|
||||||
.idea
|
.idea
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
/src/bin/abiword.exe
|
|
||||||
/src/bin/convertSettings.json
|
/src/bin/convertSettings.json
|
||||||
/src/bin/etherpad-1.deb
|
/src/bin/etherpad-1.deb
|
||||||
/src/bin/node.exe
|
/src/bin/node.exe
|
||||||
|
|
|
||||||
11
Dockerfile
11
Dockerfile
|
|
@ -62,15 +62,7 @@ ARG ETHERPAD_LOCAL_PLUGINS=
|
||||||
# ETHERPAD_GITHUB_PLUGINS="ether/ep_plugin"
|
# ETHERPAD_GITHUB_PLUGINS="ether/ep_plugin"
|
||||||
ARG ETHERPAD_GITHUB_PLUGINS=
|
ARG ETHERPAD_GITHUB_PLUGINS=
|
||||||
|
|
||||||
# Control whether abiword 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, abiword will be installed.
|
|
||||||
#
|
|
||||||
# EXAMPLE:
|
|
||||||
# INSTALL_ABIWORD=true
|
|
||||||
ARG INSTALL_ABIWORD=
|
|
||||||
|
|
||||||
# Control whether libreoffice will be installed, enabling exports to DOC/PDF/ODT formats.
|
|
||||||
# By default, it is not installed.
|
# By default, it is not installed.
|
||||||
# If given any value, libreoffice will be installed.
|
# If given any value, libreoffice will be installed.
|
||||||
#
|
#
|
||||||
|
|
@ -110,7 +102,6 @@ RUN \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
git \
|
git \
|
||||||
${INSTALL_ABIWORD:+abiword abiword-plugin-command} \
|
|
||||||
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} && \
|
${INSTALL_SOFFICE:+libreoffice openjdk8-jre libreoffice-common} && \
|
||||||
rm -rf /var/cache/apk/*
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
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
|
If you want to be able to export your pads to DOC/DOCX/PDF/ODT files, you can
|
||||||
either Abiword or Libreoffice via setting a build variable.
|
install Libreoffice via setting the `INSTALL_SOFFICE` build variable to any
|
||||||
|
value.
|
||||||
===== 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 `<BASEDIR>/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.
|
|
||||||
|
|
||||||
Also, you will need to configure the path to the libreoffice executable
|
Also, you will need to configure the path to the libreoffice executable
|
||||||
via setting the `soffice` property in `<BASEDIR>/settings.json.docker` to
|
via setting the `soffice` property in `<BASEDIR>/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.
|
| 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)
|
| `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`
|
| `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`
|
| `null`
|
||||||
|
|
||||||
| `ALLOW_UNKNOWN_FILE_ENDS`
|
| `ALLOW_UNKNOWN_FILE_ENDS`
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
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
|
If you want to be able to export your pads to DOC/DOCX/PDF/ODT files, you can
|
||||||
either Abiword or Libreoffice via setting a build variable.
|
install Libreoffice via setting the `INSTALL_SOFFICE` build variable to any
|
||||||
|
value.
|
||||||
#### 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 `<BASEDIR>/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.
|
|
||||||
|
|
||||||
Also, you will need to configure the path to the libreoffice executable
|
Also, you will need to configure the path to the libreoffice executable
|
||||||
via setting the `soffice` property in `<BASEDIR>/settings.json.docker` to
|
via setting the `soffice` property in `<BASEDIR>/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` |
|
| `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` |
|
| `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) |
|
| `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` | 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` |
|
||||||
| `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` |
|
|
||||||
| `ALLOW_UNKNOWN_FILE_ENDS` | Allow import of file types other than the supported ones: txt, doc, docx, rtf, odt, html & htm | `true` |
|
| `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_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` |
|
| `REQUIRE_AUTHORIZATION` | Require authorization by a module, or a user with is_admin set, see below. | `false` |
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@
|
||||||
* "password": "${PASSW:}" // if PASSW is not defined would result in password === ''
|
* "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,
|
* 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,
|
* 3) if you want to use newlines in the default value of a string parameter,
|
||||||
* use "\n" as usual.
|
* use "\n" as usual.
|
||||||
|
|
@ -348,25 +348,23 @@
|
||||||
*/
|
*/
|
||||||
"maxAge": "${MAX_AGE:21600}", // 60 * 60 * 6 = 6 hours
|
"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.
|
* This is the absolute path to the soffice executable.
|
||||||
*
|
*
|
||||||
* LibreOffice can be used in lieu of Abiword to export pads.
|
* LibreOffice is used for advanced import/export of pads (docx, pdf, odt).
|
||||||
* Setting it to null disables LibreOffice exporting.
|
* Setting it to null disables LibreOffice and will only allow plain text
|
||||||
|
* and HTML import/exports.
|
||||||
*/
|
*/
|
||||||
"soffice": "${SOFFICE:null}",
|
"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
|
* txt, doc, docx, rtf, odt, html & htm
|
||||||
*/
|
*/
|
||||||
"allowUnknownFileEnds": "${ALLOW_UNKNOWN_FILE_ENDS:true}",
|
"allowUnknownFileEnds": "${ALLOW_UNKNOWN_FILE_ENDS:true}",
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@
|
||||||
* "password": "${PASSW:}" // if PASSW is not defined would result in password === ''
|
* "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,
|
* 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,
|
* 3) if you want to use newlines in the default value of a string parameter,
|
||||||
* use "\n" as usual.
|
* use "\n" as usual.
|
||||||
|
|
@ -335,25 +335,23 @@
|
||||||
*/
|
*/
|
||||||
"maxAge": 21600, // 60 * 60 * 6 = 6 hours
|
"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.
|
* This is the absolute path to the soffice executable.
|
||||||
*
|
*
|
||||||
* LibreOffice can be used in lieu of Abiword to export pads.
|
* LibreOffice is used for advanced import/export of pads (docx, pdf, odt).
|
||||||
* Setting it to null disables LibreOffice exporting.
|
* Setting it to null disables LibreOffice and will only allow plain text
|
||||||
|
* and HTML import/exports.
|
||||||
*/
|
*/
|
||||||
"soffice": null,
|
"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
|
* txt, doc, docx, rtf, odt, html & htm
|
||||||
*/
|
*/
|
||||||
"allowUnknownFileEnds": true,
|
"allowUnknownFileEnds": true,
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
"pad.importExport.exportword": "Microsoft Word",
|
"pad.importExport.exportword": "Microsoft Word",
|
||||||
"pad.importExport.exportpdf": "PDF",
|
"pad.importExport.exportpdf": "PDF",
|
||||||
"pad.importExport.exportopen": "ODF (Open Document Format)",
|
"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 <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">install AbiWord or LibreOffice</a>.",
|
"pad.importExport.noConverter.innerHTML": "You can only import from plain text or HTML formats. For more advanced import features, please <a href=\"https://github.com/ether/etherpad-lite/wiki/How-to-enable-importing-and-exporting-different-file-formats-with-AbiWord\">install LibreOffice</a>.",
|
||||||
|
|
||||||
"pad.modals.connected": "Connected.",
|
"pad.modals.connected": "Connected.",
|
||||||
"pad.modals.reconnecting": "Reconnecting to your pad…",
|
"pad.modals.reconnecting": "Reconnecting to your pad…",
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ exports.doExport = async (req: any, res: any, padId: string, readOnlyId: string,
|
||||||
// ensure html can be collected by the garbage collector
|
// ensure html can be collected by the garbage collector
|
||||||
html = null;
|
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}`;
|
const destFile = `${tempDirectory}/etherpad_export_${randNum}.${type}`;
|
||||||
|
|
||||||
// Allow plugins to overwrite the convert in export process
|
// 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) {
|
if (result.length > 0) {
|
||||||
// console.log("export handled by plugin", destFile);
|
// console.log("export handled by plugin", destFile);
|
||||||
} else {
|
} else {
|
||||||
const converter =
|
const converter = require('../utils/LibreOffice');
|
||||||
settings.soffice != null ? require('../utils/LibreOffice')
|
|
||||||
: settings.abiword != null ? require('../utils/Abiword')
|
|
||||||
: null;
|
|
||||||
await converter.convertFile(srcFile, destFile, type);
|
await converter.convertFile(srcFile, destFile, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,6 @@ const rm = async (path: string) => {
|
||||||
let converter:any = null;
|
let converter:any = null;
|
||||||
let exportExtension = 'htm';
|
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
|
// load soffice only if it is enabled
|
||||||
if (settings.soffice != null) {
|
if (settings.soffice != null) {
|
||||||
converter = require('../utils/LibreOffice');
|
converter = require('../utils/LibreOffice');
|
||||||
|
|
@ -81,7 +76,7 @@ const tmpDirectory = os.tmpdir();
|
||||||
*/
|
*/
|
||||||
const doImport = async (req:any, res:any, padId:string, authorId:string) => {
|
const doImport = async (req:any, res:any, padId:string, authorId:string) => {
|
||||||
// pipe to a file
|
// pipe to a file
|
||||||
// convert file to html via abiword or soffice
|
// convert file to html via soffice
|
||||||
// set html in the pad
|
// set html in the pad
|
||||||
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
const randNum = Math.floor(Math.random() * 0xFFFFFFFF);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ import padutils from '../../static/js/pad_utils';
|
||||||
import readOnlyManager from '../db/ReadOnlyManager';
|
import readOnlyManager from '../db/ReadOnlyManager';
|
||||||
import settings, {
|
import settings, {
|
||||||
exportAvailable,
|
exportAvailable,
|
||||||
abiwordAvailable,
|
|
||||||
sofficeAvailable
|
sofficeAvailable
|
||||||
} from '../utils/Settings';
|
} from '../utils/Settings';
|
||||||
const securityManager = require('../db/SecurityManager');
|
const securityManager = require('../db/SecurityManager');
|
||||||
|
|
@ -1043,9 +1042,9 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
||||||
serverTimestamp: Date.now(),
|
serverTimestamp: Date.now(),
|
||||||
sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
|
sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
|
||||||
userId: sessionInfo.author,
|
userId: sessionInfo.author,
|
||||||
abiwordAvailable: abiwordAvailable(),
|
|
||||||
sofficeAvailable: sofficeAvailable(),
|
sofficeAvailable: sofficeAvailable(),
|
||||||
exportAvailable: exportAvailable(),
|
exportAvailable: exportAvailable(),
|
||||||
|
docxExport: settings.docxExport,
|
||||||
plugins: {
|
plugins: {
|
||||||
plugins: plugins.plugins,
|
plugins: plugins.plugins,
|
||||||
parts: plugins.parts,
|
parts: plugins.parts,
|
||||||
|
|
|
||||||
|
|
@ -28,22 +28,22 @@ exports.expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Functio
|
||||||
args.app.use('/p/:pad{/:rev}/export/:type', limiter);
|
args.app.use('/p/:pad{/:rev}/export/:type', limiter);
|
||||||
args.app.get('/p/:pad{/:rev}/export/:type', (req:any, res:any, next:Function) => {
|
args.app.get('/p/:pad{/:rev}/export/:type', (req:any, res:any, next:Function) => {
|
||||||
(async () => {
|
(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
|
// send a 404 if we don't support this filetype
|
||||||
if (types.indexOf(req.params.type) === -1) {
|
if (types.indexOf(req.params.type) === -1) {
|
||||||
return next();
|
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' &&
|
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.` +
|
console.error(`Impossible to export pad "${req.params.pad}" in ${req.params.type} format.` +
|
||||||
' There is no converter configured');
|
' There is no converter configured');
|
||||||
|
|
||||||
// ACHTUNG: do not include req.params.type in res.send() because there is
|
// ACHTUNG: do not include req.params.type in res.send() because there is
|
||||||
// no HTML escaping and it would lead to an XSS
|
// 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' +
|
res.send('This export is not enabled at this Etherpad instance. Set the path to soffice ' +
|
||||||
' or soffice (LibreOffice) in settings.json to enable this feature');
|
'(LibreOffice) in settings.json to enable this feature');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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<void>((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});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -254,8 +254,7 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
||||||
|
|
||||||
let s = taker.take(chars);
|
let s = taker.take(chars);
|
||||||
|
|
||||||
// removes the characters with the code 12. Don't know where they come
|
// form feed (0x0C) is a legacy control char with no meaning in HTML
|
||||||
// from but they break the abiword parser and are completly useless
|
|
||||||
s = s.replace(String.fromCharCode(12), '');
|
s = s.replace(String.fromCharCode(12), '');
|
||||||
|
|
||||||
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
|
assem.append(_encodeWhitespace(Security.escapeHTML(s)));
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,7 @@ const getTXTFromAtext = (pad: PadType, atext: AText, authorColors?:string) => {
|
||||||
|
|
||||||
const s = taker.take(chars);
|
const s = taker.take(chars);
|
||||||
|
|
||||||
// removes the characters with the code 12. Don't know where they come
|
// form feed (0x0C) stripping — left commented historically
|
||||||
// from but they break the abiword parser and are completly useless
|
|
||||||
// s = s.replace(String.fromCharCode(12), "");
|
// s = s.replace(String.fromCharCode(12), "");
|
||||||
|
|
||||||
// remove * from s, it's just not needed on a blank line.. This stops
|
// remove * from s, it's just not needed on a blank line.. This stops
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,8 @@ export type SettingsType = {
|
||||||
editOnly: boolean,
|
editOnly: boolean,
|
||||||
maxAge: number,
|
maxAge: number,
|
||||||
minify: boolean,
|
minify: boolean,
|
||||||
abiword: string | null,
|
|
||||||
soffice: string | null,
|
soffice: string | null,
|
||||||
|
docxExport: boolean,
|
||||||
allowUnknownFileEnds: boolean,
|
allowUnknownFileEnds: boolean,
|
||||||
loglevel: string,
|
loglevel: string,
|
||||||
logLayoutType: string,
|
logLayoutType: string,
|
||||||
|
|
@ -475,14 +475,15 @@ const settings: SettingsType = {
|
||||||
* A flag that shows if minification is enabled or not
|
* A flag that shows if minification is enabled or not
|
||||||
*/
|
*/
|
||||||
minify: true,
|
minify: true,
|
||||||
/**
|
|
||||||
* The path of the abiword executable
|
|
||||||
*/
|
|
||||||
abiword: null,
|
|
||||||
/**
|
/**
|
||||||
* The path of the libreoffice executable
|
* The path of the libreoffice executable
|
||||||
*/
|
*/
|
||||||
soffice: null,
|
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?
|
* 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')};
|
settings.dbSettings = {filename: path.join(settings.root, 'var/rusty.db')};
|
||||||
// END OF SETTINGS
|
// 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 = () => {
|
export const sofficeAvailable = () => {
|
||||||
if (settings.soffice != null) {
|
if (settings.soffice != null) {
|
||||||
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
return os.type().indexOf('Windows') !== -1 ? 'withoutPDF' : 'yes';
|
||||||
|
|
@ -701,19 +693,7 @@ export const sofficeAvailable = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const exportAvailable = () => {
|
export const exportAvailable = () => sofficeAvailable();
|
||||||
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';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Return etherpad version from package.json
|
// Return etherpad version from package.json
|
||||||
|
|
@ -767,7 +747,7 @@ const storeSettings = (settingsObj: any) => {
|
||||||
* no coercition for "null" values.
|
* no coercition for "null" values.
|
||||||
*
|
*
|
||||||
* If the user wants a variable to be null by default, he'll have to use the
|
* 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.
|
* in the literal string "null", instead.
|
||||||
*/
|
*/
|
||||||
const coerceValue = (stringValue: string) => {
|
const coerceValue = (stringValue: string) => {
|
||||||
|
|
@ -962,6 +942,15 @@ export const reloadSettings = () => {
|
||||||
storeSettings(settingsParsed);
|
storeSettings(settingsParsed);
|
||||||
storeSettings(credentials);
|
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
|
// Init logging config
|
||||||
settings.logconfig = defaultLogConfig(
|
settings.logconfig = defaultLogConfig(
|
||||||
settings.loglevel ? settings.loglevel : defaultLogLevel,
|
settings.loglevel ? settings.loglevel : defaultLogLevel,
|
||||||
|
|
@ -1015,20 +1004,6 @@ export const reloadSettings = () => {
|
||||||
logger.info(`Using skin "${settings.skinName}" in dir: ${skinPath}`);
|
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) {
|
if (settings.soffice) {
|
||||||
fs.exists(settings.soffice, (exists: boolean) => {
|
fs.exists(settings.soffice, (exists: boolean) => {
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
|
|
|
||||||
|
|
@ -11,16 +11,10 @@
|
||||||
|
|
||||||
/* hidden element */
|
/* hidden element */
|
||||||
#importstatusball,
|
#importstatusball,
|
||||||
#importmessagesuccess,
|
#importmessagesuccess {
|
||||||
#importmessageabiword {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#importmessageabiword {
|
|
||||||
color: #900;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
#importsubmitinput {
|
#importsubmitinput {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
@ -144,23 +144,23 @@ const padimpexp = (() => {
|
||||||
$('#exportetherpada').attr('href', `${padRootPath}/export/etherpad`);
|
$('#exportetherpada').attr('href', `${padRootPath}/export/etherpad`);
|
||||||
$('#exportplaina').attr('href', `${padRootPath}/export/txt`);
|
$('#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') {
|
if (clientVars.exportAvailable === 'no') {
|
||||||
$('#exportworda').remove();
|
$('#exportworda').remove();
|
||||||
$('#exportpdfa').remove();
|
$('#exportpdfa').remove();
|
||||||
$('#exportopena').remove();
|
$('#exportopena').remove();
|
||||||
|
$('#importmessagenoconverter').prop('hidden', false);
|
||||||
$('#importmessageabiword').show();
|
|
||||||
} else if (clientVars.exportAvailable === 'withoutPDF') {
|
} else if (clientVars.exportAvailable === 'withoutPDF') {
|
||||||
$('#exportpdfa').remove();
|
$('#exportpdfa').remove();
|
||||||
|
|
||||||
$('#exportworda').attr('href', `${padRootPath}/export/doc`);
|
$('#exportworda').attr('href', `${padRootPath}/export/${wordFormat}`);
|
||||||
$('#exportopena').attr('href', `${padRootPath}/export/odt`);
|
$('#exportopena').attr('href', `${padRootPath}/export/odt`);
|
||||||
|
|
||||||
$('#importexport').css({height: '142px'});
|
$('#importexport').css({height: '142px'});
|
||||||
$('#importexportline').css({height: '142px'});
|
$('#importexportline').css({height: '142px'});
|
||||||
} else {
|
} else {
|
||||||
$('#exportworda').attr('href', `${padRootPath}/export/doc`);
|
$('#exportworda').attr('href', `${padRootPath}/export/${wordFormat}`);
|
||||||
$('#exportpdfa').attr('href', `${padRootPath}/export/pdf`);
|
$('#exportpdfa').attr('href', `${padRootPath}/export/pdf`);
|
||||||
$('#exportopena').attr('href', `${padRootPath}/export/odt`);
|
$('#exportopena').attr('href', `${padRootPath}/export/odt`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,13 @@ export type ClientVarPayload = {
|
||||||
skinName: string
|
skinName: string
|
||||||
skinVariants: string,
|
skinVariants: string,
|
||||||
exportAvailable: string
|
exportAvailable: string
|
||||||
|
docxExport: boolean
|
||||||
savedRevisions: PadRevision[],
|
savedRevisions: PadRevision[],
|
||||||
initialRevisionList: number[],
|
initialRevisionList: number[],
|
||||||
padShortcutEnabled: MapArrayType<boolean>,
|
padShortcutEnabled: MapArrayType<boolean>,
|
||||||
initialTitle: string,
|
initialTitle: string,
|
||||||
opts: {}
|
opts: {}
|
||||||
numConnectedUsers: number
|
numConnectedUsers: number
|
||||||
abiwordAvailable: string
|
|
||||||
sofficeAvailable: string
|
sofficeAvailable: string
|
||||||
plugins: {
|
plugins: {
|
||||||
plugins: MapArrayType<any>
|
plugins: MapArrayType<any>
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
#importmessagefail {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@
|
||||||
<div class="acl-write">
|
<div class="acl-write">
|
||||||
<% e.begin_block("importColumn"); %>
|
<% e.begin_block("importColumn"); %>
|
||||||
<h2 data-l10n-id="pad.importExport.import"></h2>
|
<h2 data-l10n-id="pad.importExport.import"></h2>
|
||||||
<div class="importmessage" id="importmessageabiword" data-l10n-id="pad.importExport.abiword.innerHTML"></div><br>
|
<div class="importmessage" id="importmessagenoconverter" data-l10n-id="pad.importExport.noConverter.innerHTML" hidden></div>
|
||||||
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
||||||
<div class="importformdiv" id="importformfilediv">
|
<div class="importformdiv" id="importformfilediv">
|
||||||
<input type="file" name="file" size="10" id="importfileinput">
|
<input type="file" name="file" size="10" id="importfileinput">
|
||||||
|
|
|
||||||
|
|
@ -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 () {
|
before(async function () {
|
||||||
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
if (!settings.soffice || settings.soffice.indexOf('/') === -1) {
|
||||||
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// For some reason word import does not work in testing..
|
// For some reason word import does not work in testing..
|
||||||
// TODO: fix support for .doc files..
|
// 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`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.set("authorization", await common.generateJWTToken())
|
.set("authorization", await common.generateJWTToken())
|
||||||
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
||||||
|
|
@ -230,7 +229,15 @@ describe(__filename, function () {
|
||||||
.expect((res:any) => assert(res.body.length >= 9000));
|
.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`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.set("authorization", await common.generateJWTToken())
|
.set("authorization", await common.generateJWTToken())
|
||||||
.attach('file', wordXDoc, {
|
.attach('file', wordXDoc, {
|
||||||
|
|
@ -255,7 +262,15 @@ describe(__filename, function () {
|
||||||
.expect((res:any) => assert(res.body.length >= 9100));
|
.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`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.set("authorization", await common.generateJWTToken())
|
.set("authorization", await common.generateJWTToken())
|
||||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
||||||
|
|
@ -276,7 +291,7 @@ describe(__filename, function () {
|
||||||
.expect((res:any) => assert(res.body.length >= 1000));
|
.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`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.set("authorization", await common.generateJWTToken())
|
.set("authorization", await common.generateJWTToken())
|
||||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
||||||
|
|
@ -296,7 +311,7 @@ describe(__filename, function () {
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect((res:any) => assert(res.body.length >= 7000));
|
.expect((res:any) => assert(res.body.length >= 7000));
|
||||||
});
|
});
|
||||||
}); // End of AbiWord/LibreOffice tests.
|
}); // End of LibreOffice tests.
|
||||||
|
|
||||||
it('Tries to import .etherpad', async function () {
|
it('Tries to import .etherpad', async function () {
|
||||||
this.timeout(3000);
|
this.timeout(3000);
|
||||||
|
|
|
||||||
|
|
@ -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/}"]}]}}
|
{"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/}"]}]}}
|
||||||
|
|
@ -402,7 +402,6 @@
|
||||||
<div class="acl-write">
|
<div class="acl-write">
|
||||||
|
|
||||||
<h2 data-l10n-id="pad.importExport.import"></h2>
|
<h2 data-l10n-id="pad.importExport.import"></h2>
|
||||||
<div class="importmessage" id="importmessageabiword" data-l10n-id="pad.importExport.abiword.innerHTML"></div><br>
|
|
||||||
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
||||||
<div class="importformdiv" id="importformfilediv">
|
<div class="importformdiv" id="importformfilediv">
|
||||||
<input type="file" name="file" size="10" id="importfileinput">
|
<input type="file" name="file" size="10" id="importfileinput">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue