Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/application/diff/DiffChangeset.js
12242 views
1
/**
2
* @provides phabricator-diff-changeset
3
* @requires javelin-dom
4
* javelin-util
5
* javelin-stratcom
6
* javelin-install
7
* javelin-workflow
8
* javelin-router
9
* javelin-behavior-device
10
* javelin-vector
11
* phabricator-diff-inline
12
* phabricator-diff-path-view
13
* phuix-button-view
14
* javelin-external-editor-link-engine
15
* @javelin
16
*/
17
18
JX.install('DiffChangeset', {
19
20
construct : function(node) {
21
this._node = node;
22
23
var data = this._getNodeData();
24
25
this._renderURI = data.renderURI;
26
this._ref = data.ref;
27
this._loaded = data.loaded;
28
this._treeNodeID = data.treeNodeID;
29
30
this._leftID = data.left;
31
this._rightID = data.right;
32
33
this._displayPath = JX.$H(data.displayPath);
34
this._pathParts = data.pathParts;
35
this._icon = data.icon;
36
37
this._editorURITemplate = data.editorURITemplate;
38
this._editorConfigureURI = data.editorConfigureURI;
39
this._showPathURI = data.showPathURI;
40
this._showDirectoryURI = data.showDirectoryURI;
41
42
this._pathIconIcon = data.pathIconIcon;
43
this._pathIconColor = data.pathIconColor;
44
this._isLowImportance = data.isLowImportance;
45
this._isOwned = data.isOwned;
46
this._isLoading = true;
47
48
this._inlines = null;
49
50
if (data.changesetState) {
51
this._loadChangesetState(data.changesetState);
52
}
53
54
JX.enableDispatch(window, 'selectstart');
55
56
var onselect = JX.bind(this, this._onClickHeader);
57
JX.DOM.listen(
58
this._node,
59
['mousedown', 'selectstart'],
60
'changeset-header',
61
onselect);
62
},
63
64
members: {
65
_node: null,
66
_loaded: false,
67
_sequence: 0,
68
_stabilize: false,
69
70
_renderURI: null,
71
_ref: null,
72
_rendererKey: null,
73
_highlight: null,
74
_requestDocumentEngineKey: null,
75
_responseDocumentEngineKey: null,
76
_availableDocumentEngineKeys: null,
77
_characterEncoding: null,
78
_undoTemplates: null,
79
80
_leftID: null,
81
_rightID: null,
82
83
_inlines: null,
84
_visible: true,
85
86
_displayPath: null,
87
88
_changesetList: null,
89
_icon: null,
90
91
_editorURITemplate: null,
92
_editorConfigureURI: null,
93
_showPathURI: null,
94
_showDirectoryURI: null,
95
96
_pathView: null,
97
98
_pathIconIcon: null,
99
_pathIconColor: null,
100
_isLowImportance: null,
101
_isOwned: null,
102
_isHidden: null,
103
_isSelected: false,
104
_viewMenu: null,
105
106
getEditorURITemplate: function() {
107
return this._editorURITemplate;
108
},
109
110
getEditorConfigureURI: function() {
111
return this._editorConfigureURI;
112
},
113
114
getShowPathURI: function() {
115
return this._showPathURI;
116
},
117
118
getShowDirectoryURI: function() {
119
return this._showDirectoryURI;
120
},
121
122
getLeftChangesetID: function() {
123
return this._leftID;
124
},
125
126
getRightChangesetID: function() {
127
return this._rightID;
128
},
129
130
setChangesetList: function(list) {
131
this._changesetList = list;
132
return this;
133
},
134
135
setViewMenu: function(menu) {
136
this._viewMenu = menu;
137
return this;
138
},
139
140
getIcon: function() {
141
if (!this._visible) {
142
return 'fa-file-o';
143
}
144
145
return this._icon;
146
},
147
148
getColor: function() {
149
if (!this._visible) {
150
return 'grey';
151
}
152
153
return 'blue';
154
},
155
156
getChangesetList: function() {
157
return this._changesetList;
158
},
159
160
/**
161
* Has the content of this changeset been loaded?
162
*
163
* This method returns `true` if a request has been fired, even if the
164
* response has not returned yet.
165
*
166
* @return bool True if the content has been loaded.
167
*/
168
isLoaded: function() {
169
return this._loaded;
170
},
171
172
173
/**
174
* Configure stabilization of the document position on content load.
175
*
176
* When we dump the changeset into the document, we can try to stabilize
177
* the document scroll position so that the user doesn't feel like they
178
* are jumping around as things load in. This is generally useful when
179
* populating initial changes.
180
*
181
* However, if a user explicitly requests a content load by clicking a
182
* "Load" link or using the dropdown menu, this stabilization generally
183
* feels unnatural, so we don't use it in response to explicit user action.
184
*
185
* @param bool True to stabilize the next content fill.
186
* @return this
187
*/
188
setStabilize: function(stabilize) {
189
this._stabilize = stabilize;
190
return this;
191
},
192
193
194
/**
195
* Should this changeset load immediately when the page loads?
196
*
197
* Normally, changes load immediately, but if a diff or commit is very
198
* large we stop doing this and have the user load files explicitly, or
199
* choose to load everything.
200
*
201
* @return bool True if the changeset should load automatically when the
202
* page loads.
203
*/
204
shouldAutoload: function() {
205
return this._getNodeData().autoload;
206
},
207
208
209
/**
210
* Load this changeset, if it isn't already loading.
211
*
212
* This fires a request to fill the content of this changeset, provided
213
* there isn't already a request in flight. To force a reload, use
214
* @{method:reload}.
215
*
216
* @return this
217
*/
218
load: function() {
219
if (this._loaded) {
220
return this;
221
}
222
223
return this.reload();
224
},
225
226
227
/**
228
* Reload the changeset content.
229
*
230
* This method always issues a request, even if the content is already
231
* loading. To load conditionally, use @{method:load}.
232
*
233
* @return this
234
*/
235
reload: function(state) {
236
this._loaded = true;
237
this._sequence++;
238
239
var workflow = this._newReloadWorkflow(state)
240
.setHandler(JX.bind(this, this._onresponse, this._sequence));
241
242
this._startContentWorkflow(workflow);
243
244
var pht = this.getChangesetList().getTranslations();
245
246
JX.DOM.setContent(
247
this._getContentFrame(),
248
JX.$N(
249
'div',
250
{className: 'differential-loading'},
251
pht('Loading...')));
252
253
return this;
254
},
255
256
_newReloadWorkflow: function(state) {
257
var params = this._getViewParameters(state);
258
return new JX.Workflow(this._renderURI, params);
259
},
260
261
/**
262
* Load missing context in a changeset.
263
*
264
* We do this when the user clicks "Show X Lines". We also expand all of
265
* the missing context when they "Show All Context".
266
*
267
* @param string Line range specification, like "0-40/0-20".
268
* @param node Row where the context should be rendered after loading.
269
* @param bool True if this is a bulk load of multiple context blocks.
270
* @return this
271
*/
272
loadContext: function(range, target, bulk) {
273
var params = this._getViewParameters();
274
params.range = range;
275
276
var pht = this.getChangesetList().getTranslations();
277
278
var container = JX.DOM.scry(target, 'td')[0];
279
JX.DOM.setContent(container, pht('Loading...'));
280
JX.DOM.alterClass(target, 'differential-show-more-loading', true);
281
282
var workflow = new JX.Workflow(this._renderURI, params)
283
.setHandler(JX.bind(this, this._oncontext, target));
284
285
if (bulk) {
286
// If we're loading a bunch of these because the viewer clicked
287
// "Show All Context" or similar, use lower-priority requests
288
// and draw a progress bar.
289
this._startContentWorkflow(workflow);
290
} else {
291
// If this is a single click on a context link, use a higher priority
292
// load without a chrome change.
293
workflow.start();
294
}
295
296
return this;
297
},
298
299
loadAllContext: function() {
300
var nodes = JX.DOM.scry(this._node, 'tr', 'context-target');
301
for (var ii = 0; ii < nodes.length; ii++) {
302
var show = JX.DOM.scry(nodes[ii], 'a', 'show-more');
303
for (var jj = 0; jj < show.length; jj++) {
304
var data = JX.Stratcom.getData(show[jj]);
305
if (data.type != 'all') {
306
continue;
307
}
308
this.loadContext(data.range, nodes[ii], true);
309
}
310
}
311
},
312
313
_startContentWorkflow: function(workflow) {
314
var routable = workflow.getRoutable();
315
316
routable
317
.setPriority(500)
318
.setType('content')
319
.setKey(this._getRoutableKey());
320
321
JX.Router.getInstance().queue(routable);
322
},
323
324
getDisplayPath: function() {
325
return this._displayPath;
326
},
327
328
/**
329
* Receive a response to a context request.
330
*/
331
_oncontext: function(target, response) {
332
// TODO: This should be better structured.
333
// If the response comes back with several top-level nodes, the last one
334
// is the actual context; the others are headers. Add any headers first,
335
// then copy the new rows into the document.
336
var markup = JX.$H(response.changeset).getFragment();
337
var len = markup.childNodes.length;
338
var diff = JX.DOM.findAbove(target, 'table', 'differential-diff');
339
340
for (var ii = 0; ii < len - 1; ii++) {
341
diff.parentNode.insertBefore(markup.firstChild, diff);
342
}
343
344
var table = markup.firstChild;
345
var root = target.parentNode;
346
this._moveRows(table, root, target);
347
root.removeChild(target);
348
349
this._onchangesetresponse(response);
350
},
351
352
_moveRows: function(src, dst, before) {
353
var rows = JX.DOM.scry(src, 'tr');
354
for (var ii = 0; ii < rows.length; ii++) {
355
356
// Find the table this <tr /> belongs to. If it's a sub-table, like a
357
// table in an inline comment, don't copy it.
358
if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
359
continue;
360
}
361
362
if (before) {
363
dst.insertBefore(rows[ii], before);
364
} else {
365
dst.appendChild(rows[ii]);
366
}
367
}
368
},
369
370
/**
371
* Get parameters which define the current rendering options.
372
*/
373
_getViewParameters: function(state) {
374
var parameters = {
375
ref: this._ref,
376
device: this._getDefaultDeviceRenderer()
377
};
378
379
if (state) {
380
JX.copy(parameters, state);
381
}
382
383
return parameters;
384
},
385
386
/**
387
* Get the active @{class:JX.Routable} for this changeset.
388
*
389
* After issuing a request with @{method:load} or @{method:reload}, you
390
* can adjust routable settings (like priority) by querying the routable
391
* with this method. Note that there may not be a current routable.
392
*
393
* @return JX.Routable|null Active routable, if one exists.
394
*/
395
getRoutable: function() {
396
return JX.Router.getInstance().getRoutableByKey(this._getRoutableKey());
397
},
398
399
getRendererKey: function() {
400
return this._rendererKey;
401
},
402
403
_getDefaultDeviceRenderer: function() {
404
// NOTE: If you load the page at one device resolution and then resize to
405
// a different one we don't re-render the diffs, because it's a
406
// complicated mess and you could lose inline comments, cursor positions,
407
// etc.
408
return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up';
409
},
410
411
getUndoTemplates: function() {
412
return this._undoTemplates;
413
},
414
415
getCharacterEncoding: function() {
416
return this._characterEncoding;
417
},
418
419
getHighlight: function() {
420
return this._highlight;
421
},
422
423
getRequestDocumentEngineKey: function() {
424
return this._requestDocumentEngineKey;
425
},
426
427
getResponseDocumentEngineKey: function() {
428
return this._responseDocumentEngineKey;
429
},
430
431
getAvailableDocumentEngineKeys: function() {
432
return this._availableDocumentEngineKeys;
433
},
434
435
getSelectableItems: function() {
436
var items = [];
437
438
items.push({
439
type: 'file',
440
changeset: this,
441
target: this,
442
nodes: {
443
begin: this._node,
444
end: null
445
}
446
});
447
448
if (!this._visible) {
449
return items;
450
}
451
452
var rows = JX.DOM.scry(this._node, 'tr');
453
454
var blocks = [];
455
var block;
456
var ii;
457
var parent_node = null;
458
for (ii = 0; ii < rows.length; ii++) {
459
var type = this._getRowType(rows[ii]);
460
461
// This row might be part of a diff inside an inline comment, showing
462
// an inline edit suggestion. Before we accept it as a possible target
463
// for selection, make sure it's a child of the right parent.
464
465
if (parent_node === null) {
466
parent_node = rows[ii].parentNode;
467
}
468
469
if (type !== null) {
470
if (rows[ii].parentNode !== parent_node) {
471
type = null;
472
}
473
}
474
475
if (!block || (block.type !== type)) {
476
block = {
477
type: type,
478
items: []
479
};
480
blocks.push(block);
481
}
482
483
block.items.push(rows[ii]);
484
}
485
486
var last_inline = null;
487
var last_inline_item = null;
488
for (ii = 0; ii < blocks.length; ii++) {
489
block = blocks[ii];
490
491
if (block.type == 'change') {
492
items.push({
493
type: block.type,
494
changeset: this,
495
target: block.items[0],
496
nodes: {
497
begin: block.items[0],
498
end: block.items[block.items.length - 1]
499
}
500
});
501
}
502
503
if (block.type == 'comment') {
504
for (var jj = 0; jj < block.items.length; jj++) {
505
var inline = this.getInlineForRow(block.items[jj]);
506
507
// When comments are being edited, they have a hidden row with
508
// the actual comment and then a visible row with the editor.
509
510
// In this case, we only want to generate one item, but it should
511
// use the editor as a scroll target. To accomplish this, check if
512
// this row has the same inline as the previous row. If so, update
513
// the last item to use this row's nodes.
514
515
if (inline === last_inline) {
516
last_inline_item.nodes.begin = block.items[jj];
517
last_inline_item.nodes.end = block.items[jj];
518
continue;
519
} else {
520
last_inline = inline;
521
}
522
523
var is_saved = (!inline.isDraft() && !inline.isEditing());
524
525
last_inline_item = {
526
type: block.type,
527
changeset: this,
528
target: inline,
529
hidden: inline.isHidden(),
530
collapsed: inline.isCollapsed(),
531
deleted: !inline.getID() && !inline.isEditing(),
532
nodes: {
533
begin: block.items[jj],
534
end: block.items[jj]
535
},
536
attributes: {
537
unsaved: inline.isEditing(),
538
anyDraft: inline.isDraft() || inline.isDraftDone(),
539
undone: (is_saved && !inline.isDone()),
540
done: (is_saved && inline.isDone())
541
}
542
};
543
544
items.push(last_inline_item);
545
}
546
}
547
}
548
549
return items;
550
},
551
552
_getRowType: function(row) {
553
// NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy
554
// magic.
555
556
if (row.className.indexOf('inline') !== -1) {
557
return 'comment';
558
}
559
560
var cells = JX.DOM.scry(row, 'td');
561
for (var ii = 0; ii < cells.length; ii++) {
562
if (cells[ii].className.indexOf('old') !== -1 ||
563
cells[ii].className.indexOf('new') !== -1) {
564
return 'change';
565
}
566
}
567
},
568
569
_getNodeData: function() {
570
return JX.Stratcom.getData(this._node);
571
},
572
573
getVectors: function() {
574
return {
575
pos: JX.$V(this._node),
576
dim: JX.Vector.getDim(this._node)
577
};
578
},
579
580
_onresponse: function(sequence, response) {
581
if (sequence != this._sequence) {
582
// If this isn't the most recent request, ignore it. This normally
583
// means the user changed view settings between the time the page loaded
584
// and the content filled.
585
return;
586
}
587
588
// As we populate the changeset list, we try to hold the document scroll
589
// position steady, so that, e.g., users who want to leave a comment on a
590
// diff with a large number of changes don't constantly have the text
591
// area scrolled off the bottom of the screen until the entire diff loads.
592
//
593
// There are several major cases here:
594
//
595
// - If we're near the top of the document, never scroll.
596
// - If we're near the bottom of the document, always scroll, unless
597
// we have an anchor.
598
// - Otherwise, scroll if the changes were above (or, at least,
599
// almost entirely above) the viewport.
600
//
601
// We don't scroll if the changes were just near the top of the viewport
602
// because this makes us scroll incorrectly when an anchored change is
603
// visible. See T12779.
604
605
var target = this._node;
606
607
var old_pos = JX.Vector.getScroll();
608
var old_view = JX.Vector.getViewport();
609
var old_dim = JX.Vector.getDocument();
610
611
// Number of pixels away from the top or bottom of the document which
612
// count as "nearby".
613
var sticky = 480;
614
615
var near_top = (old_pos.y <= sticky);
616
var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky));
617
618
// If we have an anchor in the URL, never stick to the bottom of the
619
// page. See T11784 for discussion.
620
if (window.location.hash) {
621
near_bot = false;
622
}
623
624
var target_pos = JX.Vector.getPos(target);
625
var target_dim = JX.Vector.getDim(target);
626
var target_bot = (target_pos.y + target_dim.y);
627
628
// Detect if the changeset is entirely (or, at least, almost entirely)
629
// above us. The height here is roughly the height of the persistent
630
// banner.
631
var above_screen = (target_bot < old_pos.y + 64);
632
633
// If we have a URL anchor and are currently nearby, stick to it
634
// no matter what.
635
var on_target = null;
636
if (window.location.hash) {
637
try {
638
var anchor = JX.$(window.location.hash.replace('#', ''));
639
if (anchor) {
640
var anchor_pos = JX.$V(anchor);
641
if ((anchor_pos.y > old_pos.y) &&
642
(anchor_pos.y < old_pos.y + 96)) {
643
on_target = anchor;
644
}
645
}
646
} catch (ignored) {
647
// If we have a bogus anchor, just ignore it.
648
}
649
}
650
651
var frame = this._getContentFrame();
652
JX.DOM.setContent(frame, JX.$H(response.changeset));
653
654
if (this._stabilize) {
655
if (on_target) {
656
JX.DOM.scrollToPosition(old_pos.x, JX.$V(on_target).y - 60);
657
} else if (!near_top) {
658
if (near_bot || above_screen) {
659
// Figure out how much taller the document got.
660
var delta = (JX.Vector.getDocument().y - old_dim.y);
661
JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta);
662
}
663
}
664
this._stabilize = false;
665
}
666
667
this._onchangesetresponse(response);
668
},
669
670
_onchangesetresponse: function(response) {
671
// Code shared by autoload and context responses.
672
673
this._loadChangesetState(response);
674
this._rebuildAllInlines();
675
676
JX.Stratcom.invoke('resize');
677
},
678
679
_loadChangesetState: function(state) {
680
if (state.coverage) {
681
for (var k in state.coverage) {
682
try {
683
JX.DOM.replace(JX.$(k), JX.$H(state.coverage[k]));
684
} catch (ignored) {
685
// Not terribly important.
686
}
687
}
688
}
689
690
if (state.undoTemplates) {
691
this._undoTemplates = state.undoTemplates;
692
}
693
694
this._rendererKey = state.rendererKey;
695
this._highlight = state.highlight;
696
this._characterEncoding = state.characterEncoding;
697
this._requestDocumentEngineKey = state.requestDocumentEngineKey;
698
this._responseDocumentEngineKey = state.responseDocumentEngineKey;
699
this._availableDocumentEngineKeys = state.availableDocumentEngineKeys;
700
this._isHidden = state.isHidden;
701
702
var is_hidden = !this.isVisible();
703
if (this._isHidden != is_hidden) {
704
this.setVisible(!this._isHidden);
705
}
706
707
this._isLoading = false;
708
this.getPathView().setIsLoading(this._isLoading);
709
},
710
711
_getContentFrame: function() {
712
return JX.DOM.find(this._node, 'div', 'changeset-view-content');
713
},
714
715
_getRoutableKey: function() {
716
return 'changeset-view.' + this._ref + '.' + this._sequence;
717
},
718
719
getInlineForRow: function(node) {
720
var data = JX.Stratcom.getData(node);
721
722
if (!data.inline) {
723
var inline = this._newInlineForRow(node);
724
this.getInlines().push(inline);
725
}
726
727
return data.inline;
728
},
729
730
_newInlineForRow: function(node) {
731
return new JX.DiffInline()
732
.setChangeset(this)
733
.bindToRow(node);
734
},
735
736
newInlineForRange: function(origin, target, options) {
737
var list = this.getChangesetList();
738
739
var src = list.getLineNumberFromHeader(origin);
740
var dst = list.getLineNumberFromHeader(target);
741
742
var changeset_id = null;
743
var side = list.getDisplaySideFromHeader(origin);
744
if (side == 'right') {
745
changeset_id = this.getRightChangesetID();
746
} else {
747
changeset_id = this.getLeftChangesetID();
748
}
749
750
var is_new = false;
751
if (side == 'right') {
752
is_new = true;
753
} else if (this.getRightChangesetID() != this.getLeftChangesetID()) {
754
is_new = true;
755
}
756
757
var data = {
758
origin: origin,
759
target: target,
760
number: src,
761
length: dst - src,
762
changesetID: changeset_id,
763
displaySide: side,
764
isNewFile: is_new
765
};
766
767
JX.copy(data, options || {});
768
769
var inline = new JX.DiffInline()
770
.setChangeset(this)
771
.bindToRange(data);
772
773
this.getInlines().push(inline);
774
775
inline.create();
776
777
return inline;
778
},
779
780
newInlineReply: function(original, state) {
781
var inline = new JX.DiffInline()
782
.setChangeset(this)
783
.bindToReply(original);
784
785
this._inlines.push(inline);
786
787
inline.create(state);
788
789
return inline;
790
},
791
792
getInlineByID: function(id) {
793
return this._queryInline('id', id);
794
},
795
796
getInlineByPHID: function(phid) {
797
return this._queryInline('phid', phid);
798
},
799
800
_queryInline: function(field, value) {
801
// First, look for the inline in the objects we've already built.
802
var inline = this._findInline(field, value);
803
if (inline) {
804
return inline;
805
}
806
807
// If we haven't found a matching inline yet, rebuild all the inlines
808
// present in the document, then look again.
809
this._rebuildAllInlines();
810
return this._findInline(field, value);
811
},
812
813
_findInline: function(field, value) {
814
var inlines = this.getInlines();
815
816
for (var ii = 0; ii < inlines.length; ii++) {
817
var inline = inlines[ii];
818
819
var target;
820
switch (field) {
821
case 'id':
822
target = inline.getID();
823
break;
824
case 'phid':
825
target = inline.getPHID();
826
break;
827
}
828
829
if (target == value) {
830
return inline;
831
}
832
}
833
834
return null;
835
},
836
837
getInlines: function() {
838
if (this._inlines === null) {
839
this._rebuildAllInlines();
840
}
841
842
return this._inlines;
843
},
844
845
_rebuildAllInlines: function() {
846
this._inlines = [];
847
848
var rows = JX.DOM.scry(this._node, 'tr');
849
var ii;
850
for (ii = 0; ii < rows.length; ii++) {
851
var row = rows[ii];
852
if (this._getRowType(row) != 'comment') {
853
continue;
854
}
855
856
this._inlines.push(this._newInlineForRow(row));
857
}
858
},
859
860
redrawFileTree: function() {
861
var inlines = this.getInlines();
862
var done = [];
863
var undone = [];
864
var inline;
865
866
for (var ii = 0; ii < inlines.length; ii++) {
867
inline = inlines[ii];
868
869
if (inline.isDeleted()) {
870
continue;
871
}
872
873
if (inline.isUndo()) {
874
continue;
875
}
876
877
if (inline.isSynthetic()) {
878
continue;
879
}
880
881
if (inline.isEditing()) {
882
continue;
883
}
884
885
if (!inline.getID()) {
886
// These are new comments which have been cancelled, and do not
887
// count as anything.
888
continue;
889
}
890
891
if (inline.isDraft()) {
892
continue;
893
}
894
895
if (!inline.isDone()) {
896
undone.push(inline);
897
} else {
898
done.push(inline);
899
}
900
}
901
902
var total = done.length + undone.length;
903
904
var hint;
905
var is_visible;
906
var is_completed;
907
if (total) {
908
if (done.length) {
909
hint = [done.length, '/', total];
910
} else {
911
hint = total;
912
}
913
is_visible = true;
914
is_completed = (done.length == total);
915
} else {
916
hint = '-';
917
is_visible = false;
918
is_completed = false;
919
}
920
921
var node = this.getPathView().getInlineNode();
922
923
JX.DOM.setContent(node, hint);
924
925
JX.DOM.alterClass(node, 'diff-tree-path-inlines-visible', is_visible);
926
JX.DOM.alterClass(node, 'diff-tree-path-inlines-completed', is_completed);
927
},
928
929
_onClickHeader: function(e) {
930
// If the user clicks the actual path name text, don't count this as
931
// a selection action: we want to let them select the path.
932
var path_name = e.getNode('changeset-header-path-name');
933
if (path_name) {
934
return;
935
}
936
937
// Don't allow repeatedly clicking a header to begin a "select word" or
938
// "select line" operation.
939
if (e.getType() === 'selectstart') {
940
e.kill();
941
return;
942
}
943
944
// NOTE: Don't prevent or kill the event. If the user has text selected,
945
// clicking a header should clear the selection (and dismiss any inline
946
// context menu, if one exists) as clicking elsewhere in the document
947
// normally would.
948
949
if (this._isSelected) {
950
this.getChangesetList().selectChangeset(null);
951
} else {
952
this.select(false);
953
}
954
},
955
956
toggleVisibility: function() {
957
this.setVisible(!this._visible);
958
959
var attrs = {
960
hidden: this.isVisible() ? 0 : 1,
961
discard: 1
962
};
963
964
var workflow = this._newReloadWorkflow(attrs)
965
.setHandler(JX.bag);
966
967
this._startContentWorkflow(workflow);
968
},
969
970
setVisible: function(visible) {
971
this._visible = visible;
972
973
var diff = this._getDiffNode();
974
var options = this._getViewButtonNode();
975
var show = this._getShowButtonNode();
976
977
if (this._visible) {
978
JX.DOM.show(diff);
979
JX.DOM.show(options);
980
JX.DOM.hide(show);
981
} else {
982
JX.DOM.hide(diff);
983
JX.DOM.hide(options);
984
JX.DOM.show(show);
985
986
if (this._viewMenu) {
987
this._viewMenu.close();
988
}
989
}
990
991
JX.Stratcom.invoke('resize');
992
993
var node = this._node;
994
JX.DOM.alterClass(node, 'changeset-content-hidden', !this._visible);
995
996
this.getPathView().setIsHidden(!this._visible);
997
},
998
999
setIsSelected: function(is_selected) {
1000
this._isSelected = !!is_selected;
1001
1002
var node = this._node;
1003
JX.DOM.alterClass(node, 'changeset-selected', this._isSelected);
1004
1005
return this;
1006
},
1007
1008
_getDiffNode: function() {
1009
if (!this._diffNode) {
1010
this._diffNode = JX.DOM.find(this._node, 'table', 'differential-diff');
1011
}
1012
return this._diffNode;
1013
},
1014
1015
_getViewButtonNode: function() {
1016
if (!this._viewButtonNode) {
1017
this._viewButtonNode = JX.DOM.find(
1018
this._node,
1019
'a',
1020
'differential-view-options');
1021
}
1022
return this._viewButtonNode;
1023
},
1024
1025
_getShowButtonNode: function() {
1026
if (!this._showButtonNode) {
1027
var pht = this.getChangesetList().getTranslations();
1028
1029
var show_button = new JX.PHUIXButtonView()
1030
.setIcon('fa-angle-double-down')
1031
.setText(pht('Show Changeset'))
1032
.setColor('grey');
1033
1034
var button_node = show_button.getNode();
1035
this._getViewButtonNode().parentNode.appendChild(button_node);
1036
1037
var onshow = JX.bind(this, this._onClickShowButton);
1038
JX.DOM.listen(button_node, 'click', null, onshow);
1039
1040
this._showButtonNode = button_node;
1041
}
1042
return this._showButtonNode;
1043
},
1044
1045
_onClickShowButton: function(e) {
1046
e.prevent();
1047
1048
// We're always showing the changeset, but want to make sure the state
1049
// change is persisted on the server.
1050
this.toggleVisibility();
1051
},
1052
1053
isVisible: function() {
1054
return this._visible;
1055
},
1056
1057
getPathView: function() {
1058
if (!this._pathView) {
1059
var view = new JX.DiffPathView()
1060
.setChangeset(this)
1061
.setPath(this._pathParts)
1062
.setIsLowImportance(this._isLowImportance)
1063
.setIsOwned(this._isOwned)
1064
.setIsLoading(this._isLoading);
1065
1066
view.getIcon()
1067
.setIcon(this._pathIconIcon)
1068
.setColor(this._pathIconColor);
1069
1070
this._pathView = view;
1071
}
1072
1073
return this._pathView;
1074
},
1075
1076
select: function(scroll) {
1077
this.getChangesetList().selectChangeset(this, scroll);
1078
return this;
1079
}
1080
},
1081
1082
statics: {
1083
getForNode: function(node) {
1084
var data = JX.Stratcom.getData(node);
1085
if (!data.changesetViewManager) {
1086
data.changesetViewManager = new JX.DiffChangeset(node);
1087
}
1088
return data.changesetViewManager;
1089
}
1090
}
1091
});
1092
1093