Changes for page MentionsMacro

Last modified by Drunk Monkey on 2023-11-02 09:50

From version 17.1
edited by Drunk Monkey
on 2023-04-26 18:21
Change comment: Migrated property [type] from class [XWiki.WikiMacroParameterClass]
To version 2.1
edited by Drunk Monkey
on 2020-07-06 08:28
Change comment: Install extension [org.xwiki.platform:xwiki-platform-mentions-ui/12.5.1]

Summary

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,37 +1,57 @@
1 1  require.config({
2 2   paths: {
3 - 'xwiki-suggestUsers': $jsontool.serialize($xwiki.getSkinFile('uicomponents/suggest/suggestUsersAndGroups.js'))
3 + 'xwiki-suggestUsers': "$xwiki.getSkinFile('uicomponents/suggest/suggestUsersAndGroups.js', true))" +
4 + "?v=$escapetool.url($xwiki.version)"
4 4   }
5 5  });
6 6  require(['deferred!ckeditor', 'xwiki-suggestUsers', 'jquery', 'xwiki-meta'], function (ckeditorPromise, suggestUsers, $, xm) {
7 -
8 + /*
9 + * Keep records of the added anchors during the current edit session.
10 + * Note that the anchors saved here are not only the ones added on the current session,
11 + * but also those already present in the document.
12 + * For more information, see #getAnchor.
13 + * The information are stored on the form:
14 + * { reference: Array<String> }
15 + */
16 + const anchorIds = {};
17 +
8 8   /**
9 9   * Get the current wiki scope for displaying global, local or global and local users
10 10   */
11 11   const userScope = "$!services.wiki.user.userScope";
12 -
13 - // see https://stackoverflow.com/a/6248722/657524
14 - function random6chars() {
15 - // I generate the UID from two parts here
16 - // to ensure the random number provide enough bits.
17 - var firstPart = (Math.random() * 46656) | 0;
18 - var secondPart = (Math.random() * 46656) | 0;
19 - firstPart = ("000" + firstPart.toString(36)).slice(-3);
20 - secondPart = ("000" + secondPart.toString(36)).slice(-3);
21 - return firstPart + secondPart;
22 - }
23 -
22 +
24 24   /**
25 25   * Compute a new unique anchor for the given reference.
26 - * The unique anchor is based on the mentionned user id, concatenaed with a random string of 6 alphanumeric
27 - * characters.
28 - * The chances of collision are quite low, about 46k mentions for a given mentioned user on a given page (assuming
29 - * that no mentions are ever deleted).
25 + * The uniqueness of the anchor is given by two mechanisms:
26 + * - retrieve on first call all mentions available on current document and store them
27 + * - then use that information to compute a next anchor not overlapping an existing one on the current document.
28 + * The mechanism is obviously not perfect and might be improved later but should be enough for most usage.
30 30   */
31 31   const getAnchor = function (reference) {
32 - const refId = reference.replace(/[.:]/g, '-');
33 - const randomId = random6chars();
34 - return refId + '-' + randomId;
31 + var existingIds;
32 +
33 + if (anchorIds.hasOwnProperty(reference)) {
34 + existingIds = anchorIds[reference];
35 + } else {
36 + existingIds = [];
37 + $('.xwiki-mention').each(function() {
38 + var mention = $(this);
39 + if (mention.attr('data-reference') === reference) {
40 + existingIds.push(mention.attr('id'));
41 + }
42 + });
43 + anchorIds[reference] = existingIds;
44 + }
45 +
46 + var counter = existingIds.length + 1;
47 + var refId = reference.replace(/[.:]/g, '-');
48 + var proposedAnchor = refId + "-" + counter;
49 + while (existingIds.indexOf(proposedAnchor) != -1) {
50 + counter++;
51 + proposedAnchor = refId + "-" + counter;
52 + }
53 + anchorIds[reference].push(proposedAnchor);
54 + return proposedAnchor;
35 35   };
36 36  
37 37   const search = function (text, callback) {
... ... @@ -39,8 +39,8 @@
39 39   'input': text,
40 40   'limit': 6,
41 41   };
42 - suggestUsers.loadUsers(userScope, params).then(users => {
43 - const cct = users.map(function (x) {
62 + $.when(suggestUsers.loadUsers(userScope, params)).then(function (user) {
63 + const cct = user.map(function (x) {
44 44   // insert an id because that's required by the mentions plugins.
45 45   x.id = x.value;
46 46   // Make sure to display the icon avatar or the image one.
... ... @@ -56,11 +56,11 @@
56 56   return x;
57 57   });
58 58   callback(cct);
59 - });
79 + })
60 60   }
61 61  
62 - ckeditorPromise.then(ckeditor => {
63 - function getUserMentionsConfig(editor) {
82 + ckeditorPromise.done(function (ckeditor) {
83 + function confMentions(name) {
64 64   return {
65 65   feed: function (opts, callback) {
66 66   search(opts.query, callback);
... ... @@ -68,25 +68,33 @@
68 68   marker: '@',
69 69   minChars: 0,
70 70   itemsLimit: 6,
71 - itemTemplate:
72 - `<li data-id="{id}" class="ckeditor-autocomplete-item">
73 - <div>
74 - <span class="ckeditor-autocomplete-item-icon-wrapper">
75 - <span class="{cssClass}"></span>
76 - <img src="{imgUrl}" class="{imgClass}"/>
77 - </span>
78 - <span class="ckeditor-autocomplete-item-label">{label}</span>
79 - </div>
80 - </li>`,
91 + itemTemplate: '<li data-id="{id}" class="ckeditor-autocomplete-item">'+
92 + '<div>'+
93 + '<span class="ckeditor-autocomplete-item-icon-wrapper">'+
94 + '<span class="{cssClass}"></span>'+
95 + '<img src="{imgUrl}" class="{imgClass}"/>'+
96 + '</span>'+
97 + '<span class="ckeditor-autocomplete-item-label">{label}</span>'+
98 + '</div>'+
99 + '</li>',
81 81   outputTemplate: function (param) {
82 - editor.once('afterInsertHtml', function() {
101 + var editor = ckeditor.instances[name];
102 + var currentWikiReference = xm.documentReference.extractReference(XWiki.EntityType.WIKI);
103 +
104 + // Compute an absolute reference containing the wiki reference even if the user is local.
105 + var documentReference = XWiki.Model.resolve(param.id, XWiki.EntityType.DOCUMENT);
106 + if (!documentReference.extractReference(XWiki.EntityType.WIKI)) {
107 + documentReference = documentReference.appendParent(currentWikiReference);
108 + }
109 + var serializedReference = XWiki.Model.serialize(documentReference);
110 + editor.once('afterInsertHtml', function () {
83 83   editor.execCommand('xwiki-macro-insert', {
84 84   name: 'mention',
85 - inline: 'enforce',
113 + inline: true,
86 86   parameters: {
87 - reference: param.id,
115 + reference: serializedReference,
88 88   style: 'FULL_NAME',
89 - anchor: getAnchor(param.id)
117 + anchor: getAnchor(serializedReference)
90 90   }
91 91   });
92 92   });
... ... @@ -97,19 +97,22 @@
97 97   };
98 98   }
99 99  
100 - function updateConfig(editor) {
101 - editor.config.mentions = editor.config.mentions || [];
102 - editor.config.mentions.push(getUserMentionsConfig(editor));
128 + function updateConf(config, name) {
129 + const newConf = config;
130 + newConf.mentions = newConf.mentions || [];
131 + newConf.mentions.push(confMentions(name));
132 + return newConf;
103 103   }
104 104  
105 - ckeditor.on('instanceCreated', function(event) {
106 - // The editor instance was created but it not yet initialized. Unfortunately the configuration object passed when
107 - // the instance was created has not been merged with the global configuration yet.
108 - event.editor.once('configLoaded', function(event) {
109 - // The editor configuration has been loaded (the instance configuration has been merged with the global
110 - // configuration) but the editor has not been fully initialized yet so we can modify the configuration.
111 - updateConfig(event.editor);
112 - });
113 - });
135 + var oldReplace = ckeditor.replace;
136 + ckeditor.replace = function (element, config) {
137 + return oldReplace.call(this, element, updateConf(config, element.id));
138 + };
139 +
140 + var oldInline = ckeditor.inline;
141 + ckeditor.inline = function (element, config) {
142 + return oldInline.call(this, element, updateConf(config, element.id));
143 + };
114 114   });
115 115  });
146 +
XWiki.StyleSheetExtension[0]
Code
... ... @@ -11,7 +11,3 @@
11 11  .xwiki-mention.removed {
12 12   text-decoration: line-through;
13 13  }
14 -
15 -blockquote.mention-quote {
16 - font-size: inherit;
17 -}
XWiki.WikiMacroClass[0]
Macro code
... ... @@ -1,18 +1,26 @@
1 1  {{velocity}}
2 2  #set ($reference = $wikimacro.parameters.reference)
3 3  #set ($style = $wikimacro.parameters.style)
4 -#set ($type = "$!wikimacro.parameters.type")
5 -#set ($content = $services.mentions.format($reference.reference, $style, $type))
6 6  #set ($anchor = $wikimacro.parameters.anchor)
7 -#set ($isCurrentUser = $xcontext.userReference == $reference.reference && ($type == '' || $type == 'user'))
5 +#set ($userProperties = $services.user.getProperties($reference))
6 +#set ($firstName = $userProperties.getFirstName())
7 +#set ($lastName = $userProperties.getLastName())
8 +#set ($isCurrentUser = $xcontext.userReference == $reference.reference)
8 8  #set ($cssClasses = ['xwiki-mention', 'user'])
9 9  #if ($isCurrentUser)
10 10   #set ($discard = $cssClasses.add('self'))
11 11  #end
13 +#if ("$!firstName" == "")
14 + #set($content = "@$reference.reference.name")
15 +#elseif ($style == 'FIRST_NAME')
16 + #set($content = "@$firstName")
17 +#elseif ($style == 'LOGIN')
18 + #set($content = "@$reference.reference.name")
19 +#else
20 + #set($content = "@$firstName $!lastName")
21 +#end
12 12  #set ($link = $xwiki.getURL($reference.reference, 'view'))
13 13  {{html}}
14 -<a id="$escapetool.xml($anchor)" class="$stringtool.join($cssClasses, ' ')" data-reference="$escapetool.xml($services.model.serialize($reference.reference, 'default'))" href="$escapetool.xml($link)">##
15 - $escapetool.xml($content)## Do not remove this comment as it ensures that the spacing after mention is not broken.
16 -</a>
24 +<a id="$anchor" class="$stringtool.join($cssClasses, ' ')" data-reference="$services.model.serialize($reference.reference, 'default')" href="$link">$content</a>
17 17  {{/html}}
18 18  {{/velocity}}
Macro description
... ... @@ -1,1 +1,1 @@
1 -Inserts a user mention.
1 +Insert a user mention.
Default categories
... ... @@ -1,1 +1,0 @@
1 -Notifications
XWiki.WikiMacroParameterClass[3]
Parameter description
... ... @@ -1,1 +1,0 @@
1 -The type of mentioned actor.
Parameter name
... ... @@ -1,1 +1,0 @@
1 -type
Parameter type
... ... @@ -1,1 +1,0 @@
1 -java.lang.String