diff --git a/customize.dist/src/less2/include/colortheme-dark.less b/customize.dist/src/less2/include/colortheme-dark.less
index 7d3a83264..c7365d722 100644
--- a/customize.dist/src/less2/include/colortheme-dark.less
+++ b/customize.dist/src/less2/include/colortheme-dark.less
@@ -11,8 +11,8 @@
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
- whiteboard: #a72ba7;
- diagram: #f447fa; // XXX placeholder
+ whiteboard: #8f40f5; // XXX TBC
+ diagram: #ce3ad3; // XXX TBC
kanban: #8C4;
sheet: #40865c;
doc: #5170B5;
diff --git a/customize.dist/src/less2/include/colortheme.less b/customize.dist/src/less2/include/colortheme.less
index f0b14cda7..e826b4cb5 100644
--- a/customize.dist/src/less2/include/colortheme.less
+++ b/customize.dist/src/less2/include/colortheme.less
@@ -11,8 +11,8 @@
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
- whiteboard: #a72ba7;
- diagram: #f447fa; // XXX placeholder
+ whiteboard: #8f40f5; // XXX TBC
+ diagram: #ce3ad3; // XXX TBC
kanban: #8C4;
sheet: #40865c;
doc: #5170B5;
diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js
index 2a89713bf..87880a12e 100644
--- a/www/common/application_config_internal.js
+++ b/www/common/application_config_internal.js
@@ -13,6 +13,7 @@ define(function() {
*/
AppConfig.availablePadTypes = ['drive', 'teams', 'sheet', 'doc', 'presentation', 'pad', 'kanban', 'code', 'form', 'poll', 'whiteboard',
'file', 'contacts', 'slide', 'convert', 'diagram'];
+
/* The registered only types are apps restricted to registered users.
* You should never remove apps from this list unless you know what you're doing. The apps
* listed here by default can't work without a user account.
@@ -192,7 +193,12 @@ define(function() {
poll: 'cptools-poll',
form: 'cptools-poll',
whiteboard: 'cptools-whiteboard',
+<<<<<<< HEAD
diagram: 'cptools-diagram',
+||||||| c94666efb
+=======
+ drawio: 'cptools-diagram',
+>>>>>>> origin/drawio-bower
todo: 'cptools-todo',
contacts: 'fa-address-book',
calendar: 'fa-calendar',
diff --git a/www/drawio/app.less b/www/drawio/app.less
new file mode 100644
index 000000000..93e92c4b4
--- /dev/null
+++ b/www/drawio/app.less
@@ -0,0 +1,42 @@
+body.cp-app-generic {
+
+@import (once) "../../customize.dist/src/less2/include/browser.less";
+@import (once) "../../customize.dist/src/less2/include/framework.less";
+
+ .framework_main(
+ @bg-color: @colortheme_apps[drawio],
+ );
+
+// body
+&.cp-app-generic {
+ display: flex;
+ flex-flow: column;
+ max-height: 100%;
+ min-height: auto;
+
+ .cp-app-generic-container {
+ display: inline-flex;
+ flex-flow: column;
+ height: 100%;
+ min-height: 100%;
+ width: 100%;
+ resize: horizontal;
+ overflow: hidden;
+ }
+ .cp-app-generic-editor {
+ flex: 1;
+ display: flex;
+ flex-flow: row;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ @media (max-width: @browser_media-medium-screen) {
+ .cp-app-drawio-container {
+ flex: 1;
+ max-width: 100%;
+ resize: none;
+ }
+ }
+}
+}
diff --git a/www/drawio/drawio.css b/www/drawio/drawio.css
new file mode 100644
index 000000000..f9ed9bcd2
--- /dev/null
+++ b/www/drawio/drawio.css
@@ -0,0 +1,92 @@
+/* The diagram editor styles should be loaded after the skin and before our overwrites. */
+/*
+@import url("mxgraph-editor/3.7.2/styles/grapheditor.css");
+*/
+
+body {
+ background-color: #ebebeb;
+}
+
+.diagram-editor {
+ height: 110%;
+ min-height: 20px;
+ background-color: #ebebeb;
+}
+
+.diagram-editor .geFooterContainer {
+ display: none;
+}
+
+.diagram-editor input[type="checkbox"], .diagram-editor input[type="radio"],
+.mxPopupMenu input[type="checkbox"], .mxPopupMenu input[type="radio"],
+.mxWindow input[type="checkbox"], .mxWindow input[type="radio"],
+.geDialog input[type="checkbox"], .geDialog input[type="radio"] {
+ vertical-align: text-bottom;
+}
+
+/**
+ * Overwrite XWiki skin styles
+ */
+.diagram-editor *,
+.mxPopupMenu *,
+.mxWindow *,
+.geDialog,
+.geDialog * {
+ box-sizing: content-box;
+}
+
+.mxPopupMenu,
+.mxWindow,
+.geDialog {
+ /* We need the same font size as on draw.io because the dialog height is hard-coded. */
+ font-size: 10pt;
+}
+
+.diagram-editor button, .diagram-editor select,
+.mxPopupMenu button, .mxPopupMenu select,
+.mxWindow button, .mxWindow select,
+.geDialog button, .geDialog select {
+ box-sizing: border-box;
+}
+
+.diagram-editor input[type="text"],
+.mxPopupMenu input[type="text"],
+.mxWindow input[type="text"],
+.geDialog input[type="text"] {
+ font-size: inherit;
+ height: auto;
+ padding: 1px;
+}
+
+.diagram-editor img,
+.mxPopupMenu img,
+.mxWindow img,
+.geDialog img {
+ vertical-align: baseline;
+}
+
+.diagram-editor hr,
+.mxPopupMenu hr,
+.mxWindow hr,
+.geDialog hr {
+ margin: 0;
+}
+
+.mxPopupMenu table,
+.mxWindow table,
+.geDialog table {
+ margin-bottom: 0;
+ width: auto;
+}
+
+.diagram-editor table > tbody > tr > td,
+.mxPopupMenu table > tbody > tr > td,
+.mxWindow table > tbody > tr > td,
+.geDialog table > tbody > tr > td {
+ border-top: 0 none;
+}
+
+.geDialog table > tbody > tr > td {
+ padding: 0;
+ vertical-align: baseline;
+}
diff --git a/www/drawio/index.html b/www/drawio/index.html
new file mode 100644
index 000000000..20a9de476
--- /dev/null
+++ b/www/drawio/index.html
@@ -0,0 +1,38 @@
+
+
+
+
+ CryptPad
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/www/drawio/inner.html b/www/drawio/inner.html
new file mode 100644
index 000000000..b265909ae
--- /dev/null
+++ b/www/drawio/inner.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/www/drawio/inner.js b/www/drawio/inner.js
new file mode 100644
index 000000000..c509ca36b
--- /dev/null
+++ b/www/drawio/inner.js
@@ -0,0 +1,200 @@
+// This is the initialization loading the CryptPad libraries
+define([
+ '/common/sframe-app-framework.js',
+ '/customize/messages.js', // translation keys
+ '/bower_components/pako/dist/pako.min.js',
+ '/bower_components/js-base64/base64.js',
+ '/bower_components/x2js/xml2json.min.js',
+ 'less!/drawio/app.less',
+ 'css!/drawio/drawio.css',
+], function (
+ Framework,
+ Messages,
+ pako,
+ base64,
+ X2JS) {
+
+ // As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/
+ const decompressDrawioXml = function(xmlDocStr) {
+ var TEXT_NODE = 3;
+
+ var parser = new DOMParser();
+ var doc = parser.parseFromString(xmlDocStr, "application/xml");
+
+ var errorNode = doc.querySelector("parsererror");
+ if (errorNode) {
+ console.error("error while parsing", errorNode);
+ return xmlDocStr;
+ }
+
+ doc.firstChild.removeAttribute('modified');
+ doc.firstChild.removeAttribute('agent');
+ doc.firstChild.removeAttribute('etag');
+
+ var diagrams = doc.querySelectorAll('diagram');
+
+ diagrams.forEach(function(diagram) {
+ if (diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) {
+ const innerText = diagram.firstChild.nodeValue;
+ const bin = base64.toUint8Array(innerText);
+ const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'});
+ const xmlStr = decodeURIComponent(xmlUrlStr);
+ const diagramDoc = parser.parseFromString(xmlStr, "application/xml");
+ diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild);
+ }
+ });
+
+
+ var result = new XMLSerializer().serializeToString(doc);
+ return result;
+ };
+
+ const deepEqual = function(o1, o2) {
+ return JSON.stringify(o1) === JSON.stringify(o2);
+ };
+
+ // This is the main initialization loop
+ var onFrameworkReady = function (framework) {
+ var EMPTY_DRAWIO = "";
+ var drawioFrame = document.querySelector('#cp-app-drawio-content');
+ var x2js = new X2JS();
+ var lastContent = x2js.xml_str2json(EMPTY_DRAWIO);
+ var drawIoInitalized = false;
+
+ var postMessageToDrawio = function(msg) {
+ if (!drawIoInitalized) {
+ console.log('draw.io postMessageToDrawio blocked', msg);
+ return;
+ }
+
+ console.log('draw.io postMessageToDrawio', msg);
+ drawioFrame.contentWindow.postMessage(JSON.stringify(msg), '*');
+ };
+
+ const jsonContentAsXML = (content) => x2js.json2xml_str(content);
+
+ var onDrawioInit = function() {
+ drawIoInitalized = true;
+ var xmlStr = jsonContentAsXML(lastContent);
+ postMessageToDrawio({
+ action: 'load',
+ xml: xmlStr,
+ autosave: 1
+ });
+ };
+
+ const xmlAsJsonContent = (xml) => {
+ var decompressedXml = decompressDrawioXml(xml);
+ return x2js.xml_str2json(decompressedXml);
+ };
+
+ var onDrawioChange = function(newXml) {
+ var newJson = xmlAsJsonContent(newXml);
+ if (!deepEqual(lastContent, newJson)) {
+ lastContent = newJson;
+ framework.localChange();
+ }
+ };
+
+ var onDrawioAutosave = function(data) {
+ onDrawioChange(data.xml);
+
+ // Tell draw.io to hide "Unsaved changes" message
+ postMessageToDrawio({action: 'status', message: '', modified: false});
+ };
+
+ var drawioHandlers = {
+ init: onDrawioInit,
+ autosave: onDrawioAutosave,
+ };
+
+ // This is the function from which you will receive updates from CryptPad
+ framework.onContentUpdate(function (newContent) {
+ lastContent = newContent;
+ var xmlStr = jsonContentAsXML(lastContent);
+ postMessageToDrawio({
+ action: 'merge',
+ xml: xmlStr,
+ });
+
+ framework.localChange();
+ });
+
+ // This is the function called to get the current state of the data in your app
+ framework.setContentGetter(function () {
+ return lastContent;
+ });
+
+ framework.setFileImporter(
+ {accept: ['.drawio', 'application/x-drawio']},
+ (content) => {
+ return xmlAsJsonContent(content);
+ }
+ );
+
+ framework.setFileExporter(
+ '.drawio',
+ () => {
+ return new Blob([jsonContentAsXML(lastContent)], {type: 'application/x-drawio'});
+ }
+ );
+
+ framework.onEditableChange(function () {
+ const editable = !framework.isLocked() && !framework.isReadOnly();
+ postMessageToDrawio({
+ action: 'spinner',
+ message: Messages.reconnecting,
+ show: !editable
+ });
+
+ document.getElementById('overlay').className = editable
+ ? ""
+ : "show";
+ });
+
+ // starting the CryptPad framework
+ framework.start();
+
+ drawioFrame.src = '/bower_components/drawio/src/main/webapp/index.html?'
+ + new URLSearchParams({
+ // pages: 0,
+ // dev: 1,
+ test: 1,
+ stealth: 1,
+ embed: 1,
+ drafts: 0,
+
+ chrome: framework.isReadOnly() ? 0 : 1,
+ dark: window.CryptPad_theme === "dark" ? 1 : 0,
+
+ // Hide save and exit buttons
+ noSaveBtn: 1,
+ saveAndExit: 0,
+ noExitBtn: 1,
+
+ modified: 'unsavedChanges',
+ proto: 'json',
+ });
+
+ window.addEventListener("message", (event) => {
+ if (event.source === drawioFrame.contentWindow) {
+ var data = JSON.parse(event.data);
+ console.log('draw.io got message', data);
+ var eventType = data.event;
+ var handler = drawioHandlers[eventType];
+ if (handler) {
+ handler(data);
+ }
+ }
+ }, false);
+ };
+
+ // Framework initialization
+ Framework.create({
+ toolbarContainer: '#cme_toolbox',
+ contentContainer: '#cp-app-drawio-editor',
+ // validateContent: validateXml,
+ }, function (framework) {
+ onFrameworkReady(framework);
+ });
+});