Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/core/Prefab.js
12241 views
1
/**
2
* @provides phabricator-prefab
3
* @requires javelin-install
4
* javelin-util
5
* javelin-dom
6
* javelin-typeahead
7
* javelin-tokenizer
8
* javelin-typeahead-preloaded-source
9
* javelin-typeahead-ondemand-source
10
* javelin-dom
11
* javelin-stratcom
12
* javelin-util
13
* @javelin
14
*/
15
16
/**
17
* Utilities for client-side rendering (the greatest thing in the world).
18
*/
19
JX.install('Prefab', {
20
21
statics : {
22
renderSelect : function(map, selected, attrs, order) {
23
var select = JX.$N('select', attrs || {});
24
25
// Callers may optionally pass "order" to force options into a specific
26
// order. Although most browsers do retain order, maps in Javascript
27
// aren't technically ordered. Safari, at least, will reorder maps with
28
// numeric keys.
29
30
order = order || JX.keys(map);
31
32
var k;
33
for (var ii = 0; ii < order.length; ii++) {
34
k = order[ii];
35
select.options[select.options.length] = new Option(map[k], k);
36
if (k == selected) {
37
select.value = k;
38
}
39
}
40
41
select.value = select.value || order[0];
42
43
return select;
44
},
45
46
newTokenizerFromTemplate: function(markup, config) {
47
var template = JX.$H(markup).getFragment().firstChild;
48
var container = JX.DOM.find(template, 'div', 'tokenizer-container');
49
50
container.id = '';
51
config.root = container;
52
53
var build = JX.Prefab.buildTokenizer(config);
54
build.node = template;
55
return build;
56
},
57
58
/**
59
* Build a Phabricator tokenizer out of a configuration with application
60
* sorting, datasource and placeholder rules.
61
*
62
* - `id` Root tokenizer ID (alternatively, pass `root`).
63
* - `root` Root tokenizer node (replaces `id`).
64
* - `src` Datasource URI.
65
* - `ondemand` Optional, use an ondemand source.
66
* - `value` Optional, initial value.
67
* - `limit` Optional, token limit.
68
* - `placeholder` Optional, placeholder text.
69
* - `username` Optional, username to sort first (i.e., viewer).
70
* - `icons` Optional, map of icons.
71
*
72
*/
73
buildTokenizer : function(config) {
74
config.icons = config.icons || {};
75
76
var root;
77
78
try {
79
root = config.root || JX.$(config.id);
80
} catch (ex) {
81
// If the root element does not exist, just return without building
82
// anything. This happens in some cases -- like Conpherence -- where we
83
// may load a tokenizer but not put it in the document.
84
return;
85
}
86
87
var datasource;
88
89
// Default to an ondemand source if no alternate configuration is
90
// provided.
91
var ondemand = true;
92
if ('ondemand' in config) {
93
ondemand = config.ondemand;
94
}
95
96
if (ondemand) {
97
datasource = new JX.TypeaheadOnDemandSource(config.src);
98
} else {
99
datasource = new JX.TypeaheadPreloadedSource(config.src);
100
}
101
102
datasource.setSortHandler(
103
JX.bind(datasource, JX.Prefab.sortHandler, config));
104
datasource.setTransformer(JX.Prefab.transformDatasourceResults);
105
106
var typeahead = new JX.Typeahead(
107
root,
108
JX.DOM.find(root, 'input', 'tokenizer-input'));
109
typeahead.setDatasource(datasource);
110
111
var tokenizer = new JX.Tokenizer(root);
112
tokenizer.setTypeahead(typeahead);
113
tokenizer.setRenderTokenCallback(function(value, key, container) {
114
var result;
115
if (value && (typeof value == 'object') && ('id' in value)) {
116
// TODO: In this case, we've been passed the decoded wire format
117
// dictionary directly. Token rendering is kind of a huge mess that
118
// should be cleaned up and made more consistent. Just force our
119
// way through for now.
120
result = value;
121
} else {
122
result = datasource.getResult(key);
123
}
124
125
var icon;
126
var type;
127
var color;
128
var availability_color;
129
if (result) {
130
icon = result.icon;
131
value = result.displayName;
132
type = result.tokenType;
133
color = result.color;
134
availability_color = result.availabilityColor;
135
} else {
136
icon = (config.icons || {})[key];
137
type = (config.types || {})[key];
138
color = (config.colors || {})[key];
139
availability_color = (config.availabilityColors || {})[key];
140
}
141
142
if (icon) {
143
icon = JX.Prefab._renderIcon(icon);
144
}
145
146
type = type || 'object';
147
JX.DOM.alterClass(container, 'jx-tokenizer-token-' + type, true);
148
149
if (color) {
150
JX.DOM.alterClass(container, color, true);
151
}
152
153
var dot;
154
if (availability_color) {
155
dot = JX.$N(
156
'span',
157
{
158
className: 'phui-tag-dot phui-tag-color-' + availability_color
159
});
160
}
161
162
return [icon, dot, value];
163
});
164
165
if (config.placeholder) {
166
tokenizer.setPlaceholder(config.placeholder);
167
}
168
169
if (config.limit) {
170
tokenizer.setLimit(config.limit);
171
}
172
173
if (config.value) {
174
tokenizer.setInitialValue(config.value);
175
}
176
177
if (config.browseURI) {
178
tokenizer.setBrowseURI(config.browseURI);
179
}
180
181
if (config.disabled) {
182
tokenizer.setDisabled(true);
183
}
184
185
JX.Stratcom.addData(root, {'tokenizer' : tokenizer});
186
187
return {
188
tokenizer: tokenizer
189
};
190
},
191
192
sortHandler: function(config, value, list, cmp) {
193
// Sort results so that the viewing user always comes up first; after
194
// that, prefer unixname matches to realname matches.
195
var priority_hits = {};
196
var self_hits = {};
197
198
// We'll put matches where the user's input is a prefix of the name
199
// above matches where that isn't true.
200
var prefix_hits = {};
201
202
var tokens = this.tokenize(value);
203
var normal = this.normalize(value);
204
205
for (var ii = 0; ii < list.length; ii++) {
206
var item = list[ii];
207
208
if (this.normalize(item.name).indexOf(normal) === 0) {
209
prefix_hits[item.id] = true;
210
}
211
212
if (!item.priority) {
213
continue;
214
}
215
216
if (config.username && item.priority == config.username) {
217
self_hits[item.id] = true;
218
}
219
}
220
221
list.sort(function(u, v) {
222
if (self_hits[u.id] != self_hits[v.id]) {
223
return self_hits[v.id] ? 1 : -1;
224
}
225
226
// If one result is open and one is closed, show the open result
227
// first. The "!" tricks here are because closed values are display
228
// strings, so the value is either `null` or some truthy string. If
229
// we compare the values directly, we'll apply this rule to two
230
// objects which are both closed but for different reasons, like
231
// "Archived" and "Disabled".
232
233
var u_open = !u.closed;
234
var v_open = !v.closed;
235
236
if (u_open != v_open) {
237
if (u_open) {
238
return -1;
239
} else {
240
return 1;
241
}
242
}
243
244
if (prefix_hits[u.id] != prefix_hits[v.id]) {
245
return prefix_hits[v.id] ? 1 : -1;
246
}
247
248
// Sort users ahead of other result types.
249
if (u.priorityType != v.priorityType) {
250
if (u.priorityType == 'user') {
251
return -1;
252
}
253
if (v.priorityType == 'user') {
254
return 1;
255
}
256
}
257
258
// Sort functions after other result types.
259
var uf = (u.tokenType == 'function');
260
var vf = (v.tokenType == 'function');
261
if (uf != vf) {
262
return uf ? 1 : -1;
263
}
264
265
return cmp(u, v);
266
});
267
},
268
269
270
/**
271
* Transform results from a wire format into a usable format in a standard
272
* way.
273
*/
274
transformDatasourceResults: function(fields) {
275
var closed = fields[9];
276
var closed_ui;
277
if (closed) {
278
closed_ui = JX.$N(
279
'div',
280
{className: 'tokenizer-closed'},
281
closed);
282
}
283
284
var icon = fields[8];
285
var icon_ui;
286
if (icon) {
287
icon_ui = JX.Prefab._renderIcon(icon);
288
}
289
290
var availability_ui;
291
var availability_color = fields[16];
292
if (availability_color) {
293
availability_ui = JX.$N(
294
'span',
295
{
296
className: 'phui-tag-dot phui-tag-color-' + availability_color
297
});
298
}
299
300
var display = JX.$N(
301
'div',
302
{className: 'tokenizer-result'},
303
[icon_ui, availability_ui, fields[4] || fields[0], closed_ui]);
304
if (closed) {
305
JX.DOM.alterClass(display, 'tokenizer-result-closed', true);
306
}
307
308
return {
309
name: fields[0],
310
displayName: fields[4] || fields[0],
311
display: display,
312
uri: fields[1],
313
id: fields[2],
314
priority: fields[3],
315
priorityType: fields[7],
316
imageURI: fields[6],
317
icon: icon,
318
closed: closed,
319
type: fields[5],
320
sprite: fields[10],
321
color: fields[11],
322
tokenType: fields[12],
323
unique: fields[13] || false,
324
autocomplete: fields[14],
325
sort: JX.TypeaheadNormalizer.normalize(fields[0]),
326
availabilityColor: availability_color
327
};
328
},
329
330
_renderIcon: function(icon) {
331
return JX.$N(
332
'span',
333
{className: 'phui-icon-view phui-font-fa ' + icon});
334
}
335
336
}
337
338
});
339
340