Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
12242 views
1
<?php
2
3
final class PHUIDiffInlineCommentDetailView
4
extends PHUIDiffInlineCommentView {
5
6
private $handles;
7
private $markupEngine;
8
private $editable;
9
private $preview;
10
private $allowReply;
11
private $canMarkDone;
12
private $objectOwnerPHID;
13
14
public function isHidden() {
15
return $this->getInlineComment()->isHidden();
16
}
17
18
public function setHandles(array $handles) {
19
assert_instances_of($handles, 'PhabricatorObjectHandle');
20
$this->handles = $handles;
21
return $this;
22
}
23
24
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
25
$this->markupEngine = $engine;
26
return $this;
27
}
28
29
public function setEditable($editable) {
30
$this->editable = $editable;
31
return $this;
32
}
33
34
public function setPreview($preview) {
35
$this->preview = $preview;
36
return $this;
37
}
38
39
public function setAllowReply($allow_reply) {
40
$this->allowReply = $allow_reply;
41
return $this;
42
}
43
44
public function setCanMarkDone($can_mark_done) {
45
$this->canMarkDone = $can_mark_done;
46
return $this;
47
}
48
49
public function getCanMarkDone() {
50
return $this->canMarkDone;
51
}
52
53
public function setObjectOwnerPHID($phid) {
54
$this->objectOwnerPHID = $phid;
55
return $this;
56
}
57
58
public function getObjectOwnerPHID() {
59
return $this->objectOwnerPHID;
60
}
61
62
public function getAnchorName() {
63
$inline = $this->getInlineComment();
64
if ($inline->getID()) {
65
return 'inline-'.$inline->getID();
66
}
67
return null;
68
}
69
70
public function getScaffoldCellID() {
71
$anchor = $this->getAnchorName();
72
if ($anchor) {
73
return 'anchor-'.$anchor;
74
}
75
return null;
76
}
77
78
public function render() {
79
require_celerity_resource('phui-inline-comment-view-css');
80
$inline = $this->getInlineComment();
81
82
$is_synthetic = false;
83
if ($inline->getSyntheticAuthor()) {
84
$is_synthetic = true;
85
}
86
87
$is_preview = $this->preview;
88
89
$metadata = $this->getInlineCommentMetadata();
90
91
$classes = array(
92
'differential-inline-comment',
93
);
94
95
$sigil = 'differential-inline-comment';
96
if ($is_preview) {
97
$sigil = $sigil.' differential-inline-comment-preview';
98
99
$classes[] = 'inline-comment-preview';
100
} else {
101
$classes[] = 'inline-comment-element';
102
}
103
104
$handles = $this->handles;
105
106
$links = array();
107
108
$draft_text = null;
109
if (!$is_synthetic) {
110
// This display is controlled by CSS
111
$draft_text = id(new PHUITagView())
112
->setType(PHUITagView::TYPE_SHADE)
113
->setName(pht('Unsubmitted'))
114
->setSlimShady(true)
115
->setColor(PHUITagView::COLOR_RED)
116
->addClass('mml inline-draft-text');
117
}
118
119
$ghost_tag = null;
120
$ghost = $inline->getIsGhost();
121
$ghost_id = null;
122
if ($ghost) {
123
if ($ghost['new']) {
124
$ghosticon = 'fa-fast-forward';
125
$reason = pht('View on forward revision');
126
} else {
127
$ghosticon = 'fa-fast-backward';
128
$reason = pht('View on previous revision');
129
}
130
131
$ghost_icon = id(new PHUIIconView())
132
->setIcon($ghosticon)
133
->addSigil('has-tooltip')
134
->setMetadata(
135
array(
136
'tip' => $reason,
137
'size' => 300,
138
));
139
$ghost_tag = phutil_tag(
140
'a',
141
array(
142
'class' => 'ghost-icon',
143
'href' => $ghost['href'],
144
'target' => '_blank',
145
),
146
$ghost_icon);
147
$classes[] = 'inline-comment-ghost';
148
}
149
150
if ($inline->getReplyToCommentPHID()) {
151
$classes[] = 'inline-comment-is-reply';
152
}
153
154
$viewer_phid = $this->getUser()->getPHID();
155
$owner_phid = $this->getObjectOwnerPHID();
156
157
if ($viewer_phid) {
158
if ($viewer_phid == $owner_phid) {
159
$classes[] = 'viewer-is-object-owner';
160
}
161
}
162
163
$anchor_name = $this->getAnchorName();
164
165
$action_buttons = array();
166
$menu_items = array();
167
168
if ($this->editable && !$is_preview) {
169
$menu_items[] = array(
170
'label' => pht('Edit Comment'),
171
'icon' => 'fa-pencil',
172
'action' => 'edit',
173
'key' => 'e',
174
);
175
} else if ($is_preview) {
176
$links[] = javelin_tag(
177
'a',
178
array(
179
'class' => 'inline-button-divider pml msl',
180
'meta' => array(
181
'inlineCommentID' => $inline->getID(),
182
),
183
'sigil' => 'differential-inline-preview-jump',
184
),
185
pht('View'));
186
187
$action_buttons[] = id(new PHUIButtonView())
188
->setTag('a')
189
->setTooltip(pht('Delete'))
190
->setIcon('fa-trash-o')
191
->addSigil('differential-inline-delete')
192
->setMustCapture(true)
193
->setAuralLabel(pht('Delete'));
194
}
195
196
if (!$is_preview && $this->canHide()) {
197
$menu_items[] = array(
198
'label' => pht('Collapse'),
199
'icon' => 'fa-times',
200
'action' => 'collapse',
201
'key' => 'q',
202
);
203
}
204
205
$can_reply =
206
(!$this->editable) &&
207
(!$is_preview) &&
208
($this->allowReply) &&
209
210
// NOTE: No product reason why you can't reply to synthetic comments,
211
// but the reply mechanism currently sends the inline comment ID to the
212
// server, not file/line information, and synthetic comments don't have
213
// an inline comment ID.
214
(!$is_synthetic);
215
216
if ($can_reply) {
217
$menu_items[] = array(
218
'label' => pht('Reply to Comment'),
219
'icon' => 'fa-reply',
220
'action' => 'reply',
221
'key' => 'r',
222
);
223
224
$menu_items[] = array(
225
'label' => pht('Quote Comment'),
226
'icon' => 'fa-quote-left',
227
'action' => 'quote',
228
'key' => 'R',
229
);
230
}
231
232
if (!$is_preview) {
233
$xaction_phid = $inline->getTransactionPHID();
234
$storage = $inline->getStorageObject();
235
236
if ($xaction_phid) {
237
$menu_items[] = array(
238
'label' => pht('View Raw Remarkup'),
239
'icon' => 'fa-code',
240
'action' => 'raw',
241
'uri' => $storage->getRawRemarkupURI(),
242
);
243
}
244
}
245
246
if ($this->editable && !$is_preview) {
247
$menu_items[] = array(
248
'label' => pht('Delete Comment'),
249
'icon' => 'fa-trash-o',
250
'action' => 'delete',
251
);
252
}
253
254
$done_button = null;
255
256
$mark_done = $this->getCanMarkDone();
257
258
// Allow users to mark their own draft inlines as "Done".
259
if ($viewer_phid == $inline->getAuthorPHID()) {
260
if ($inline->isDraft()) {
261
$mark_done = true;
262
}
263
}
264
265
if (!$is_synthetic) {
266
$draft_state = false;
267
switch ($inline->getFixedState()) {
268
case PhabricatorInlineComment::STATE_DRAFT:
269
$is_done = $mark_done;
270
$draft_state = true;
271
break;
272
case PhabricatorInlineComment::STATE_UNDRAFT:
273
$is_done = !$mark_done;
274
$draft_state = true;
275
break;
276
case PhabricatorInlineComment::STATE_DONE:
277
$is_done = true;
278
break;
279
default:
280
case PhabricatorInlineComment::STATE_UNDONE:
281
$is_done = false;
282
break;
283
}
284
285
// If you don't have permission to mark the comment as "Done", you also
286
// can not see the draft state.
287
if (!$mark_done) {
288
$draft_state = false;
289
}
290
291
if ($is_done) {
292
$classes[] = 'inline-is-done';
293
}
294
295
if ($draft_state) {
296
$classes[] = 'inline-state-is-draft';
297
}
298
299
if ($mark_done && !$is_preview) {
300
$done_input = javelin_tag(
301
'input',
302
array(
303
'type' => 'checkbox',
304
'checked' => ($is_done ? 'checked' : null),
305
'class' => 'differential-inline-done',
306
'sigil' => 'differential-inline-done',
307
));
308
$done_button = phutil_tag(
309
'label',
310
array(
311
'class' => 'differential-inline-done-label ',
312
),
313
array(
314
$done_input,
315
pht('Done'),
316
));
317
} else {
318
if ($is_done) {
319
$icon = id(new PHUIIconView())->setIcon('fa-check sky msr');
320
$label = pht('Done');
321
$class = 'button-done';
322
} else {
323
$icon = null;
324
$label = pht('Not Done');
325
$class = 'button-not-done';
326
}
327
$done_button = phutil_tag(
328
'div',
329
array(
330
'class' => 'done-label '.$class,
331
),
332
array(
333
$icon,
334
$label,
335
));
336
}
337
}
338
339
$content = $this->markupEngine->getOutput(
340
$inline,
341
PhabricatorInlineComment::MARKUP_FIELD_BODY);
342
343
if ($is_preview) {
344
$anchor = null;
345
} else {
346
$anchor = phutil_tag(
347
'a',
348
array(
349
'name' => $anchor_name,
350
'id' => $anchor_name,
351
'class' => 'differential-inline-comment-anchor',
352
),
353
'');
354
}
355
356
if ($inline->isDraft() && !$is_synthetic) {
357
$classes[] = 'inline-state-is-draft';
358
}
359
if ($is_synthetic) {
360
$classes[] = 'differential-inline-comment-synthetic';
361
}
362
$classes = implode(' ', $classes);
363
364
$author_owner = null;
365
if ($is_synthetic) {
366
$author = $inline->getSyntheticAuthor();
367
} else {
368
$author = $handles[$inline->getAuthorPHID()]->getName();
369
if ($inline->getAuthorPHID() == $this->objectOwnerPHID) {
370
$author_owner = id(new PHUITagView())
371
->setType(PHUITagView::TYPE_SHADE)
372
->setName(pht('Author'))
373
->setSlimShady(true)
374
->setColor(PHUITagView::COLOR_YELLOW)
375
->addClass('mml');
376
}
377
}
378
379
$actions = null;
380
if ($action_buttons || $menu_items) {
381
$actions = new PHUIButtonBarView();
382
$actions->setBorderless(true);
383
$actions->addClass('inline-button-divider');
384
foreach ($action_buttons as $button) {
385
$actions->addButton($button);
386
}
387
388
if (!$is_preview) {
389
$menu_button = id(new PHUIButtonView())
390
->setTag('a')
391
->setColor(PHUIButtonView::GREY)
392
->setDropdown(true)
393
->setAuralLabel(pht('Inline Actions'))
394
->addSigil('inline-action-dropdown');
395
396
$actions->addButton($menu_button);
397
}
398
}
399
400
$group_left = phutil_tag(
401
'div',
402
array(
403
'class' => 'inline-head-left',
404
),
405
array(
406
$author,
407
$author_owner,
408
$draft_text,
409
$ghost_tag,
410
));
411
412
$group_right = phutil_tag(
413
'div',
414
array(
415
'class' => 'inline-head-right',
416
),
417
array(
418
$done_button,
419
$links,
420
$actions,
421
));
422
423
$snippet = id(new PhutilUTF8StringTruncator())
424
->setMaximumGlyphs(96)
425
->truncateString($inline->getContent());
426
$metadata['snippet'] = pht('%s: %s', $author, $snippet);
427
428
$metadata['menuItems'] = $menu_items;
429
430
$suggestion_content = $this->newSuggestionView($inline);
431
432
$inline_content = phutil_tag(
433
'div',
434
array(
435
'class' => 'phabricator-remarkup',
436
),
437
$content);
438
439
$markup = javelin_tag(
440
'div',
441
array(
442
'class' => $classes,
443
'sigil' => $sigil,
444
'meta' => $metadata,
445
),
446
array(
447
javelin_tag(
448
'div',
449
array(
450
'class' => 'differential-inline-comment-head grouped',
451
'sigil' => 'differential-inline-header',
452
),
453
array(
454
$group_left,
455
$group_right,
456
)),
457
phutil_tag(
458
'div',
459
array(
460
'class' => 'differential-inline-comment-content',
461
),
462
array(
463
$suggestion_content,
464
$inline_content,
465
)),
466
));
467
468
$summary = phutil_tag(
469
'div',
470
array(
471
'class' => 'differential-inline-summary',
472
),
473
array(
474
phutil_tag('strong', array(), pht('%s:', $author)),
475
' ',
476
$snippet,
477
));
478
479
return array(
480
$anchor,
481
$markup,
482
$summary,
483
);
484
}
485
486
private function canHide() {
487
$inline = $this->getInlineComment();
488
489
if ($inline->isDraft()) {
490
return false;
491
}
492
493
if (!$inline->getID()) {
494
return false;
495
}
496
497
$viewer = $this->getUser();
498
if (!$viewer->isLoggedIn()) {
499
return false;
500
}
501
502
if (!$inline->supportsHiding()) {
503
return false;
504
}
505
506
return true;
507
}
508
509
private function newSuggestionView(PhabricatorInlineComment $inline) {
510
$content_state = $inline->getContentState();
511
if (!$content_state->getContentHasSuggestion()) {
512
return null;
513
}
514
515
$context = $inline->getInlineContext();
516
if (!$context) {
517
return null;
518
}
519
520
$head_lines = $context->getHeadLines();
521
$head_lines = implode('', $head_lines);
522
523
$tail_lines = $context->getTailLines();
524
$tail_lines = implode('', $tail_lines);
525
526
$old_lines = $context->getBodyLines();
527
$old_lines = implode('', $old_lines);
528
$old_lines = $head_lines.$old_lines.$tail_lines;
529
if (strlen($old_lines) && !preg_match('/\n\z/', $old_lines)) {
530
$old_lines .= "\n";
531
}
532
533
$new_lines = $content_state->getContentSuggestionText();
534
$new_lines = $head_lines.$new_lines.$tail_lines;
535
if (strlen($new_lines) && !preg_match('/\n\z/', $new_lines)) {
536
$new_lines .= "\n";
537
}
538
539
if ($old_lines === $new_lines) {
540
return null;
541
}
542
543
$viewer = $this->getViewer();
544
545
$changeset = id(new PhabricatorDifferenceEngine())
546
->generateChangesetFromFileContent($old_lines, $new_lines);
547
548
$changeset->setFilename($context->getFilename());
549
550
$viewstate = new PhabricatorChangesetViewState();
551
552
$parser = id(new DifferentialChangesetParser())
553
->setViewer($viewer)
554
->setViewstate($viewstate)
555
->setChangeset($changeset);
556
557
$fragment = $inline->getInlineCommentCacheFragment();
558
if ($fragment !== null) {
559
$cache_key = sprintf(
560
'%s.suggestion-view(v1, %s)',
561
$fragment,
562
PhabricatorHash::digestForIndex($new_lines));
563
$parser->setRenderCacheKey($cache_key);
564
}
565
566
$renderer = new DifferentialChangesetOneUpRenderer();
567
$renderer->setSimpleMode(true);
568
569
$parser->setRenderer($renderer);
570
571
// See PHI1896. If a user leaves an inline on a very long range with
572
// suggestions at the beginning and end, we'll hide context in the middle
573
// by default. We don't want to do this in the context of an inline
574
// suggestion, so build a mask to force display of all lines.
575
576
// (We don't know exactly how many lines the diff has, we just know that
577
// it can't have more lines than the old file plus the new file, so we're
578
// using that as an upper bound.)
579
580
$min = 0;
581
582
$old_len = count(phutil_split_lines($old_lines));
583
$new_len = count(phutil_split_lines($new_lines));
584
$max = ($old_len + $new_len);
585
586
$mask = array_fill($min, ($max - $min), true);
587
588
$diff_view = $parser->render($min, ($max - $min), $mask);
589
590
$view = phutil_tag(
591
'div',
592
array(
593
'class' => 'inline-suggestion-view PhabricatorMonospaced',
594
),
595
$diff_view);
596
597
return $view;
598
}
599
}
600
601