mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
import: Ajaxify pad import
This eliminates an inline script (good for Content Security Policy) and improves the user experience.
This commit is contained in:
parent
fba55fa6cf
commit
b711ff6acf
3 changed files with 76 additions and 67 deletions
|
@ -258,21 +258,18 @@ const doImport = async (req, res, padId) => {
|
|||
};
|
||||
|
||||
exports.doImport = async (req, res, padId) => {
|
||||
let status = 'ok';
|
||||
let httpStatus = 200;
|
||||
let code = 0;
|
||||
let message = 'ok';
|
||||
let directDatabaseAccess;
|
||||
try {
|
||||
directDatabaseAccess = await doImport(req, res, padId);
|
||||
} catch (err) {
|
||||
if (!(err instanceof ImportError) || !err.status) throw err;
|
||||
status = err.status;
|
||||
const known = err instanceof ImportError && err.status;
|
||||
if (!known) logger.error(`Internal error during import: ${err.stack || err}`);
|
||||
httpStatus = known ? 400 : 500;
|
||||
code = known ? 1 : 2;
|
||||
message = known ? err.status : 'internalError';
|
||||
}
|
||||
// close the connection
|
||||
res.send([
|
||||
'<script>',
|
||||
"document.addEventListener('DOMContentLoaded', () => {",
|
||||
' window.parent.padimpexp.handleFrameCall(',
|
||||
` ${JSON.stringify(directDatabaseAccess)}, ${JSON.stringify(status)});`,
|
||||
'});',
|
||||
'</script>',
|
||||
].join('\n'));
|
||||
res.status(httpStatus).json({code, message, data: {directDatabaseAccess}});
|
||||
};
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
*/
|
||||
|
||||
const padimpexp = (() => {
|
||||
// /// import
|
||||
let currentImportTimer = null;
|
||||
let pad;
|
||||
|
||||
// /// import
|
||||
const addImportFrames = () => {
|
||||
$('#import .importframe').remove();
|
||||
const iframe = $('<iframe>')
|
||||
|
@ -42,35 +42,38 @@ const padimpexp = (() => {
|
|||
$('#importmessagefail').fadeOut('fast');
|
||||
};
|
||||
|
||||
const fileInputSubmit = () => {
|
||||
const fileInputSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
$('#importmessagefail').fadeOut('fast');
|
||||
if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) return false;
|
||||
currentImportTimer = window.setTimeout(() => {
|
||||
if (!currentImportTimer) return;
|
||||
currentImportTimer = null;
|
||||
importErrorMessage('Request timed out.');
|
||||
importDone();
|
||||
}, 25000); // time out after some number of seconds
|
||||
if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) return;
|
||||
$('#importsubmitinput').attr({disabled: true}).val(html10n.get('pad.impexp.importing'));
|
||||
window.setTimeout(() => $('#importfileinput').attr({disabled: true}), 0);
|
||||
$('#importarrow').stop(true, true).hide();
|
||||
$('#importstatusball').show();
|
||||
return true;
|
||||
};
|
||||
|
||||
const importDone = () => {
|
||||
(async () => {
|
||||
const {code, message, data: {directDatabaseAccess} = {}} = await $.ajax({
|
||||
url: `${window.location.href.split('?')[0].split('#')[0]}/import`,
|
||||
method: 'POST',
|
||||
data: new FormData(this),
|
||||
processData: false,
|
||||
contentType: false,
|
||||
dataType: 'json',
|
||||
timeout: 25000,
|
||||
}).catch((err) => {
|
||||
if (err.responseJSON) return err.responseJSON;
|
||||
return {code: 2, message: 'Unknown import error'};
|
||||
});
|
||||
if (code !== 0) {
|
||||
importErrorMessage(message);
|
||||
} else {
|
||||
$('#import_export').removeClass('popup-show');
|
||||
if (directDatabaseAccess) pad.switchToPad(clientVars.padId);
|
||||
}
|
||||
$('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton'));
|
||||
window.setTimeout(() => $('#importfileinput').removeAttr('disabled'), 0);
|
||||
$('#importstatusball').hide();
|
||||
importClearTimeout();
|
||||
addImportFrames();
|
||||
};
|
||||
|
||||
const importClearTimeout = () => {
|
||||
if (currentImportTimer) {
|
||||
window.clearTimeout(currentImportTimer);
|
||||
currentImportTimer = null;
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
const importErrorMessage = (status) => {
|
||||
|
@ -118,7 +121,6 @@ const padimpexp = (() => {
|
|||
}
|
||||
|
||||
// ///
|
||||
let pad = undefined;
|
||||
const self = {
|
||||
init: (_pad) => {
|
||||
pad = _pad;
|
||||
|
@ -126,9 +128,6 @@ const padimpexp = (() => {
|
|||
// get /p/padname
|
||||
// if /p/ isn't available due to a rewrite we use the clientVars padId
|
||||
const padRootPath = /.*\/p\/[^/]+/.exec(document.location.pathname) || clientVars.padId;
|
||||
// get http://example.com/p/padname without Params
|
||||
const dl = document.location;
|
||||
const padRootUrl = `${dl.protocol}//${dl.host}${dl.pathname}`;
|
||||
|
||||
// i10l buttom import
|
||||
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
|
||||
|
@ -141,9 +140,6 @@ const padimpexp = (() => {
|
|||
$('#exportetherpada').attr('href', `${padRootPath}/export/etherpad`);
|
||||
$('#exportplaina').attr('href', `${padRootPath}/export/txt`);
|
||||
|
||||
// activate action to import in the form
|
||||
$('#importform').attr('action', `${padRootUrl}/import`);
|
||||
|
||||
// hide stuff thats not avaible if abiword/soffice is disabled
|
||||
if (clientVars.exportAvailable === 'no') {
|
||||
$('#exportworda').remove();
|
||||
|
@ -170,21 +166,6 @@ const padimpexp = (() => {
|
|||
$('#importform').unbind('submit').submit(fileInputSubmit);
|
||||
$('.disabledexport').click(cantExport);
|
||||
},
|
||||
handleFrameCall: (directDatabaseAccess, status) => {
|
||||
if (status !== 'ok') {
|
||||
importErrorMessage(status);
|
||||
} else {
|
||||
$('#import_export').removeClass('popup-show');
|
||||
}
|
||||
|
||||
if (directDatabaseAccess) {
|
||||
// Switch to the pad without redrawing the page
|
||||
pad.switchToPad(clientVars.padId);
|
||||
$('#import_export').removeClass('popup-show');
|
||||
}
|
||||
|
||||
importDone();
|
||||
},
|
||||
disable: () => {
|
||||
$('#impexp-disabled-clickcatcher').show();
|
||||
$('#import').css('opacity', 0.5);
|
||||
|
|
|
@ -127,6 +127,8 @@ describe(__filename, function () {
|
|||
|
||||
|
||||
describe('Import/Export tests requiring AbiWord/LibreOffice', function () {
|
||||
this.timeout(60000);
|
||||
|
||||
before(async function () {
|
||||
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
||||
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
||||
|
@ -141,7 +143,12 @@ describe(__filename, function () {
|
|||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: false},
|
||||
}));
|
||||
});
|
||||
|
||||
it('exports DOC', async function () {
|
||||
|
@ -161,7 +168,12 @@ describe(__filename, function () {
|
|||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: false},
|
||||
}));
|
||||
});
|
||||
|
||||
it('exports DOC from imported DOCX', async function () {
|
||||
|
@ -177,7 +189,12 @@ describe(__filename, function () {
|
|||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: false},
|
||||
}));
|
||||
});
|
||||
|
||||
it('exports PDF', async function () {
|
||||
|
@ -193,7 +210,12 @@ describe(__filename, function () {
|
|||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: false},
|
||||
}));
|
||||
});
|
||||
|
||||
it('exports ODT', async function () {
|
||||
|
@ -213,7 +235,12 @@ describe(__filename, function () {
|
|||
contentType: 'application/etherpad',
|
||||
})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('true', 'ok'\);/);
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: true},
|
||||
}));
|
||||
});
|
||||
|
||||
it('exports Etherpad', async function () {
|
||||
|
@ -237,8 +264,12 @@ describe(__filename, function () {
|
|||
settings.allowUnknownFileEnds = false;
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
||||
.expect(200)
|
||||
.expect((res) => assert.doesNotMatch(res.text, /FrameCall\('undefined', 'ok'\);/));
|
||||
.expect(400)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => {
|
||||
assert.equal(res.body.code, 1);
|
||||
assert.equal(res.body.message, 'uploadFailed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import authorization checks', function () {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue