diff --git a/customize.dist/ckeditor-contents.css b/customize.dist/ckeditor-contents.css
index cca5d1d20..d37f17f5c 100644
--- a/customize.dist/ckeditor-contents.css
+++ b/customize.dist/ckeditor-contents.css
@@ -52,6 +52,12 @@ body > .non-realtime:first-child + * {
margin-top: 0;
}
+@media (max-width: 600px) {
+ body {
+ padding-bottom: 100px;
+ }
+}
+
.cke_editable
{
font-size: 16px;
diff --git a/customize.dist/pages/install.js b/customize.dist/pages/install.js
index 7bbf9e4cd..dbe68b308 100644
--- a/customize.dist/pages/install.js
+++ b/customize.dist/pages/install.js
@@ -14,11 +14,6 @@ define([
return;
}
-/*
-Msg.install_header = "CryptPad Install"; // XXX
-Msg.install_notes = "
- Create your first admin account using this form.
" +
- "- Please note your password carefully. If you lose it there is no way we can recover your data.
"; // XXX
-*/
Msg.install_token = "Install token";
document.title = Msg.install_header;
diff --git a/lib/decrees.js b/lib/decrees.js
index 7afac7578..c7fd79f8a 100644
--- a/lib/decrees.js
+++ b/lib/decrees.js
@@ -329,8 +329,6 @@ commands.ADD_INSTALL_TOKEN = function (Env, args) {
var token = args[0];
- // XXX check length, etc. ?
-
Env.installToken = token;
return true;
diff --git a/scripts/build.js b/scripts/build.js
index 1f63f50aa..b0c0b5597 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -229,7 +229,7 @@ appIndexesToBuild.forEach(function (app) {
write(built, `./www/${app}/index.html`);
- // XXX preloading version for inner.html
+ // TODO preloading version for inner.html
});
var instance;
diff --git a/scripts/install.js b/scripts/install.js
index 0587ce789..c0e57a5eb 100644
--- a/scripts/install.js
+++ b/scripts/install.js
@@ -22,7 +22,6 @@ nThen(function (w) {
console.log('Existing token');
token = Env.installToken;
}
- // XXX IF ADMINS ABORT?
}));
}).nThen(function (w) {
if (Env.installToken) { return; }
diff --git a/www/calendar/inner.js b/www/calendar/inner.js
index 14a6c2333..bee31eab9 100644
--- a/www/calendar/inner.js
+++ b/www/calendar/inner.js
@@ -1397,8 +1397,8 @@ ICS ==> create a new event with the same UID and a RECURRENCE-ID field (with a v
if (updatedOn) { delete APP.recurrenceRule._next; }
APP.wasRecurrent = Boolean(APP.recurrenceRule);
-// XXX TEST
/*
+// Test data:
APP.recurrenceRule = {
freq: 'yearly',
interval: 2,
diff --git a/www/code/app-code.less b/www/code/app-code.less
index 406f02cd0..ab9f0129a 100644
--- a/www/code/app-code.less
+++ b/www/code/app-code.less
@@ -146,6 +146,9 @@
flex: 1;
max-width: 100%;
resize: none;
+ .CodeMirror-sizer > div {
+ padding-bottom: 100px;
+ }
}
#cp-app-code-preview {
display: none !important;
diff --git a/www/common/inner/access.js b/www/common/inner/access.js
index 58d388271..70de82b99 100644
--- a/www/common/inner/access.js
+++ b/www/common/inner/access.js
@@ -332,6 +332,12 @@ define([
: Messages.error;
return void UI.warn(text);
}
+ sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
+ if (err || (res && res.error)) {
+ return void console.error(err || res.error);
+ }
+ UI.log(Messages.saved);
+ });
}));
}
}).nThen(function (waitFor) {
@@ -867,7 +873,7 @@ define([
// In the properties, we should have the edit href if we know it.
// We should know it because the pad is stored, but it's better to check...
//if (!data.noEditPassword && !opts.noEditPassword && owned && data.href) {
- if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // XXX password change in forms block responses (validation & decryption)
+ if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption)
var isOO = parsed.type === 'sheet';
var isFile = parsed.hashData.type === 'file';
var isSharedFolder = parsed.type === 'drive';
diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js
index 3e06c4818..8dca48b31 100644
--- a/www/common/onlyoffice/inner.js
+++ b/www/common/onlyoffice/inner.js
@@ -2134,9 +2134,7 @@ Uncaught TypeError: Cannot read property 'calculatedType' of null
var exportXLSXFile = function() {
var text = getContent();
var suggestion = Title.suggestTitle(Title.defaultTitle);
- var ext = ['.xlsx', '.ods', '.bin',
- //'.csv', // XXX 4.11.0
- '.pdf'];
+ var ext = ['.xlsx', '.ods', '.bin', '.pdf'];
var type = common.getMetadataMgr().getPrivateData().ooType;
var warning = '';
if (type==="presentation") {
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 327309f20..901487dbc 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -1694,7 +1694,7 @@ define([
var ed = Util.find(store, ['proxy', 'teams', teamId, 'keys', 'drive', 'edPublic']);
var edPrivate = Util.find(store, ['proxy', 'teams', teamId, 'keys', 'drive', 'edPrivate']);
if (allowed.indexOf(ed) === -1) { return false; }
- if (!edPrivate) { return false; } // XXX: Only editors can authenticate...
+ if (!edPrivate) { return false; } // FIXME: Only editors can authenticate...
// This team is allowed: use its rpc
var t = teamModule.getTeam(teamId);
_store = t;
@@ -1952,11 +1952,15 @@ define([
// contactPadOwner is used to send "REQUEST_ACCESS" messages
// and to notify form owners when sending a response
Store.contactPadOwner = function (clientId, data, cb) {
- var owner = data.owner;
+ var owners = data.owners;
// If send is true, send the request to the owner.
- if (owner) {
- if (data.send) {
+ if (!Array.isArray(owners) || !owners.length) { return cb({state: false}); }
+
+ if (!data.send) { return void cb({state: true}); }
+
+ nThen(function (waitFor) {
+ owners.forEach(function (owner) {
var sendTo = function (query, msg, user, _cb) {
if (store.mailbox && !data.anon) {
return store.mailbox.sendTo(query, msg, user, _cb);
@@ -1969,14 +1973,11 @@ define([
}, {
channel: owner.notifications,
curvePublic: owner.curvePublic
- }, function () {
- cb({state: true});
- });
- return;
- }
- return void cb({state: true});
- }
- cb({state: false});
+ }, waitFor());
+ });
+ }).nThen(function () {
+ cb({state: true});
+ });
};
Store.givePadAccess = function (clientId, data, cb) {
var edPublic = store.proxy.edPublic;
diff --git a/www/common/outer/calendar.js b/www/common/outer/calendar.js
index c09208110..486e10dd9 100644
--- a/www/common/outer/calendar.js
+++ b/www/common/outer/calendar.js
@@ -8,9 +8,10 @@ define([
'/customize/messages.js',
'/bower_components/nthen/index.js',
'chainpad-listmap',
+ '/lib/datepicker/flatpickr.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js',
-], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, Crypto, ChainPad) {
+], function (Util, Hash, Constants, Realtime, Cache, Rec, Messages, nThen, Listmap, FP, Crypto, ChainPad) {
var Calendar = {};
var getStore = function (ctx, id) {
@@ -131,9 +132,9 @@ define([
var last = ctx.store.data.lastVisit;
if (ev.isAllDay) {
- if (ev.startDay) { ev.start = +new Date(ev.startDay); }
+ if (ev.startDay) { ev.start = +FP.parseDate(ev.startDay); }
if (ev.endDay) {
- var endDate = new Date(ev.endDay);
+ var endDate = FP.parseDate(ev.endDay);
endDate.setHours(23);
endDate.setMinutes(59);
endDate.setSeconds(59);
@@ -223,7 +224,7 @@ define([
};
var addReminders = function (ctx, id, ev) {
var calendar = ctx.calendars[id];
- if (!ev) { return; } // XXX deleted event remote: delete reminders
+ if (!ev) { return; }
if (!calendar || !calendar.reminders) { return; }
if (calendar.stores.length === 1 && calendar.stores[0] === 0) { return; }
@@ -1063,7 +1064,6 @@ define([
Calendar.init = function (cfg, waitFor, emit) {
var calendar = {};
var store = cfg.store;
- //if (!store.loggedIn || !store.proxy.edPublic) { return; } // XXX logged in only? we should al least allow read-only for URL calendars
var ctx = {
loggedIn: store.loggedIn && store.proxy.edPublic,
store: store,
diff --git a/www/common/outer/roster.js b/www/common/outer/roster.js
index 93f0880f4..0b72dd6a4 100644
--- a/www/common/outer/roster.js
+++ b/www/common/outer/roster.js
@@ -470,7 +470,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
delete clone.previewChannel;
members[curve] = clone;
- // XXX
var remaining = members[author].remaining || 1;
if (remaining === -1) { return true; } // Infinite uses, keep the link
if (remaining > 1) { // Remove 1 use
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
index 0a9a6c407..84d64401e 100644
--- a/www/common/outer/userObject.js
+++ b/www/common/outer/userObject.js
@@ -856,7 +856,7 @@ define([
}
}
if (!Hash.isValidChannel(el.channel)) {
- // XXX delete channel? replace with parsed.channel?
+ // FIXME delete channel? replace with parsed.channel?
console.error('Remove invalid channel', el.channel, el);
// toClean.push(id);
}
diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js
index b4c985dc9..60147c245 100644
--- a/www/common/proxy-manager.js
+++ b/www/common/proxy-manager.js
@@ -249,7 +249,7 @@ define([
var obj = Util.clone(proxy.metadata || {});
for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) {
- if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // XXX "deleted folder" for restricted shared folders when viewer in a team
+ if (typeof(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]) === "undefined") { // TODO "deleted folder" for restricted shared folders when viewer in a team
continue;
}
var data = Util.clone(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k]);
diff --git a/www/common/sframe-common-codemirror.js b/www/common/sframe-common-codemirror.js
index 316a49da5..e245d9cff 100644
--- a/www/common/sframe-common-codemirror.js
+++ b/www/common/sframe-common-codemirror.js
@@ -140,10 +140,15 @@ define([
return text.trim();
};
+ var isMobile = /Android|iPhone/i.test(navigator.userAgent);
+
module.mkIndentSettings = function (editor, metadataMgr) {
var setIndentation = function (units, useTabs, fontSize, spellcheck, brackets) {
if (typeof(units) !== 'number') { return; }
var doc = editor.getDoc();
+ if (isMobile && fontSize < 16) {
+ fontSize = 16;
+ }
editor.setOption('indentUnit', units);
editor.setOption('tabSize', units);
editor.setOption('indentWithTabs', useTabs);
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 219dc915d..038fd4412 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -943,6 +943,50 @@ define([
}, href);
});
+ sframeChan.on('Q_ACCEPT_OWNERSHIP', function (data, cb) {
+ var parsed = Utils.Hash.parsePadUrl(data.href);
+ if (parsed.type === 'drive') {
+ // Shared folder
+ var secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, data.password);
+ Cryptpad.addSharedFolder(null, secret, cb);
+ } else {
+ var _data = {
+ password: data.password,
+ href: data.href,
+ channel: data.channel,
+ title: data.title,
+ owners: data.metadata ? data.metadata.owners : data.owners,
+ expire: data.metadata ? data.metadata.expire : data.expire,
+ forceSave: true
+ };
+ Cryptpad.setPadTitle(_data, function (err) {
+ cb({error: err});
+ });
+ }
+
+ // Also add your mailbox to the metadata object
+ var padParsed = Utils.Hash.parsePadUrl(data.href);
+ var padSecret = Utils.Hash.getSecrets(padParsed.type, padParsed.hash, data.password);
+ var padCrypto = Utils.Crypto.createEncryptor(padSecret.keys);
+ try {
+ var value = {};
+ value[edPublic] = padCrypto.encrypt(JSON.stringify({
+ notifications: notifications,
+ curvePublic: curvePublic
+ }));
+ var msg = {
+ channel: data.channel,
+ command: 'ADD_MAILBOX',
+ value: value
+ };
+ Cryptpad.setPadMetadata(msg, function (res) {
+ if (res.error) { console.error(res.error); }
+ });
+ } catch (err) {
+ return void console.error(err);
+ }
+ });
+
// Add or remove our mailbox from the list if we're an owner
sframeChan.on('Q_UPDATE_MAILBOX', function (data, cb) {
var metadata = data.metadata;
@@ -1024,7 +1068,7 @@ define([
}
var send = data.send;
var metadata = data.metadata;
- var owner, owners;
+ var owners = [];
var _secret = secret;
if (metadata && metadata.roHref) {
var _parsed = Utils.Hash.parsePadUrl(metadata.roHref);
@@ -1037,24 +1081,24 @@ define([
nThen(function (waitFor) {
// Try to get the owner's mailbox from the pad metadata first.
var todo = function (obj) {
- owners = obj.owners;
-
- var mailbox;
- // Get the first available mailbox (the field can be an string or an object)
- // TODO maybe we should send the request to all the owners?
- if (typeof (obj.mailbox) === "string") {
- mailbox = obj.mailbox;
- } else if (obj.mailbox && obj.owners && obj.owners.length) {
- mailbox = obj.mailbox[obj.owners[0]];
- }
- if (mailbox) {
+ var decrypt = function (mailbox) {
try {
var dataStr = crypto.decrypt(mailbox, true, true);
var data = JSON.parse(dataStr);
if (!data.notifications || !data.curvePublic) { return; }
- owner = data;
+ return data;
} catch (e) { console.error(e); }
+ };
+ if (typeof (obj.mailbox) === "string") {
+ owners = [decrypt(obj.mailbox)];
+ return;
}
+ if (!obj.mailbox || !obj.owners || !obj.owners.length) { return; }
+ owners = obj.owners.map(function (edPublic) {
+ var mailbox = obj.mailbox[edPublic];
+ if (typeof(mailbox) !== "string") { return; }
+ return decrypt(mailbox);
+ }).filter(Boolean);
};
// If we already have metadata, use it, otherwise, try to get it
@@ -1069,7 +1113,7 @@ define([
}));
}).nThen(function () {
// If we are just checking (send === false) and there is a mailbox field, cb state true
- if (!send) { return void cb({state: Boolean(owner)}); }
+ if (!send) { return void cb({state: Boolean(owners.length)}); }
Cryptpad.padRpc.contactOwner({
send: send,
@@ -1077,7 +1121,6 @@ define([
query: data.query,
msgData: data.msgData,
channel: _secret.channel,
- owner: owner,
owners: owners
}, cb);
});
@@ -1238,50 +1281,6 @@ define([
});
});
- sframeChan.on('Q_ACCEPT_OWNERSHIP', function (data, cb) {
- var parsed = Utils.Hash.parsePadUrl(data.href);
- if (parsed.type === 'drive') {
- // Shared folder
- var secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, data.password);
- Cryptpad.addSharedFolder(null, secret, cb);
- } else {
- var _data = {
- password: data.password,
- href: data.href,
- channel: data.channel,
- title: data.title,
- owners: data.metadata.owners,
- expire: data.metadata.expire,
- forceSave: true
- };
- Cryptpad.setPadTitle(_data, function (err) {
- cb({error: err});
- });
- }
-
- // Also add your mailbox to the metadata object
- var padParsed = Utils.Hash.parsePadUrl(data.href);
- var padSecret = Utils.Hash.getSecrets(padParsed.type, padParsed.hash, data.password);
- var padCrypto = Utils.Crypto.createEncryptor(padSecret.keys);
- try {
- var value = {};
- value[edPublic] = padCrypto.encrypt(JSON.stringify({
- notifications: notifications,
- curvePublic: curvePublic
- }));
- var msg = {
- channel: data.channel,
- command: 'ADD_MAILBOX',
- value: value
- };
- Cryptpad.setPadMetadata(msg, function (res) {
- if (res.error) { console.error(res.error); }
- });
- } catch (err) {
- return void console.error(err);
- }
- });
-
sframeChan.on('Q_IMPORT_MEDIATAG', function (obj, cb) {
var key = obj.key;
var channel = obj.channel;
diff --git a/www/common/test.js b/www/common/test.js
index 9600df0e1..eb83ff09c 100644
--- a/www/common/test.js
+++ b/www/common/test.js
@@ -1,7 +1,6 @@
define([], function () {
if (window.__CRYPTPAD_TEST_OBJ_) { return window.__CRYPTPAD_TEST_OBJ_; }
/*
- // XXX localhost secureiframe fix
var out = function () {};
out.options = {};
out.testing = false;
diff --git a/www/convert/inner.js b/www/convert/inner.js
index 4db50209c..e09b1f718 100644
--- a/www/convert/inner.js
+++ b/www/convert/inner.js
@@ -294,6 +294,7 @@ define([
Messages.convertPage = "Convert"; // XXX 4.11.0
Messages.convert_hint = "Pick the file you want to convert. The list of output format will be visible afterwards."; // XXX 4.11.0
+ Messages.convert_unsupported = "UNSUPPORTED FILE TYPE :("; // XXX
var createToolbar = function () {
var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle', 'notifications'];
@@ -328,7 +329,6 @@ define([
type: 'file'
});
APP.$rightside.append([hint, picker]);
- Messages.convert_unsupported = "UNSUPPORTED FILE TYPE :("; // XXX
$(picker).on('change', function () {
APP.$rightside.find('button, div.notice').remove();
diff --git a/www/form/inner.js b/www/form/inner.js
index 11f29ba20..d99fb4fef 100644
--- a/www/form/inner.js
+++ b/www/form/inner.js
@@ -4005,7 +4005,7 @@ define([
$container.empty().append(_content);
-// XXX Delete key form_updateMsg
+// XXX Delete translation key form_updateMsg
if (editable) {
var responseMsg = h('div.cp-form-response-msg-container');
var $responseMsg = $(responseMsg).appendTo($container);
diff --git a/www/form/main.js b/www/form/main.js
index 4ef169869..ccb46bbaf 100644
--- a/www/form/main.js
+++ b/www/form/main.js
@@ -314,7 +314,7 @@ define([
if (obj && obj.error) { err = obj.error; return; }
var messages = obj.messages;
if (!messages.length) {
- // XXX TODO delete from drive.forms
+ // TODO delete from drive.forms?
return;
}
if (obj.lastKnownHash !== answer.hash) { return; }
diff --git a/www/pad/comments.js b/www/pad/comments.js
index 6280a72bf..edfb40957 100644
--- a/www/pad/comments.js
+++ b/www/pad/comments.js
@@ -79,7 +79,7 @@ define([
Env.metadataMgr.updateMetadata(md);
};
- var sendReplyNotification = function(Env, uid) {
+ var sendReplyNotification = function(Env, uid, mentionedCurve) {
if (!Env.comments || !Env.comments.data || !Env.comments.authors) { return; }
if (!Env.common.isLoggedIn()) { return; }
var thread = Env.comments.data[uid];
@@ -88,8 +88,6 @@ define([
var privateData = Env.metadataMgr.getPrivateData();
var others = {};
-
- // XXX mentioned users should be excluded from the list of notified recipients to avoid notifying them twice
// Get all the other registered users with a mailbox
thread.m.forEach(function(obj) {
var u = obj.u;
@@ -97,6 +95,9 @@ define([
var author = Env.comments.authors[u];
if (!author || others[u] || !author.notifications || !author.curvePublic) { return; }
if (author.curvePublic === userData.curvePublic) { return; } // don't send to yourself
+ if (Object.keys(mentionedCurve || {}).includes(author.curvePublic)) {
+ return; // Don't send to mentioned users
+ }
others[u] = {
curvePublic: author.curvePublic,
comment: obj.m,
@@ -203,7 +204,7 @@ define([
});
// Push the content
- cb(content);
+ cb(content, notify);
});
$(cancel).click(function(e) {
e.stopPropagation();
@@ -525,7 +526,7 @@ define([
$(reply).click(function(e) {
e.stopPropagation();
$actions.hide();
- var form = getCommentForm(Env, key, function(val) {
+ var form = getCommentForm(Env, key, function(val, mentioned) {
// Show the "reply" and "resolve" buttons again
$(form).closest('.cp-comment-container')
.find('.cp-comment-actions').css('display', '');
@@ -551,7 +552,7 @@ define([
});
// Notify other users
- sendReplyNotification(Env, key);
+ sendReplyNotification(Env, key, mentioned);
// Send to chainpad
updateMetadata(Env);
diff --git a/www/pad/inner.js b/www/pad/inner.js
index 705350554..1958a1ea0 100644
--- a/www/pad/inner.js
+++ b/www/pad/inner.js
@@ -903,6 +903,10 @@ define([
$toc.addClass('hidden');
localHide = true;
if (store) { store.put(key, '1'); }
+
+ if (APP.tocScroll) {
+ APP.tocScroll();
+ }
});
$(showBtn).click(function () {
$toc.removeClass('hidden');
@@ -922,6 +926,23 @@ define([
e.stopPropagation();
if (!obj.el || UIElements.isVisible(obj.el, $contentContainer)) { return; }
obj.el.scrollIntoView();
+ var $iframe = $('iframe').contents();
+ var onScroll = function () {
+ APP.tocScrollOff();
+ };
+ APP.tocScrollOff = function () {
+ delete APP.tocScroll;
+ delete APP.tocScrollOff;
+ $iframe.off('scroll', onScroll);
+ };
+ APP.tocScroll = function () {
+ obj.el.scrollIntoView();
+ APP.tocScrollOff();
+ };
+ //$(window).on('scroll', onScroll);
+ setTimeout(function () {
+ $iframe.on('scroll', onScroll);
+ });
});
a.innerHTML = title;
content.push(h('p.cp-pad-toc-'+level, a));
@@ -1003,6 +1024,8 @@ define([
if (scrollMax) {
$iframe.scrollTop($iframe.innerHeight());
}
+
+ if (APP.tocScrollOff) { APP.tocScrollOff(); }
});
framework.setTextContentGetter(function() {
@@ -1017,6 +1040,8 @@ define([
return str;
});
framework.setContentGetter(function() {
+ if (APP.tocScrollOff) { APP.tocScrollOff(); }
+
$inner.find('span[data-cke-display-name="media-tag"]:empty').each(function(i, el) {
$(el).remove();
});
diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js
index 8c33a2ff1..5ba4b7657 100644
--- a/www/secureiframe/main.js
+++ b/www/secureiframe/main.js
@@ -28,7 +28,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- // XXX extra sandboxing features are temporarily disabled as I suspect this is the cause of a regression in Safari
+ // FIXME extra sandboxing features are temporarily disabled as I suspect this is the cause of a regression in Safari
$('#sbox-secure-iframe')/*.attr('sandbox', 'allow-scripts allow-popups allow-modals')*/.attr('src',
ApiConfig.httpSafeOrigin + '/secureiframe/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
diff --git a/www/slide/app-slide.less b/www/slide/app-slide.less
index ce12b1de3..1c246f8b3 100644
--- a/www/slide/app-slide.less
+++ b/www/slide/app-slide.less
@@ -147,6 +147,16 @@
}
}
+ @media (max-width: @browser_media-medium-screen) {
+ #cp-app-slide-editor {
+ #cp-app-slide-editor-container {
+ .CodeMirror-sizer > div {
+ padding-bottom: 100px;
+ }
+ }
+ }
+ }
+
/* Slide position (print mode) */
@ratio:0.9;
@media print {