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) => {
|
exports.doImport = async (req, res, padId) => {
|
||||||
let status = 'ok';
|
let httpStatus = 200;
|
||||||
|
let code = 0;
|
||||||
|
let message = 'ok';
|
||||||
let directDatabaseAccess;
|
let directDatabaseAccess;
|
||||||
try {
|
try {
|
||||||
directDatabaseAccess = await doImport(req, res, padId);
|
directDatabaseAccess = await doImport(req, res, padId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!(err instanceof ImportError) || !err.status) throw err;
|
const known = err instanceof ImportError && err.status;
|
||||||
status = 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.status(httpStatus).json({code, message, data: {directDatabaseAccess}});
|
||||||
res.send([
|
|
||||||
'<script>',
|
|
||||||
"document.addEventListener('DOMContentLoaded', () => {",
|
|
||||||
' window.parent.padimpexp.handleFrameCall(',
|
|
||||||
` ${JSON.stringify(directDatabaseAccess)}, ${JSON.stringify(status)});`,
|
|
||||||
'});',
|
|
||||||
'</script>',
|
|
||||||
].join('\n'));
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const padimpexp = (() => {
|
const padimpexp = (() => {
|
||||||
// /// import
|
let pad;
|
||||||
let currentImportTimer = null;
|
|
||||||
|
|
||||||
|
// /// import
|
||||||
const addImportFrames = () => {
|
const addImportFrames = () => {
|
||||||
$('#import .importframe').remove();
|
$('#import .importframe').remove();
|
||||||
const iframe = $('<iframe>')
|
const iframe = $('<iframe>')
|
||||||
|
@ -42,35 +42,38 @@ const padimpexp = (() => {
|
||||||
$('#importmessagefail').fadeOut('fast');
|
$('#importmessagefail').fadeOut('fast');
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileInputSubmit = () => {
|
const fileInputSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
$('#importmessagefail').fadeOut('fast');
|
$('#importmessagefail').fadeOut('fast');
|
||||||
if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) return false;
|
if (!window.confirm(html10n.get('pad.impexp.confirmimport'))) return;
|
||||||
currentImportTimer = window.setTimeout(() => {
|
|
||||||
if (!currentImportTimer) return;
|
|
||||||
currentImportTimer = null;
|
|
||||||
importErrorMessage('Request timed out.');
|
|
||||||
importDone();
|
|
||||||
}, 25000); // time out after some number of seconds
|
|
||||||
$('#importsubmitinput').attr({disabled: true}).val(html10n.get('pad.impexp.importing'));
|
$('#importsubmitinput').attr({disabled: true}).val(html10n.get('pad.impexp.importing'));
|
||||||
window.setTimeout(() => $('#importfileinput').attr({disabled: true}), 0);
|
window.setTimeout(() => $('#importfileinput').attr({disabled: true}), 0);
|
||||||
$('#importarrow').stop(true, true).hide();
|
$('#importarrow').stop(true, true).hide();
|
||||||
$('#importstatusball').show();
|
$('#importstatusball').show();
|
||||||
return true;
|
(async () => {
|
||||||
};
|
const {code, message, data: {directDatabaseAccess} = {}} = await $.ajax({
|
||||||
|
url: `${window.location.href.split('?')[0].split('#')[0]}/import`,
|
||||||
const importDone = () => {
|
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'));
|
$('#importsubmitinput').removeAttr('disabled').val(html10n.get('pad.impexp.importbutton'));
|
||||||
window.setTimeout(() => $('#importfileinput').removeAttr('disabled'), 0);
|
window.setTimeout(() => $('#importfileinput').removeAttr('disabled'), 0);
|
||||||
$('#importstatusball').hide();
|
$('#importstatusball').hide();
|
||||||
importClearTimeout();
|
|
||||||
addImportFrames();
|
addImportFrames();
|
||||||
};
|
})();
|
||||||
|
|
||||||
const importClearTimeout = () => {
|
|
||||||
if (currentImportTimer) {
|
|
||||||
window.clearTimeout(currentImportTimer);
|
|
||||||
currentImportTimer = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const importErrorMessage = (status) => {
|
const importErrorMessage = (status) => {
|
||||||
|
@ -118,7 +121,6 @@ const padimpexp = (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ///
|
// ///
|
||||||
let pad = undefined;
|
|
||||||
const self = {
|
const self = {
|
||||||
init: (_pad) => {
|
init: (_pad) => {
|
||||||
pad = _pad;
|
pad = _pad;
|
||||||
|
@ -126,9 +128,6 @@ const padimpexp = (() => {
|
||||||
// get /p/padname
|
// get /p/padname
|
||||||
// if /p/ isn't available due to a rewrite we use the clientVars padId
|
// if /p/ isn't available due to a rewrite we use the clientVars padId
|
||||||
const padRootPath = /.*\/p\/[^/]+/.exec(document.location.pathname) || 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
|
// i10l buttom import
|
||||||
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
|
$('#importsubmitinput').val(html10n.get('pad.impexp.importbutton'));
|
||||||
|
@ -141,9 +140,6 @@ 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`);
|
||||||
|
|
||||||
// activate action to import in the form
|
|
||||||
$('#importform').attr('action', `${padRootUrl}/import`);
|
|
||||||
|
|
||||||
// hide stuff thats not avaible if abiword/soffice is disabled
|
// hide stuff thats not avaible if abiword/soffice is disabled
|
||||||
if (clientVars.exportAvailable === 'no') {
|
if (clientVars.exportAvailable === 'no') {
|
||||||
$('#exportworda').remove();
|
$('#exportworda').remove();
|
||||||
|
@ -170,21 +166,6 @@ const padimpexp = (() => {
|
||||||
$('#importform').unbind('submit').submit(fileInputSubmit);
|
$('#importform').unbind('submit').submit(fileInputSubmit);
|
||||||
$('.disabledexport').click(cantExport);
|
$('.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: () => {
|
disable: () => {
|
||||||
$('#impexp-disabled-clickcatcher').show();
|
$('#impexp-disabled-clickcatcher').show();
|
||||||
$('#import').css('opacity', 0.5);
|
$('#import').css('opacity', 0.5);
|
||||||
|
|
|
@ -127,6 +127,8 @@ describe(__filename, function () {
|
||||||
|
|
||||||
|
|
||||||
describe('Import/Export tests requiring AbiWord/LibreOffice', function () {
|
describe('Import/Export tests requiring AbiWord/LibreOffice', function () {
|
||||||
|
this.timeout(60000);
|
||||||
|
|
||||||
before(async function () {
|
before(async function () {
|
||||||
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
||||||
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
||||||
|
@ -141,7 +143,12 @@ describe(__filename, function () {
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
||||||
.expect(200)
|
.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 () {
|
it('exports DOC', async function () {
|
||||||
|
@ -161,7 +168,12 @@ describe(__filename, function () {
|
||||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
})
|
})
|
||||||
.expect(200)
|
.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 () {
|
it('exports DOC from imported DOCX', async function () {
|
||||||
|
@ -177,7 +189,12 @@ describe(__filename, function () {
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
||||||
.expect(200)
|
.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 () {
|
it('exports PDF', async function () {
|
||||||
|
@ -193,7 +210,12 @@ describe(__filename, function () {
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
||||||
.expect(200)
|
.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 () {
|
it('exports ODT', async function () {
|
||||||
|
@ -213,7 +235,12 @@ describe(__filename, function () {
|
||||||
contentType: 'application/etherpad',
|
contentType: 'application/etherpad',
|
||||||
})
|
})
|
||||||
.expect(200)
|
.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 () {
|
it('exports Etherpad', async function () {
|
||||||
|
@ -237,8 +264,12 @@ describe(__filename, function () {
|
||||||
settings.allowUnknownFileEnds = false;
|
settings.allowUnknownFileEnds = false;
|
||||||
await agent.post(`/p/${testPadId}/import`)
|
await agent.post(`/p/${testPadId}/import`)
|
||||||
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
||||||
.expect(200)
|
.expect(400)
|
||||||
.expect((res) => assert.doesNotMatch(res.text, /FrameCall\('undefined', 'ok'\);/));
|
.expect('Content-Type', /json/)
|
||||||
|
.expect((res) => {
|
||||||
|
assert.equal(res.body.code, 1);
|
||||||
|
assert.equal(res.body.message, 'uploadFailed');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Import authorization checks', function () {
|
describe('Import authorization checks', function () {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue