Problems when developing a custom macro

Hi, I am trying to develop a macro which using embed external draw.io to draw charts in xwiki page.
the code of XWiki.WikiMacroClass

{{velocity}}
$xwiki.jsx.use('Charts.WebHome')
#if($wikimacro.content == '')
#set ($wikimacro.content = 'iVBORw0KGgoAAAANSUhEUgAAAIMAAABHCAYAAAAk5PTEAAAC4HRFWHRteGZpbGUAJTNDbXhmaWxlJTIwaG9zdCUzRCUyMjE3Mi4yNS4xNjEuMjExJTIyJTIwbW9kaWZpZWQlM0QlMjIyMDIxLTExLTA4VDA3JTNBNDglM0EzMC4wMzdaJTIyJTIwYWdlbnQlM0QlMjI1LjAlMjAoWDExKSUyMiUyMGV0YWclM0QlMjJ4MTM1OUpQR29sZ21udEFCTFIxSCUyMiUyMHZlcnNpb24lM0QlMjIxNS42LjIlMjIlMjB0eXBlJTNEJTIyZGV2aWNlJTIyJTNFJTNDZGlhZ3JhbSUyMGlkJTNEJTIyNEd0aVp4cWRuemJnanpmMC0zXzElMjIlMjBuYW1lJTNEJTIyUGFnZS0xJTIyJTNFalpKTlQ4UWdFSVolMkZUWThtTFdqdFhsMjdidHg0cXNaNEpHVXNKTFEwTEYxYWY3MVVwbCUyRlpiT0tKNFprUFp0NGhvdnU2ZnpHc0ZXJTJCYWc0cEl6UHVJUGtlRWtEZ2wlMkZoakpFRWlTa0N5UXlraU9iQUdGJTJGQUdFTWRKT2NqaHZBcTNXeXNwMkMwdmRORkRhRFdQR2FMY04lMkI5WnElMkIyckxLcmdDUmNuVU5mMlUzSXBBTSUyRks0OENQSVNrd3ZKJTJCa3VlR28yQmVNa1o4RzRkaXRFODRqdWpkWTJXSFclMkZCeldxTiUyQmtTOGc0M3ZITmpCaHI3bjRRRFl6S1Z1OVBISlQ5JTJCdVpOOUw5enJIVmE1TU5YaHdOaXNIU1lGak80YURtT1JPS0pQVGtnTFJjdkswZXY4MGowVHRsYiUyQmxuZ1R5NEd4ME4lMkZzTTVtbjklMkY4R2RBM1dERDRFRSUyQmpEZlVqQkwwTXpGTkF0JTJCaWVUcUdLbGZZcU00Y3FydWZTaWlqZFFtT202TE9EUHQlMkZySE5QOEYlM0MlMkZkaWFncmFtJTNFJTNDJTJGbXhmaWxlJTNFUXJoXAAAAKNJREFUeJzt0rENACEQwDD2X5rvUoOe6mRL2SBrAQDAA1vjO7aZywzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzEDMQMxAzkegaNDwAA/vkAmk+zV9RvOdkAAAAASUVORK5CYII=')
#end
{{html clean="false"}}
<img class="drawio" style="cursor: default" src="data:image/png;base64,$wikimacro.content" />
{{/html}}
{{/velocity}}

the code of XWiki.JavaScriptExtension:

// Edits an image with drawio class on double click
document.observe("xwiki:actions:save", function(event){
  console.log(event);
});

document.observe("xwiki:document:saved", function(event){
  console.log(event);
});

document.addEventListener('dblclick', function(evt) {
  var url = 'http://172.25.161.211:8280/?embed=1&ui=atlas&spin=1&modified=unsavedChanges&proto=json';
  var source = evt.srcElement || evt.target;

  if (source.nodeName == 'IMG' && source.className == 'drawio'){
    if (source.drawIoWindow == null || source.drawIoWindow.closed) {
      // Implements protocol for loading and exporting with embedded XML
      var receive = function(evt) {
        if (evt.data.length > 0 && evt.source == source.drawIoWindow) {
          var msg = JSON.parse(evt.data);
          // Received if the editor is ready
          if (msg.event == 'init') {
            // Sends the data URI with embedded XML to editor
            source.drawIoWindow.postMessage(JSON.stringify({action: 'load', xmlpng: source.getAttribute('src')}), '*');
          }
          // Received if the user clicks save
          else if (msg.event == 'save')
          {
            // Sends a request to export the diagram as XML with embedded PNG
            source.drawIoWindow.postMessage(JSON.stringify(
              {action: 'export', format: 'xmlpng', spinKey: 'saving'}), '*');
          }
          // Received if the export request was processed
          else if (msg.event == 'export')
          {
            // Updates the data URI of the image
            // TODO save to xwiki server as macro's content
            source.setAttribute('src', msg.data);
          }

          // Received if the user clicks exit or after export
          if (msg.event == 'exit' || msg.event == 'export')
          {
            // Closes the editor
            window.removeEventListener('message', receive);
            source.drawIoWindow.close();
            source.drawIoWindow = null;
          }
        }
      };

      // Opens the editor
      window.addEventListener('message', receive);
      source.drawIoWindow = window.open(url);
    }
    else
    {
      // Shows existing editor window
      source.drawIoWindow.focus();
    }
  }
});

the problem is:

  1. how to check current page is in editing mode from client side javascript? so I can ignore double click when in view mode. I know thre is property window.XWiki.currentaction, but when editing using real-time WYSISWYG editor, this property is also equal to ‘view’. currently I am checking with this.URL.endsWith('#edit')
  2. when editing with CKEditor editor(not the real-time one), my double click handler is not fired, it will open the macro editing dialog. I don’t know how to override the default handler.
  3. when draw.io window finish editing, image data come back as img’s src attribute, but I don’t know how to update back to xwiki server from client side, I have tried xwiki:actions:save event by inspect what can be modifed before post to server, but found nothing helpful.

please help!

I am using newest version of xwiki.

ad 1:
The only method I found was checking the not existence of variables in the binding (with groovy):

{{groovy}}
editmode= ! binding.hasVariable("inplaceEditingConfig")
""
{{/groovy}}
{{velocity}}edit: $editmode{{/velocity}}

I wonder if the URL contains “#edit”, as local anchors are not part of the URL - may be a browser property.

ad 3: Are you in (WYSIWYG-) edit mode during editing with draw.io?

Yes, I am using WYSIWYG editor, do you know how to save from client side using javascript ?

I have found a way to save macro’s content in client side, just update the data-macro attribute.
image

but still don’t know how to prevent the macro editing dialog.

You can trigger the editor buttons (save, save and continue, abort) within javascript by

function exitEditorWithSave() {require(["jquery"],function($) {
	   $("input[name=action_save]").click();
       });};

function abortEditor() {require(["jquery"],function($) {
	   $("input[name=action_cancel]").click();
       });};

function saveEditorAndContinue() {require(["jquery"],function($) {
	   $("input[name=action_saveandcontinue]").click();
       });};

If you want to change a document by content which is known within the javascript, it is more complicated - as far as I know:

You can use the rest api of xwiki by AJAX or XMLHttpRequest.

see: REST API (XWiki.org)
and: Bitloom - An overview of the XWiki’s RESTful API

The http/AJAX requests needs a defined xml structure which defines the document.

A little tip: You can see the required xml elements, if you look at the returned data of document view call with the query “?xpage=xml” added. This information even contains documents objects like comments.
This XML format is used by the XAR-Export (=ZIP compressed) of a document.

I hope this helps.

Norbert

I finally got everything work:

/**
 * A XWiki extension to integrate external diagrame.net chartting service. 
 * please note: this extension doesn't support the in-place editing feature, aka rtWYSIWYG.
 * you can disable rtWYSIWYG by setting edit.document.inPlaceEditing.enabled = false
 * @author west_farmer@163.com
 */

const drawioUrl = 'http://172.25.161.211:8280/?embed=1&ui=atlas&spin=1&modified=unsavedChanges&proto=json'
const imgDBLClick = function (evt) {
    var source = evt.target
    if (XWiki.editor != 'wysiwyg') return
    if (source.drawIoWindow == null || source.drawIoWindow.closed) {
        var receive = function (evt) {
            if (evt.data.length > 0 && evt.source == source.drawIoWindow) {
                var msg = JSON.parse(evt.data)
                if (msg.event == 'init') {
                    source.drawIoWindow.postMessage(JSON.stringify({ action: 'load', xmlpng: source.getAttribute('src') }), '*')
                }
                else if (msg.event == 'save') {
                    source.drawIoWindow.postMessage(JSON.stringify({ action: 'export', format: 'xmlpng', spinKey: 'saving' }), '*')
                }
                else if (msg.event == 'export') {
                    source.setAttribute('src', msg.data)
                    source.setAttribute('data-cke-saved-src', msg.data)
                    // TODO this seems hacky, looking for a offical way to do it
                    source.parentNode.parentNode.parentNode.parentNode.setAttribute('data-macro', 'startmacro:drawio|-||-|' + msg.data.substr(22))
                }
                if (msg.event == 'exit' || msg.event == 'export') {
                    window.removeEventListener('message', receive)
                    source.drawIoWindow.close()
                    source.drawIoWindow = null
                }
            }
        }
        window.addEventListener('message', receive)
        source.drawIoWindow = window.open(drawioUrl)
    } else {
        source.drawIoWindow.focus()
    }
    evt.stopPropagation()
}

require(['jquery', 'bootstrap'], function ($) {
    const observer = new MutationObserver(function (mutations_list) {
        mutations_list.forEach(function (mutation) {
            mutation.addedNodes.forEach(function (node) {
                if (node.tagName == 'IFRAME' && node.className.indexOf('cke_wysiwyg_frame') >= 0) {
                    observer.disconnect()
                    const iframe = node
                    iframe.contentWindow.addEventListener('DOMContentLoaded', onFrameDOMContentLoaded, true)
                    function onFrameDOMContentLoaded(ee) {
                        iframe.onload = function () {
                            iframe.contentWindow.document.querySelector('img.drawio').addEventListener('dblclick', imgDBLClick)
                        }
                    }
                }
            })
        })
    })
    observer.observe(document.querySelector('body'), { subtree: true, childList: true })
})

Great that you succeeded!

small note: rtWYSIWYG is realtime WYSIWYG which is different from in-place editing :wink: