# Changes for page Message Sender Macro

Last modified by superadmin on 2017/11/28 14:52

#### Export

#### Office Formats

Export as PDF Export as ODT Export as RTF

#### Other Formats

Export as HTML
From empty
To version 1.1
edited by superadmin
on 2017/11/28 14:52
Change comment: Install extension [org.xwiki.platform:xwiki-platform-messagestream-ui/9.9]

## Summary

- Page properties (5 modified, 0 added, 0 removed)
- Objects (0 modified, 6 added, 0 removed)
  - XWiki.JavaScriptExtension[0]
  - XWiki.StyleSheetExtension[0]
  - XWiki.WikiMacroClass[0]
  - XWiki.WikiMacroParameterClass[0]
  - XWiki.WikiMacroParameterClass[1]
  - XWiki.WikiMacroParameterClass[2]

## Details

Page properties
  Title

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Message Sender Macro

  Parent

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Main.WebHome

  Author

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -XWiki.XWikiGuest
    | 1 | +xwiki:XWiki.superadmin

  Hidden

    ... | ... | @@ -1,1 +1,1 @@
    1 |  | -false
    | 1 | +true

  Content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +{{messageSender/}}

XWiki.JavaScriptExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,138 @@
    | 1 | +#set ($userSuggestScope = 'local')
    | 2 | +#if ($services.wiki.user && $services.wiki.user.userScope != "LOCAL_ONLY")
    | 3 | + #set ($userSuggestScope = 'global')
    | 4 | +#end
    | 5 | +var XWiki = (function (XWiki) {
    | 6 | +// Start XWiki augmentation.
    | 7 | +/**
    | 8 | + * Extends the UserPicker in order to display the selected user/group inline after the target text input.
    | 9 | + */
    | 10 | +var CustomUserPicker = Class.create(XWiki.widgets.UserPicker, {
    | 11 | + // @Override
    | 12 | + _createSelectionManager: function($super, options) {
    | 13 | + var selectionManager = $super(Object.extend(options, {
    | 14 | + listInsertionPosition : 'after',
    | 15 | + acceptFreeText : false
    | 16 | + }));
    | 17 | + // Overwrite the way the selected item is displayed.
    | 18 | + selectionManager.displayItem = function(suggestion) {
    | 19 | + var targetInfo = new Element('span').update(suggestion.info).insert(this.createDeleteTool());
    | 20 | + var targetClass = this.suggest.sources[0].script.indexOf('uorg=group') > 0 ? 'target-group' : 'target-user';
    | 21 | + return new Element('li', {'class': targetClass}).insert(targetInfo).insert(this.createItemInput(suggestion));
    | 22 | + }.bind(selectionManager);
    | 23 | + return selectionManager;
    | 24 | + }
    | 25 | +});
    | 26 | +
    | 27 | +XWiki.MessageStream = Class.create({
    | 28 | + targetsWithName : ['user', 'group'],
    | 29 | + suggestParameters : {
    | 30 | + user : {
    | 31 | + script: '$doc.getURL("get", "xpage=uorgsuggest&uorg=user&wiki=${userSuggestScope}")&',
    | 32 | + noresults: "$escapetool.javascript($services.localization.render('core.widgets.userPicker.noResults'))"
    | 33 | + },
    | 34 | + group : {
    | 35 | + script: "$doc.getURL('get', 'xpage=uorgsuggest&uorg=group')&",
    | 36 | + noresults: "$escapetool.javascript($services.localization.render('core.widgets.groupPicker.noResults'))"
    | 37 | + }
    | 38 | + },
    | 39 | + initialize : function() {
    | 40 | + this.prepareForms();
    | 41 | + this.enhanceSelect();
    | 42 | + },
    | 43 | + prepareTargetInput : function(event, element) {
    | 44 | + var targetType = element.options[element.selectedIndex].value;
    | 45 | + element.className = targetType;
    | 46 | + if (!element.__targetNameInput) {return;}
    | 47 | + if (this.targetsWithName.indexOf(targetType) < 0) {
    | 48 | + element.__targetNameInput.addClassName('hidden');
    | 49 | + if (element.__targetNameInput.__x_suggest) {
    | 50 | + element.__targetNameInput.__x_suggest.detach();
    | 51 | + }
    | 52 | + } else {
    | 53 | + // Focus the text input so that the place-holder value is cleared if present. Otherwise the user picker will treat
    | 54 | + // the place-holder value as if it was typed by the user and will automatically select the corresponding suggestion.
    | 55 | + element.__targetNameInput.removeClassName('hidden').activate();
    | 56 | + // Defer the creation of the user picker to allow the focus listeners to be called first (in order to clear the
    | 57 | + // place-holder value).
    | 58 | + this.suggestParameters[targetType] && function() {
    | 59 | + new CustomUserPicker(element.__targetNameInput, this.suggestParameters[targetType]);
    | 60 | + }.bind(this).defer();
    | 61 | + }
    | 62 | + },
    | 63 | + enhanceSelect: function () {
    | 64 | + $$('.messagestream select[name="visibilityLevel"]').each(function(element) {
    | 65 | + element.addClassName(element.options[element.selectedIndex].value);
    | 66 | + element.__targetNameInput = element.up('.messagestream').down('input[name="targetName"]');
    | 67 | + if (element.__targetNameInput && this.targetsWithName.indexOf(element.options[element.selectedIndex].value) < 0) {
    | 68 | + element.__targetNameInput.addClassName('hidden');
    | 69 | + }
    | 70 | + element.observe('change', this.prepareTargetInput.bindAsEventListener(this, element));
    | 71 | + this.prepareTargetInput(null, element);
    | 72 | + }.bind(this));
    | 73 | + },
    | 74 | + prepareForms: function() {
    | 75 | + $$('.messagestream form').each(function(msForm) {
    | 76 | + msForm.action = msForm.action.replace(/xredirect=.*$/, 'xpage=plain')
    | 77 | + msForm.observe('submit', function(event) {
    | 78 | + event.stop();
    | 79 | + if (msForm._disabled == true || msForm.down('textarea').value.strip() == '') {
    | 80 | + return;
    | 81 | + }
    | 82 | + new Ajax.Request(
    | 83 | + msForm.action, {
    | 84 | + parameters: msForm.serialize(true),
    | 85 | + onCreate: function() {
    | 86 | + msForm._disabled = true;
    | 87 | + msForm._notification = new XWiki.widgets.Notification("$services.localization.render('xe.activity.messages.submit.inProgress')", 'inprogress');
    | 88 | + },
    | 89 | + onSuccess: function() {
    | 90 | + if (msForm) {
    | 91 | + msForm.messagestream_message.value = '';
    | 92 | + }
    | 93 | + if (msForm && msForm._notification) {
    | 94 | + msForm._notification.hide();
    | 95 | + }
    | 96 | + msForm._notification = new XWiki.widgets.Notification("$services.localization.render('xe.activity.messages.submit.success')", 'done');
    | 97 | +
    | 98 | + document.fire('xwiki:activity:newActivity', msForm);
    | 99 | + },
    | 100 | + onFailure: function(response) {
    | 101 | + var failureReason = '';
    | 102 | + if (response.statusText == '' /* No response */ || response.status == 12031 /* In IE */) {
    | 103 | + failureReason = 'Server not responding';
    | 104 | + } else {
    | 105 | + failureReason = response.statusText;
    | 106 | + }
    | 107 | + if (msForm) {
    | 108 | + msForm._disabled = false
    | 109 | + if (msForm._notification) {
    | 110 | + msForm._notification.hide();
    | 111 | + }
    | 112 | + msForm._notification = new XWiki.widgets.Notification("$services.localization.render('xe.activity.messages.submit.failed'): " + failureReason, 'error');
    | 113 | + }
    | 114 | + },
    | 115 | + on1223 : function(response) { /*SUCCESS*/
    | 116 | + response.request.options.onSuccess(response);
    | 117 | + },
    | 118 | + on0 : function(response) { /*FAILURE*/
    | 119 | + response.request.options.onFailure(response);
    | 120 | + },
    | 121 | + onComplete: function() {
    | 122 | + msForm._disabled = false;
    | 123 | + }.bind(this)
    | 124 | + }
    | 125 | + );
    | 126 | + }.bindAsEventListener(this));
    | 127 | + }.bind(this));
    | 128 | + }
    | 129 | +});
    | 130 | +
    | 131 | +var init = function() {
    | 132 | + return new XWiki.MessageStream();
    | 133 | +};
    | 134 | +(XWiki.domIsLoaded && init()) || document.observe('xwiki:dom:loaded', init);
    | 135 | +
    | 136 | +// End XWiki augmentation.
    | 137 | +return XWiki;
    | 138 | +}(XWiki || {}));

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +User status update

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +onDemand

XWiki.StyleSheetExtension[0]
  Caching policy

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +long

  Code

    ... | ... | @@ -1,0 +1,29 @@
    | 1 | +#template('colorThemeInit.vm')
    | 2 | +
    | 3 | +.messagestream-tools {
    | 4 | + position: relative;
    | 5 | + margin: .5em 0 1em;
    | 6 | +}
    | 7 | +.messagestream-tools .accepted-suggestions {
    | 8 | + display: inline;
    | 9 | +}
    | 10 | +.messagestream-tools .accepted-suggestions .target-user,
    | 11 | +.messagestream-tools .accepted-suggestions .target-group {
    | 12 | + background: none no-repeat scroll left center transparent;
    | 13 | + display: inline-block;
    | 14 | + margin-right: 1em;
    | 15 | + padding: 0 1px 0 18px;
    | 16 | +}
    | 17 | +.messagestream-tools .accepted-suggestions .target-user {
    | 18 | + background-image: url("$xwiki.getSkinFile('icons/silk/user.png')");
    | 19 | +}
    | 20 | +.messagestream-tools .accepted-suggestions .target-group {
    | 21 | + background-image: url("$xwiki.getSkinFile('icons/silk/group.png')");
    | 22 | +}
    | 23 | +.messagestream select {
    | 24 | + width: auto;
    | 25 | +}
    | 26 | +.messagestream input.targetName {
    | 27 | + margin-right: .3em;
    | 28 | + width: auto;
    | 29 | +}

  Content Type

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +CSS

  Name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Message stream form styling

  Parse content

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Yes

  Use this extension

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +onDemand

XWiki.WikiMacroClass[0]
  Macro code

    ... | ... | @@ -1,0 +1,185 @@
    | 1 | +{{velocity output='false'}}
    | 2 | +#* Duplicate code (from Main.Activity) START *#
    | 3 | +##
    | 4 | +## User status configuration
    | 5 | +## -------------------------------------------------------------
    | 6 | +#set ($config = 'XWiki.MessageStreamConfig')
    | 7 | +#set ($configClass = $xwiki.getDocument($config).getxWikiClass())
    | 8 | +#set ($isMessageStreamActive = $services.messageStream.isActive())
    | 9 | +
    | 10 | +#set ($currentUser = $services.model.serialize($xcontext.userReference, 'default'))
    | 11 | +
    | 12 | +#set ($activityActionsMap = {
    | 13 | + 'publicMessage': 'icons/silk/user_comment.png',
    | 14 | + 'personalMessage': 'icons/silk/user_comment.png',
    | 15 | + 'directMessage': 'icons/silk/user_go.png',
    | 16 | + 'groupMessage': 'icons/silk/group_go.png'
    | 17 | +})
    | 18 | +##
    | 19 | +#**
    | 20 | + * Display user avatar and action message
    | 21 | + *#
    | 22 | +#macro(activitySnapshot $eventUser $eventType)
    | 23 | + <span class='activitySnapshot'>
    | 24 | + #if ($eventUser != 'XWiki.XWikiGuest')
    | 25 | + <a href="$xwiki.getURL($eventUser)">#smallUserAvatar("$!eventUser")</a>
    | 26 | + #else
    | 27 | + #smallUserAvatar("$!eventUser")
    | 28 | + #end
    | 29 | + <img class='activityActionAvatar' src="$xwiki.getSkinFile($activityActionsMap.get($eventType), true)" alt="$services.localization.render("xe.activity.action.${eventType}", [$relatedEventsNo])" title="$services.localization.render("xe.activity.action.${eventType}", [$relatedEventsNo])" />
    | 30 | + </span>
    | 31 | +#end
    | 32 | +##
    | 33 | +#* Duplicate code END *#
    | 34 | +##
    | 35 | +## Skin Extensions
    | 36 | +##--------------------------------------------------------------
    | 37 | +#set ($discard = $xwiki.jsfx.use('uicomponents/suggest/suggestPicker.js'))
    | 38 | +#set ($discard = $xwiki.jsfx.use('uicomponents/widgets/userpicker/userPicker.js'))
    | 39 | +#set ($discard = $xwiki.ssx.use('Main.Activity'))
    | 40 | +#set ($discard = $xwiki.ssx.use('Main.MessageSenderMacro'))
    | 41 | +#set ($discard = $xwiki.jsx.use('Main.MessageSenderMacro'))
    | 42 | +{{/velocity}}
    | 43 | +
    | 44 | +{{velocity}}
    | 45 | +#if ($isMessageStreamActive)
    | 46 | + #set ($xredirect = $doc.getURL($xcontext.action, $!{request.queryString}))
    | 47 | + #set ($errorMessageKeyPrefix = "messageSender.post.error.")
    | 48 | + #set ($errorMessage = $request.session.getAttribute("${errorMessageKeyPrefix}${xredirect}"))
    | 49 | + #if ("$!errorMessage" != '')
    | 50 | + ## Clean the error and display the message.
    | 51 | + #set ($discard = $request.session.removeAttribute("${errorMessageKeyPrefix}${xredirect}"))
    | 52 | + {{error}}$errorMessage{{/error}}
    | 53 | +
    | 54 | + ##
    | 55 | + ## Handle postMessage action here so that the macro can be self contained.
    | 56 | + ##
    | 57 | + #elseif ("$!{request.xaction}" != '')
    | 58 | + #if ("$!{request.xaction}" == 'postMessage')
    | 59 | + #if(!$services.csrf.isTokenValid(${request.form_token}))
    | 60 | + #set($redirectURL = $services.csrf.getResubmissionURL())
    | 61 | + $response.sendRedirect($redirectURL)
    | 62 | + #else
    | 63 | + #set ($message = "$!{request.messagestream_message.trim()}")
    | 64 | + #if ($message == '')
    | 65 | + ## Do nothing with empty messages
    | 66 | + #if ("$!{request.xredirect}" != '')
    | 67 | + $response.sendRedirect(${request.xredirect})
    | 68 | + #end
    | 69 | + #else
    | 70 | + #set ($success = false)
    | 71 | + #if (!$isGuest)
    | 72 | + #set ($messageType = "$!{request.visibilityLevel}")
    | 73 | + #if ($messageType == 'everyone')
    | 74 | + #set ($success = $services.messageStream.postPublicMessage($message))
    | 75 | + #elseif ($messageType == 'followers')
    | 76 | + #set ($success = $services.messageStream.postPersonalMessage($message))
    | 77 | + #elseif ($messageType == 'group')
    | 78 | + #set ($success = $services.messageStream.postMessageToGroup($message, $services.model.resolveDocument("$!{request.targetName}")))
    | 79 | + #elseif ($messageType == 'user')
    | 80 | + #set ($success = $services.messageStream.postDirectMessageToUser($message, $services.model.resolveDocument("$!{request.targetName}")))
    | 81 | + #end
    | 82 | + #end
    | 83 | + #if (!$success)
    | 84 | + #if ($request.xpage == 'plain')
    | 85 | + #if ($isGuest)
    | 86 | + $response.setStatus(401)## Unauthorized
    | 87 | + #else
    | 88 | + $response.setStatus(403)## Forbidden
    | 89 | + #end
    | 90 | + $services.localization.render('xe.activity.messages.submit.failed')
    | 91 | + #elseif ("$!{request.xredirect}" != '')
    | 92 | + ## Pass the error message in the xredirect so that we display it in the calling page and not in the macro page.
    | 93 | + $request.session.setAttribute("${errorMessageKeyPrefix}${request.xredirect}", $services.localization.render('xe.activity.messages.submit.failed'))
    | 94 | + $response.sendRedirect($request.xredirect)
    | 95 | + #else
    | 96 | + {{error}}$services.localization.render('xe.activity.messages.submit.failed'){{/error}}
    | 97 | +
    | 98 | + #end
    | 99 | + #elseif ("$!{request.xredirect}" != '')
    | 100 | + $response.sendRedirect(${request.xredirect})
    | 101 | + #elseif ($request.xpage == 'plain')
    | 102 | + ## Just print something for confirmation.
    | 103 | + $services.localization.render('xe.activity.messages.submit.success')
    | 104 | + #end
    | 105 | + #end
    | 106 | + #end
    | 107 | + #end
    | 108 | + #end
    | 109 | + ##
    | 110 | + ## Display the UI. Don't display it for AJAX calls and guests.
    | 111 | + ##
    | 112 | + #if ($request.xpage != 'plain' && !$isGuest)
    | 113 | + ## When this macro is displayed in edit/inline mode, we disable the inputs and no longer display the form (because it would clash with the main form of inline edit).
    | 114 | + #set ($inEditMode = ($xcontext.action == 'inline' || $xcontext.action == 'edit'))
    | 115 | + #set ($actionQueryString = "xredirect=$escapetool.url($xredirect)")
    | 116 | + {{html}}
    | 117 | + ## Wrap the form in a div so that the layout is preserved in inline mode (where the form is stripped by the rendering).
    | 118 | + <div class='messagestream activityUser xform'>
    | 119 | + #if (!$inEditMode)
    | 120 | + <form action="${xwiki.getURL('Main.MessageSenderMacro', 'view', $!{actionQueryString})}" method='post'>
    | 121 | + #end
    | 122 | + <div class='activityContent'>
    | 123 | + #if(!$inEditMode)
    | 124 | + ## CSRF prevention
    | 125 | + <input type="hidden" name="form_token" value="$!{services.csrf.getToken()}" />
    | 126 | + <input type='hidden' name='xaction' value='postMessage' />
    | 127 | + #end
    | 128 | + #activitySnapshot($xcontext.user, 'personalMessage')
    | 129 | + <div class='messagestream-data'>
    | 130 | + <textarea name='messagestream_message' rows='3' cols='40'#if($inEditMode) disabled='disabled'#end></textarea>
    | 131 | + </div>
    | 132 | + <div class='messagestream-tools'>
    | 133 | + #set ($possibleTargets = $xcontext.macro.params.get('visibilityOptions').split('\s*,\s*'))
    | 134 | + #if (!$possibleTargets)
    | 135 | + #set ($possibleTargets = $configClass.get('visibilityLevel').getListValues())
    | 136 | + #end
    | 137 | + ##
    | 138 | + #set ($targetName = $xcontext.macro.params.get('visibilityParameter'))
    | 139 | + ##
    | 140 | + #set ($defaultTarget = $xcontext.macro.params.get('visibility'))
    | 141 | + #if (!$defaultTarget)
    | 142 | + #if ($doc.getObject('XWiki.XWikiGroups'))
    | 143 | + #set ($defaultTarget = 'group')
    | 144 | + #set ($targetName = $doc.prefixedFullName)
    | 145 | + #elseif ($doc.getObject('XWiki.XWikiUsers'))
    | 146 | + #if ($doc.prefixedFullName == $currentUser)
    | 147 | + #set ($defaultTarget = 'followers')
    | 148 | + #else
    | 149 | + #set ($defaultTarget = 'user')
    | 150 | + #set ($targetName = $doc.prefixedFullName)
    | 151 | + #end
    | 152 | + #else
    | 153 | + #set ($defaultTarget = 'everyone')
    | 154 | + #end
    | 155 | + #end
    | 156 | + <label>$services.localization.render('xe.activity.messages.visibility')
    | 157 | + <select name='visibilityLevel'#if($inEditMode) disabled='disabled'#end>
    | 158 | + #foreach ($target in $possibleTargets)
    | 159 | + <option value="${target}"#if ($defaultTarget == $target) selected='selected'#end>$services.localization.render("XWiki.MessageStreamConfig_visibilityLevel_${target}")</option>
    | 160 | + #end
    | 161 | + </select>
    | 162 | + </label>
    | 163 | + <label for="targetName$!{targetNameCounter}">
    | 164 | + <span class='hidden'>Name:</span>
    | 165 | + <input type="text" name="targetName" id="targetName$!targetNameCounter" class="targetName withTip useTitleAsTip" value="$!targetName" size="15"
    | 166 | + title="$services.localization.render('xe.activity.messages.visibility.targetName.tip')" #if ($inEditMode) disabled="disabled"#end/>
    | 167 | + </label>
    | 168 | + #if (!$targetNameCounter)
    | 169 | + #set ($targetNameCounter = 1)
    | 170 | + #else
    | 171 | + #set ($targetNameCounter = $targetNameCounter + 1)
    | 172 | + #end
    | 173 | + </div>
    | 174 | + <div class='messagestream-buttons'>
    | 175 | + <span class='butonwrapper'><input type='submit' value="$services.localization.render('xe.activity.messages.submit')" class='button'#if($inEditMode) disabled='disabled'#end/></span>
    | 176 | + </div>
    | 177 | + </div>
    | 178 | + #if (!$inEditMode)
    | 179 | + </form>
    | 180 | + #end
    | 181 | + </div>
    | 182 | + {{/html}}
    | 183 | + #end
    | 184 | +#end
    | 185 | +{{/velocity}}

  Macro content type

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No content

  Default category

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Social

  Macro description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +A control that allows users to enter messages that are handled by the MessageStream module.

  Macro id

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +messageSender

  Macro name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Message Sender

  Supports inline mode

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +No

  Macro visibility

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Current Wiki

XWiki.WikiMacroParameterClass[0]
  Parameter description

    ... | ... | @@ -1,0 +1,5 @@
    | 1 | +Default selected visibility when the macro is displayed.
    | 2 | +
    | 3 | +If not specified, it is determined automatically based on the page where the macro is used.
    | 4 | +
    | 5 | +Valid values are: 'everyone', 'followers', 'group' or 'user'.

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +visibility

XWiki.WikiMacroParameterClass[1]
  Parameter description

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +Some visibility levels (like 'user' and 'group') accept a parameter. In the case of the 2 mentioned levels, the value can be a serialized reference of a user or a group document.

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +visibilityParameter

XWiki.WikiMacroParameterClass[2]
  Parameter description

    ... | ... | @@ -1,0 +1,3 @@
    | 1 | +Comma separated list of visibility options that the macro should allow the user to choose from.
    | 2 | +
    | 3 | +This list should be a sublist of the defualt ones: 'everyone', 'followers', 'group', 'user'.

  Parameter name

    ... | ... | @@ -1,0 +1,1 @@
    | 1 | +visibilityOptions