Merge branch 'staging' into sessionStorage

pull/1/head
yflory 4 years ago
commit 9f96b737e7

@ -278,14 +278,7 @@ button.primary:hover{
].join('');
var built = false;
// XXX
var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end'];
Messages.loading_state_0 = "Less";
Messages.loading_state_1 = "Drive";
Messages.loading_state_2 = "Migrate";
Messages.loading_state_3 = "SF";
Messages.loading_state_4 = "Team";
Messages.loading_state_5 = "Pad";
var current;
var makeList = function (data) {
var c = types.indexOf(data.type);
@ -333,6 +326,8 @@ button.primary:hover{
window.CryptPad_loadingError = function (err) {
if (!built) { return; }
try {
var node = document.querySelector('.cp-loading-progress');
if (node.parentNode) { node.parentNode.removeChild(node); }
document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;');
document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;');
document.querySelector('#cp-loading-message').innerText = err;

@ -9,10 +9,6 @@
max-height: none;
overflow: visible;
display: block;
@page {
margin: 0;
size: landscape;
}
// Slide app
body.cp-app-slide {
display: block;
@ -48,11 +44,15 @@
// Code app
body.cp-app-code {
display: block;
height: auto;
* {
visibility: hidden;
height: auto;
max-height: none;
}
.cp-toolbar-userlist-drawer {
display: none;
}
#cme_toolbox {
display: none;
}
@ -64,6 +64,7 @@
#cp-app-code-preview {
display: block;
#cp-app-code-print {
font-size: 20px;
display: block;
overflow: visible !important;
width: 100%;

@ -0,0 +1,5 @@
@page {
margin: 0;
size: A4 landscape;
}

@ -0,0 +1,4 @@
@page {
margin: 3cm;
size: A4 portrait;
}

@ -136,7 +136,6 @@
#cp-app-code-print {
position: relative;
display: none;
margin: 50px;
.markdown_preformatted-code;
.markdown_gfm-table(black);
}

@ -42,6 +42,7 @@ define([
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
'css!/customize/src/print.css',
'less!/code/app-code.less'
], function (

@ -43,6 +43,9 @@ define([
console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers");
return void console.log();
}
if (window.CryptPad_loadingError) {
window.CryptPad_loadingError(e);
}
throw e;
};

@ -281,12 +281,10 @@ define([
var $root = $t.parent();
Messages.add = "Add"; // XXX
Messages.edit = "Edit"; // XXX
var $input = $root.find('.token-input');
var $button = $(h('button.btn.btn-primary', [
h('i.fa.fa-plus'),
h('span', Messages.add)
h('span', Messages.tag_add)
]));
@ -311,7 +309,7 @@ define([
}
$form.append($input);
$form.append($button);
if (isEdit) { $button.find('span').text(Messages.edit); }
if (isEdit) { $button.find('span').text(Messages.tag_edit); }
else { $button.find('span').text(Messages.add); }
$container.append($form);
$input.focus();
@ -941,8 +939,6 @@ define([
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
if (config.newProgress) {
// XXX re-add progress bar for step 6 after password prompt for PPP
// XXX also burn after reading
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-list'),
h('p.cp-loading-progress-container')

@ -634,6 +634,7 @@ define([
button
.click(common.prepareFeedback(type))
.click(function () {
if (callback) { return void callback(); }
UIElements.openTemplatePicker(common, true);
});
break;
@ -2069,7 +2070,7 @@ define([
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var privateData = metadataMgr.getPrivateData();
var type = metadataMgr.getMetadataLazy().type;
var type = metadataMgr.getMetadataLazy().type || privateData.app;
var fromFileData = privateData.fromFileData;
var $body = $('body');

@ -688,6 +688,7 @@ define([
if (!val) {
return void cb('ENOENT');
}
if (data.oo) { return void cb(val); } // OnlyOffice template: are handled in inner
try {
// Try to fix the title before importing the template
var parsed = JSON.parse(val);

@ -114,6 +114,7 @@ define([
};
var getEditor = function () {
if (!window.frames || !window.frames[0]) { return; }
return window.frames[0].editor || window.frames[0].editorCell;
};
@ -416,7 +417,7 @@ define([
clearTimeout(pendingChanges[key]);
delete pendingChanges[key];
});
if (APP.stopHistory) { APP.history = false; }
if (APP.stopHistory || APP.template) { APP.history = false; }
startOO(blob, type, true);
};
@ -426,14 +427,15 @@ define([
var file = getFileType();
blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type;
var data = {
hash: APP.history ? ooChannel.historyLastHash : ooChannel.lastHash,
index: APP.history ? ooChannel.currentIndex : ooChannel.cpIndex
hash: (APP.history || APP.template) ? ooChannel.historyLastHash : ooChannel.lastHash,
index: (APP.history || APP.template) ? ooChannel.currentIndex : ooChannel.cpIndex
};
fixSheets();
ooChannel.ready = false;
ooChannel.queue = [];
data.callback = function () {
if (APP.template) { APP.template = false; }
resetData(blob, file);
};
@ -1292,6 +1294,13 @@ define([
}
}
if (APP.template) {
getEditor().setViewModeDisconnect();
UI.removeLoadingScreen();
makeCheckpoint(true);
return;
}
if (APP.history) {
try {
getEditor().asc_setRestriction(true);
@ -1825,6 +1834,108 @@ define([
pinImages();
};
var loadCp = function (cp, keepQueue) {
loadLastDocument(cp, function () {
var file = getFileType();
var type = common.getMetadataMgr().getPrivateData().ooType;
var blob = loadInitDocument(type, true);
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
}, function (blob, file) {
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
});
};
var loadTemplate = function (href, pw, parsed) {
APP.history = true;
APP.template = true;
var editor = getEditor();
if (editor) { editor.setViewModeDisconnect(); }
var content = parsed.content;
// Get checkpoint
var hashes = content.hashes || {};
var idx = sortCpIndex(hashes);
var lastIndex = idx[idx.length - 1];
var lastCp = hashes[lastIndex] || {};
// Current cp or initial hash (invalid hash ==> initial hash)
var toHash = lastCp.hash || 'NONE';
// Last hash
var fromHash = 'NONE';
sframeChan.query('Q_GET_HISTORY_RANGE', {
href: href,
password: pw,
channel: content.channel,
lastKnownHash: fromHash,
toHash: toHash,
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
// The first "cp" in history is the empty doc. It doesn't include the first patch
// of the history
var initialCp = !lastCp.hash;
var messages = (data.messages || []).slice(initialCp ? 0 : 1);
ooChannel.queue = messages.map(function (obj) {
return {
hash: obj.serverHash,
msg: JSON.parse(obj.msg)
};
});
ooChannel.historyLastHash = ooChannel.lastHash;
ooChannel.currentIndex = ooChannel.cpIndex;
loadCp(lastCp, true);
});
};
var openTemplatePicker = function () {
var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getPrivateData().app;
var sframeChan = common.getSframeChannel();
var pickerCfgInit = {
types: [type],
where: ['template'],
hidden: true
};
var pickerCfg = {
types: [type],
where: ['template'],
};
var onConfirm = function () {
common.openFilePicker(pickerCfg, function (data) {
if (data.type !== type) { return; }
UI.addLoadingScreen({hideTips: true});
sframeChan.query('Q_OO_TEMPLATE_USE', {
href: data.href,
}, function (err, val) {
var parsed;
try {
parsed = JSON.parse(val);
} catch (e) {
console.error(e, val);
UI.removeLoadingScreen();
return void UI.warn(Messages.error);
}
console.error(data);
loadTemplate(data.href, data.password, parsed);
});
});
};
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
if (data) {
common.openFilePicker(pickerCfgInit);
onConfirm();
} else {
UI.alert(Messages.template_empty);
}
});
};
config.onInit = function (info) {
var privateData = metadataMgr.getPrivateData();
@ -1846,6 +1957,7 @@ define([
Title.setToolbar(toolbar);
if (window.CP_DEV_MODE) {
var $save = common.createButton('save', true, {}, function () {
makeCheckpoint(true);
});
@ -1862,18 +1974,6 @@ define([
APP.stopHistory = true;
makeCheckpoint(true);
};
var loadCp = function (cp, keepQueue) {
loadLastDocument(cp, function () {
var file = getFileType();
var type = common.getMetadataMgr().getPrivateData().ooType;
var blob = loadInitDocument(type, true);
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
}, function (blob, file) {
if (!keepQueue) { ooChannel.queue = []; }
resetData(blob, file);
});
};
var onPatch = function (patch) {
// Patch on the current cp
ooChannel.send(JSON.parse(patch.msg));
@ -1967,6 +2067,10 @@ define([
load: loadSnapshot
});
toolbar.$drawer.append($snapshot);
// Import template
var $template = common.createButton('importtemplate', true, {}, openTemplatePicker);
$template.appendTo(toolbar.$drawer);
})();
}
@ -2125,11 +2229,18 @@ define([
openRtChannel(function () {
setMyId();
oldHashes = JSON.parse(JSON.stringify(content.hashes));
loadDocument(newDoc, useNewDefault);
initializing = false;
common.openPadChat(APP.onLocal);
if (APP.startWithTemplate) {
var template = APP.startWithTemplate;
loadTemplate(template.href, template.password, template.content);
return;
}
loadDocument(newDoc, useNewDefault);
setEditable(!readOnly);
UI.removeLoadingScreen();
common.openPadChat(APP.onLocal);
});
};
@ -2253,8 +2364,11 @@ define([
}));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
common.getSframeChannel().on('EV_OO_TEMPLATE', function (data) {
APP.startWithTemplate = data;
});
common.handleNewFile(waitFor, {
noTemplates: true
//noTemplates: true
});
}).nThen(function (/*waitFor*/) {
andThen(common);

@ -123,6 +123,9 @@ define([
});
SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) {
Utils.sframeChan = sframeChan = sfc;
window.CryptPad_loadingError = function (e) {
sfc.event('EV_LOADING_ERROR', e)
};
}));
});
window.addEventListener('message', whenReady);
@ -1140,6 +1143,10 @@ define([
nSecret = Utils.Hash.getSecrets('drive', hash, password);
}
}
if (data.href) {
var _parsed = Utils.Hash.parsePadUrl(data.href);
nSecret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, data.password);
}
var channel = nSecret.channel;
var validate = nSecret.keys.validateKey;
var crypto = Crypto.createEncryptor(nSecret.keys);
@ -1316,6 +1323,10 @@ define([
sframeChan.on('Q_TEMPLATE_USE', function (data, cb) {
Cryptpad.useTemplate(data, Cryptget, cb);
});
sframeChan.on('Q_OO_TEMPLATE_USE', function (data, cb) {
data.oo = true;
Cryptpad.useTemplate(data, Cryptget, cb);
});
sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) {
Cryptpad.listTemplates(type, function (err, templates) {
cb(templates.length > 0);
@ -1670,6 +1681,7 @@ define([
rtConfig.metadata.validateKey = (secret.keys && secret.keys.validateKey) || undefined;
Utils.rtConfig = rtConfig;
var templatePw;
nThen(function(waitFor) {
if (data.templateId) {
if (data.templateId === -1) {
@ -1679,11 +1691,34 @@ define([
}
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
data.template = d.href;
templatePw = d.password;
}));
}
}).nThen(function () {
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
if (data.template) {
// Start OO with a template...
// Cryptget and give href, password and content to inner
if (parsed.type === "sheet") {
var then = function () {
startRealtime(rtConfig);
cb();
};
var _parsed = Utils.Hash.parsePadUrl(data.template);
Cryptget.get(_parsed.hash, function (err, val) {
if (err || !val) { return void then(); }
try {
var parsed = JSON.parse(val);
sframeChan.event('EV_OO_TEMPLATE', {
href: data.template,
password: templatePw,
content: parsed
});
} catch (e) { console.error(e); }
then();
}, {password: templatePw});
return;
}
// Pass rtConfig to useTemplate because Cryptput will create the file and
// we need to have the owners and expiration time in the first line on the
// server

@ -188,12 +188,12 @@
"help_button": "Ohje",
"historyText": "Historia",
"historyButton": "Näytä asiakirjan historia",
"history_next": "Uudempi versio",
"history_prev": "Vanhempi versio",
"history_next": "Seuraava versio",
"history_prev": "Edellinen versio",
"history_loadMore": "Lataa lisää historiatietoja",
"history_closeTitle": "Sulje historia",
"history_restoreTitle": "Palauta asiakirjan valittu versio",
"history_restorePrompt": "Oletko varma, että haluat korvata asiakirjan nykyisen version esitetyllä versiolla?",
"history_restorePrompt": "Oletko varma, että haluat korvata asiakirjan tämänhetkisen version näytetyllä versiolla?",
"history_restoreDone": "Asiakirja palautettu",
"history_version": "Versio:",
"openLinkInNewTab": "Avaa linkki uuteen välilehteen",
@ -452,7 +452,7 @@
"settings_backupHint": "Varmuuskopioi tai palauta CryptDrivesi sisältö kokonaisuudessaan. Varmuuskopio ei sisällä padiesi sisältöä, ainoastaan niiden käyttöön tarvittavat avaimet.",
"settings_backup": "Varmuuskopioi",
"settings_restore": "Palauta",
"settings_backupHint2": "Lataa kaikkien padiesi nykyinen sisältö. Padit ladataan luettavassa tiedostomuodossa, jos sellainen on saatavilla.",
"settings_backupHint2": "Lataa kaikkien asiakirjojen nykyinen sisältö tietokoneellesi. Asiakirjat ladataan muissa sovelluksissa toimivissa tiedostomuodoissa, jos se on mahdollista. Jos sopivaa tiedostomuotoa ei ole saatavilla, asiakirjat ladataan CryptPad-yhteensopivassa tiedostomuodossa.",
"settings_backup2": "Lataa oma CryptDrive tietokoneellesi",
"settings_backup2Confirm": "Tämä lataa kaikki CryptDrivesi padit ja tiedostot tietokoneellesi. Jos haluat jatkaa, valitse nimi ja paina OK",
"settings_exportTitle": "Vie oma CryptDrive",
@ -1176,9 +1176,9 @@
"settings_safeLinksTitle": "Turvalliset linkit",
"settings_cat_security": "Luottamuksellisuus",
"imprint": "Oikeudellinen huomautus",
"oo_sheetMigration_anonymousEditor": "Taulukon muokkaaminen on poistettu käytöstä anonyymeille käyttäjille, kunnes rekisteröitynyt käyttäjä päivittää sen viimeisimpään versioon.",
"oo_sheetMigration_anonymousEditor": "Rekisteröitymättömät käyttäjät eivät voi muokata taulukkoa ennen kuin rekisteröitynyt käyttäjä päivittää sen viimeisimpään versioon.",
"oo_sheetMigration_complete": "Päivitetty versio saatavilla, paina OK ladataksesi uudelleen.",
"oo_sheetMigration_loading": "Päivitetään taulukkoasi viimeisimpään versioon",
"oo_sheetMigration_loading": "Päivitetään taulukkoasi viimeisimpään versioon. Ole hyvä ja odota noin 1 minuutti.",
"oo_exportInProgress": "Vienti menossa",
"oo_importInProgress": "Tuonti menossa",
"oo_invalidFormat": "Tätä tiedostoa ei voida tuoda",
@ -1404,5 +1404,60 @@
"readme_cat1_l2": "Avaa padeja CryptDrivestasi: kaksoisnapsauta padin kuvaketta avataksesi sen.",
"readme_cat1_l1": "Luo padi: Siirry CryptDriveesi, napsauta {0} ja sitten {1}, ja voit luoda padin.",
"readme_cat1": "Tutustu CryptDriveesi",
"readme_p2": "Tämä padi toimii pikaisena perehdytyksenä CryptPadin ominaisuuksiin - kuinka tehdä muistiinpanoja, järjestellä niitä ja tehdä yhteistyötä niiden parissa."
"readme_p2": "Tämä padi toimii pikaisena perehdytyksenä CryptPadin ominaisuuksiin - kuinka tehdä muistiinpanoja, järjestellä niitä ja tehdä yhteistyötä niiden parissa.",
"fm_shareFolderPassword": "Suojaa kansio salasanalla (vapaaehtoinen)",
"access_destroyPad": "Tuhoa tämä asiakirja tai kansio lopullisesti",
"fm_deletedFolder": "Poistettu kansio",
"admin_limitUser": "Käyttäjän julkinen avain",
"team_exportButton": "Lataa",
"team_exportHint": "Lataa kaikki tiimin CryptDriveen tallennetut asiakirjat tietokoneellesi. Asiakirjat ladataan muissa sovelluksissa toimivissa tiedostomuodoissa, jos se on mahdollista. Jos sopivaa tiedostomuotoa ei ole saatavilla, asiakirjat ladataan CryptPad-yhteensopivassa tiedostomuodossa.",
"team_exportTitle": "Lataa tiimin CryptDrive",
"admin_cat_quota": "Käyttäjätallennustila",
"admin_invalLimit": "Virheellinen arvo kiintiökentässä",
"admin_invalKey": "Virheellinen julkinen avain",
"admin_limitSetNote": "Huomautus",
"admin_limitMB": "Kiintiö (megatavuissa)",
"admin_setlimitTitle": "Ota mukautettu kiintiö käyttöön",
"admin_setlimitHint": "Aseta mukautettu tallennustilakiintiö käyttäjälle tämän julkisen avaimen avulla. Voit päivittää tai poistaa olemassaolevan tallennustilakiintiön.",
"admin_limitNote": "Huomautus: {0}",
"admin_limitPlan": "Tilaus: {0}",
"admin_defaultlimitHint": "Tallennustilakiintiö käyttäjien ja tiimien CryptDriveille, joilla ei ole erikseen määriteltyjä sääntöjä",
"admin_defaultlimitTitle": "Tallennustilakiintiö (Mt)",
"admin_getlimitsHint": "Listaa muokatut tallennustilakiintiöt, jotka ovat käytössä CryptPad-palvelimellasi.",
"admin_getlimitsTitle": "Muokatut tallennustilakiintiöt",
"admin_limit": "Nykyinen rajoitus: {0}",
"admin_setlimitButton": "Aseta rajoitus",
"admin_registrationAllow": "Avaa",
"admin_registrationButton": "Sulje",
"admin_registrationTitle": "Sulje rekisteröityminen",
"admin_registrationHint": "Älä salli uusien käyttäjien rekisteröitymistä",
"snapshots_cantMake": "Tilannevedoksen luominen epäonnistui. Yhteytesi on katkennut.",
"snapshots_notFound": "Tämä tilannevedos ei ole enää saatavilla, koska asiakirjan historia on poistettu.",
"snapshot_error_exists": "Tästä versiosta on jo olemassa tilannevedos",
"snapshots_ooPickVersion": "Valitse versio, jotta voit luoda tilannevedoksen",
"oo_version": "Versio: ",
"oo_version_latest": "Viimeisin",
"snapshots_delete": "Poista",
"oo_deletedVersion": "Tämä versio ei ole enää saatavilla historiassa.",
"snapshots_close": "Sulje",
"snapshots_restore": "Palauta",
"snapshots_open": "Avaa",
"snapshots_placeholder": "Tilannevedoksen otsikko",
"snapshots_new": "Uusi tilannevedos",
"snapshots_button": "Tilannevedokset",
"snaphot_title": "Tilannevedos",
"infobar_versionHash": "Katselet asiakirjan vanhaa versiota ({0}).",
"history_restoreDriveDone": "CryptDrive palautettu",
"history_restoreDrivePrompt": "Oletko varma, että haluat korvata CryptDriven nykyisen version valitulla versiolla?",
"history_restoreDriveTitle": "Palauta CryptDrive valitsemaasi versioon",
"history_userNext": "Seuraava laatija",
"history_fastNext": "Seuraava muokkaussessio",
"history_userPrev": "Edellinen laatija",
"history_fastPrev": "Edellinen muokkaussessio",
"share_versionHash": "Olet jakamassa valitsemasi historiaversion asiakirjastasi vain luku-tilassa. Tämä antaa myös <b>katseluoikeuden</b> asiakirjan kaikkiin versioihin.",
"history_shareTitle": "Jaa linkki tähän versioon",
"history_cantRestore": "Palauttaminen epäonnistui. Yhteytesi on katkennut.",
"history_close": "Sulje",
"history_restore": "Palauta",
"share_bar": "Luo linkki"
}

@ -1459,5 +1459,11 @@
"admin_limitUser": "Clé publique de l'utilisateur",
"fm_shareFolderPassword": "Protéger ce dossier avec un mot de passe (optionnel)",
"access_destroyPad": "Détruire ce document ou dossier définitivement",
"fm_deletedFolder": "Dossier supprimé"
"fm_deletedFolder": "Dossier supprimé",
"loading_state_5": "Reconstruction du document",
"loading_state_4": "Chargement des équipes",
"loading_state_3": "Chargement des dossiers partagés",
"loading_state_2": "Mise à jour du contenu",
"loading_state_1": "Chargement du drive",
"loading_state_0": "Construction de l'interface"
}

@ -1459,5 +1459,13 @@
"admin_limitUser": "User's public key",
"fm_deletedFolder": "Deleted folder",
"access_destroyPad": "Destroy this document or folder permanently",
"fm_shareFolderPassword": "Protect this folder with a password (optional)"
"fm_shareFolderPassword": "Protect this folder with a password (optional)",
"loading_state_0": "Build interface",
"loading_state_1": "Load drive",
"loading_state_2": "Update content",
"loading_state_3": "Load shared folders",
"loading_state_4": "Load Teams",
"loading_state_5": "Reconstruct document",
"tag_add": "Add",
"tag_edit": "Edit"
}

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html class="cp-app-noscroll">
<html class="cp-app-noscroll cp-app-print">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>

@ -47,6 +47,7 @@ define([
'/bower_components/diff-dom/diffDOM.js',
'css!/customize/src/print.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/pad/app-pad.less'
@ -500,6 +501,12 @@ define([
var mkPrintButton = function (framework, editor) {
var $printButton = framework._.sfCommon.createButton('print', true);
$printButton.click(function () {
/*
// NOTE: alternative print system in case we keep having more issues on Firefox
var $iframe = $('html').find('iframe');
var iframe = $iframe[0].contentWindow;
iframe.print();
*/
editor.execCommand('print');
framework.feedback('PRINT_PAD');
});

@ -124,6 +124,7 @@ define([
}
sframeChan.event("EV_SECURE_ACTION", {
type: parsed.type,
password: data.password,
href: data.url,
name: data.name
});

@ -15,6 +15,7 @@ define([
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/customize/src/print-landscape.css',
'less!/slide/app-slide.less',
'css!cm/lib/codemirror.css',

Loading…
Cancel
Save