Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index d9c1bcda8d..eef8a2b476 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2197 +1,2198 @@
<?php
/**
* This file is automatically generated. Use 'bin/celerity map' to rebuild it.
*
* @generated
*/
return array(
'names' => array(
'core.pkg.css' => 'a9770fbb',
'core.pkg.js' => 'a77025a1',
'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => 'd8866ed8',
- 'differential.pkg.js' => '7b5a4aa4',
+ 'differential.pkg.js' => '9e55f9f5',
'diffusion.pkg.css' => '591664fa',
'diffusion.pkg.js' => 'bfc0737b',
'maniphest.pkg.css' => '68d4dd3d',
'maniphest.pkg.js' => 'df4aa49f',
'rsrc/css/aphront/aphront-bars.css' => '231ac33c',
'rsrc/css/aphront/dark-console.css' => '6378ef3d',
'rsrc/css/aphront/dialog-view.css' => 'd2e76b88',
'rsrc/css/aphront/lightbox-attachment.css' => '7acac05d',
'rsrc/css/aphront/list-filter-view.css' => '2ae43867',
'rsrc/css/aphront/multi-column.css' => '41a848c0',
'rsrc/css/aphront/notification.css' => '9c279160',
'rsrc/css/aphront/pager-view.css' => '2e3539af',
'rsrc/css/aphront/panel-view.css' => '7e0c3a56',
'rsrc/css/aphront/phabricator-nav-view.css' => '7aeaf435',
'rsrc/css/aphront/table-view.css' => 'b22b7216',
'rsrc/css/aphront/tokenizer.css' => '82ce2142',
'rsrc/css/aphront/tooltip.css' => '4099b97e',
'rsrc/css/aphront/transaction.css' => '5d0cae25',
'rsrc/css/aphront/two-column.css' => '16ab3ad2',
'rsrc/css/aphront/typeahead.css' => '0e403212',
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
'rsrc/css/application/auth/auth.css' => '1e655982',
'rsrc/css/application/base/main-menu-view.css' => 'f9f5cd1b',
'rsrc/css/application/base/notification-menu.css' => '6aa0a74b',
'rsrc/css/application/base/phabricator-application-launch-view.css' => '16ca323f',
'rsrc/css/application/base/standard-page-view.css' => 'df338a4b',
'rsrc/css/application/chatlog/chatlog.css' => '852140ff',
'rsrc/css/application/config/config-options.css' => '7fedf08b',
'rsrc/css/application/config/config-template.css' => '8e6c6fcd',
'rsrc/css/application/config/config-welcome.css' => '6abd79be',
'rsrc/css/application/config/setup-issue.css' => '22270af2',
'rsrc/css/application/config/unhandled-exception.css' => '37d4f9a2',
'rsrc/css/application/conpherence/durable-column.css' => '3b836442',
'rsrc/css/application/conpherence/menu.css' => '73774137',
'rsrc/css/application/conpherence/message-pane.css' => '17a9517f',
'rsrc/css/application/conpherence/notification.css' => '04a6e10a',
'rsrc/css/application/conpherence/update.css' => '1099a660',
'rsrc/css/application/conpherence/widget-pane.css' => '3d575438',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
'rsrc/css/application/countdown/timer.css' => '86b7b0a0',
'rsrc/css/application/dashboard/dashboard.css' => '17937d22',
'rsrc/css/application/diff/inline-comment-summary.css' => 'eb5f8e8c',
'rsrc/css/application/differential/add-comment.css' => 'c478bcaa',
'rsrc/css/application/differential/changeset-view.css' => 'b600950c',
'rsrc/css/application/differential/core.css' => '7ac3cabc',
'rsrc/css/application/differential/results-table.css' => '181aa9d9',
'rsrc/css/application/differential/revision-comment.css' => '48186045',
'rsrc/css/application/differential/revision-history.css' => '0e8eb855',
'rsrc/css/application/differential/revision-list.css' => 'f3c47d33',
'rsrc/css/application/differential/table-of-contents.css' => '63f3ef4a',
'rsrc/css/application/diffusion/diffusion-icons.css' => '9c5828da',
'rsrc/css/application/diffusion/diffusion-source.css' => '66fdf661',
'rsrc/css/application/feed/feed.css' => 'b513b5f4',
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
'rsrc/css/application/flag/flag.css' => '5337623f',
'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4',
'rsrc/css/application/herald/herald-test.css' => '778b008e',
'rsrc/css/application/herald/herald.css' => '826075fa',
'rsrc/css/application/home/home.css' => 'e34bf140',
'rsrc/css/application/maniphest/batch-editor.css' => '8f380ebc',
'rsrc/css/application/maniphest/report.css' => 'd8ed71ad',
'rsrc/css/application/maniphest/task-edit.css' => '8e23031b',
'rsrc/css/application/maniphest/task-summary.css' => 'ab2fc691',
'rsrc/css/application/objectselector/object-selector.css' => '029a133d',
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => 'eb997ddd',
'rsrc/css/application/people/people-profile.css' => '25970776',
'rsrc/css/application/phame/phame.css' => '88bd4705',
'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
'rsrc/css/application/pholio/pholio.css' => '95174bdd',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '8391eb02',
'rsrc/css/application/phortune/phortune.css' => '9149f103',
'rsrc/css/application/phrequent/phrequent.css' => 'ffc185ad',
'rsrc/css/application/phriction/phriction-document-css.css' => '0d16bc9a',
'rsrc/css/application/policy/policy-edit.css' => '815c66f7',
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
'rsrc/css/application/policy/policy.css' => '957ea14c',
'rsrc/css/application/ponder/comments.css' => '6cdccea7',
'rsrc/css/application/ponder/feed.css' => 'e62615b6',
'rsrc/css/application/ponder/post.css' => 'ebab8a70',
'rsrc/css/application/ponder/vote.css' => '8ed6ed8b',
'rsrc/css/application/profile/profile-view.css' => '1a20dcbf',
'rsrc/css/application/projects/project-icon.css' => 'c2ecb7f1',
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
'rsrc/css/application/search/search-results.css' => '559cc554',
'rsrc/css/application/slowvote/slowvote.css' => '266df6a1',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => '86bfbe8c',
'rsrc/css/core/remarkup.css' => '2dbff225',
'rsrc/css/core/syntax.css' => '56c1ba38',
'rsrc/css/core/z-index.css' => '9ec70c03',
'rsrc/css/diviner/diviner-shared.css' => '38813222',
'rsrc/css/font/font-awesome.css' => 'ae9a7b4d',
'rsrc/css/font/font-source-sans-pro.css' => '0d859f60',
'rsrc/css/font/phui-font-icon-base.css' => '3dad2ae3',
'rsrc/css/layout/phabricator-filetree-view.css' => 'fccf9f82',
'rsrc/css/layout/phabricator-hovercard-view.css' => '893f4783',
'rsrc/css/layout/phabricator-side-menu-view.css' => '7e8c6341',
'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894',
'rsrc/css/phui/calendar/phui-calendar-day.css' => 'de035c8a',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1d0ca59',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'a92e47d2',
'rsrc/css/phui/calendar/phui-calendar.css' => '8675968e',
'rsrc/css/phui/phui-action-header-view.css' => '89c497e7',
'rsrc/css/phui/phui-action-list.css' => '9ee9910a',
'rsrc/css/phui/phui-action-panel.css' => '3ee9afd5',
'rsrc/css/phui/phui-box.css' => '7b3a2eed',
'rsrc/css/phui/phui-button.css' => '21cb97f9',
'rsrc/css/phui/phui-crumbs-view.css' => '594d719e',
'rsrc/css/phui/phui-document.css' => '0f83a7df',
'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5',
'rsrc/css/phui/phui-fontkit.css' => 'd30f4fa3',
'rsrc/css/phui/phui-form-view.css' => '76f0b086',
'rsrc/css/phui/phui-form.css' => 'f535f938',
'rsrc/css/phui/phui-header-view.css' => '083669db',
'rsrc/css/phui/phui-icon.css' => 'd35aa857',
'rsrc/css/phui/phui-image-mask.css' => '5a8b09c8',
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-info-view.css' => '6c6cc4dd',
'rsrc/css/phui/phui-list.css' => '53deb25c',
'rsrc/css/phui/phui-object-box.css' => 'd68ce5dc',
'rsrc/css/phui/phui-object-item-list-view.css' => '9db65899',
'rsrc/css/phui/phui-pinboard-view.css' => '3dd4a269',
'rsrc/css/phui/phui-property-list-view.css' => '51480060',
'rsrc/css/phui/phui-remarkup-preview.css' => '19ad512b',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8',
'rsrc/css/phui/phui-tag-view.css' => 'ea469f3a',
'rsrc/css/phui/phui-text.css' => 'cf019f54',
'rsrc/css/phui/phui-timeline-view.css' => 'b0fbc4d7',
'rsrc/css/phui/phui-workboard-view.css' => '8896938c',
'rsrc/css/phui/phui-workpanel-view.css' => 'e495a5cc',
'rsrc/css/sprite-gradient.css' => '4bdb98a7',
'rsrc/css/sprite-login.css' => 'a355d921',
'rsrc/css/sprite-main-header.css' => '28d01b0b',
'rsrc/css/sprite-menu.css' => '9ef76324',
'rsrc/css/sprite-projects.css' => 'b0d9e24f',
'rsrc/css/sprite-tokens.css' => '1706b943',
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '5fb6fb0e',
'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => 'a653cb11',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => '80526fc8',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '4924d54d',
'rsrc/externals/font/sourcesans/SourceSansPro-Bold.woff2' => '165f5f74',
'rsrc/externals/font/sourcesans/SourceSansPro-BoldIt.woff' => 'd09a7d54',
'rsrc/externals/font/sourcesans/SourceSansPro-BoldIt.woff2' => 'd2e33102',
'rsrc/externals/font/sourcesans/SourceSansPro-It.woff' => '3f21af52',
'rsrc/externals/font/sourcesans/SourceSansPro-It.woff2' => '30a7cf60',
'rsrc/externals/font/sourcesans/SourceSansPro-Regular.woff2' => 'e89b04b1',
'rsrc/externals/font/sourcesans/SourceSansPro.woff' => '3614608c',
'rsrc/externals/font/sourcesans/SourceSansProBold.woff' => 'cbf46566',
'rsrc/externals/javelin/core/Event.js' => '85ea0626',
'rsrc/externals/javelin/core/Stratcom.js' => '6c53634d',
'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '717554e4',
'rsrc/externals/javelin/core/__tests__/install.js' => 'c432ee85',
'rsrc/externals/javelin/core/__tests__/stratcom.js' => '88bf7313',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e251703d',
'rsrc/externals/javelin/core/init.js' => '2bd3c675',
'rsrc/externals/javelin/core/init_node.js' => 'c234aded',
'rsrc/externals/javelin/core/install.js' => '05270951',
'rsrc/externals/javelin/core/util.js' => '93cc50d6',
'rsrc/externals/javelin/docs/Base.js' => '74676256',
'rsrc/externals/javelin/docs/onload.js' => 'e819c479',
'rsrc/externals/javelin/ext/fx/Color.js' => '7e41274a',
'rsrc/externals/javelin/ext/fx/FX.js' => '54b612ba',
'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => 'f6555212',
'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '2b8de964',
'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '1ad0a787',
'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '76f4ebed',
'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => 'c90a04fc',
'rsrc/externals/javelin/ext/view/HTMLView.js' => 'fe287620',
'rsrc/externals/javelin/ext/view/View.js' => '0f764c35',
'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => 'f829edb3',
'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => '47830651',
'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '6c2b09a2',
'rsrc/externals/javelin/ext/view/ViewVisitor.js' => 'efe49472',
'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => 'f92d7bcb',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => '6450b38b',
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => '7a94d6a5',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '6ea96ac9',
'rsrc/externals/javelin/lib/Cookie.js' => '62dfea03',
'rsrc/externals/javelin/lib/DOM.js' => '6f7962d5',
'rsrc/externals/javelin/lib/History.js' => '2e0148bc',
'rsrc/externals/javelin/lib/JSON.js' => '69adf288',
'rsrc/externals/javelin/lib/Leader.js' => '331b1611',
'rsrc/externals/javelin/lib/Mask.js' => '8a41885b',
'rsrc/externals/javelin/lib/Quicksand.js' => 'f960d43d',
'rsrc/externals/javelin/lib/Request.js' => '94b750d2',
'rsrc/externals/javelin/lib/Resource.js' => '44959b73',
'rsrc/externals/javelin/lib/Routable.js' => 'b3e7d692',
'rsrc/externals/javelin/lib/Router.js' => '29274e2b',
'rsrc/externals/javelin/lib/Scrollbar.js' => '5b2f5a08',
'rsrc/externals/javelin/lib/URI.js' => '6eff08aa',
'rsrc/externals/javelin/lib/Vector.js' => '2caa8fb8',
'rsrc/externals/javelin/lib/WebSocket.js' => 'e292eaf4',
'rsrc/externals/javelin/lib/Workflow.js' => '84d6aea0',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => '5ed109e8',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => 'c984504b',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '837a7d68',
'rsrc/externals/javelin/lib/__tests__/URI.js' => '1e45fda9',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '1ea62783',
'rsrc/externals/javelin/lib/behavior.js' => '61cbc29a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '7644823e',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => '70baed2f',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => '6f7a9da8',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '503e17fd',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '8b3fd187',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '54f314a0',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '2818f5ce',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '316b8fa1',
'rsrc/externals/raphael/g.raphael.js' => '40dde778',
'rsrc/externals/raphael/g.raphael.line.js' => '40da039e',
'rsrc/externals/raphael/raphael.js' => '51ee6b43',
'rsrc/favicons/apple-touch-icon-120x120.png' => '43742962',
'rsrc/favicons/apple-touch-icon-152x152.png' => '669eaec3',
'rsrc/favicons/apple-touch-icon-76x76.png' => 'ecdef672',
'rsrc/favicons/favicon-128.png' => '47cdff03',
'rsrc/favicons/favicon-16x16.png' => 'ee2523ac',
'rsrc/favicons/favicon-32x32.png' => 'b6a8150e',
'rsrc/favicons/favicon-96x96.png' => '8f7ea177',
'rsrc/image/BFCFDA.png' => 'd5ec91f4',
'rsrc/image/actions/edit.png' => '2fc41442',
'rsrc/image/avatar.png' => '3eb28cd9',
'rsrc/image/checker_dark.png' => 'd8e65881',
'rsrc/image/checker_light.png' => 'a0155918',
'rsrc/image/checker_lighter.png' => 'd5da91b6',
'rsrc/image/darkload.gif' => '1ffd3ec6',
'rsrc/image/divot.png' => '94dded62',
'rsrc/image/examples/hero.png' => '979a86ae',
'rsrc/image/grippy_texture.png' => 'aca81e2f',
'rsrc/image/icon/fatcow/arrow_branch.png' => '2537c01c',
'rsrc/image/icon/fatcow/arrow_merge.png' => '21b660e0',
'rsrc/image/icon/fatcow/bullet_black.png' => 'ff190031',
'rsrc/image/icon/fatcow/bullet_orange.png' => 'e273e5bb',
'rsrc/image/icon/fatcow/bullet_red.png' => 'c0b75434',
'rsrc/image/icon/fatcow/calendar_edit.png' => '24632275',
'rsrc/image/icon/fatcow/document_black.png' => '45fe1c60',
'rsrc/image/icon/fatcow/flag_blue.png' => 'a01abb1d',
'rsrc/image/icon/fatcow/flag_finish.png' => '67825cee',
'rsrc/image/icon/fatcow/flag_ghost.png' => '20ca8783',
'rsrc/image/icon/fatcow/flag_green.png' => '7e0eaa7a',
'rsrc/image/icon/fatcow/flag_orange.png' => '9e73df66',
'rsrc/image/icon/fatcow/flag_pink.png' => '7e92f3b2',
'rsrc/image/icon/fatcow/flag_purple.png' => 'cc517522',
'rsrc/image/icon/fatcow/flag_red.png' => '04ec726f',
'rsrc/image/icon/fatcow/flag_yellow.png' => '73946fd4',
'rsrc/image/icon/fatcow/folder.png' => '95a435af',
'rsrc/image/icon/fatcow/folder_go.png' => '001cbc94',
'rsrc/image/icon/fatcow/key_question.png' => '52a0c26a',
'rsrc/image/icon/fatcow/link.png' => '7afd4d5e',
'rsrc/image/icon/fatcow/page_white_edit.png' => '39a2eed8',
'rsrc/image/icon/fatcow/page_white_link.png' => 'a90023c7',
'rsrc/image/icon/fatcow/page_white_put.png' => '08c95a0c',
'rsrc/image/icon/fatcow/page_white_text.png' => '1e1f79c3',
'rsrc/image/icon/fatcow/source/conduit.png' => '4ea01d2f',
'rsrc/image/icon/fatcow/source/email.png' => '9bab3239',
'rsrc/image/icon/fatcow/source/fax.png' => '04195e68',
'rsrc/image/icon/fatcow/source/mobile.png' => 'f1321264',
'rsrc/image/icon/fatcow/source/tablet.png' => '49396799',
'rsrc/image/icon/fatcow/source/web.png' => '136ccb5d',
'rsrc/image/icon/fatcow/thumbnails/default.p100.png' => '7d490b01',
'rsrc/image/icon/fatcow/thumbnails/default160x120.png' => 'f2e8a2eb',
'rsrc/image/icon/fatcow/thumbnails/default280x210.png' => '43e8926a',
'rsrc/image/icon/fatcow/thumbnails/default60x45.png' => '0118abed',
'rsrc/image/icon/fatcow/thumbnails/image.p100.png' => 'da23cf97',
'rsrc/image/icon/fatcow/thumbnails/image160x120.png' => '79bb556a',
'rsrc/image/icon/fatcow/thumbnails/image280x210.png' => '91ae054a',
'rsrc/image/icon/fatcow/thumbnails/image60x45.png' => 'c5e1685e',
'rsrc/image/icon/fatcow/thumbnails/pdf.p100.png' => '87d5e065',
'rsrc/image/icon/fatcow/thumbnails/pdf160x120.png' => 'ac9edbf5',
'rsrc/image/icon/fatcow/thumbnails/pdf280x210.png' => '1c585653',
'rsrc/image/icon/fatcow/thumbnails/pdf60x45.png' => 'c0db4143',
'rsrc/image/icon/fatcow/thumbnails/zip.p100.png' => '6ea5aae4',
'rsrc/image/icon/fatcow/thumbnails/zip160x120.png' => '75f9cd0f',
'rsrc/image/icon/fatcow/thumbnails/zip280x210.png' => 'dfda5b8e',
'rsrc/image/icon/fatcow/thumbnails/zip60x45.png' => 'af11bf3e',
'rsrc/image/icon/lightbox/close-2.png' => 'cc40e7c8',
'rsrc/image/icon/lightbox/close-hover-2.png' => 'fb5d6d9e',
'rsrc/image/icon/lightbox/left-arrow-2.png' => '8426133b',
'rsrc/image/icon/lightbox/left-arrow-hover-2.png' => '701e5ee3',
'rsrc/image/icon/lightbox/right-arrow-2.png' => '6d5519a0',
'rsrc/image/icon/lightbox/right-arrow-hover-2.png' => '3a04aa21',
'rsrc/image/icon/subscribe.png' => 'd03ed5a5',
'rsrc/image/icon/tango/attachment.png' => 'ecc8022e',
'rsrc/image/icon/tango/edit.png' => '929a1363',
'rsrc/image/icon/tango/go-down.png' => '96d95e43',
'rsrc/image/icon/tango/log.png' => 'b08cc63a',
'rsrc/image/icon/tango/upload.png' => '7bbb7984',
'rsrc/image/icon/unsubscribe.png' => '25725013',
'rsrc/image/lightblue-header.png' => '5c168b6d',
'rsrc/image/loading.gif' => '75d384cc',
'rsrc/image/loading/boating_24.gif' => '5c90f086',
'rsrc/image/loading/compass_24.gif' => 'b36b4f46',
'rsrc/image/loading/loading_24.gif' => '26bc9adc',
'rsrc/image/loading/loading_48.gif' => '6a4994c7',
'rsrc/image/loading/loading_d48.gif' => 'cdcbe900',
'rsrc/image/loading/loading_w24.gif' => '7662fa2b',
'rsrc/image/main_texture.png' => '29a2c5ad',
'rsrc/image/menu_texture.png' => '5a17580d',
'rsrc/image/people/harding.png' => '45aa614e',
'rsrc/image/people/jefferson.png' => 'afca0e53',
'rsrc/image/people/lincoln.png' => '9369126d',
'rsrc/image/people/mckinley.png' => 'fb8f16ce',
'rsrc/image/people/taft.png' => 'd7bc402c',
'rsrc/image/people/washington.png' => '40dd301c',
'rsrc/image/phrequent_active.png' => 'a466a8ed',
'rsrc/image/phrequent_inactive.png' => 'bfc15a69',
'rsrc/image/search-white.png' => '64cc0d45',
'rsrc/image/search.png' => '82625a7e',
'rsrc/image/sprite-gradient.png' => 'ec15a417',
'rsrc/image/sprite-login-X2.png' => '5ae6de3a',
'rsrc/image/sprite-login.png' => '07f2c67c',
'rsrc/image/sprite-main-header.png' => '39419fa6',
'rsrc/image/sprite-menu-X2.png' => '1c90d7bc',
'rsrc/image/sprite-menu.png' => '619781ee',
'rsrc/image/sprite-projects-X2.png' => '8c91c839',
'rsrc/image/sprite-projects.png' => 'ef9dc9b5',
'rsrc/image/sprite-tokens-X2.png' => 'b4776580',
'rsrc/image/sprite-tokens.png' => '25b75533',
'rsrc/image/texture/card-gradient.png' => '815f26e8',
'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8',
'rsrc/image/texture/dark-menu.png' => '7e22296e',
'rsrc/image/texture/grip.png' => '719404f3',
'rsrc/image/texture/panel-header-gradient.png' => 'e3b8dcfe',
'rsrc/image/texture/phlnx-bg.png' => '8d819209',
'rsrc/image/texture/pholio-background.gif' => 'ba29239c',
'rsrc/image/texture/table_header.png' => '5c433037',
'rsrc/image/texture/table_header_hover.png' => '038ec3b9',
'rsrc/image/texture/table_header_tall.png' => 'd56b434f',
'rsrc/js/application/aphlict/Aphlict.js' => '2be71d56',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'cc2d9c80',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '851f167c',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de',
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'a88a26cc',
'rsrc/js/application/conpherence/behavior-menu.js' => '869e3445',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '86df5915',
'rsrc/js/application/conpherence/behavior-widget-pane.js' => '40b1ff90',
'rsrc/js/application/countdown/timer.js' => 'e4cc26b3',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '469c0d9e',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '82439934',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
- 'rsrc/js/application/differential/ChangesetViewManager.js' => 'c024db3d',
- 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => 'f2441746',
+ 'rsrc/js/application/differential/ChangesetViewManager.js' => 'fce415a0',
+ 'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '6a049cf7',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => '6932def3',
'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1',
- 'rsrc/js/application/differential/behavior-dropdown-menus.js' => 'e33d4bc5',
+ 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb',
'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '65936067',
'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492',
'rsrc/js/application/differential/behavior-populate.js' => 'bdb3e4d0',
'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf',
- 'rsrc/js/application/differential/behavior-show-more.js' => '954d2de0',
+ 'rsrc/js/application/differential/behavior-show-more.js' => 'c662904a',
'rsrc/js/application/differential/behavior-toggle-files.js' => 'ca3f91eb',
'rsrc/js/application/differential/behavior-user-select.js' => 'a8d8459d',
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => 'b42eddc7',
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'd835b03a',
'rsrc/js/application/diffusion/behavior-commit-branches.js' => 'bdaf4d04',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'f7f1289f',
'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef',
'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => '2b228192',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781',
'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
'rsrc/js/application/herald/HeraldRuleEditor.js' => '6e2de6f2',
'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec',
'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3',
'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'f24f3253',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '7b98d7c5',
'rsrc/js/application/maniphest/behavior-line-chart.js' => '88f0c5b3',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'a9f88de2',
'rsrc/js/application/maniphest/behavior-subpriorityeditor.js' => '84845b5b',
'rsrc/js/application/maniphest/behavior-transaction-controls.js' => '44168bad',
'rsrc/js/application/maniphest/behavior-transaction-expand.js' => '5fefb143',
'rsrc/js/application/maniphest/behavior-transaction-preview.js' => 'f8248bc5',
'rsrc/js/application/owners/OwnersPathEditor.js' => 'aa1733d0',
'rsrc/js/application/owners/owners-path-editor.js' => '7a68dda3',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '3cb0b2fc',
'rsrc/js/application/phame/phame-post-preview.js' => 'be807912',
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '9c2623f4',
'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => 'e58bf807',
'rsrc/js/application/phortune/behavior-balanced-payment-form.js' => 'b2c03e60',
'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '3f5d6dbf',
'rsrc/js/application/phortune/behavior-test-payment-form.js' => 'fc91ab6c',
'rsrc/js/application/phortune/phortune-credit-card-form.js' => '2290aeef',
'rsrc/js/application/policy/behavior-policy-control.js' => 'f3fef818',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => 'fe9a552f',
'rsrc/js/application/ponder/behavior-votebox.js' => '4e9b766b',
'rsrc/js/application/projects/behavior-boards-dropdown.js' => '0ec56e1d',
'rsrc/js/application/projects/behavior-project-boards.js' => '87cb6b51',
'rsrc/js/application/projects/behavior-project-create.js' => '065227cc',
'rsrc/js/application/projects/behavior-reorder-columns.js' => 'e1d25dfb',
'rsrc/js/application/releeph/releeph-preview-branch.js' => 'b2b4fbaf',
'rsrc/js/application/releeph/releeph-request-state-change.js' => 'a0b57eb8',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'de2e896f',
'rsrc/js/application/repository/repository-crossreference.js' => 'f9539603',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => 'dbbf48b6',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '9f7309fb',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '13c739ea',
'rsrc/js/application/uiexample/JavelinViewExample.js' => 'd4a14807',
'rsrc/js/application/uiexample/ReactorButtonExample.js' => 'd19198c8',
'rsrc/js/application/uiexample/ReactorCheckboxExample.js' => '519705ea',
'rsrc/js/application/uiexample/ReactorFocusExample.js' => '40a6a403',
'rsrc/js/application/uiexample/ReactorInputExample.js' => '886fd850',
'rsrc/js/application/uiexample/ReactorMouseoverExample.js' => '47c794d8',
'rsrc/js/application/uiexample/ReactorRadioExample.js' => '988040b4',
'rsrc/js/application/uiexample/ReactorSelectExample.js' => 'a155550f',
'rsrc/js/application/uiexample/ReactorSendClassExample.js' => '1def2711',
'rsrc/js/application/uiexample/ReactorSendPropertiesExample.js' => 'b1f0ccee',
'rsrc/js/application/uiexample/busy-example.js' => '60479091',
'rsrc/js/application/uiexample/gesture-example.js' => '558829c2',
'rsrc/js/application/uiexample/notification-example.js' => '8ce821c5',
'rsrc/js/core/Busy.js' => '6453c869',
'rsrc/js/core/DragAndDropFileUpload.js' => '8c49f386',
'rsrc/js/core/DraggableList.js' => 'a16ec1c6',
'rsrc/js/core/FileUpload.js' => 'a4ae61bf',
'rsrc/js/core/Hovercard.js' => '7e8468ae',
'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2',
'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f',
'rsrc/js/core/MultirowRowManager.js' => 'b5d57730',
'rsrc/js/core/Notification.js' => '0c6946e7',
'rsrc/js/core/Prefab.js' => '72da38cc',
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
'rsrc/js/core/TextAreaUtils.js' => '5c93c52c',
'rsrc/js/core/Title.js' => '5c1c758c',
'rsrc/js/core/ToolTip.js' => '1d298e3a',
'rsrc/js/core/behavior-active-nav.js' => 'e379b58e',
'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
'rsrc/js/core/behavior-choose-control.js' => '6153c708',
'rsrc/js/core/behavior-crop.js' => 'fa0f4fc2',
'rsrc/js/core/behavior-dark-console.js' => '08883e8b',
'rsrc/js/core/behavior-device.js' => '03d6ed07',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '92eb531d',
'rsrc/js/core/behavior-error-log.js' => '6882e80a',
'rsrc/js/core/behavior-fancy-datepicker.js' => 'c51ae228',
'rsrc/js/core/behavior-file-tree.js' => '88236f00',
'rsrc/js/core/behavior-form.js' => '5c54cbf3',
'rsrc/js/core/behavior-gesture.js' => '3ab51e2c',
'rsrc/js/core/behavior-global-drag-and-drop.js' => '07f199d8',
'rsrc/js/core/behavior-high-security-warning.js' => '8fc1c918',
'rsrc/js/core/behavior-history-install.js' => '7ee2b591',
'rsrc/js/core/behavior-hovercard.js' => 'f36e01af',
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => 'd75709e6',
'rsrc/js/core/behavior-konami.js' => '5bc2cb21',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7',
'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '49b73b36',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '14d7a8b8',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'e32d14ab',
'rsrc/js/core/behavior-refresh-csrf.js' => '7814b593',
'rsrc/js/core/behavior-remarkup-preview.js' => 'f7379f45',
'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e',
'rsrc/js/core/behavior-reveal-content.js' => '60821bc7',
'rsrc/js/core/behavior-scrollbar.js' => '834a1173',
'rsrc/js/core/behavior-search-typeahead.js' => '724b1247',
'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6',
'rsrc/js/core/behavior-toggle-class.js' => 'e566f52c',
'rsrc/js/core/behavior-tokenizer.js' => 'b3a4b884',
'rsrc/js/core/behavior-tooltip.js' => '3ee3408b',
'rsrc/js/core/behavior-watch-anchor.js' => '9f36c42d',
'rsrc/js/core/behavior-workflow.js' => '0a3f3021',
'rsrc/js/core/phtize.js' => 'd254d646',
'rsrc/js/phui/behavior-phui-object-box-tabs.js' => '2bfa2836',
'rsrc/js/phui/behavior-phui-timeline-dropdown-menu.js' => '4d94d9c3',
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '6e8cefa4',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
),
'symbols' => array(
'almanac-css' => 'dbb9b3af',
'aphront-bars' => '231ac33c',
'aphront-dark-console-css' => '6378ef3d',
'aphront-dialog-view-css' => 'd2e76b88',
'aphront-list-filter-view-css' => '2ae43867',
'aphront-multi-column-view-css' => '41a848c0',
'aphront-pager-view-css' => '2e3539af',
'aphront-panel-view-css' => '7e0c3a56',
'aphront-table-view-css' => 'b22b7216',
'aphront-tokenizer-control-css' => '82ce2142',
'aphront-tooltip-css' => '4099b97e',
'aphront-two-column-view-css' => '16ab3ad2',
'aphront-typeahead-control-css' => '0e403212',
'auth-css' => '1e655982',
- 'changeset-view-manager' => 'c024db3d',
+ 'changeset-view-manager' => 'fce415a0',
'config-options-css' => '7fedf08b',
'config-welcome-css' => '6abd79be',
'conpherence-durable-column-view' => '3b836442',
'conpherence-menu-css' => '73774137',
'conpherence-message-pane-css' => '17a9517f',
'conpherence-notification-css' => '04a6e10a',
'conpherence-update-css' => '1099a660',
'conpherence-widget-pane-css' => '3d575438',
'differential-changeset-view-css' => 'b600950c',
'differential-core-view-css' => '7ac3cabc',
- 'differential-inline-comment-editor' => 'f2441746',
+ 'differential-inline-comment-editor' => '6a049cf7',
'differential-results-table-css' => '181aa9d9',
'differential-revision-add-comment-css' => 'c478bcaa',
'differential-revision-comment-css' => '48186045',
'differential-revision-history-css' => '0e8eb855',
'differential-revision-list-css' => 'f3c47d33',
'differential-table-of-contents-css' => '63f3ef4a',
'diffusion-icons-css' => '9c5828da',
'diffusion-source-css' => '66fdf661',
'diviner-shared-css' => '38813222',
'font-fontawesome' => 'ae9a7b4d',
'font-source-sans-pro' => '0d859f60',
'global-drag-and-drop-css' => '697324ad',
'harbormaster-css' => '49d64eb4',
'herald-css' => '826075fa',
'herald-rule-editor' => '6e2de6f2',
'herald-test-css' => '778b008e',
'homepage-panel-css' => 'e34bf140',
'inline-comment-summary-css' => 'eb5f8e8c',
'javelin-aphlict' => '2be71d56',
'javelin-behavior' => '61cbc29a',
'javelin-behavior-aphlict-dropdown' => 'cc2d9c80',
'javelin-behavior-aphlict-listen' => '851f167c',
'javelin-behavior-aphlict-status' => 'ea681761',
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
'javelin-behavior-aphront-crop' => 'fa0f4fc2',
'javelin-behavior-aphront-drag-and-drop-textarea' => '92eb531d',
'javelin-behavior-aphront-form-disable-on-submit' => '5c54cbf3',
'javelin-behavior-aphront-more' => 'a80d0378',
'javelin-behavior-audio-source' => '59b251eb',
'javelin-behavior-audit-preview' => 'd835b03a',
'javelin-behavior-balanced-payment-form' => 'b2c03e60',
'javelin-behavior-boards-dropdown' => '0ec56e1d',
'javelin-behavior-choose-control' => '6153c708',
'javelin-behavior-config-reorder-fields' => '14a827de',
'javelin-behavior-conpherence-menu' => '869e3445',
'javelin-behavior-conpherence-pontificate' => '86df5915',
'javelin-behavior-conpherence-widget-pane' => '40b1ff90',
'javelin-behavior-countdown-timer' => 'e4cc26b3',
'javelin-behavior-dark-console' => '08883e8b',
'javelin-behavior-dashboard-async-panel' => '469c0d9e',
'javelin-behavior-dashboard-move-panels' => '82439934',
'javelin-behavior-dashboard-query-panel-select' => '453c5375',
'javelin-behavior-dashboard-tab-panel' => 'd4eecc63',
'javelin-behavior-device' => '03d6ed07',
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
'javelin-behavior-differential-comment-jump' => '4fdb476d',
'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
- 'javelin-behavior-differential-dropdown-menus' => 'e33d4bc5',
+ 'javelin-behavior-differential-dropdown-menus' => '2035b9cb',
'javelin-behavior-differential-edit-inline-comments' => '65936067',
'javelin-behavior-differential-feedback-preview' => '6932def3',
'javelin-behavior-differential-keyboard-navigation' => '2c426492',
'javelin-behavior-differential-populate' => 'bdb3e4d0',
'javelin-behavior-differential-show-field-details' => 'bba9eedf',
- 'javelin-behavior-differential-show-more' => '954d2de0',
+ 'javelin-behavior-differential-show-more' => 'c662904a',
'javelin-behavior-differential-toggle-files' => 'ca3f91eb',
'javelin-behavior-differential-user-select' => 'a8d8459d',
'javelin-behavior-diffusion-commit-branches' => 'bdaf4d04',
'javelin-behavior-diffusion-commit-graph' => 'f7f1289f',
'javelin-behavior-diffusion-jump-to' => '73d09eef',
'javelin-behavior-diffusion-locate-file' => '6d3e1947',
'javelin-behavior-diffusion-pull-lastmodified' => '2b228192',
'javelin-behavior-doorkeeper-tag' => 'e5822781',
'javelin-behavior-durable-column' => 'a88a26cc',
'javelin-behavior-error-log' => '6882e80a',
'javelin-behavior-fancy-datepicker' => 'c51ae228',
'javelin-behavior-global-drag-and-drop' => '07f199d8',
'javelin-behavior-herald-rule-editor' => '7ebaeed3',
'javelin-behavior-high-security-warning' => '8fc1c918',
'javelin-behavior-history-install' => '7ee2b591',
'javelin-behavior-icon-composer' => '8ef9ab58',
'javelin-behavior-konami' => '5bc2cb21',
'javelin-behavior-launch-icon-composer' => '48086888',
'javelin-behavior-lightbox-attachments' => 'f8ba29d7',
'javelin-behavior-line-chart' => '88f0c5b3',
'javelin-behavior-load-blame' => '42126667',
'javelin-behavior-maniphest-batch-editor' => 'f24f3253',
'javelin-behavior-maniphest-batch-selector' => '7b98d7c5',
'javelin-behavior-maniphest-list-editor' => 'a9f88de2',
'javelin-behavior-maniphest-subpriority-editor' => '84845b5b',
'javelin-behavior-maniphest-transaction-controls' => '44168bad',
'javelin-behavior-maniphest-transaction-expand' => '5fefb143',
'javelin-behavior-maniphest-transaction-preview' => 'f8248bc5',
'javelin-behavior-owners-path-editor' => '7a68dda3',
'javelin-behavior-passphrase-credential-control' => '3cb0b2fc',
'javelin-behavior-persona-login' => '9414ff18',
'javelin-behavior-phabricator-active-nav' => 'e379b58e',
'javelin-behavior-phabricator-autofocus' => '7319e029',
'javelin-behavior-phabricator-busy-example' => '60479091',
'javelin-behavior-phabricator-file-tree' => '88236f00',
'javelin-behavior-phabricator-gesture' => '3ab51e2c',
'javelin-behavior-phabricator-gesture-example' => '558829c2',
'javelin-behavior-phabricator-hovercards' => 'f36e01af',
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => 'd75709e6',
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
'javelin-behavior-phabricator-nav' => '14d7a8b8',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '49b73b36',
'javelin-behavior-phabricator-oncopy' => '2926fff2',
'javelin-behavior-phabricator-remarkup-assist' => 'e32d14ab',
'javelin-behavior-phabricator-reveal-content' => '60821bc7',
'javelin-behavior-phabricator-search-typeahead' => '724b1247',
'javelin-behavior-phabricator-show-older-transactions' => 'dbbf48b6',
'javelin-behavior-phabricator-tooltips' => '3ee3408b',
'javelin-behavior-phabricator-transaction-comment-form' => '9f7309fb',
'javelin-behavior-phabricator-transaction-list' => '13c739ea',
'javelin-behavior-phabricator-watch-anchor' => '9f36c42d',
'javelin-behavior-phame-post-preview' => 'be807912',
'javelin-behavior-pholio-mock-edit' => '9c2623f4',
'javelin-behavior-pholio-mock-view' => 'e58bf807',
'javelin-behavior-phui-object-box-tabs' => '2bfa2836',
'javelin-behavior-phui-timeline-dropdown-menu' => '4d94d9c3',
'javelin-behavior-policy-control' => 'f3fef818',
'javelin-behavior-policy-rule-editor' => 'fe9a552f',
'javelin-behavior-ponder-votebox' => '4e9b766b',
'javelin-behavior-project-boards' => '87cb6b51',
'javelin-behavior-project-create' => '065227cc',
'javelin-behavior-refresh-csrf' => '7814b593',
'javelin-behavior-releeph-preview-branch' => 'b2b4fbaf',
'javelin-behavior-releeph-request-state-change' => 'a0b57eb8',
'javelin-behavior-releeph-request-typeahead' => 'de2e896f',
'javelin-behavior-remarkup-preview' => 'f7379f45',
'javelin-behavior-reorder-applications' => '76b9fc3e',
'javelin-behavior-reorder-columns' => 'e1d25dfb',
'javelin-behavior-repository-crossreference' => 'f9539603',
'javelin-behavior-scrollbar' => '834a1173',
'javelin-behavior-search-reorder-queries' => 'e9581f08',
'javelin-behavior-select-on-click' => '4e3e79a6',
'javelin-behavior-slowvote-embed' => '887ad43f',
'javelin-behavior-stripe-payment-form' => '3f5d6dbf',
'javelin-behavior-test-payment-form' => 'fc91ab6c',
'javelin-behavior-toggle-class' => 'e566f52c',
'javelin-behavior-view-placeholder' => '47830651',
'javelin-behavior-workflow' => '0a3f3021',
'javelin-color' => '7e41274a',
'javelin-cookie' => '62dfea03',
'javelin-diffusion-locate-file-source' => 'b42eddc7',
'javelin-dom' => '6f7962d5',
'javelin-dynval' => 'f6555212',
'javelin-event' => '85ea0626',
'javelin-fx' => '54b612ba',
'javelin-history' => '2e0148bc',
'javelin-install' => '05270951',
'javelin-json' => '69adf288',
'javelin-leader' => '331b1611',
'javelin-magical-init' => '2bd3c675',
'javelin-mask' => '8a41885b',
'javelin-quicksand' => 'f960d43d',
'javelin-reactor' => '2b8de964',
'javelin-reactor-dom' => 'c90a04fc',
'javelin-reactor-node-calmer' => '76f4ebed',
'javelin-reactornode' => '1ad0a787',
'javelin-request' => '94b750d2',
'javelin-resource' => '44959b73',
'javelin-routable' => 'b3e7d692',
'javelin-router' => '29274e2b',
'javelin-scrollbar' => '5b2f5a08',
'javelin-stratcom' => '6c53634d',
'javelin-tokenizer' => '7644823e',
'javelin-typeahead' => '70baed2f',
'javelin-typeahead-composite-source' => '503e17fd',
'javelin-typeahead-normalizer' => '6f7a9da8',
'javelin-typeahead-ondemand-source' => '8b3fd187',
'javelin-typeahead-preloaded-source' => '54f314a0',
'javelin-typeahead-source' => '2818f5ce',
'javelin-typeahead-static-source' => '316b8fa1',
'javelin-uri' => '6eff08aa',
'javelin-util' => '93cc50d6',
'javelin-vector' => '2caa8fb8',
'javelin-view' => '0f764c35',
'javelin-view-html' => 'fe287620',
'javelin-view-interpreter' => 'f829edb3',
'javelin-view-renderer' => '6c2b09a2',
'javelin-view-visitor' => 'efe49472',
'javelin-websocket' => 'e292eaf4',
'javelin-workflow' => '84d6aea0',
'lightbox-attachment-css' => '7acac05d',
'maniphest-batch-editor' => '8f380ebc',
'maniphest-report-css' => 'd8ed71ad',
'maniphest-task-edit-css' => '8e23031b',
'maniphest-task-summary-css' => 'ab2fc691',
'multirow-row-manager' => 'b5d57730',
'owners-path-editor' => 'aa1733d0',
'owners-path-editor-css' => '2f00933b',
'paste-css' => 'eb997ddd',
'path-typeahead' => 'f7fc67ec',
'people-profile-css' => '25970776',
'phabricator-action-list-view-css' => '9ee9910a',
'phabricator-application-launch-view-css' => '16ca323f',
'phabricator-busy' => '6453c869',
'phabricator-chatlog-css' => '852140ff',
'phabricator-content-source-view-css' => '4b8b05d4',
'phabricator-core-css' => '86bfbe8c',
'phabricator-countdown-css' => '86b7b0a0',
'phabricator-dashboard-css' => '17937d22',
'phabricator-drag-and-drop-file-upload' => '8c49f386',
'phabricator-draggable-list' => 'a16ec1c6',
'phabricator-fatal-config-template-css' => '8e6c6fcd',
'phabricator-feed-css' => 'b513b5f4',
'phabricator-file-upload' => 'a4ae61bf',
'phabricator-filetree-view-css' => 'fccf9f82',
'phabricator-flag-css' => '5337623f',
'phabricator-hovercard' => '7e8468ae',
'phabricator-hovercard-view-css' => '893f4783',
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c1700f6f',
'phabricator-main-menu-view' => 'f9f5cd1b',
'phabricator-nav-view-css' => '7aeaf435',
'phabricator-notification' => '0c6946e7',
'phabricator-notification-css' => '9c279160',
'phabricator-notification-menu-css' => '6aa0a74b',
'phabricator-object-selector-css' => '029a133d',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '72da38cc',
'phabricator-profile-css' => '1a20dcbf',
'phabricator-remarkup-css' => '2dbff225',
'phabricator-search-results-css' => '559cc554',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => '7e8c6341',
'phabricator-slowvote-css' => '266df6a1',
'phabricator-source-code-view-css' => '2ceee894',
'phabricator-standard-page-view' => 'df338a4b',
'phabricator-textareautils' => '5c93c52c',
'phabricator-title' => '5c1c758c',
'phabricator-tooltip' => '1d298e3a',
'phabricator-transaction-view-css' => '5d0cae25',
'phabricator-ui-example-css' => '528b19de',
'phabricator-uiexample-javelin-view' => 'd4a14807',
'phabricator-uiexample-reactor-button' => 'd19198c8',
'phabricator-uiexample-reactor-checkbox' => '519705ea',
'phabricator-uiexample-reactor-focus' => '40a6a403',
'phabricator-uiexample-reactor-input' => '886fd850',
'phabricator-uiexample-reactor-mouseover' => '47c794d8',
'phabricator-uiexample-reactor-radio' => '988040b4',
'phabricator-uiexample-reactor-select' => 'a155550f',
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '9ec70c03',
'phame-css' => '88bd4705',
'pholio-css' => '95174bdd',
'pholio-edit-css' => '3ad9d1ee',
'pholio-inline-comments-css' => '8e545e49',
'phortune-credit-card-form' => '2290aeef',
'phortune-credit-card-form-css' => '8391eb02',
'phortune-css' => '9149f103',
'phrequent-css' => 'ffc185ad',
'phriction-document-css' => '0d16bc9a',
'phui-action-header-view-css' => '89c497e7',
'phui-action-panel-css' => '3ee9afd5',
'phui-box-css' => '7b3a2eed',
'phui-button-css' => '21cb97f9',
'phui-calendar-css' => '8675968e',
'phui-calendar-day-css' => 'de035c8a',
'phui-calendar-list-css' => 'c1d0ca59',
'phui-calendar-month-css' => 'a92e47d2',
'phui-crumbs-view-css' => '594d719e',
'phui-document-view-css' => '0f83a7df',
'phui-feed-story-css' => 'c9f3a0b5',
'phui-font-icon-base-css' => '3dad2ae3',
'phui-fontkit-css' => 'd30f4fa3',
'phui-form-css' => 'f535f938',
'phui-form-view-css' => '76f0b086',
'phui-header-view-css' => '083669db',
'phui-icon-view-css' => 'd35aa857',
'phui-image-mask-css' => '5a8b09c8',
'phui-info-panel-css' => '27ea50a1',
'phui-info-view-css' => '6c6cc4dd',
'phui-list-view-css' => '53deb25c',
'phui-object-box-css' => 'd68ce5dc',
'phui-object-item-list-view-css' => '9db65899',
'phui-pinboard-view-css' => '3dd4a269',
'phui-property-list-view-css' => '51480060',
'phui-remarkup-preview-css' => '19ad512b',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
'phui-tag-view-css' => 'ea469f3a',
'phui-text-css' => 'cf019f54',
'phui-timeline-view-css' => 'b0fbc4d7',
'phui-workboard-view-css' => '8896938c',
'phui-workpanel-view-css' => 'e495a5cc',
'phuix-action-list-view' => 'b5c256b8',
'phuix-action-view' => '6e8cefa4',
'phuix-dropdown-menu' => 'bd4c8dca',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
'ponder-comment-table-css' => '6cdccea7',
'ponder-feed-view-css' => 'e62615b6',
'ponder-post-css' => 'ebab8a70',
'ponder-vote-css' => '8ed6ed8b',
'project-icon-css' => 'c2ecb7f1',
'raphael-core' => '51ee6b43',
'raphael-g' => '40dde778',
'raphael-g-line' => '40da039e',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
'releeph-request-differential-create-dialog' => '8d8b92cd',
'releeph-request-typeahead-css' => '667a48ae',
'setup-issue-css' => '22270af2',
'sprite-gradient-css' => '4bdb98a7',
'sprite-login-css' => 'a355d921',
'sprite-main-header-css' => '28d01b0b',
'sprite-menu-css' => '9ef76324',
'sprite-projects-css' => 'b0d9e24f',
'sprite-tokens-css' => '1706b943',
'syntax-highlighting-css' => '56c1ba38',
'tokens-css' => '3d0f239e',
'unhandled-exception-css' => '37d4f9a2',
),
'requires' => array(
'029a133d' => array(
'aphront-dialog-view-css',
),
'03d6ed07' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'05270951' => array(
'javelin-util',
'javelin-magical-init',
),
'065227cc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
),
'07f199d8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'08883e8b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-util',
'javelin-dom',
'javelin-request',
'phabricator-keyboard-shortcut',
),
'0a3f3021' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-router',
),
'0c6946e7' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'phabricator-notification-css',
),
'0ec56e1d' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phuix-dropdown-menu',
),
'0f764c35' => array(
'javelin-install',
'javelin-util',
),
'13c739ea' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-uri',
'phabricator-textareautils',
),
'1499a8cb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
),
'14a827de' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-json',
'phabricator-draggable-list',
),
'14d7a8b8' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-dom',
'javelin-magical-init',
'javelin-vector',
'javelin-request',
'javelin-util',
),
'1ad0a787' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
'javelin-reactor-node-calmer',
),
'1ae869f2' => array(
'javelin-install',
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1d298e3a' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'1def2711' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
+ '2035b9cb' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-workflow',
+ 'phuix-dropdown-menu',
+ 'phuix-action-list-view',
+ 'phuix-action-view',
+ 'phabricator-phtize',
+ 'changeset-view-manager',
+ ),
'2290aeef' => array(
'javelin-install',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-util',
),
'2818f5ce' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'2926fff2' => array(
'javelin-behavior',
'javelin-dom',
),
'29274e2b' => array(
'javelin-install',
'javelin-util',
),
'2b228192' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'2b8de964' => array(
'javelin-install',
'javelin-util',
),
'2be71d56' => array(
'javelin-install',
'javelin-util',
'javelin-websocket',
'javelin-leader',
'javelin-json',
),
'2bfa2836' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'2c426492' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-keyboard-shortcut',
),
'2caa8fb8' => array(
'javelin-install',
'javelin-event',
),
'2e0148bc' => array(
'javelin-stratcom',
'javelin-install',
'javelin-uri',
'javelin-util',
),
'316b8fa1' => array(
'javelin-install',
'javelin-typeahead-source',
),
'331b1611' => array(
'javelin-install',
),
'3ab51e2c' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-magical-init',
),
'3cb0b2fc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'javelin-uri',
),
'3ee3408b' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'phabricator-tooltip',
),
'3f5d6dbf' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'40a6a403' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
'40b1ff90' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'javelin-behavior-device',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
),
42126667 => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
),
'44168bad' => array(
'javelin-behavior',
'javelin-dom',
'phabricator-prefab',
),
'44959b73' => array(
'javelin-util',
'javelin-uri',
'javelin-install',
),
'453c5375' => array(
'javelin-behavior',
'javelin-dom',
),
'469c0d9e' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
47830651 => array(
'javelin-behavior',
'javelin-dom',
'javelin-view-renderer',
'javelin-install',
),
'47c794d8' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
48086888 => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'49b73b36' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'4d94d9c3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'4e3e79a6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'4e9b766b' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-request',
),
'4fdb476d' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'503e17fd' => array(
'javelin-install',
'javelin-typeahead-source',
'javelin-util',
),
'519705ea' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
'54b612ba' => array(
'javelin-color',
'javelin-install',
'javelin-util',
),
'54f314a0' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'558829c2' => array(
'javelin-stratcom',
'javelin-behavior',
'javelin-vector',
'javelin-dom',
),
'59b251eb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
),
'5b2f5a08' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'5bc2cb21' => array(
'javelin-behavior',
'javelin-stratcom',
),
'5c1c758c' => array(
'javelin-install',
),
'5c54cbf3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5c93c52c' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
),
'5fefb143' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
'javelin-stratcom',
),
60479091 => array(
'phabricator-busy',
'javelin-behavior',
),
'60821bc7' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'6153c708' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'61cbc29a' => array(
'javelin-magical-init',
'javelin-util',
),
'62dfea03' => array(
'javelin-install',
'javelin-util',
),
'6453c869' => array(
'javelin-install',
'javelin-dom',
'javelin-fx',
),
65936067 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
'javelin-vector',
'differential-inline-comment-editor',
),
'6882e80a' => array(
'javelin-dom',
),
'6932def3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-request',
'javelin-util',
'phabricator-shaped-request',
),
'69adf288' => array(
'javelin-install',
),
+ '6a049cf7' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-request',
+ 'javelin-workflow',
+ ),
'6c2b09a2' => array(
'javelin-install',
'javelin-util',
),
'6c53634d' => array(
'javelin-install',
'javelin-event',
'javelin-util',
'javelin-magical-init',
),
'6d3e1947' => array(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
'javelin-dom',
'javelin-typeahead',
'javelin-uri',
),
'6e2de6f2' => array(
'multirow-row-manager',
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-json',
'phabricator-prefab',
),
'6e8cefa4' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'6eff08aa' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
),
'6f7962d5' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'6f7a9da8' => array(
'javelin-install',
),
'70baed2f' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'724b1247' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
),
'72da38cc' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead',
'javelin-tokenizer',
'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'7319e029' => array(
'javelin-behavior',
'javelin-dom',
),
'73d09eef' => array(
'javelin-behavior',
'javelin-vector',
'javelin-dom',
),
'7644823e' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'76b9fc3e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'76f4ebed' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
),
'7814b593' => array(
'javelin-request',
'javelin-behavior',
'javelin-dom',
'javelin-router',
'javelin-util',
'phabricator-busy',
),
'7a68dda3' => array(
'owners-path-editor',
'javelin-behavior',
),
'7b98d7c5' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'7cbe244b' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-router',
),
'7e41274a' => array(
'javelin-install',
),
'7e8468ae' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'7ebaeed3' => array(
'herald-rule-editor',
'javelin-behavior',
),
'7ee2b591' => array(
'javelin-behavior',
'javelin-history',
),
82439934 => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'82ce2142' => array(
'aphront-typeahead-control-css',
),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'84845b5b' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'84d6aea0' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'851f167c' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'javelin-leader',
'phabricator-notification',
),
'85ea0626' => array(
'javelin-install',
),
'869e3445' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'javelin-behavior-device',
'javelin-history',
'javelin-vector',
'phabricator-shaped-request',
),
'86df5915' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'87cb6b51' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'88236f00' => array(
'javelin-behavior',
'phabricator-keyboard-shortcut',
'javelin-stratcom',
),
'886fd850' => array(
'javelin-install',
'javelin-reactor-dom',
'javelin-view-html',
'javelin-view-interpreter',
'javelin-view-renderer',
),
'887ad43f' => array(
'javelin-behavior',
'javelin-request',
'javelin-stratcom',
'javelin-dom',
),
'88f0c5b3' => array(
'javelin-behavior',
'javelin-dom',
'javelin-vector',
),
'8a41885b' => array(
'javelin-install',
'javelin-dom',
),
'8b3fd187' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'8c49f386' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'8ce821c5' => array(
'phabricator-notification',
'javelin-stratcom',
'javelin-behavior',
),
'8ef9ab58' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'8fc1c918' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'92eb531d' => array(
'javelin-behavior',
'javelin-dom',
'phabricator-drag-and-drop-file-upload',
'phabricator-textareautils',
),
'9414ff18' => array(
'javelin-behavior',
'javelin-resource',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
),
'94b750d2' => array(
'javelin-install',
'javelin-stratcom',
'javelin-util',
'javelin-behavior',
'javelin-json',
'javelin-dom',
'javelin-resource',
'javelin-routable',
),
- '954d2de0' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-workflow',
- 'javelin-util',
- 'javelin-stratcom',
- ),
'988040b4' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
'9c2623f4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
'phabricator-phtize',
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
'9f36c42d' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'9f7309fb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
'phabricator-shaped-request',
),
'a0b57eb8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-keyboard-shortcut',
),
'a155550f' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
'a16ec1c6' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'a4ae61bf' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'a80d0378' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'a88a26cc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
'javelin-behavior-conpherence-widget-pane',
),
'a8d8459d' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'a8da01f0' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'a9f88de2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-fx',
'javelin-util',
),
'aa1733d0' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
),
'b1f0ccee' => array(
'javelin-install',
'javelin-dom',
'javelin-reactor-dom',
),
'b2b4fbaf' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-request',
),
'b2c03e60' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'b3a4b884' => array(
'javelin-behavior',
'phabricator-prefab',
),
'b3e7d692' => array(
'javelin-install',
),
'b42eddc7' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'b5c256b8' => array(
'javelin-install',
'javelin-dom',
),
'b5d57730' => array(
'javelin-install',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
),
'bba9eedf' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'bd4c8dca' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'bdaf4d04' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
),
'bdb3e4d0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-tooltip',
'changeset-view-manager',
),
'be807912' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
- 'c024db3d' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-workflow',
- 'javelin-router',
- 'javelin-behavior-device',
- 'javelin-vector',
- ),
'c1700f6f' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'c51ae228' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
+ 'c662904a' => array(
+ 'javelin-behavior',
+ 'javelin-dom',
+ 'javelin-workflow',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'changeset-view-manager',
+ ),
'c90a04fc' => array(
'javelin-dom',
'javelin-dynval',
'javelin-reactor',
'javelin-reactornode',
'javelin-install',
'javelin-util',
),
'ca3f91eb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-phtize',
),
'cc2d9c80' => array(
'javelin-behavior',
'javelin-request',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-uri',
'javelin-behavior-device',
'phabricator-title',
),
'd19198c8' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-dynval',
'javelin-reactor-dom',
),
'd254d646' => array(
'javelin-util',
),
'd4a14807' => array(
'javelin-install',
'javelin-dom',
'javelin-view',
),
'd4eecc63' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd75709e6' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'd835b03a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'dbbf48b6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'de2e896f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-typeahead',
'javelin-typeahead-ondemand-source',
'javelin-dom',
),
'e10f8e18' => array(
'javelin-behavior',
'javelin-dom',
'phabricator-prefab',
),
'e1d25dfb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'e1ff79b1' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e292eaf4' => array(
'javelin-install',
),
'e32d14ab' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
),
- 'e33d4bc5' => array(
- 'javelin-behavior',
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-workflow',
- 'phuix-dropdown-menu',
- 'phuix-action-list-view',
- 'phuix-action-view',
- 'phabricator-phtize',
- 'changeset-view-manager',
- ),
'e379b58e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-uri',
),
'e4cc26b3' => array(
'javelin-behavior',
'javelin-dom',
),
'e566f52c' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e5822781' => array(
'javelin-behavior',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-magical-init',
),
'e58bf807' => array(
'javelin-behavior',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-magical-init',
'javelin-request',
'javelin-history',
'javelin-workflow',
'javelin-mask',
'javelin-behavior-device',
'phabricator-keyboard-shortcut',
),
'e9581f08' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'ea681761' => array(
'javelin-behavior',
'javelin-aphlict',
'phabricator-phtize',
'javelin-dom',
),
'efe49472' => array(
'javelin-install',
'javelin-util',
),
- 'f2441746' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-request',
- 'javelin-workflow',
- ),
'f24f3253' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'multirow-row-manager',
'javelin-json',
),
'f36e01af' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'phabricator-hovercard',
),
'f3fef818' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'javelin-workflow',
),
'f6555212' => array(
'javelin-install',
'javelin-reactornode',
'javelin-util',
'javelin-reactor',
),
'f7379f45' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'f7f1289f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'f7fc67ec' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'f8248bc5' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-json',
'javelin-stratcom',
'phabricator-shaped-request',
),
'f829edb3' => array(
'javelin-view',
'javelin-install',
'javelin-dom',
),
'f8ba29d7' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phabricator-busy',
),
'f9539603' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'f960d43d' => array(
'javelin-install',
),
'fa0f4fc2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-vector',
'javelin-magical-init',
),
'fc91ab6c' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
+ 'fce415a0' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-workflow',
+ 'javelin-router',
+ 'javelin-behavior-device',
+ 'javelin-vector',
+ ),
'fe287620' => array(
'javelin-install',
'javelin-dom',
'javelin-view-visitor',
'javelin-util',
),
'fe9a552f' => array(
'javelin-behavior',
'multirow-row-manager',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'javelin-json',
),
),
'packages' => array(
'core.pkg.css' => array(
'phabricator-core-css',
'phabricator-zindex-css',
'phui-button-css',
'phabricator-standard-page-view',
'aphront-dialog-view-css',
'phui-form-view-css',
'aphront-panel-view-css',
'aphront-table-view-css',
'aphront-tokenizer-control-css',
'aphront-typeahead-control-css',
'aphront-list-filter-view-css',
'phabricator-remarkup-css',
'syntax-highlighting-css',
'aphront-pager-view-css',
'phabricator-transaction-view-css',
'aphront-tooltip-css',
'phabricator-flag-css',
'phui-info-view-css',
'sprite-gradient-css',
'sprite-menu-css',
'phabricator-main-menu-view',
'phabricator-notification-css',
'phabricator-notification-menu-css',
'lightbox-attachment-css',
'phui-header-view-css',
'phabricator-filetree-view-css',
'phabricator-nav-view-css',
'phabricator-side-menu-view-css',
'phui-crumbs-view-css',
'phui-object-item-list-view-css',
'global-drag-and-drop-css',
'phui-spacing-css',
'phui-form-css',
'phui-icon-view-css',
'phabricator-application-launch-view-css',
'phabricator-action-list-view-css',
'phui-property-list-view-css',
'phui-tag-view-css',
'phui-list-view-css',
'font-fontawesome',
'phui-font-icon-base-css',
'sprite-main-header-css',
'phui-box-css',
'phui-object-box-css',
'phui-timeline-view-css',
'sprite-tokens-css',
'tokens-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-action-header-view-css',
),
'core.pkg.js' => array(
'javelin-util',
'javelin-install',
'javelin-event',
'javelin-stratcom',
'javelin-behavior',
'javelin-resource',
'javelin-request',
'javelin-vector',
'javelin-dom',
'javelin-json',
'javelin-uri',
'javelin-workflow',
'javelin-mask',
'javelin-typeahead',
'javelin-typeahead-normalizer',
'javelin-typeahead-source',
'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source',
'javelin-tokenizer',
'javelin-history',
'javelin-router',
'javelin-routable',
'javelin-behavior-aphront-basic-tokenizer',
'javelin-behavior-workflow',
'javelin-behavior-aphront-form-disable-on-submit',
'phabricator-keyboard-shortcut-manager',
'phabricator-keyboard-shortcut',
'javelin-behavior-phabricator-keyboard-shortcuts',
'javelin-behavior-refresh-csrf',
'javelin-behavior-phabricator-watch-anchor',
'javelin-behavior-phabricator-autofocus',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phabricator-phtize',
'javelin-behavior-phabricator-oncopy',
'phabricator-tooltip',
'javelin-behavior-phabricator-tooltips',
'phabricator-prefab',
'javelin-behavior-device',
'javelin-behavior-toggle-class',
'javelin-behavior-lightbox-attachments',
'phabricator-busy',
'javelin-aphlict',
'phabricator-notification',
'javelin-behavior-aphlict-listen',
'javelin-behavior-phabricator-search-typeahead',
'javelin-behavior-konami',
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-active-nav',
'javelin-behavior-phabricator-nav',
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phabricator-hovercard',
'javelin-behavior-phabricator-hovercards',
'javelin-color',
'javelin-fx',
'phabricator-draggable-list',
'javelin-behavior-phabricator-transaction-list',
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-timeline-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
),
'darkconsole.pkg.js' => array(
'javelin-behavior-dark-console',
'javelin-behavior-error-log',
),
'differential.pkg.css' => array(
'differential-core-view-css',
'differential-changeset-view-css',
'differential-results-table-css',
'differential-revision-history-css',
'differential-revision-list-css',
'differential-table-of-contents-css',
'differential-revision-comment-css',
'differential-revision-add-comment-css',
'phabricator-object-selector-css',
'phabricator-content-source-view-css',
'inline-comment-summary-css',
),
'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload',
'phabricator-shaped-request',
'javelin-behavior-differential-feedback-preview',
'javelin-behavior-differential-edit-inline-comments',
'javelin-behavior-differential-populate',
'javelin-behavior-differential-show-more',
'javelin-behavior-differential-diff-radios',
'javelin-behavior-differential-comment-jump',
'javelin-behavior-differential-add-reviewers-and-ccs',
'javelin-behavior-differential-keyboard-navigation',
'javelin-behavior-aphront-drag-and-drop-textarea',
'javelin-behavior-phabricator-object-selector',
'javelin-behavior-repository-crossreference',
'javelin-behavior-load-blame',
'differential-inline-comment-editor',
'javelin-behavior-differential-dropdown-menus',
'javelin-behavior-differential-toggle-files',
'javelin-behavior-differential-user-select',
'javelin-behavior-aphront-more',
),
'diffusion.pkg.css' => array(
'diffusion-icons-css',
),
'diffusion.pkg.js' => array(
'javelin-behavior-diffusion-pull-lastmodified',
'javelin-behavior-diffusion-commit-graph',
'javelin-behavior-audit-preview',
),
'maniphest.pkg.css' => array(
'maniphest-task-summary-css',
),
'maniphest.pkg.js' => array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-transaction-controls',
'javelin-behavior-maniphest-transaction-preview',
'javelin-behavior-maniphest-transaction-expand',
'javelin-behavior-maniphest-subpriority-editor',
'javelin-behavior-maniphest-list-editor',
),
),
);
diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php
index 33597e0020..59691aa408 100644
--- a/src/applications/differential/controller/DifferentialChangesetViewController.php
+++ b/src/applications/differential/controller/DifferentialChangesetViewController.php
@@ -1,388 +1,382 @@
<?php
final class DifferentialChangesetViewController extends DifferentialController {
public function shouldAllowPublic() {
return true;
}
public function processRequest() {
$request = $this->getRequest();
$author_phid = $request->getUser()->getPHID();
$rendering_reference = $request->getStr('ref');
$parts = explode('/', $rendering_reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
$id = (int)$id;
$vs = (int)$vs;
$load_ids = array($id);
if ($vs && ($vs != -1)) {
$load_ids[] = $vs;
}
$changesets = id(new DifferentialChangesetQuery())
->setViewer($request->getUser())
->withIDs($load_ids)
->needHunks(true)
->execute();
$changesets = mpull($changesets, null, 'getID');
$changeset = idx($changesets, $id);
if (!$changeset) {
return new Aphront404Response();
}
$vs_changeset = null;
if ($vs && ($vs != -1)) {
$vs_changeset = idx($changesets, $vs);
if (!$vs_changeset) {
return new Aphront404Response();
}
}
$view = $request->getStr('view');
if ($view) {
$phid = idx($changeset->getMetadata(), "$view:binary-phid");
if ($phid) {
return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/");
}
switch ($view) {
case 'new':
return $this->buildRawFileResponse($changeset, $is_new = true);
case 'old':
if ($vs_changeset) {
return $this->buildRawFileResponse($vs_changeset, $is_new = true);
}
return $this->buildRawFileResponse($changeset, $is_new = false);
default:
return new Aphront400Response();
}
}
if (!$vs) {
$right = $changeset;
$left = null;
$right_source = $right->getID();
$right_new = true;
$left_source = $right->getID();
$left_new = false;
$render_cache_key = $right->getID();
} else if ($vs == -1) {
$right = null;
$left = $changeset;
$right_source = $left->getID();
$right_new = false;
$left_source = $left->getID();
$left_new = true;
$render_cache_key = null;
} else {
$right = $changeset;
$left = $vs_changeset;
$right_source = $right->getID();
$right_new = true;
$left_source = $left->getID();
$left_new = true;
$render_cache_key = null;
}
if ($left) {
$left_data = $left->makeNewFile();
if ($right) {
$right_data = $right->makeNewFile();
} else {
$right_data = $left->makeOldFile();
}
$engine = new PhabricatorDifferenceEngine();
$synthetic = $engine->generateChangesetFromFileContent(
$left_data,
$right_data);
$choice = clone nonempty($left, $right);
$choice->attachHunks($synthetic->getHunks());
$changeset = $choice;
}
$coverage = null;
if ($right && $right->getDiffID()) {
$unit = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$right->getDiffID(),
'arc:unit');
if ($unit) {
$coverage = array();
foreach ($unit->getData() as $result) {
$result_coverage = idx($result, 'coverage');
if (!$result_coverage) {
continue;
}
$file_coverage = idx($result_coverage, $right->getFileName());
if (!$file_coverage) {
continue;
}
$coverage[] = $file_coverage;
}
$coverage = ArcanistUnitTestResult::mergeCoverage($coverage);
}
}
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$parser = new DifferentialChangesetParser();
$parser->setCoverage($coverage);
$parser->setChangeset($changeset);
$parser->setRenderingReference($rendering_reference);
$parser->setRenderCacheKey($render_cache_key);
$parser->setRightSideCommentMapping($right_source, $right_new);
$parser->setLeftSideCommentMapping($left_source, $left_new);
- $parser->setWhitespaceMode($request->getStr('whitespace'));
- $parser->setCharacterEncoding($request->getStr('encoding'));
- $parser->setHighlightAs($request->getStr('highlight'));
- if ($request->getStr('renderer') == '1up') {
- $parser->setRenderer(new DifferentialChangesetOneUpRenderer());
- }
+ $parser->readParametersFromRequest($request);
if ($left && $right) {
$parser->setOriginals($left, $right);
}
// Load both left-side and right-side inline comments.
$inlines = $this->loadInlineComments(
array($left_source, $right_source),
$author_phid);
if ($left_new) {
$inlines = array_merge(
$inlines,
$this->buildLintInlineComments($left));
}
if ($right_new) {
$inlines = array_merge(
$inlines,
$this->buildLintInlineComments($right));
}
$phids = array();
foreach ($inlines as $inline) {
$parser->parseInlineComment($inline);
if ($inline->getAuthorPHID()) {
$phids[$inline->getAuthorPHID()] = true;
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$parser->setHandles($handles);
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($request->getUser());
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$parser->setMarkupEngine($engine);
$parser->setUser($request->getUser());
if ($request->isAjax()) {
$parser->setShowEditAndReplyLinks(true);
} else {
$parser->setShowEditAndReplyLinks(false);
}
$output = $parser->render($range_s, $range_e, $mask);
$mcov = $parser->renderModifiedCoverage();
if ($request->isAjax()) {
$coverage = array(
'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov,
);
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($output)
->setCoverage($coverage);
}
// TODO: [HTML] Clean up DifferentialChangesetParser output, but it's
// undergoing like six kinds of refactoring anyway.
$output = phutil_safe_html($output);
$detail = id(new DifferentialChangesetDetailView())
->setUser($this->getViewer())
->setChangeset($changeset)
+ ->setRenderingRef($rendering_reference)
+ ->setRenderURI('/differential/changeset/')
+ ->setRenderer($parser->getRenderer()->getRendererKey())
->appendChild($output)
->setVsChangesetID($left_source);
Javelin::initBehavior('differential-populate', array(
'changesetViewIDs' => array($detail->getID()),
));
- Javelin::initBehavior('differential-show-more', array(
- 'uri' => '/differential/changeset/',
- 'whitespace' => $request->getStr('whitespace'),
- ));
-
+ Javelin::initBehavior('differential-show-more');
Javelin::initBehavior('differential-comment-jump', array());
$panel = new DifferentialPrimaryPaneView();
$panel->appendChild(
phutil_tag(
'div',
array(
'class' => 'differential-review-stage',
'id' => 'differential-review-stage',
),
$detail->render()));
$crumbs = $this->buildApplicationCrumbs();
$revision_id = $changeset->getDiff()->getRevisionID();
if ($revision_id) {
$crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id);
}
$diff_id = $changeset->getDiff()->getID();
if ($diff_id) {
$crumbs->addTextCrumb(
pht('Diff %d', $diff_id),
$this->getApplicationURI('diff/'.$diff_id));
}
$crumbs->addTextCrumb($changeset->getDisplayFilename());
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Standalone View'))
->appendChild($panel);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => pht('Changeset View'),
'device' => false,
));
}
private function loadInlineComments(array $changeset_ids, $author_phid) {
$changeset_ids = array_unique(array_filter($changeset_ids));
if (!$changeset_ids) {
return;
}
return id(new DifferentialInlineCommentQuery())
->withViewerAndChangesetIDs($author_phid, $changeset_ids)
->execute();
}
private function buildRawFileResponse(
DifferentialChangeset $changeset,
$is_new) {
$viewer = $this->getRequest()->getUser();
if ($is_new) {
$key = 'raw:new:phid';
} else {
$key = 'raw:old:phid';
}
$metadata = $changeset->getMetadata();
$file = null;
$phid = idx($metadata, $key);
if ($phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->execute();
if ($file) {
$file = head($file);
}
}
if (!$file) {
// This is just building a cache of the changeset content in the file
// tool, and is safe to run on a read pathway.
$unguard = AphrontWriteGuard::beginScopedUnguardedWrites();
if ($is_new) {
$data = $changeset->makeNewFile();
} else {
$data = $changeset->makeOldFile();
}
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => $changeset->getFilename(),
'mime-type' => 'text/plain',
));
$metadata[$key] = $file->getPHID();
$changeset->setMetadata($metadata);
$changeset->save();
unset($unguard);
}
return $file->getRedirectResponse();
}
private function buildLintInlineComments($changeset) {
$lint = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$changeset->getDiffID(),
'arc:lint');
if (!$lint) {
return array();
}
$lint = $lint->getData();
$inlines = array();
foreach ($lint as $msg) {
if ($msg['path'] != $changeset->getFilename()) {
continue;
}
$inline = new DifferentialInlineComment();
$inline->setChangesetID($changeset->getID());
$inline->setIsNewFile(1);
$inline->setSyntheticAuthor('Lint: '.$msg['name']);
$inline->setLineNumber($msg['line']);
$inline->setLineLength(0);
$inline->setContent('%%%'.$msg['description'].'%%%');
$inlines[] = $inline;
}
return $inlines;
}
}
diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php
index 7d979beddc..cd689153f0 100644
--- a/src/applications/differential/parser/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/DifferentialChangesetParser.php
@@ -1,1308 +1,1346 @@
<?php
final class DifferentialChangesetParser {
protected $visible = array();
protected $new = array();
protected $old = array();
protected $intra = array();
protected $newRender = null;
protected $oldRender = null;
protected $filename = null;
protected $hunkStartLines = array();
protected $comments = array();
protected $specialAttributes = array();
protected $changeset;
protected $whitespaceMode = null;
protected $renderCacheKey = null;
private $handles = array();
private $user;
private $leftSideChangesetID;
private $leftSideAttachesToNewFile;
private $rightSideChangesetID;
private $rightSideAttachesToNewFile;
private $originalLeft;
private $originalRight;
private $renderingReference;
private $isSubparser;
private $isTopLevel;
private $coverage;
private $markupEngine;
private $highlightErrors;
private $disableCache;
private $renderer;
private $characterEncoding;
private $highlightAs;
private $showEditAndReplyLinks = true;
public function setShowEditAndReplyLinks($bool) {
$this->showEditAndReplyLinks = $bool;
return $this;
}
public function getShowEditAndReplyLinks() {
return $this->showEditAndReplyLinks;
}
public function setHighlightAs($highlight_as) {
$this->highlightAs = $highlight_as;
return $this;
}
public function getHighlightAs() {
return $this->highlightAs;
}
public function setCharacterEncoding($character_encoding) {
$this->characterEncoding = $character_encoding;
return $this;
}
public function getCharacterEncoding() {
return $this->characterEncoding;
}
public function setRenderer(DifferentialChangesetRenderer $renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
if (!$this->renderer) {
return new DifferentialChangesetTwoUpRenderer();
}
return $this->renderer;
}
public function setDisableCache($disable_cache) {
$this->disableCache = $disable_cache;
return $this;
}
public function getDisableCache() {
return $this->disableCache;
}
+ public static function getDefaultRendererForViewer(PhabricatorUser $viewer) {
+ $prefs = $viewer->loadPreferences();
+ $pref_unified = PhabricatorUserPreferences::PREFERENCE_DIFF_UNIFIED;
+ if ($prefs->getPreference($pref_unified) == 'unified') {
+ return '1up';
+ }
+ return null;
+ }
+
+ public function readParametersFromRequest(AphrontRequest $request) {
+ $this->setWhitespaceMode($request->getStr('whitespace'));
+ $this->setCharacterEncoding($request->getStr('encoding'));
+ $this->setHighlightAs($request->getStr('highlight'));
+
+ $renderer = null;
+
+ // If the viewer prefers unified diffs, always set the renderer to unified.
+ // Otherwise, we leave it unspecified and the client will choose a
+ // renderer based on the screen size.
+
+ if ($request->getStr('renderer')) {
+ $renderer = $request->getStr('renderer');
+ } else {
+ $renderer = self::getDefaultRendererForViewer($request->getViewer());
+ }
+
+ switch ($renderer) {
+ case '1up':
+ $this->setRenderer(new DifferentialChangesetOneUpRenderer());
+ break;
+ default:
+ $this->setRenderer(new DifferentialChangesetTwoUpRenderer());
+ break;
+ }
+
+ return $this;
+ }
+
const CACHE_VERSION = 11;
const CACHE_MAX_SIZE = 8e6;
const ATTR_GENERATED = 'attr:generated';
const ATTR_DELETED = 'attr:deleted';
const ATTR_UNCHANGED = 'attr:unchanged';
const ATTR_WHITELINES = 'attr:white';
const ATTR_MOVEAWAY = 'attr:moveaway';
const LINES_CONTEXT = 8;
const WHITESPACE_SHOW_ALL = 'show-all';
const WHITESPACE_IGNORE_TRAILING = 'ignore-trailing';
const WHITESPACE_IGNORE_MOST = 'ignore-most';
const WHITESPACE_IGNORE_ALL = 'ignore-all';
public function setOldLines(array $lines) {
$this->old = $lines;
return $this;
}
public function setNewLines(array $lines) {
$this->new = $lines;
return $this;
}
public function setSpecialAttributes(array $attributes) {
$this->specialAttributes = $attributes;
return $this;
}
public function setIntraLineDiffs(array $diffs) {
$this->intra = $diffs;
return $this;
}
public function setVisibileLinesMask(array $mask) {
$this->visible = $mask;
return $this;
}
/**
* Configure which Changeset comments added to the right side of the visible
* diff will be attached to. The ID must be the ID of a real Differential
* Changeset.
*
* The complexity here is that we may show an arbitrary side of an arbitrary
* changeset as either the left or right part of a diff. This method allows
* the left and right halves of the displayed diff to be correctly mapped to
* storage changesets.
*
* @param id The Differential Changeset ID that comments added to the right
* side of the visible diff should be attached to.
* @param bool If true, attach new comments to the right side of the storage
* changeset. Note that this may be false, if the left side of
* some storage changeset is being shown as the right side of
* a display diff.
* @return this
*/
public function setRightSideCommentMapping($id, $is_new) {
$this->rightSideChangesetID = $id;
$this->rightSideAttachesToNewFile = $is_new;
return $this;
}
/**
* See setRightSideCommentMapping(), but this sets information for the left
* side of the display diff.
*/
public function setLeftSideCommentMapping($id, $is_new) {
$this->leftSideChangesetID = $id;
$this->leftSideAttachesToNewFile = $is_new;
return $this;
}
public function setOriginals(
DifferentialChangeset $left,
DifferentialChangeset $right) {
$this->originalLeft = $left;
$this->originalRight = $right;
}
public function diffOriginals() {
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent(
implode('', mpull($this->originalLeft->getHunks(), 'getChanges')),
implode('', mpull($this->originalRight->getHunks(), 'getChanges')));
$parser = new DifferentialHunkParser();
return $parser->parseHunksForHighlightMasks(
$changeset->getHunks(),
$this->originalLeft->getHunks(),
$this->originalRight->getHunks());
}
/**
* Set a key for identifying this changeset in the render cache. If set, the
* parser will attempt to use the changeset render cache, which can improve
* performance for frequently-viewed changesets.
*
* By default, there is no render cache key and parsers do not use the cache.
* This is appropriate for rarely-viewed changesets.
*
* NOTE: Currently, this key must be a valid Differential Changeset ID.
*
* @param string Key for identifying this changeset in the render cache.
* @return this
*/
public function setRenderCacheKey($key) {
$this->renderCacheKey = $key;
return $this;
}
private function getRenderCacheKey() {
return $this->renderCacheKey;
}
public function setChangeset(DifferentialChangeset $changeset) {
$this->changeset = $changeset;
$this->setFilename($changeset->getFilename());
return $this;
}
public function setWhitespaceMode($whitespace_mode) {
$this->whitespaceMode = $whitespace_mode;
return $this;
}
public function setRenderingReference($ref) {
$this->renderingReference = $ref;
return $this;
}
private function getRenderingReference() {
return $this->renderingReference;
}
public function getChangeset() {
return $this->changeset;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
public function setCoverage($coverage) {
$this->coverage = $coverage;
return $this;
}
private function getCoverage() {
return $this->coverage;
}
public function parseInlineComment(
PhabricatorInlineCommentInterface $comment) {
// Parse only comments which are actually visible.
if ($this->isCommentVisibleOnRenderedDiff($comment)) {
$this->comments[] = $comment;
}
return $this;
}
private function loadCache() {
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$data = null;
$changeset = new DifferentialChangeset();
$conn_r = $changeset->establishConnection('r');
$data = queryfx_one(
$conn_r,
'SELECT * FROM %T WHERE id = %d',
$changeset->getTableName().'_parse_cache',
$render_cache_key);
if (!$data) {
return false;
}
if ($data['cache'][0] == '{') {
// This is likely an old-style JSON cache which we will not be able to
// deserialize.
return false;
}
$data = unserialize($data['cache']);
if (!is_array($data) || !$data) {
return false;
}
foreach (self::getCacheableProperties() as $cache_key) {
if (!array_key_exists($cache_key, $data)) {
// If we're missing a cache key, assume we're looking at an old cache
// and ignore it.
return false;
}
}
if ($data['cacheVersion'] !== self::CACHE_VERSION) {
return false;
}
// Someone displays contents of a partially cached shielded file.
if (!isset($data['newRender']) && (!$this->isTopLevel || $this->comments)) {
return false;
}
unset($data['cacheVersion'], $data['cacheHost']);
$cache_prop = array_select_keys($data, self::getCacheableProperties());
foreach ($cache_prop as $cache_key => $v) {
$this->$cache_key = $v;
}
return true;
}
protected static function getCacheableProperties() {
return array(
'visible',
'new',
'old',
'intra',
'newRender',
'oldRender',
'specialAttributes',
'hunkStartLines',
'cacheVersion',
'cacheHost',
);
}
public function saveCache() {
if ($this->highlightErrors) {
return false;
}
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$cache = array();
foreach (self::getCacheableProperties() as $cache_key) {
switch ($cache_key) {
case 'cacheVersion':
$cache[$cache_key] = self::CACHE_VERSION;
break;
case 'cacheHost':
$cache[$cache_key] = php_uname('n');
break;
default:
$cache[$cache_key] = $this->$cache_key;
break;
}
}
$cache = serialize($cache);
// We don't want to waste too much space by a single changeset.
if (strlen($cache) > self::CACHE_MAX_SIZE) {
return;
}
$changeset = new DifferentialChangeset();
$conn_w = $changeset->establishConnection('w');
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
queryfx(
$conn_w,
'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %B, %d)
ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
DifferentialChangeset::TABLE_CACHE,
$render_cache_key,
$cache,
time());
} catch (AphrontQueryException $ex) {
// Ignore these exceptions. A common cause is that the cache is
// larger than 'max_allowed_packet', in which case we're better off
// not writing it.
// TODO: It would be nice to tailor this more narrowly.
}
unset($unguarded);
}
private function markGenerated($new_corpus_block = '') {
$generated_guess = (strpos($new_corpus_block, '@'.'generated') !== false);
if (!$generated_guess) {
$generated_path_regexps = PhabricatorEnv::getEnvConfig(
'differential.generated-paths');
foreach ($generated_path_regexps as $regexp) {
if (preg_match($regexp, $this->changeset->getFilename())) {
$generated_guess = true;
break;
}
}
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED,
array(
'corpus' => $new_corpus_block,
'is_generated' => $generated_guess,
)
);
PhutilEventEngine::dispatchEvent($event);
$generated = $event->getValue('is_generated');
$this->specialAttributes[self::ATTR_GENERATED] = $generated;
}
public function isGenerated() {
return idx($this->specialAttributes, self::ATTR_GENERATED, false);
}
public function isDeleted() {
return idx($this->specialAttributes, self::ATTR_DELETED, false);
}
public function isUnchanged() {
return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
}
public function isWhitespaceOnly() {
return idx($this->specialAttributes, self::ATTR_WHITELINES, false);
}
public function isMoveAway() {
return idx($this->specialAttributes, self::ATTR_MOVEAWAY, false);
}
private function applyIntraline(&$render, $intra, $corpus) {
foreach ($render as $key => $text) {
if (isset($intra[$key])) {
$render[$key] = ArcanistDiffUtils::applyIntralineDiff(
$text,
$intra[$key]);
}
}
}
private function getHighlightFuture($corpus) {
$language = $this->highlightAs;
if (!$language) {
$language = $this->highlightEngine->getLanguageFromFilename(
$this->filename);
}
return $this->highlightEngine->getHighlightFuture(
$language,
$corpus);
}
protected function processHighlightedSource($data, $result) {
$result_lines = phutil_split_lines($result);
foreach ($data as $key => $info) {
if (!$info) {
unset($result_lines[$key]);
}
}
return $result_lines;
}
private function tryCacheStuff() {
$whitespace_mode = $this->whitespaceMode;
switch ($whitespace_mode) {
case self::WHITESPACE_SHOW_ALL:
case self::WHITESPACE_IGNORE_TRAILING:
case self::WHITESPACE_IGNORE_ALL:
break;
default:
$whitespace_mode = self::WHITESPACE_IGNORE_MOST;
break;
}
$skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_MOST);
if ($this->disableCache) {
$skip_cache = true;
}
if ($this->characterEncoding) {
$skip_cache = true;
}
if ($this->highlightAs) {
$skip_cache = true;
}
$this->whitespaceMode = $whitespace_mode;
$changeset = $this->changeset;
if ($changeset->getFileType() != DifferentialChangeType::FILE_TEXT &&
$changeset->getFileType() != DifferentialChangeType::FILE_SYMLINK) {
$this->markGenerated();
} else {
if ($skip_cache || !$this->loadCache()) {
$this->process();
if (!$skip_cache) {
$this->saveCache();
}
}
}
}
private function process() {
$whitespace_mode = $this->whitespaceMode;
$changeset = $this->changeset;
$ignore_all = (($whitespace_mode == self::WHITESPACE_IGNORE_MOST) ||
($whitespace_mode == self::WHITESPACE_IGNORE_ALL));
$force_ignore = ($whitespace_mode == self::WHITESPACE_IGNORE_ALL);
if (!$force_ignore) {
if ($ignore_all && $changeset->getWhitespaceMatters()) {
$ignore_all = false;
}
}
// The "ignore all whitespace" algorithm depends on rediffing the
// files, and we currently need complete representations of both
// files to do anything reasonable. If we only have parts of the files,
// don't use the "ignore all" algorithm.
if ($ignore_all) {
$hunks = $changeset->getHunks();
if (count($hunks) !== 1) {
$ignore_all = false;
} else {
$first_hunk = reset($hunks);
if ($first_hunk->getOldOffset() != 1 ||
$first_hunk->getNewOffset() != 1) {
$ignore_all = false;
}
}
}
if ($ignore_all) {
$old_file = $changeset->makeOldFile();
$new_file = $changeset->makeNewFile();
if ($old_file == $new_file) {
// If the old and new files are exactly identical, the synthetic
// diff below will give us nonsense and whitespace modes are
// irrelevant anyway. This occurs when you, e.g., copy a file onto
// itself in Subversion (see T271).
$ignore_all = false;
}
}
$hunk_parser = new DifferentialHunkParser();
$hunk_parser->setWhitespaceMode($whitespace_mode);
$hunk_parser->parseHunksForLineData($changeset->getHunks());
// Depending on the whitespace mode, we may need to compute a different
// set of changes than the set of changes in the hunk data (specificaly,
// we might want to consider changed lines which have only whitespace
// changes as unchanged).
if ($ignore_all) {
$engine = new PhabricatorDifferenceEngine();
$engine->setIgnoreWhitespace(true);
$no_whitespace_changeset = $engine->generateChangesetFromFileContent(
$old_file,
$new_file);
$type_parser = new DifferentialHunkParser();
$type_parser->parseHunksForLineData($no_whitespace_changeset->getHunks());
$hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap());
$hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap());
}
$hunk_parser->reparseHunksForSpecialAttributes();
$unchanged = false;
if (!$hunk_parser->getHasAnyChanges()) {
$filetype = $this->changeset->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
$unchanged = true;
}
}
$moveaway = false;
$changetype = $this->changeset->getChangeType();
if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) {
$moveaway = true;
}
$this->setSpecialAttributes(array(
self::ATTR_UNCHANGED => $unchanged,
self::ATTR_DELETED => $hunk_parser->getIsDeleted(),
self::ATTR_WHITELINES => !$hunk_parser->getHasTextChanges(),
self::ATTR_MOVEAWAY => $moveaway,
));
$hunk_parser->generateIntraLineDiffs();
$hunk_parser->generateVisibileLinesMask();
$this->setOldLines($hunk_parser->getOldLines());
$this->setNewLines($hunk_parser->getNewLines());
$this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs());
$this->setVisibileLinesMask($hunk_parser->getVisibleLinesMask());
$this->hunkStartLines = $hunk_parser->getHunkStartLines(
$changeset->getHunks());
$new_corpus = $hunk_parser->getNewCorpus();
$new_corpus_block = implode('', $new_corpus);
$this->markGenerated($new_corpus_block);
if ($this->isTopLevel &&
!$this->comments &&
($this->isGenerated() ||
$this->isUnchanged() ||
$this->isDeleted())) {
return;
}
$old_corpus = $hunk_parser->getOldCorpus();
$old_corpus_block = implode('', $old_corpus);
$old_future = $this->getHighlightFuture($old_corpus_block);
$new_future = $this->getHighlightFuture($new_corpus_block);
$futures = array(
'old' => $old_future,
'new' => $new_future,
);
$corpus_blocks = array(
'old' => $old_corpus_block,
'new' => $new_corpus_block,
);
$this->highlightErrors = false;
foreach (new FutureIterator($futures) as $key => $future) {
try {
try {
$highlighted = $future->resolve();
} catch (PhutilSyntaxHighlighterException $ex) {
$this->highlightErrors = true;
$highlighted = id(new PhutilDefaultSyntaxHighlighter())
->getHighlightFuture($corpus_blocks[$key])
->resolve();
}
switch ($key) {
case 'old':
$this->oldRender = $this->processHighlightedSource(
$this->old,
$highlighted);
break;
case 'new':
$this->newRender = $this->processHighlightedSource(
$this->new,
$highlighted);
break;
}
} catch (Exception $ex) {
phlog($ex);
throw $ex;
}
}
$this->applyIntraline(
$this->oldRender,
ipull($this->intra, 0),
$old_corpus);
$this->applyIntraline(
$this->newRender,
ipull($this->intra, 1),
$new_corpus);
}
private function shouldRenderPropertyChangeHeader($changeset) {
if (!$this->isTopLevel) {
// We render properties only at top level; otherwise we get multiple
// copies of them when a user clicks "Show More".
return false;
}
return true;
}
public function render(
$range_start = null,
$range_len = null,
$mask_force = array()) {
// "Top level" renders are initial requests for the whole file, versus
// requests for a specific range generated by clicking "show more". We
// generate property changes and "shield" UI elements only for toplevel
// requests.
$this->isTopLevel = (($range_start === null) && ($range_len === null));
$this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
$encoding = null;
if ($this->characterEncoding) {
// We are forcing this changeset to be interpreted with a specific
// character encoding, so force all the hunks into that encoding and
// propagate it to the renderer.
$encoding = $this->characterEncoding;
foreach ($this->changeset->getHunks() as $hunk) {
$hunk->forceEncoding($this->characterEncoding);
}
} else {
// We're just using the default, so tell the renderer what that is
// (by reading the encoding from the first hunk).
foreach ($this->changeset->getHunks() as $hunk) {
$encoding = $hunk->getDataEncoding();
break;
}
}
$this->tryCacheStuff();
$render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
$rows = max(
count($this->old),
count($this->new));
$renderer = $this->getRenderer()
->setUser($this->getUser())
->setChangeset($this->changeset)
->setRenderPropertyChangeHeader($render_pch)
->setIsTopLevel($this->isTopLevel)
->setOldRender($this->oldRender)
->setNewRender($this->newRender)
->setHunkStartLines($this->hunkStartLines)
->setOldChangesetID($this->leftSideChangesetID)
->setNewChangesetID($this->rightSideChangesetID)
->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)
->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)
->setCodeCoverage($this->getCoverage())
->setRenderingReference($this->getRenderingReference())
->setMarkupEngine($this->markupEngine)
->setHandles($this->handles)
->setOldLines($this->old)
->setNewLines($this->new)
->setOriginalCharacterEncoding($encoding)
->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks());
$shield = null;
if ($this->isTopLevel && !$this->comments) {
if ($this->isGenerated()) {
$shield = $renderer->renderShield(
pht(
'This file contains generated code, which does not normally '.
'need to be reviewed.'));
} else if ($this->isMoveAway()) {
// We put an empty shield on these files. Normally, they do not have
// any diff content anyway. However, if they come through `arc`, they
// may have content. We don't want to show it (it's not useful) and
// we bailed out of fully processing it earlier anyway.
// We could show a message like "this file was moved", but we show
// that as a change header anyway, so it would be redundant. Instead,
// just render an empty shield to skip rendering the diff body.
$shield = '';
} else if ($this->isUnchanged()) {
$type = 'text';
if (!$rows) {
// NOTE: Normally, diffs which don't change files do not include
// file content (for example, if you "chmod +x" a file and then
// run "git show", the file content is not available). Similarly,
// if you move a file from A to B without changing it, diffs normally
// do not show the file content. In some cases `arc` is able to
// synthetically generate content for these diffs, but for raw diffs
// we'll never have it so we need to be prepared to not render a link.
$type = 'none';
}
$shield = $renderer->renderShield(
pht('The contents of this file were not changed.'),
$type);
} else if ($this->isWhitespaceOnly()) {
$shield = $renderer->renderShield(
pht('This file was changed only by adding or removing whitespace.'),
'whitespace');
} else if ($this->isDeleted()) {
$shield = $renderer->renderShield(
pht('This file was completely deleted.'));
} else if ($this->changeset->getAffectedLineCount() > 2500) {
$lines = number_format($this->changeset->getAffectedLineCount());
$shield = $renderer->renderShield(
pht(
'This file has a very large number of changes (%s lines).',
$lines));
}
}
if ($shield !== null) {
return $renderer->renderChangesetTable($shield);
}
$old_comments = array();
$new_comments = array();
$old_mask = array();
$new_mask = array();
$feedback_mask = array();
if ($this->comments) {
foreach ($this->comments as $comment) {
$start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
$end = $comment->getLineNumber() +
$comment->getLineLength() +
self::LINES_CONTEXT;
$new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
for ($ii = $start; $ii <= $end; $ii++) {
if ($new_side) {
$new_mask[$ii] = true;
} else {
$old_mask[$ii] = true;
}
}
}
foreach ($this->old as $ii => $old) {
if (isset($old['line']) && isset($old_mask[$old['line']])) {
$feedback_mask[$ii] = true;
}
}
foreach ($this->new as $ii => $new) {
if (isset($new['line']) && isset($new_mask[$new['line']])) {
$feedback_mask[$ii] = true;
}
}
$this->comments = msort($this->comments, 'getID');
foreach ($this->comments as $comment) {
$final = $comment->getLineNumber() +
$comment->getLineLength();
$final = max(1, $final);
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$new_comments[$final][] = $comment;
} else {
$old_comments[$final][] = $comment;
}
}
}
$renderer
->setOldComments($old_comments)
->setNewComments($new_comments);
switch ($this->changeset->getFileType()) {
case DifferentialChangeType::FILE_IMAGE:
$old = null;
$new = null;
// TODO: Improve the architectural issue as discussed in D955
// https://secure.phabricator.com/D955
$reference = $this->getRenderingReference();
$parts = explode('/', $reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
$id = (int)$id;
$vs = (int)$vs;
if (!$vs) {
$metadata = $this->changeset->getMetadata();
$data = idx($metadata, 'attachment-data');
$old_phid = idx($metadata, 'old:binary-phid');
$new_phid = idx($metadata, 'new:binary-phid');
} else {
$vs_changeset = id(new DifferentialChangeset())->load($vs);
$old_phid = null;
$new_phid = null;
// TODO: This is spooky, see D6851
if ($vs_changeset) {
$vs_metadata = $vs_changeset->getMetadata();
$old_phid = idx($vs_metadata, 'new:binary-phid');
}
$changeset = id(new DifferentialChangeset())->load($id);
if ($changeset) {
$metadata = $changeset->getMetadata();
$new_phid = idx($metadata, 'new:binary-phid');
}
}
if ($old_phid || $new_phid) {
// grab the files, (micro) optimization for 1 query not 2
$file_phids = array();
if ($old_phid) {
$file_phids[] = $old_phid;
}
if ($new_phid) {
$file_phids[] = $new_phid;
}
$files = id(new PhabricatorFileQuery())
->setViewer($this->getUser())
->withPHIDs($file_phids)
->execute();
foreach ($files as $file) {
if (empty($file)) {
continue;
}
if ($file->getPHID() == $old_phid) {
$old = $file;
} else if ($file->getPHID() == $new_phid) {
$new = $file;
}
}
}
$renderer->attachOldFile($old);
$renderer->attachNewFile($new);
return $renderer->renderFileChange($old, $new, $id, $vs);
case DifferentialChangeType::FILE_DIRECTORY:
case DifferentialChangeType::FILE_BINARY:
$output = $renderer->renderChangesetTable(null);
return $output;
}
if ($this->originalLeft && $this->originalRight) {
list($highlight_old, $highlight_new) = $this->diffOriginals();
$highlight_old = array_flip($highlight_old);
$highlight_new = array_flip($highlight_new);
$renderer
->setHighlightOld($highlight_old)
->setHighlightNew($highlight_new);
}
$renderer
->setOriginalOld($this->originalLeft)
->setOriginalNew($this->originalRight);
if ($range_start === null) {
$range_start = 0;
}
if ($range_len === null) {
$range_len = $rows;
}
$range_len = min($range_len, $rows - $range_start);
list($gaps, $mask, $depths) = $this->calculateGapsMaskAndDepths(
$mask_force,
$feedback_mask,
$range_start,
$range_len);
$renderer
->setGaps($gaps)
->setMask($mask)
->setDepths($depths);
$html = $renderer->renderTextChange(
$range_start,
$range_len,
$rows);
return $renderer->renderChangesetTable($html);
}
/**
* This function calculates a lot of stuff we need to know to display
* the diff:
*
* Gaps - compute gaps in the visible display diff, where we will render
* "Show more context" spacers. If a gap is smaller than the context size,
* we just display it. Otherwise, we record it into $gaps and will render a
* "show more context" element instead of diff text below. A given $gap
* is a tuple of $gap_line_number_start and $gap_length.
*
* Mask - compute the actual lines that need to be shown (because they
* are near changes lines, near inline comments, or the request has
* explicitly asked for them, i.e. resulting from the user clicking
* "show more"). The $mask returned is a sparesely populated dictionary
* of $visible_line_number => true.
*
* Depths - compute how indented any given line is. The $depths returned
* is a sparesely populated dictionary of $visible_line_number => $depth.
*
* This function also has the side effect of modifying member variable
* new such that tabs are normalized to spaces for each line of the diff.
*
* @return array($gaps, $mask, $depths)
*/
private function calculateGapsMaskAndDepths($mask_force,
$feedback_mask,
$range_start,
$range_len) {
// Calculate gaps and mask first
$gaps = array();
$gap_start = 0;
$in_gap = false;
$base_mask = $this->visible + $mask_force + $feedback_mask;
$base_mask[$range_start + $range_len] = true;
for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
if (isset($base_mask[$ii])) {
if ($in_gap) {
$gap_length = $ii - $gap_start;
if ($gap_length <= self::LINES_CONTEXT) {
for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
$base_mask[$jj] = true;
}
} else {
$gaps[] = array($gap_start, $gap_length);
}
$in_gap = false;
}
} else {
if (!$in_gap) {
$gap_start = $ii;
$in_gap = true;
}
}
}
$gaps = array_reverse($gaps);
$mask = $base_mask;
// Time to calculate depth.
// We need to go backwards to properly indent whitespace in this code:
//
// 0: class C {
// 1:
// 1: function f() {
// 2:
// 2: return;
// 1:
// 1: }
// 0:
// 0: }
//
$depths = array();
$last_depth = 0;
$range_end = $range_start + $range_len;
if (!isset($this->new[$range_end])) {
$range_end--;
}
for ($ii = $range_end; $ii >= $range_start; $ii--) {
// We need to expand tabs to process mixed indenting and to round
// correctly later.
$line = str_replace("\t", ' ', $this->new[$ii]['text']);
$trimmed = ltrim($line);
if ($trimmed != '') {
// We round down to flatten "/**" and " *".
$last_depth = floor((strlen($line) - strlen($trimmed)) / 2);
}
$depths[$ii] = $last_depth;
}
return array($gaps, $mask, $depths);
}
/**
* Determine if an inline comment will appear on the rendered diff,
* taking into consideration which halves of which changesets will actually
* be shown.
*
* @param PhabricatorInlineCommentInterface Comment to test for visibility.
* @return bool True if the comment is visible on the rendered diff.
*/
private function isCommentVisibleOnRenderedDiff(
PhabricatorInlineCommentInterface $comment) {
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
if ($changeset_id == $this->leftSideChangesetID &&
$is_new == $this->leftSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Determine if a comment will appear on the right side of the display diff.
* Note that the comment must appear somewhere on the rendered changeset, as
* per isCommentVisibleOnRenderedDiff().
*
* @param PhabricatorInlineCommentInterface Comment to test for display
* location.
* @return bool True for right, false for left.
*/
private function isCommentOnRightSideWhenDisplayed(
PhabricatorInlineCommentInterface $comment) {
if (!$this->isCommentVisibleOnRenderedDiff($comment)) {
throw new Exception('Comment is not visible on changeset!');
}
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Parse the 'range' specification that this class and the client-side JS
* emit to indicate that a user clicked "Show more..." on a diff. Generally,
* use is something like this:
*
* $spec = $request->getStr('range');
* $parsed = DifferentialChangesetParser::parseRangeSpecification($spec);
* list($start, $end, $mask) = $parsed;
* $parser->render($start, $end, $mask);
*
* @param string Range specification, indicating the range of the diff that
* should be rendered.
* @return tuple List of <start, end, mask> suitable for passing to
* @{method:render}.
*/
public static function parseRangeSpecification($spec) {
$range_s = null;
$range_e = null;
$mask = array();
if ($spec) {
$match = null;
if (preg_match('@^(\d+)-(\d+)(?:/(\d+)-(\d+))?$@', $spec, $match)) {
$range_s = (int)$match[1];
$range_e = (int)$match[2];
if (count($match) > 3) {
$start = (int)$match[3];
$len = (int)$match[4];
for ($ii = $start; $ii < $start + $len; $ii++) {
$mask[$ii] = true;
}
}
}
}
return array($range_s, $range_e, $mask);
}
/**
* Render "modified coverage" information; test coverage on modified lines.
* This synthesizes diff information with unit test information into a useful
* indicator of how well tested a change is.
*/
public function renderModifiedCoverage() {
$na = phutil_tag('em', array(), '-');
$coverage = $this->getCoverage();
if (!$coverage) {
return $na;
}
$covered = 0;
$not_covered = 0;
foreach ($this->new as $k => $new) {
if (!$new['line']) {
continue;
}
if (!$new['type']) {
continue;
}
if (empty($coverage[$new['line'] - 1])) {
continue;
}
switch ($coverage[$new['line'] - 1]) {
case 'C':
$covered++;
break;
case 'U':
$not_covered++;
break;
}
}
if (!$covered && !$not_covered) {
return $na;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
public function detectCopiedCode(
array $changesets,
$min_width = 30,
$min_lines = 3) {
assert_instances_of($changesets, 'DifferentialChangeset');
$map = array();
$files = array();
$types = array();
foreach ($changesets as $changeset) {
$file = $changeset->getFilename();
foreach ($changeset->getHunks() as $hunk) {
$line = $hunk->getOldOffset();
foreach (explode("\n", $hunk->getChanges()) as $code) {
$type = (isset($code[0]) ? $code[0] : '');
if ($type == '-' || $type == ' ') {
$code = trim(substr($code, 1));
$files[$file][$line] = $code;
$types[$file][$line] = $type;
if (strlen($code) >= $min_width) {
$map[$code][] = array($file, $line);
}
$line++;
}
}
}
}
foreach ($changesets as $changeset) {
$copies = array();
foreach ($changeset->getHunks() as $hunk) {
$added = array_map('trim', $hunk->getAddedLines());
for (reset($added); list($line, $code) = each($added); ) {
if (isset($map[$code])) { // We found a long matching line.
if (count($map[$code]) > 16) {
// If there are a large number of identical lines in this diff,
// don't try to figure out where this block came from: the
// analysis is O(N^2), since we need to compare every line
// against every other line. Even if we arrive at a result, it
// is unlikely to be meaningful. See T5041.
continue 2;
}
$best_length = 0;
foreach ($map[$code] as $val) { // Explore all candidates.
list($file, $orig_line) = $val;
$length = 1;
// Search also backwards for short lines.
foreach (array(-1, 1) as $direction) {
$offset = $direction;
while (!isset($copies[$line + $offset]) &&
isset($added[$line + $offset]) &&
idx($files[$file], $orig_line + $offset) ===
$added[$line + $offset]) {
$length++;
$offset += $direction;
}
}
if ($length > $best_length ||
($length == $best_length && // Prefer moves.
idx($types[$file], $orig_line) == '-')) {
$best_length = $length;
// ($offset - 1) contains number of forward matching lines.
$best_offset = $offset - 1;
$best_file = $file;
$best_line = $orig_line;
}
}
$file = ($best_file == $changeset->getFilename() ? '' : $best_file);
for ($i = $best_length; $i--; ) {
$type = idx($types[$best_file], $best_line + $best_offset - $i);
$copies[$line + $best_offset - $i] = ($best_length < $min_lines
? array() // Ignore short blocks.
: array($file, $best_line + $best_offset - $i, $type));
}
for ($i = 0; $i < $best_offset; $i++) {
next($added);
}
}
}
}
$copies = array_filter($copies);
if ($copies) {
$metadata = $changeset->getMetadata();
$metadata['copy:lines'] = $copies;
$changeset->setMetadata($metadata);
}
}
return $changesets;
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
index b4ef3b640a..b03497560b 100644
--- a/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetHTMLRenderer.php
@@ -1,530 +1,529 @@
<?php
abstract class DifferentialChangesetHTMLRenderer
extends DifferentialChangesetRenderer {
abstract protected function getRendererTableClass();
protected function renderChangeTypeHeader($force) {
$changeset = $this->getChangeset();
$change = $changeset->getChangeType();
$file = $changeset->getFileType();
$messages = array();
switch ($change) {
case DifferentialChangeType::TYPE_ADD:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was added.');
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was added.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was added.');
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was added.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was added.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was added.');
break;
}
break;
case DifferentialChangeType::TYPE_DELETE:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was deleted.');
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was deleted.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was deleted.');
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was deleted.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was deleted.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was deleted.');
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_HERE:
$from = phutil_tag('strong', array(), $changeset->getOldFile());
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was moved from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was moved from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_HERE:
$from = phutil_tag('strong', array(), $changeset->getOldFile());
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was copied from %s.', $from);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was copied from %s.', $from);
break;
}
break;
case DifferentialChangeType::TYPE_MOVE_AWAY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was moved to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was moved to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_COPY_AWAY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht('This file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This image was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This directory was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This binary file was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This symlink was copied to %s.', $paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This submodule was copied to %s.', $paths);
break;
}
break;
case DifferentialChangeType::TYPE_MULTICOPY:
$paths = phutil_tag(
'strong',
array(),
implode(', ', $changeset->getAwayPaths()));
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
$messages[] = pht(
'This file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht(
'This image was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht(
'This directory was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht(
'This binary file was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht(
'This symlink was deleted after being copied to %s.',
$paths);
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht(
'This submodule was deleted after being copied to %s.',
$paths);
break;
}
break;
default:
switch ($file) {
case DifferentialChangeType::FILE_TEXT:
// This is the default case, so we only render this header if
// forced to since it's not very useful.
if ($force) {
$messages[] = pht('This file was not modified.');
}
break;
case DifferentialChangeType::FILE_IMAGE:
$messages[] = pht('This is an image.');
break;
case DifferentialChangeType::FILE_DIRECTORY:
$messages[] = pht('This is a directory.');
break;
case DifferentialChangeType::FILE_BINARY:
$messages[] = pht('This is a binary file.');
break;
case DifferentialChangeType::FILE_SYMLINK:
$messages[] = pht('This is a symlink.');
break;
case DifferentialChangeType::FILE_SUBMODULE:
$messages[] = pht('This is a submodule.');
break;
}
break;
}
// If this is a text file with at least one hunk, we may have converted
// the text encoding. In this case, show a note.
$show_encoding = ($file == DifferentialChangeType::FILE_TEXT) &&
($changeset->getHunks());
if ($show_encoding) {
$encoding = $this->getOriginalCharacterEncoding();
if ($encoding != 'utf8') {
if ($encoding) {
$messages[] = pht(
'This file was converted from %s for display.',
phutil_tag('strong', array(), $encoding));
} else {
$messages[] = pht(
'This file uses an unknown character encoding.');
}
}
}
if (!$messages) {
return null;
}
foreach ($messages as $key => $message) {
$messages[$key] = phutil_tag('li', array(), $message);
}
return phutil_tag(
'ul',
array(
'class' => 'differential-meta-notice',
),
$messages);
}
protected function renderPropertyChangeHeader() {
$changeset = $this->getChangeset();
list($old, $new) = $this->getChangesetProperties($changeset);
// If we don't have any property changes, don't render this table.
if ($old === $new) {
return null;
}
$keys = array_keys($old + $new);
sort($keys);
$key_map = array(
'unix:filemode' => pht('File Mode'),
'file:dimensions' => pht('Image Dimensions'),
'file:mimetype' => pht('MIME Type'),
'file:size' => pht('File Size'),
);
$rows = array();
foreach ($keys as $key) {
$oval = idx($old, $key);
$nval = idx($new, $key);
if ($oval !== $nval) {
if ($oval === null) {
$oval = phutil_tag('em', array(), 'null');
} else {
$oval = phutil_escape_html_newlines($oval);
}
if ($nval === null) {
$nval = phutil_tag('em', array(), 'null');
} else {
$nval = phutil_escape_html_newlines($nval);
}
$readable_key = idx($key_map, $key, $key);
$row = array(
$readable_key,
$oval,
$nval,
);
$rows[] = $row;
}
}
$classes = array('', 'oval', 'nval');
$headers = array(
pht('Property'),
pht('Old Value'),
pht('New Value'),
);
$table = id(new AphrontTableView($rows))
->setHeaders($headers)
->setColumnClasses($classes);
return phutil_tag(
'div',
array(
'class' => 'differential-property-table',
),
$table);
}
public function renderShield($message, $force = 'default') {
$end = count($this->getOldLines());
$reference = $this->getRenderingReference();
if ($force !== 'text' &&
$force !== 'whitespace' &&
$force !== 'none' &&
$force !== 'default') {
throw new Exception("Invalid 'force' parameter '{$force}'!");
}
$range = "0-{$end}";
if ($force == 'text') {
// If we're forcing text, force the whole file to be rendered.
$range = "{$range}/0-{$end}";
}
$meta = array(
'ref' => $reference,
'range' => $range,
);
if ($force == 'whitespace') {
$meta['whitespace'] = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
}
$content = array();
$content[] = $message;
if ($force !== 'none') {
$content[] = ' ';
$content[] = javelin_tag(
'a',
array(
'mustcapture' => true,
'sigil' => 'show-more',
'class' => 'complete',
'href' => '#',
'meta' => $meta,
),
pht('Show File Contents'));
}
return $this->wrapChangeInTable(
javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'class' => 'differential-shield',
'colspan' => 6,
),
$content)));
}
abstract protected function renderColgroup();
protected function wrapChangeInTable($content) {
if (!$content) {
return null;
}
$classes = array();
$classes[] = 'differential-diff';
$classes[] = 'remarkup-code';
$classes[] = 'PhabricatorMonospaced';
$classes[] = $this->getRendererTableClass();
return javelin_tag(
'table',
array(
'class' => implode(' ', $classes),
'sigil' => 'differential-diff',
),
array(
$this->renderColgroup(),
$content,
));
}
protected function renderInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
return $this->buildInlineComment($comment, $on_right)->render();
}
protected function buildInlineComment(
PhabricatorInlineCommentInterface $comment,
$on_right = false) {
$user = $this->getUser();
$edit = $user &&
($comment->getAuthorPHID() == $user->getPHID()) &&
($comment->isDraft())
&& $this->getShowEditAndReplyLinks();
$allow_reply = (bool)$user && $this->getShowEditAndReplyLinks();
return id(new DifferentialInlineCommentView())
->setInlineComment($comment)
->setOnRight($on_right)
->setHandles($this->getHandles())
->setMarkupEngine($this->getMarkupEngine())
->setEditable($edit)
->setAllowReply($allow_reply);
}
/**
* Build links which users can click to show more context in a changeset.
*
* @param int Beginning of the line range to build links for.
* @param int Length of the line range to build links for.
* @param int Total number of lines in the changeset.
* @return markup Rendered links.
*/
protected function renderShowContextLinks($top, $len, $changeset_length) {
$block_size = 20;
$end = ($top + $len) - $block_size;
// If this is a large block, such that the "top" and "bottom" ranges are
// non-overlapping, we'll provide options to show the top, bottom or entire
// block. For smaller blocks, we only provide an option to show the entire
// block, since it would be silly to show the bottom 20 lines of a 25-line
// block.
$is_large_block = ($len > ($block_size * 2));
$links = array();
if ($is_large_block) {
$is_first_block = ($top == 0);
if ($is_first_block) {
$text = pht('Show First %d Line(s)', $block_size);
} else {
$text = pht("\xE2\x96\xB2 Show %d Line(s)", $block_size);
}
$links[] = $this->renderShowContextLink(
false,
"{$top}-{$len}/{$top}-20",
$text);
}
$links[] = $this->renderShowContextLink(
true,
"{$top}-{$len}/{$top}-{$len}",
pht('Show All %d Line(s)', $len));
if ($is_large_block) {
$is_last_block = (($top + $len) >= $changeset_length);
if ($is_last_block) {
$text = pht('Show Last %d Line(s)', $block_size);
} else {
$text = pht("\xE2\x96\xBC Show %d Line(s)", $block_size);
}
$links[] = $this->renderShowContextLink(
false,
"{$top}-{$len}/{$end}-20",
$text);
}
return phutil_implode_html(" \xE2\x80\xA2 ", $links);
}
/**
* Build a link that shows more context in a changeset.
*
* See @{method:renderShowContextLinks}.
*
* @param bool Does this link show all context when clicked?
* @param string Range specification for lines to show.
* @param string Text of the link.
* @return markup Rendered link.
*/
private function renderShowContextLink($is_all, $range, $text) {
$reference = $this->getRenderingReference();
return javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => ($is_all ? 'all' : null),
- 'ref' => $reference,
'range' => $range,
),
),
$text);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
index e3d64f872c..cdefbc303b 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php
@@ -1,115 +1,119 @@
<?php
final class DifferentialChangesetOneUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return true;
}
protected function getRendererTableClass() {
return 'diff-1up';
}
+ public function getRendererKey() {
+ return '1up';
+ }
+
protected function renderColgroup() {
return phutil_tag('colgroup', array(), array(
phutil_tag('col', array('class' => 'num')),
phutil_tag('col', array('class' => 'num')),
phutil_tag('col', array('class' => 'unified')),
));
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$primitives = $this->buildPrimitives($range_start, $range_len);
$out = array();
foreach ($primitives as $p) {
$type = $p['type'];
switch ($type) {
case 'old':
case 'new':
$out[] = hsprintf('<tr>');
if ($type == 'old') {
if ($p['htype']) {
$class = 'left old';
} else {
$class = 'left';
}
$out[] = phutil_tag('th', array(), $p['line']);
$out[] = phutil_tag('th', array());
$out[] = phutil_tag('td', array('class' => $class), $p['render']);
} else if ($type == 'new') {
if ($p['htype']) {
$class = 'right new';
$out[] = phutil_tag('th', array());
} else {
$class = 'right';
$out[] = phutil_tag('th', array(), $p['oline']);
}
$out[] = phutil_tag('th', array(), $p['line']);
$out[] = phutil_tag('td', array('class' => $class), $p['render']);
}
$out[] = hsprintf('</tr>');
break;
case 'inline':
$out[] = hsprintf('<tr><th /><th />');
$out[] = hsprintf('<td>');
$inline = $this->buildInlineComment(
$p['comment'],
$p['right']);
$inline->setBuildScaffolding(false);
$out[] = $inline->render();
$out[] = hsprintf('</td></tr>');
break;
case 'no-context':
$out[] = hsprintf(
'<tr><td class="show-more" colspan="3">%s</td></tr>',
pht('Context not available.'));
break;
case 'context':
$top = $p['top'];
$len = $p['len'];
$links = $this->renderShowContextLinks($top, $len, $rows);
$out[] = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'class' => 'show-more',
'colspan' => 3,
),
$links));
break;
default:
$out[] = hsprintf('<tr><th /><th /><td>%s</td></tr>', $type);
break;
}
}
if ($out) {
return $this->wrapChangeInTable(phutil_implode_html('', $out));
}
return null;
}
public function renderFileChange(
$old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
throw new PhutilMethodNotImplementedException();
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetOneUpTestRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpTestRenderer.php
index b6ea30e51f..a9afb43af8 100644
--- a/src/applications/differential/render/DifferentialChangesetOneUpTestRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetOneUpTestRenderer.php
@@ -1,10 +1,14 @@
<?php
final class DifferentialChangesetOneUpTestRenderer
extends DifferentialChangesetTestRenderer {
public function isOneUpRenderer() {
return true;
}
+ public function getRendererKey() {
+ return '1up-test';
+ }
+
}
diff --git a/src/applications/differential/render/DifferentialChangesetRenderer.php b/src/applications/differential/render/DifferentialChangesetRenderer.php
index be440b47e7..09eb27ab47 100644
--- a/src/applications/differential/render/DifferentialChangesetRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetRenderer.php
@@ -1,584 +1,586 @@
<?php
abstract class DifferentialChangesetRenderer {
private $user;
private $changeset;
private $renderingReference;
private $renderPropertyChangeHeader;
private $isTopLevel;
private $hunkStartLines;
private $oldLines;
private $newLines;
private $oldComments;
private $newComments;
private $oldChangesetID;
private $newChangesetID;
private $oldAttachesToNewFile;
private $newAttachesToNewFile;
private $highlightOld = array();
private $highlightNew = array();
private $codeCoverage;
private $handles;
private $markupEngine;
private $oldRender;
private $newRender;
private $originalOld;
private $originalNew;
private $gaps;
private $mask;
private $depths;
private $originalCharacterEncoding;
private $showEditAndReplyLinks;
private $oldFile = false;
private $newFile = false;
+ abstract public function getRendererKey();
+
public function setShowEditAndReplyLinks($bool) {
$this->showEditAndReplyLinks = $bool;
return $this;
}
public function getShowEditAndReplyLinks() {
return $this->showEditAndReplyLinks;
}
public function setOriginalCharacterEncoding($original_character_encoding) {
$this->originalCharacterEncoding = $original_character_encoding;
return $this;
}
public function getOriginalCharacterEncoding() {
return $this->originalCharacterEncoding;
}
public function setDepths($depths) {
$this->depths = $depths;
return $this;
}
protected function getDepths() {
return $this->depths;
}
public function setMask($mask) {
$this->mask = $mask;
return $this;
}
protected function getMask() {
return $this->mask;
}
public function setGaps($gaps) {
$this->gaps = $gaps;
return $this;
}
protected function getGaps() {
return $this->gaps;
}
public function attachOldFile(PhabricatorFile $old = null) {
$this->oldFile = $old;
return $this;
}
public function getOldFile() {
if ($this->oldFile === false) {
throw new PhabricatorDataNotAttachedException($this);
}
return $this->oldFile;
}
public function hasOldFile() {
return (bool)$this->oldFile;
}
public function attachNewFile(PhabricatorFile $new = null) {
$this->newFile = $new;
return $this;
}
public function getNewFile() {
if ($this->newFile === false) {
throw new PhabricatorDataNotAttachedException($this);
}
return $this->newFile;
}
public function hasNewFile() {
return (bool)$this->newFile;
}
public function setOriginalNew($original_new) {
$this->originalNew = $original_new;
return $this;
}
protected function getOriginalNew() {
return $this->originalNew;
}
public function setOriginalOld($original_old) {
$this->originalOld = $original_old;
return $this;
}
protected function getOriginalOld() {
return $this->originalOld;
}
public function setNewRender($new_render) {
$this->newRender = $new_render;
return $this;
}
protected function getNewRender() {
return $this->newRender;
}
public function setOldRender($old_render) {
$this->oldRender = $old_render;
return $this;
}
protected function getOldRender() {
return $this->oldRender;
}
public function setMarkupEngine(PhabricatorMarkupEngine $markup_engine) {
$this->markupEngine = $markup_engine;
return $this;
}
public function getMarkupEngine() {
return $this->markupEngine;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
protected function getHandles() {
return $this->handles;
}
public function setCodeCoverage($code_coverage) {
$this->codeCoverage = $code_coverage;
return $this;
}
protected function getCodeCoverage() {
return $this->codeCoverage;
}
public function setHighlightNew($highlight_new) {
$this->highlightNew = $highlight_new;
return $this;
}
protected function getHighlightNew() {
return $this->highlightNew;
}
public function setHighlightOld($highlight_old) {
$this->highlightOld = $highlight_old;
return $this;
}
protected function getHighlightOld() {
return $this->highlightOld;
}
public function setNewAttachesToNewFile($attaches) {
$this->newAttachesToNewFile = $attaches;
return $this;
}
protected function getNewAttachesToNewFile() {
return $this->newAttachesToNewFile;
}
public function setOldAttachesToNewFile($attaches) {
$this->oldAttachesToNewFile = $attaches;
return $this;
}
protected function getOldAttachesToNewFile() {
return $this->oldAttachesToNewFile;
}
public function setNewChangesetID($new_changeset_id) {
$this->newChangesetID = $new_changeset_id;
return $this;
}
protected function getNewChangesetID() {
return $this->newChangesetID;
}
public function setOldChangesetID($old_changeset_id) {
$this->oldChangesetID = $old_changeset_id;
return $this;
}
protected function getOldChangesetID() {
return $this->oldChangesetID;
}
public function setNewComments(array $new_comments) {
foreach ($new_comments as $line_number => $comments) {
assert_instances_of($comments, 'PhabricatorInlineCommentInterface');
}
$this->newComments = $new_comments;
return $this;
}
protected function getNewComments() {
return $this->newComments;
}
public function setOldComments(array $old_comments) {
foreach ($old_comments as $line_number => $comments) {
assert_instances_of($comments, 'PhabricatorInlineCommentInterface');
}
$this->oldComments = $old_comments;
return $this;
}
protected function getOldComments() {
return $this->oldComments;
}
public function setNewLines(array $new_lines) {
$this->newLines = $new_lines;
return $this;
}
protected function getNewLines() {
return $this->newLines;
}
public function setOldLines(array $old_lines) {
$this->oldLines = $old_lines;
return $this;
}
protected function getOldLines() {
return $this->oldLines;
}
public function setHunkStartLines(array $hunk_start_lines) {
$this->hunkStartLines = $hunk_start_lines;
return $this;
}
protected function getHunkStartLines() {
return $this->hunkStartLines;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
protected function getUser() {
return $this->user;
}
public function setChangeset(DifferentialChangeset $changeset) {
$this->changeset = $changeset;
return $this;
}
protected function getChangeset() {
return $this->changeset;
}
public function setRenderingReference($rendering_reference) {
$this->renderingReference = $rendering_reference;
return $this;
}
protected function getRenderingReference() {
return $this->renderingReference;
}
public function setRenderPropertyChangeHeader($should_render) {
$this->renderPropertyChangeHeader = $should_render;
return $this;
}
private function shouldRenderPropertyChangeHeader() {
return $this->renderPropertyChangeHeader;
}
public function setIsTopLevel($is) {
$this->isTopLevel = $is;
return $this;
}
private function getIsTopLevel() {
return $this->isTopLevel;
}
final public function renderChangesetTable($content) {
$props = null;
if ($this->shouldRenderPropertyChangeHeader()) {
$props = $this->renderPropertyChangeHeader();
}
$notice = null;
if ($this->getIsTopLevel()) {
$force = (!$content && !$props);
$notice = $this->renderChangeTypeHeader($force);
}
$result = $notice.$props.$content;
// TODO: Let the user customize their tab width / display style.
// TODO: We should possibly post-process "\r" as well.
// TODO: Both these steps should happen earlier.
$result = str_replace("\t", ' ', $result);
return phutil_safe_html($result);
}
abstract public function isOneUpRenderer();
abstract public function renderTextChange(
$range_start,
$range_len,
$rows);
abstract public function renderFileChange(
$old = null,
$new = null,
$id = 0,
$vs = 0);
abstract protected function renderChangeTypeHeader($force);
protected function didRenderChangesetTableContents($contents) {
return $contents;
}
/**
* Render a "shield" over the diff, with a message like "This file is
* generated and does not need to be reviewed." or "This file was completely
* deleted." This UI element hides unimportant text so the reviewer doesn't
* need to scroll past it.
*
* The shield includes a link to view the underlying content. This link
* may force certain rendering modes when the link is clicked:
*
* - `"default"`: Render the diff normally, as though it was not
* shielded. This is the default and appropriate if the underlying
* diff is a normal change, but was hidden for reasons of not being
* important (e.g., generated code).
* - `"text"`: Force the text to be shown. This is probably only relevant
* when a file is not changed.
* - `"whitespace"`: Force the text to be shown, and the diff to be
* rendered with all whitespace shown. This is probably only relevant
* when a file is changed only by altering whitespace.
* - `"none"`: Don't show the link (e.g., text not available).
*
* @param string Message explaining why the diff is hidden.
* @param string|null Force mode, see above.
* @return string Shield markup.
*/
abstract public function renderShield($message, $force = 'default');
abstract protected function renderPropertyChangeHeader();
protected function buildPrimitives($range_start, $range_len) {
$primitives = array();
$hunk_starts = $this->getHunkStartLines();
$mask = $this->getMask();
$gaps = $this->getGaps();
$old = $this->getOldLines();
$new = $this->getNewLines();
$old_render = $this->getOldRender();
$new_render = $this->getNewRender();
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
$size = count($old);
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
list($top, $len) = array_pop($gaps);
$primitives[] = array(
'type' => 'context',
'top' => $top,
'len' => $len,
);
$ii += ($len - 1);
continue;
}
$ospec = array(
'type' => 'old',
'htype' => null,
'cursor' => $ii,
'line' => null,
'oline' => null,
'render' => null,
);
$nspec = array(
'type' => 'new',
'htype' => null,
'cursor' => $ii,
'line' => null,
'oline' => null,
'render' => null,
'copy' => null,
'coverage' => null,
);
if (isset($old[$ii])) {
$ospec['line'] = (int)$old[$ii]['line'];
$nspec['oline'] = (int)$old[$ii]['line'];
$ospec['htype'] = $old[$ii]['type'];
if (isset($old_render[$ii])) {
$ospec['render'] = $old_render[$ii];
}
}
if (isset($new[$ii])) {
$nspec['line'] = (int)$new[$ii]['line'];
$ospec['oline'] = (int)$new[$ii]['line'];
$nspec['htype'] = $new[$ii]['type'];
if (isset($new_render[$ii])) {
$nspec['render'] = $new_render[$ii];
}
}
if (isset($hunk_starts[$ospec['line']])) {
$primitives[] = array(
'type' => 'no-context',
);
}
$primitives[] = $ospec;
$primitives[] = $nspec;
if ($ospec['line'] !== null && isset($old_comments[$ospec['line']])) {
foreach ($old_comments[$ospec['line']] as $comment) {
$primitives[] = array(
'type' => 'inline',
'comment' => $comment,
'right' => false,
);
}
}
if ($nspec['line'] !== null && isset($new_comments[$nspec['line']])) {
foreach ($new_comments[$nspec['line']] as $comment) {
$primitives[] = array(
'type' => 'inline',
'comment' => $comment,
'right' => true,
);
}
}
if ($hunk_starts && ($ii == $size - 1)) {
$primitives[] = array(
'type' => 'no-context',
);
}
}
if ($this->isOneUpRenderer()) {
$primitives = $this->processPrimitivesForOneUp($primitives);
}
return $primitives;
}
private function processPrimitivesForOneUp(array $primitives) {
// Primitives come out of buildPrimitives() in two-up format, because it
// is the most general, flexible format. To put them into one-up format,
// we need to filter and reorder them. In particular:
//
// - We discard unchanged lines in the old file; in one-up format, we
// render them only once.
// - We group contiguous blocks of old-modified and new-modified lines, so
// they render in "block of old, block of new" order instead of
// alternating old and new lines.
$out = array();
$old_buf = array();
$new_buf = array();
foreach ($primitives as $primitive) {
$type = $primitive['type'];
if ($type == 'old') {
if (!$primitive['htype']) {
// This is a line which appears in both the old file and the new
// file, or the spacer corresponding to a line added in the new file.
// Ignore it when rendering a one-up diff.
continue;
}
if ($new_buf) {
$out[] = $new_buf;
$new_buf = array();
}
$old_buf[] = $primitive;
} else if ($type == 'new') {
if ($primitive['line'] === null) {
// This is an empty spacer corresponding to a line removed from the
// old file. Ignore it when rendering a one-up diff.
continue;
}
if ($old_buf) {
$out[] = $old_buf;
$old_buf = array();
}
$new_buf[] = $primitive;
} else if ($type == 'context' || $type == 'no-context') {
$out[] = $old_buf;
$out[] = $new_buf;
$old_buf = array();
$new_buf = array();
$out[] = array($primitive);
} else if ($type == 'inline') {
$out[] = $old_buf;
$out[] = $new_buf;
$old_buf = array();
$new_buf = array();
$out[] = array($primitive);
} else {
throw new Exception("Unknown primitive type '{$primitive}'!");
}
}
$out[] = $old_buf;
$out[] = $new_buf;
$out = array_mergev($out);
return $out;
}
protected function getChangesetProperties($changeset) {
$old = $changeset->getOldProperties();
$new = $changeset->getNewProperties();
// When adding files, don't show the uninteresting 644 filemode change.
if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD &&
$new == array('unix:filemode' => '100644')) {
unset($new['unix:filemode']);
}
// Likewise when removing files.
if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE &&
$old == array('unix:filemode' => '100644')) {
unset($old['unix:filemode']);
}
if ($this->hasOldFile()) {
$file = $this->getOldFile();
if ($file->getImageWidth()) {
$dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
$old['file:dimensions'] = $dimensions;
}
$old['file:mimetype'] = $file->getMimeType();
$old['file:size'] = phutil_format_bytes($file->getByteSize());
}
if ($this->hasNewFile()) {
$file = $this->getNewFile();
if ($file->getImageWidth()) {
$dimensions = $file->getImageWidth().'x'.$file->getImageHeight();
$new['file:dimensions'] = $dimensions;
}
$new['file:mimetype'] = $file->getMimeType();
$new['file:size'] = phutil_format_bytes($file->getByteSize());
}
return array($old, $new);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
index 1129330045..682bcd51e4 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php
@@ -1,470 +1,474 @@
<?php
final class DifferentialChangesetTwoUpRenderer
extends DifferentialChangesetHTMLRenderer {
public function isOneUpRenderer() {
return false;
}
protected function getRendererTableClass() {
return 'diff-2up';
}
+ public function getRendererKey() {
+ return '2up';
+ }
+
protected function renderColgroup() {
return phutil_tag('colgroup', array(), array(
phutil_tag('col', array('class' => 'num')),
phutil_tag('col', array('class' => 'left')),
phutil_tag('col', array('class' => 'num')),
phutil_tag('col', array('class' => 'copy')),
phutil_tag('col', array('class' => 'right')),
phutil_tag('col', array('class' => 'cov')),
));
}
public function renderTextChange(
$range_start,
$range_len,
$rows) {
$hunk_starts = $this->getHunkStartLines();
$context_not_available = null;
if ($hunk_starts) {
$context_not_available = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
phutil_tag(
'td',
array(
'colspan' => 6,
'class' => 'show-more',
),
pht('Context not available.')));
}
$html = array();
$old_lines = $this->getOldLines();
$new_lines = $this->getNewLines();
$gaps = $this->getGaps();
$reference = $this->getRenderingReference();
$left_id = $this->getOldChangesetID();
$right_id = $this->getNewChangesetID();
// "N" stands for 'new' and means the comment should attach to the new file
// when stored, i.e. DifferentialInlineComment->setIsNewFile().
// "O" stands for 'old' and means the comment should attach to the old file.
$left_char = $this->getOldAttachesToNewFile()
? 'N'
: 'O';
$right_char = $this->getNewAttachesToNewFile()
? 'N'
: 'O';
$changeset = $this->getChangeset();
$copy_lines = idx($changeset->getMetadata(), 'copy:lines', array());
$highlight_old = $this->getHighlightOld();
$highlight_new = $this->getHighlightNew();
$old_render = $this->getOldRender();
$new_render = $this->getNewRender();
$original_left = $this->getOriginalOld();
$original_right = $this->getOriginalNew();
$depths = $this->getDepths();
$mask = $this->getMask();
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap.
// Pop information about the next gap off the $gaps stack and render
// an appropriate "Show more context" element. This branch eventually
// increments $ii by the entire size of the gap and then continues
// the loop.
$gap = array_pop($gaps);
// TODO: Move this to renderShowContextLinks() once that is stable.
$top = $gap[0];
$len = $gap[1];
$end = $top + $len - 20;
$contents = array();
if ($len > 40) {
$is_first_block = false;
if ($ii == 0) {
$is_first_block = true;
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-20",
),
),
$is_first_block
? pht('Show First 20 Lines')
: pht("\xE2\x96\xB2 Show 20 Lines"));
}
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => 'all',
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-{$len}",
),
),
pht('Show All %d Lines', $len));
$is_last_block = false;
if ($ii + $len >= $rows) {
$is_last_block = true;
}
if ($len > 40) {
$contents[] = javelin_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$end}-20",
),
),
$is_last_block
? pht('Show Last 20 Lines')
: pht("\xE2\x96\xBC Show 20 Lines"));
}
$context = null;
$context_line = null;
if (!$is_last_block && $depths[$ii + $len]) {
for ($l = $ii + $len - 1; $l >= $ii; $l--) {
$line = $new_lines[$l]['text'];
if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') {
$context = $new_render[$l];
$context_line = $new_lines[$l]['line'];
break;
}
}
}
$container = javelin_tag(
'tr',
array(
'sigil' => 'context-target',
),
array(
phutil_tag(
'td',
array(
'colspan' => 2,
'class' => 'show-more',
),
phutil_implode_html(
" \xE2\x80\xA2 ", // Bullet
$contents)),
phutil_tag(
'th',
array(
'class' => 'show-context-line',
),
$context_line ? (int)$context_line : null),
phutil_tag(
'td',
array(
'colspan' => 3,
'class' => 'show-context',
),
// TODO: [HTML] Escaping model here isn't ideal.
phutil_safe_html($context)),
));
$html[] = $container;
$ii += ($len - 1);
continue;
}
$o_num = null;
$o_classes = '';
$o_text = null;
if (isset($old_lines[$ii])) {
$o_num = $old_lines[$ii]['line'];
$o_text = isset($old_render[$ii]) ? $old_render[$ii] : null;
if ($old_lines[$ii]['type']) {
if ($old_lines[$ii]['type'] == '\\') {
$o_text = $old_lines[$ii]['text'];
$o_class = 'comment';
} else if ($original_left && !isset($highlight_old[$o_num])) {
$o_class = 'old-rebase';
} else if (empty($new_lines[$ii])) {
$o_class = 'old old-full';
} else {
$o_class = 'old';
}
$o_classes = $o_class;
}
}
$n_copy = hsprintf('<td class="copy" />');
$n_cov = null;
$n_colspan = 2;
$n_classes = '';
$n_num = null;
$n_text = null;
if (isset($new_lines[$ii])) {
$n_num = $new_lines[$ii]['line'];
$n_text = isset($new_render[$ii]) ? $new_render[$ii] : null;
$coverage = $this->getCodeCoverage();
if ($coverage !== null) {
if (empty($coverage[$n_num - 1])) {
$cov_class = 'N';
} else {
$cov_class = $coverage[$n_num - 1];
}
$cov_class = 'cov-'.$cov_class;
$n_cov = phutil_tag('td', array('class' => "cov {$cov_class}"));
$n_colspan--;
}
if ($new_lines[$ii]['type']) {
if ($new_lines[$ii]['type'] == '\\') {
$n_text = $new_lines[$ii]['text'];
$n_class = 'comment';
} else if ($original_right && !isset($highlight_new[$n_num])) {
$n_class = 'new-rebase';
} else if (empty($old_lines[$ii])) {
$n_class = 'new new-full';
} else {
$n_class = 'new';
}
$n_classes = $n_class;
if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
$n_copy = phutil_tag('td', array('class' => "copy {$n_class}"));
} else {
list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
$title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
if ($orig_file == '') {
$title .= "line {$orig_line}";
} else {
$title .=
basename($orig_file).
":{$orig_line} in dir ".
dirname('/'.$orig_file);
}
$class = ($orig_type == '-' ? 'new-move' : 'new-copy');
$n_copy = javelin_tag(
'td',
array(
'meta' => array(
'msg' => $title,
),
'class' => 'copy '.$class,
),
'');
}
}
}
if (isset($hunk_starts[$o_num])) {
$html[] = $context_not_available;
}
if ($o_num && $left_id) {
$o_id = 'C'.$left_id.$left_char.'L'.$o_num;
} else {
$o_id = null;
}
if ($n_num && $right_id) {
$n_id = 'C'.$right_id.$right_char.'L'.$n_num;
} else {
$n_id = null;
}
// NOTE: This is a unicode zero-width space, which we use as a hint when
// intercepting 'copy' events to make sure sensible text ends up on the
// clipboard. See the 'phabricator-oncopy' behavior.
$zero_space = "\xE2\x80\x8B";
// NOTE: The Javascript is sensitive to whitespace changes in this
// block!
$html[] = phutil_tag('tr', array(), array(
phutil_tag('th', array('id' => $o_id), $o_num),
phutil_tag('td', array('class' => $o_classes), $o_text),
phutil_tag('th', array('id' => $n_id), $n_num),
$n_copy,
phutil_tag(
'td',
array('class' => $n_classes, 'colspan' => $n_colspan),
array(
phutil_tag('span', array('class' => 'zwsp'), $zero_space),
$n_text,
)),
$n_cov,
));
if ($context_not_available && ($ii == $rows - 1)) {
$html[] = $context_not_available;
}
$old_comments = $this->getOldComments();
$new_comments = $this->getNewComments();
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = false);
$new = '';
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$new = $this->renderInlineComment($new_comment,
$on_right = true);
unset($new_comments[$n_num][$key]);
}
}
}
$html[] = phutil_tag('tr', array('class' => 'inline'), array(
phutil_tag('th', array()),
phutil_tag('td', array(), $comment_html),
phutil_tag('th', array()),
phutil_tag('td', array('colspan' => 3), $new),
));
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$comment_html = $this->renderInlineComment($comment,
$on_right = true);
$html[] = phutil_tag('tr', array('class' => 'inline'), array(
phutil_tag('th', array()),
phutil_tag('td', array()),
phutil_tag('th', array()),
phutil_tag(
'td',
array('colspan' => 3),
$comment_html),
));
}
}
}
return $this->wrapChangeInTable(phutil_implode_html('', $html));
}
public function renderFileChange($old_file = null,
$new_file = null,
$id = 0,
$vs = 0) {
$old = null;
if ($old_file) {
$old = phutil_tag(
'div',
array(
'class' => 'differential-image-stage',
),
phutil_tag(
'img',
array(
'src' => $old_file->getBestURI(),
)));
}
$new = null;
if ($new_file) {
$new = phutil_tag(
'div',
array(
'class' => 'differential-image-stage',
),
phutil_tag(
'img',
array(
'src' => $new_file->getBestURI(),
)));
}
$html_old = array();
$html_new = array();
foreach ($this->getOldComments() as $on_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = false);
$html_old[] = phutil_tag('tr', array('class' => 'inline'), array(
phutil_tag('th', array()),
phutil_tag('td', array(), $comment_html),
phutil_tag('th', array()),
phutil_tag('td', array('colspan' => 3)),
));
}
}
foreach ($this->getNewComments() as $lin_line => $comment_group) {
foreach ($comment_group as $comment) {
$comment_html = $this->renderInlineComment($comment, $on_right = true);
$html_new[] = phutil_tag('tr', array('class' => 'inline'), array(
phutil_tag('th', array()),
phutil_tag('td', array()),
phutil_tag('th', array()),
phutil_tag(
'td',
array('colspan' => 3),
$comment_html),
));
}
}
if (!$old) {
$th_old = phutil_tag('th', array());
} else {
$th_old = phutil_tag('th', array('id' => "C{$vs}OL1"), 1);
}
if (!$new) {
$th_new = phutil_tag('th', array());
} else {
$th_new = phutil_tag('th', array('id' => "C{$id}OL1"), 1);
}
$output = hsprintf(
'<tr class="differential-image-diff">'.
'%s'.
'<td class="differential-old-image">%s</td>'.
'%s'.
'<td class="differential-new-image" colspan="3">%s</td>'.
'</tr>'.
'%s'.
'%s',
$th_old,
$old,
$th_new,
$new,
phutil_implode_html('', $html_old),
phutil_implode_html('', $html_new));
$output = $this->wrapChangeInTable($output);
return $this->renderChangesetTable($output);
}
}
diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php
index e144577db9..226758b9fc 100644
--- a/src/applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php
+++ b/src/applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php
@@ -1,10 +1,14 @@
<?php
final class DifferentialChangesetTwoUpTestRenderer
extends DifferentialChangesetTestRenderer {
public function isOneUpRenderer() {
return false;
}
+ public function getRendererKey() {
+ return '2up-test';
+ }
+
}
diff --git a/src/applications/differential/view/DifferentialChangesetDetailView.php b/src/applications/differential/view/DifferentialChangesetDetailView.php
index 6857fe6336..7723a7be7d 100644
--- a/src/applications/differential/view/DifferentialChangesetDetailView.php
+++ b/src/applications/differential/view/DifferentialChangesetDetailView.php
@@ -1,247 +1,244 @@
<?php
final class DifferentialChangesetDetailView extends AphrontView {
private $changeset;
private $buttons = array();
private $editable;
private $symbolIndex;
private $id;
private $vsChangesetID;
private $renderURI;
private $whitespace;
private $renderingRef;
private $autoload;
+ private $renderer;
public function setAutoload($autoload) {
$this->autoload = $autoload;
return $this;
}
public function getAutoload() {
return $this->autoload;
}
public function setRenderingRef($rendering_ref) {
$this->renderingRef = $rendering_ref;
return $this;
}
public function getRenderingRef() {
return $this->renderingRef;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function getWhitespace() {
return $this->whitespace;
}
public function setRenderURI($render_uri) {
$this->renderURI = $render_uri;
return $this;
}
public function getRenderURI() {
return $this->renderURI;
}
public function setChangeset($changeset) {
$this->changeset = $changeset;
return $this;
}
public function addButton($button) {
$this->buttons[] = $button;
return $this;
}
public function setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setSymbolIndex($symbol_index) {
$this->symbolIndex = $symbol_index;
return $this;
}
+ public function setRenderer($renderer) {
+ $this->renderer = $renderer;
+ return $this;
+ }
+
+ public function getRenderer() {
+ return $this->renderer;
+ }
+
public function getID() {
if (!$this->id) {
$this->id = celerity_generate_unique_node_id();
}
return $this->id;
}
public function setID($id) {
$this->id = $id;
return $this;
}
public function setVsChangesetID($vs_changeset_id) {
$this->vsChangesetID = $vs_changeset_id;
return $this;
}
public function getVsChangesetID() {
return $this->vsChangesetID;
}
public function getFileIcon($filename) {
$path_info = pathinfo($filename);
$extension = idx($path_info, 'extension');
switch ($extension) {
case 'psd':
case 'ai':
$icon = 'fa-eye';
break;
case 'conf':
$icon = 'fa-wrench';
break;
case 'wav':
case 'mp3':
case 'aiff':
$icon = 'fa-file-sound-o';
break;
case 'm4v':
case 'mov':
$icon = 'fa-file-movie-o';
break;
case 'sql':
case 'db':
$icon = 'fa-database';
break;
case 'xls':
case 'csv':
$icon = 'fa-file-excel-o';
break;
case 'ics':
$icon = 'fa-calendar';
break;
case 'zip':
case 'tar':
case 'bz':
case 'tgz':
case 'gz':
$icon = 'fa-file-archive-o';
break;
case 'png':
case 'jpg':
case 'bmp':
case 'gif':
$icon = 'fa-file-picture-o';
break;
case 'txt':
$icon = 'fa-file-text-o';
break;
case 'doc':
case 'docx':
$icon = 'fa-file-word-o';
break;
case 'pdf':
$icon = 'fa-file-pdf-o';
break;
default:
$icon = 'fa-file-code-o';
break;
}
return $icon;
}
public function render() {
$this->requireResource('differential-changeset-view-css');
$this->requireResource('syntax-highlighting-css');
Javelin::initBehavior('phabricator-oncopy', array());
$changeset = $this->changeset;
$class = 'differential-changeset';
if (!$this->editable) {
$class .= ' differential-changeset-immutable';
}
$buttons = null;
if ($this->buttons) {
$buttons = phutil_tag(
'div',
array(
'class' => 'differential-changeset-buttons',
),
$this->buttons);
}
$id = $this->getID();
if ($this->symbolIndex) {
Javelin::initBehavior(
'repository-crossreference',
array(
'container' => $id,
) + $this->symbolIndex);
}
$display_filename = $changeset->getDisplayFilename();
$display_icon = $this->getFileIcon($display_filename);
$icon = id(new PHUIIconView())
->setIconFont($display_icon);
- $renderer = null;
-
- // If the viewer prefers unified diffs, always set the renderer to unified.
- // Otherwise, we leave it unspecified and the client will choose a
- // renderer based on the screen size.
-
- $viewer = $this->getUser();
- $prefs = $viewer->loadPreferences();
- $pref_unified = PhabricatorUserPreferences::PREFERENCE_DIFF_UNIFIED;
- if ($prefs->getPreference($pref_unified) == 'unified') {
- $renderer = '1up';
- }
-
return javelin_tag(
'div',
array(
'sigil' => 'differential-changeset',
'meta' => array(
'left' => nonempty(
$this->getVsChangesetID(),
$this->changeset->getID()),
'right' => $this->changeset->getID(),
'renderURI' => $this->getRenderURI(),
'whitespace' => $this->getWhitespace(),
'highlight' => null,
- 'renderer' => $renderer,
+ 'renderer' => $this->getRenderer(),
'ref' => $this->getRenderingRef(),
'autoload' => $this->getAutoload(),
),
'class' => $class,
'id' => $id,
),
array(
id(new PhabricatorAnchorView())
->setAnchorName($changeset->getAnchorName())
->setNavigationMarker(true)
->render(),
$buttons,
phutil_tag('h1',
array(
'class' => 'differential-file-icon-header',
),
array(
$icon,
$display_filename,
)),
javelin_tag(
'div',
array(
'class' => 'changeset-view-content',
'sigil' => 'changeset-view-content',
),
$this->renderChildren()),
));
}
}
diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php
index 4ec0e19a3f..e63c3d613f 100644
--- a/src/applications/differential/view/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/DifferentialChangesetListView.php
@@ -1,362 +1,362 @@
<?php
final class DifferentialChangesetListView extends AphrontView {
private $changesets = array();
private $visibleChangesets = array();
private $references = array();
private $inlineURI;
private $renderURI = '/differential/changeset/';
private $whitespace;
private $standaloneURI;
private $leftRawFileURI;
private $rightRawFileURI;
private $symbolIndexes = array();
private $repository;
private $branch;
private $diff;
private $vsMap = array();
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
private function getTitle() {
return $this->title;
}
public function setBranch($branch) {
$this->branch = $branch;
return $this;
}
private function getBranch() {
return $this->branch;
}
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setVisibleChangesets($visible_changesets) {
$this->visibleChangesets = $visible_changesets;
return $this;
}
public function setInlineCommentControllerURI($uri) {
$this->inlineURI = $uri;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function setRenderingReferences(array $references) {
$this->references = $references;
return $this;
}
public function setSymbolIndexes(array $indexes) {
$this->symbolIndexes = $indexes;
return $this;
}
public function setRenderURI($render_uri) {
$this->renderURI = $render_uri;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function setVsMap(array $vs_map) {
$this->vsMap = $vs_map;
return $this;
}
public function getVsMap() {
return $this->vsMap;
}
public function setStandaloneURI($uri) {
$this->standaloneURI = $uri;
return $this;
}
public function setRawFileURIs($l, $r) {
$this->leftRawFileURI = $l;
$this->rightRawFileURI = $r;
return $this;
}
public function render() {
$this->requireResource('differential-changeset-view-css');
$changesets = $this->changesets;
Javelin::initBehavior('differential-toggle-files', array(
'pht' => array(
'undo' => pht('Undo'),
'collapsed' => pht('This file content has been collapsed.'),
),
));
Javelin::initBehavior(
'differential-dropdown-menus',
array(
'pht' => array(
'Open in Editor' => pht('Open in Editor'),
'Show Entire File' => pht('Show Entire File'),
'Entire File Shown' => pht('Entire File Shown'),
"Can't Toggle Unloaded File" => pht("Can't Toggle Unloaded File"),
'Expand File' => pht('Expand File'),
'Collapse File' => pht('Collapse File'),
'Browse in Diffusion' => pht('Browse in Diffusion'),
'View Standalone' => pht('View Standalone'),
'Show Raw File (Left)' => pht('Show Raw File (Left)'),
'Show Raw File (Right)' => pht('Show Raw File (Right)'),
'Configure Editor' => pht('Configure Editor'),
'Load Changes' => pht('Load Changes'),
'View Side-by-Side' => pht('View Side-by-Side'),
'View Unified' => pht('View Unified'),
'Change Text Encoding...' => pht('Change Text Encoding...'),
'Highlight As...' => pht('Highlight As...'),
),
));
+ $renderer = DifferentialChangesetParser::getDefaultRendererForViewer(
+ $this->getUser());
+
$output = array();
$ids = array();
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
$class = 'differential-changeset';
if (!$this->inlineURI) {
$class .= ' differential-changeset-noneditable';
}
$ref = $this->references[$key];
$detail = id(new DifferentialChangesetDetailView())
->setUser($this->getUser());
$uniq_id = 'diff-'.$changeset->getAnchorName();
$detail->setID($uniq_id);
$view_options = $this->renderViewOptionsDropdown(
$detail,
$ref,
$changeset);
$detail->setChangeset($changeset);
$detail->addButton($view_options);
$detail->setSymbolIndex(idx($this->symbolIndexes, $key));
$detail->setVsChangesetID(idx($this->vsMap, $changeset->getID()));
$detail->setEditable(true);
$detail->setRenderingRef($ref);
$detail->setAutoload(isset($this->visibleChangesets[$key]));
$detail->setRenderURI($this->renderURI);
$detail->setWhitespace($this->whitespace);
+ $detail->setRenderer($renderer);
if (isset($this->visibleChangesets[$key])) {
$load = 'Loading...';
} else {
$load = javelin_tag(
'a',
array(
'class' => 'button grey',
'href' => '#'.$uniq_id,
'sigil' => 'differential-load',
'meta' => array(
'id' => $detail->getID(),
'kill' => true,
),
'mustcapture' => true,
),
pht('Load File'));
}
$detail->appendChild(
phutil_tag(
'div',
array(
'id' => $uniq_id,
),
phutil_tag('div', array('class' => 'differential-loading'), $load)));
$output[] = $detail->render();
$ids[] = $detail->getID();
}
$this->requireResource('aphront-tooltip-css');
$this->initBehavior('differential-populate', array(
'changesetViewIDs' => $ids,
));
- $this->initBehavior('differential-show-more', array(
- 'uri' => $this->renderURI,
- 'whitespace' => $this->whitespace,
- ));
-
+ $this->initBehavior('differential-show-more');
$this->initBehavior('differential-comment-jump', array());
if ($this->inlineURI) {
$undo_templates = $this->renderUndoTemplates();
Javelin::initBehavior('differential-edit-inline-comments', array(
'uri' => $this->inlineURI,
'undo_templates' => $undo_templates,
'stage' => 'differential-review-stage',
));
}
$header = id(new PHUIHeaderView())
->setHeader($this->getTitle());
$content = phutil_tag(
'div',
array(
'class' => 'differential-review-stage',
'id' => 'differential-review-stage',
),
$output);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($content);
return $object_box;
}
/**
* Render the "Undo" markup for the inline comment undo feature.
*/
private function renderUndoTemplates() {
$link = javelin_tag(
'a',
array(
'href' => '#',
'sigil' => 'differential-inline-comment-undo',
),
pht('Undo'));
$div = phutil_tag(
'div',
array(
'class' => 'differential-inline-undo',
),
array('Changes discarded. ', $link));
return array(
'l' => phutil_tag('table', array(),
phutil_tag('tr', array(), array(
phutil_tag('th', array()),
phutil_tag('td', array(), $div),
phutil_tag('th', array()),
phutil_tag('td', array('colspan' => 3)),
))),
'r' => phutil_tag('table', array(),
phutil_tag('tr', array(), array(
phutil_tag('th', array()),
phutil_tag('td', array()),
phutil_tag('th', array()),
phutil_tag('td', array('colspan' => 3), $div),
))),
);
}
private function renderViewOptionsDropdown(
DifferentialChangesetDetailView $detail,
$ref,
DifferentialChangeset $changeset) {
$meta = array();
$qparams = array(
'ref' => $ref,
'whitespace' => $this->whitespace,
);
if ($this->standaloneURI) {
$uri = new PhutilURI($this->standaloneURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['standaloneURI'] = (string)$uri;
}
$repository = $this->repository;
if ($repository) {
try {
$meta['diffusionURI'] =
(string)$repository->getDiffusionBrowseURIForPath(
$this->user,
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
idx($changeset->getMetadata(), 'line:first'),
$this->getBranch());
} catch (DiffusionSetupException $e) {
// Ignore
}
}
$change = $changeset->getChangeType();
if ($this->leftRawFileURI) {
if ($change != DifferentialChangeType::TYPE_ADD) {
$uri = new PhutilURI($this->leftRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['leftURI'] = (string)$uri;
}
}
if ($this->rightRawFileURI) {
if ($change != DifferentialChangeType::TYPE_DELETE &&
$change != DifferentialChangeType::TYPE_MULTICOPY) {
$uri = new PhutilURI($this->rightRawFileURI);
$uri->setQueryParams($uri->getQueryParams() + $qparams);
$meta['rightURI'] = (string)$uri;
}
}
$user = $this->user;
if ($user && $repository) {
$path = ltrim(
$changeset->getAbsoluteRepositoryPath($repository, $this->diff),
'/');
$line = idx($changeset->getMetadata(), 'line:first', 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
if ($editor_link) {
$meta['editor'] = $editor_link;
} else {
$meta['editorConfigure'] = '/settings/panel/display/';
}
}
$meta['containerID'] = $detail->getID();
$caret = phutil_tag('span', array('class' => 'caret'), '');
return javelin_tag(
'a',
array(
'class' => 'button grey small dropdown',
'meta' => $meta,
'href' => idx($meta, 'detailURI', '#'),
'target' => '_blank',
'sigil' => 'differential-view-options',
),
array(pht('View Options'), $caret));
}
}
diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php
index dea82dcc04..7c4dbc0bc8 100644
--- a/src/applications/phriction/controller/PhrictionDiffController.php
+++ b/src/applications/phriction/controller/PhrictionDiffController.php
@@ -1,306 +1,304 @@
<?php
final class PhrictionDiffController extends PhrictionController {
private $id;
public function shouldAllowPublic() {
return true;
}
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new PhrictionDocumentQuery())
->setViewer($user)
->withIDs(array($this->id))
->needContent(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$current = $document->getContent();
$l = $request->getInt('l');
$r = $request->getInt('r');
$ref = $request->getStr('ref');
if ($ref) {
list($l, $r) = explode(',', $ref);
}
$content = id(new PhrictionContent())->loadAllWhere(
'documentID = %d AND version IN (%Ld)',
$document->getID(),
array($l, $r));
$content = mpull($content, null, 'getVersion');
$content_l = idx($content, $l, null);
$content_r = idx($content, $r, null);
if (!$content_l || !$content_r) {
return new Aphront404Response();
}
$text_l = $content_l->getContent();
$text_r = $content_r->getContent();
$text_l = phutil_utf8_hard_wrap($text_l, 80);
$text_l = implode("\n", $text_l);
$text_r = phutil_utf8_hard_wrap($text_r, 80);
$text_r = implode("\n", $text_r);
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($text_l, $text_r);
$changeset->setFilename($content_r->getTitle());
$changeset->setOldProperties(
array(
'Title' => $content_l->getTitle(),
));
$changeset->setNewProperties(
array(
'Title' => $content_r->getTitle(),
));
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$parser = new DifferentialChangesetParser();
$parser->setUser($user);
$parser->setChangeset($changeset);
$parser->setRenderingReference("{$l},{$r}");
$parser->setWhitespaceMode($whitespace_mode);
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->process();
$parser->setMarkupEngine($engine);
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$output = $parser->render($range_s, $range_e, $mask);
if ($request->isAjax()) {
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($output);
}
$output = id(new DifferentialChangesetDetailView())
->setUser($this->getViewer())
->setChangeset($changeset)
+ ->setRenderingRef("{$l},{$r}")
+ ->setRenderURI('/phriction/diff/'.$document->getID().'/')
->appendChild($output);
require_celerity_resource('differential-changeset-view-css');
require_celerity_resource('syntax-highlighting-css');
require_celerity_resource('phriction-document-css');
Javelin::initBehavior('differential-populate', array(
'changesetViewIDs' => array($output->getID()),
));
-
- Javelin::initBehavior('differential-show-more', array(
- 'uri' => '/phriction/diff/'.$document->getID().'/',
- 'whitespace' => $whitespace_mode,
- ));
+ Javelin::initBehavior('differential-show-more');
$slug = $document->getSlug();
$revert_l = $this->renderRevertButton($content_l, $current);
$revert_r = $this->renderRevertButton($content_r, $current);
$crumbs = $this->buildApplicationCrumbs();
$crumb_views = $this->renderBreadcrumbs($slug);
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$crumbs->addTextCrumb(
pht('History'),
PhrictionDocument::getSlugURI($slug, 'history'));
$title = pht('Version %s vs %s', $l, $r);
$header = id(new PHUIHeaderView())
->setHeader($title);
$crumbs->addTextCrumb($title, $request->getRequestURI());
$comparison_table = $this->renderComparisonTable(
array(
$content_r,
$content_l,
));
$navigation_table = null;
if ($l + 1 == $r) {
$nav_l = ($l > 1);
$nav_r = ($r != $current->getVersion());
$uri = $request->getRequestURI();
if ($nav_l) {
$link_l = phutil_tag(
'a',
array(
'href' => $uri->alter('l', $l - 1)->alter('r', $r - 1),
'class' => 'button',
),
pht("\xC2\xAB Previous Change"));
} else {
$link_l = phutil_tag(
'a',
array(
'href' => '#',
'class' => 'button grey disabled',
),
pht('Original Change'));
}
$link_r = null;
if ($nav_r) {
$link_r = phutil_tag(
'a',
array(
'href' => $uri->alter('l', $l + 1)->alter('r', $r + 1),
'class' => 'button',
),
pht("Next Change \xC2\xBB"));
} else {
$link_r = phutil_tag(
'a',
array(
'href' => '#',
'class' => 'button grey disabled',
),
pht('Most Recent Change'));
}
$navigation_table = phutil_tag(
'table',
array('class' => 'phriction-history-nav-table'),
phutil_tag('tr', array(), array(
phutil_tag('td', array('class' => 'nav-prev'), $link_l),
phutil_tag('td', array('class' => 'nav-next'), $link_r),
)));
}
$output = hsprintf(
'<div class="phriction-document-history-diff">'.
'%s%s'.
'<table class="phriction-revert-table">'.
'<tr><td>%s</td><td>%s</td>'.
'</table>'.
'%s'.
'</div>',
$comparison_table->render(),
$navigation_table,
$revert_l,
$revert_r,
$output);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($output);
return $this->buildApplicationPage(
array(
$crumbs,
$object_box,
),
array(
'title' => pht('Document History'),
));
}
private function renderRevertButton(
PhrictionContent $content,
PhrictionContent $current) {
$document_id = $content->getDocumentID();
$version = $content->getVersion();
$hidden_statuses = array(
PhrictionChangeType::CHANGE_DELETE => true, // Silly
PhrictionChangeType::CHANGE_MOVE_AWAY => true, // Plain silly
PhrictionChangeType::CHANGE_STUB => true, // Utterly silly
);
if (isset($hidden_statuses[$content->getChangeType()])) {
// Don't show an edit/revert button for changes which deleted, moved or
// stubbed the content since it's silly.
return null;
}
if ($content->getID() == $current->getID()) {
return phutil_tag(
'a',
array(
'href' => '/phriction/edit/'.$document_id.'/',
'class' => 'button grey',
),
pht('Edit Current Version'));
}
return phutil_tag(
'a',
array(
'href' => '/phriction/edit/'.$document_id.'/?revert='.$version,
'class' => 'button grey',
),
pht('Revert to Version %s...', $version));
}
private function renderComparisonTable(array $content) {
assert_instances_of($content, 'PhrictionContent');
$user = $this->getRequest()->getUser();
$phids = mpull($content, 'getAuthorPHID');
$handles = $this->loadViewerHandles($phids);
$list = new PHUIObjectItemListView();
$list->setFlush(true);
$first = true;
foreach ($content as $c) {
$author = $handles[$c->getAuthorPHID()]->renderLink();
$item = id(new PHUIObjectItemView())
->setHeader(pht('%s by %s, %s',
PhrictionChangeType::getChangeTypeLabel($c->getChangeType()),
$author,
pht('Version %s', $c->getVersion())))
->addAttribute(pht('%s %s',
phabricator_date($c->getDateCreated(), $user),
phabricator_time($c->getDateCreated(), $user)));
if ($c->getDescription()) {
$item->addAttribute($c->getDescription());
}
if ($first == true) {
$item->setBarColor('green');
$first = false;
} else {
$item->setBarColor('red');
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
index a2ffe78639..31881310d0 100644
--- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
+++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
@@ -1,973 +1,973 @@
<?php
final class PhabricatorUSEnglishTranslation
extends PhutilTranslation {
public function getLocaleCode() {
return 'en_US';
}
protected function getTranslations() {
return array(
'No daemon(s) with id(s) "%s" exist!' => array(
'No daemon with id %s exists!',
'No daemons with ids %s exist!',
),
'These %d configuration value(s) are related:' => array(
'This configuration value is related:',
'These configuration values are related:',
),
'%s Task(s)' => array('Task', 'Tasks'),
'%s ERROR(S)' => array('ERROR', 'ERRORS'),
'%d Error(s)' => array('%d Error', '%d Errors'),
'%d Warning(s)' => array('%d Warning', '%d Warnings'),
'%d Auto-Fix(es)' => array('%d Auto-Fix', '%d Auto-Fixes'),
'%d Advice(s)' => array('%d Advice', '%d Pieces of Advice'),
'%d Detail(s)' => array('%d Detail', '%d Details'),
'(%d line(s))' => array('(%d line)', '(%d lines)'),
'%d line(s)' => array('%d line', '%d lines'),
'%d path(s)' => array('%d path', '%d paths'),
'%d diff(s)' => array('%d diff', '%d diffs'),
'%s DIFF LINK(S)' => array('DIFF LINK', 'DIFF LINKS'),
'You successfully created %d diff(s).' => array(
'You successfully created %d diff.',
'You successfully created %d diffs.',
),
'Diff creation failed; see body for %s error(s).' => array(
'Diff creation failed; see body for error.',
'Diff creation failed; see body for errors.',
),
'There are %d raw fact(s) in storage.' => array(
'There is %d raw fact in storage.',
'There are %d raw facts in storage.',
),
'There are %d aggregate fact(s) in storage.' => array(
'There is %d aggregate fact in storage.',
'There are %d aggregate facts in storage.',
),
'%d Commit(s) Awaiting Audit' => array(
'%d Commit Awaiting Audit',
'%d Commits Awaiting Audit',
),
'%d Problem Commit(s)' => array(
'%d Problem Commit',
'%d Problem Commits',
),
'%d Review(s) Blocking Others' => array(
'%d Review Blocking Others',
'%d Reviews Blocking Others',
),
'%d Review(s) Need Attention' => array(
'%d Review Needs Attention',
'%d Reviews Need Attention',
),
'%d Review(s) Waiting on Others' => array(
'%d Review Waiting on Others',
'%d Reviews Waiting on Others',
),
'%d Active Review(s)' => array(
'%d Active Review',
'%d Active Reviews',
),
'%d Flagged Object(s)' => array(
'%d Flagged Object',
'%d Flagged Objects',
),
'%d Object(s) Tracked' => array(
'%d Object Tracked',
'%d Objects Tracked',
),
'%d Assigned Task(s)' => array(
'%d Assigned Task',
'%d Assigned Tasks',
),
'Show %d Lint Message(s)' => array(
'Show %d Lint Message',
'Show %d Lint Messages',
),
'Hide %d Lint Message(s)' => array(
'Hide %d Lint Message',
'Hide %d Lint Messages',
),
'This is a binary file. It is %s byte(s) in length.' => array(
'This is a binary file. It is %s byte in length.',
'This is a binary file. It is %s bytes in length.',
),
'%d Action(s) Have No Effect' => array(
'Action Has No Effect',
'Actions Have No Effect',
),
'%d Action(s) With No Effect' => array(
'Action With No Effect',
'Actions With No Effect',
),
'Some of your %d action(s) have no effect:' => array(
'One of your actions has no effect:',
'Some of your actions have no effect:',
),
'Apply remaining %d action(s)?' => array(
'Apply remaining action?',
'Apply remaining actions?',
),
'Apply %d Other Action(s)' => array(
'Apply Remaining Action',
'Apply Remaining Actions',
),
'The %d action(s) you are taking have no effect:' => array(
'The action you are taking has no effect:',
'The actions you are taking have no effect:',
),
'%s edited member(s), added %d: %s; removed %d: %s.' =>
'%s edited members, added: %3$s; removed: %5$s.',
'%s added %s member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %s member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%s edited project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added: %3$s; removed: %5$s.',
'%s added %s project(s): %s.' => array(
array(
'%s added a project: %3$s.',
'%s added projects: %3$s.',
),
),
'%s removed %s project(s): %s.' => array(
array(
'%s removed a project: %3$s.',
'%s removed projects: %3$s.',
),
),
'%s merged %d task(s): %s.' => array(
array(
'%s merged a task: %3$s.',
'%s merged tasks: %3$s.',
),
),
'%s merged %d task(s) %s into %s.' => array(
array(
'%s merged %3$s into %4$s.',
'%s merged tasks %3$s into %4$s.',
),
),
'%s added %s voting user(s): %s.' => array(
array(
'%s added a voting user: %3$s.',
'%s added voting users: %3$s.',
),
),
'%s removed %s voting user(s): %s.' => array(
array(
'%s removed a voting user: %3$s.',
'%s removed voting users: %3$s.',
),
),
'%s added %s blocking task(s): %s.' => array(
array(
'%s added a blocking task: %3$s.',
'%s added blocking tasks: %3$s.',
),
),
'%s added %s blocked task(s): %s.' => array(
array(
'%s added a blocked task: %3$s.',
'%s added blocked tasks: %3$s.',
),
),
'%s removed %s blocking task(s): %s.' => array(
array(
'%s removed a blocking task: %3$s.',
'%s removed blocking tasks: %3$s.',
),
),
'%s removed %s blocked task(s): %s.' => array(
array(
'%s removed a blocked task: %3$s.',
'%s removed blocked tasks: %3$s.',
),
),
'%s added %s blocking task(s) for %s: %s.' => array(
array(
'%s added a blocking task for %3$s: %4$s.',
'%s added blocking tasks for %3$s: %4$s.',
),
),
'%s added %s blocked task(s) for %s: %s.' => array(
array(
'%s added a blocked task for %3$s: %4$s.',
'%s added blocked tasks for %3$s: %4$s.',
),
),
'%s removed %s blocking task(s) for %s: %s.' => array(
array(
'%s removed a blocking task for %3$s: %4$s.',
'%s removed blocking tasks for %3$s: %4$s.',
),
),
'%s removed %s blocked task(s) for %s: %s.' => array(
array(
'%s removed a blocked task for %3$s: %4$s.',
'%s removed blocked tasks for %3$s: %4$s.',
),
),
'%s edited blocking task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks, added: %3$s; removed: %5$s.',
'%s edited blocking task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocking tasks for %s, added: %4$s; removed: %6$s.',
'%s edited blocked task(s), added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks, added: %3$s; removed: %5$s.',
'%s edited blocked task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited blocked tasks for %s, added: %4$s; removed: %6$s.',
'%s edited answer(s), added %s: %s; removed %d: %s.' =>
'%s edited answers, added: %3$s; removed: %5$s.',
'%s added %s answer(s): %s.' => array(
array(
'%s added an answer: %3$s.',
'%s added answers: %3$s.',
),
),
'%s removed %s answer(s): %s.' => array(
array(
'%s removed a answer: %3$s.',
'%s removed answers: %3$s.',
),
),
'%s edited question(s), added %s: %s; removed %s: %s.' =>
'%s edited questions, added: %3$s; removed: %5$s.',
'%s added %s question(s): %s.' => array(
array(
'%s added a question: %3$s.',
'%s added questions: %3$s.',
),
),
'%s removed %s question(s): %s.' => array(
array(
'%s removed a question: %3$s.',
'%s removed questions: %3$s.',
),
),
'%s edited mock(s), added %s: %s; removed %s: %s.' =>
'%s edited mocks, added: %3$s; removed: %5$s.',
'%s added %s mock(s): %s.' => array(
array(
'%s added a mock: %3$s.',
'%s added mocks: %3$s.',
),
),
'%s removed %s mock(s): %s.' => array(
array(
'%s removed a mock: %3$s.',
'%s removed mocks: %3$s.',
),
),
'%s added %s task(s): %s.' => array(
array(
'%s added a task: %3$s.',
'%s added tasks: %3$s.',
),
),
'%s removed %s task(s): %s.' => array(
array(
'%s removed a task: %3$s.',
'%s removed tasks: %3$s.',
),
),
'%s edited file(s), added %s: %s; removed %s: %s.' =>
'%s edited files, added: %3$s; removed: %5$s.',
'%s added %s file(s): %s.' => array(
array(
'%s added a file: %3$s.',
'%s added files: %3$s.',
),
),
'%s removed %s file(s): %s.' => array(
array(
'%s removed a file: %3$s.',
'%s removed files: %3$s.',
),
),
'%s edited contributor(s), added %s: %s; removed %s: %s.' =>
'%s edited contributors, added: %3$s; removed: %5$s.',
'%s added %s contributor(s): %s.' => array(
array(
'%s added a contributor: %3$s.',
'%s added contributors: %3$s.',
),
),
'%s removed %s contributor(s): %s.' => array(
array(
'%s removed a contributor: %3$s.',
'%s removed contributors: %3$s.',
),
),
'%s edited %s reviewer(s), added %s: %s; removed %s: %s.' =>
'%s edited reviewers, added: %4$s; removed: %6$s.',
'%s edited %s reviewer(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited reviewers for %3$s, added: %5$s; removed: %7$s.',
'%s added %s reviewer(s): %s.' => array(
array(
'%s added a reviewer: %3$s.',
'%s added reviewers: %3$s.',
),
),
'%s removed %s reviewer(s): %s.' => array(
array(
'%s removed a reviewer: %3$s.',
'%s removed reviewers: %3$s.',
),
),
'%d other(s)' => array(
'1 other',
'%d others',
),
'%s edited subscriber(s), added %d: %s; removed %d: %s.' =>
'%s edited subscribers, added: %3$s; removed: %5$s.',
'%s added %d subscriber(s): %s.' => array(
array(
'%s added a subscriber: %3$s.',
'%s added subscribers: %3$s.',
),
),
'%s removed %d subscriber(s): %s.' => array(
array(
'%s removed a subscriber: %3$s.',
'%s removed subscribers: %3$s.',
),
),
'%s edited watcher(s), added %s: %s; removed %d: %s.' =>
'%s edited watchers, added: %3$s; removed: %5$s.',
'%s added %s watcher(s): %s.' => array(
array(
'%s added a watcher: %3$s.',
'%s added watchers: %3$s.',
),
),
'%s removed %s watcher(s): %s.' => array(
array(
'%s removed a watcher: %3$s.',
'%s removed watchers: %3$s.',
),
),
'%s edited participant(s), added %d: %s; removed %d: %s.' =>
'%s edited participants, added: %3$s; removed: %5$s.',
'%s added %d participant(s): %s.' => array(
array(
'%s added a participant: %3$s.',
'%s added participants: %3$s.',
),
),
'%s removed %d participant(s): %s.' => array(
array(
'%s removed a participant: %3$s.',
'%s removed participants: %3$s.',
),
),
'%s edited image(s), added %d: %s; removed %d: %s.' =>
'%s edited images, added: %3$s; removed: %5$s',
'%s added %d image(s): %s.' => array(
array(
'%s added an image: %3$s.',
'%s added images: %3$s.',
),
),
'%s removed %d image(s): %s.' => array(
array(
'%s removed an image: %3$s.',
'%s removed images: %3$s.',
),
),
'%s Line(s)' => array(
'%s Line',
'%s Lines',
),
'Indexing %d object(s) of type %s.' => array(
'Indexing %d object of type %s.',
'Indexing %d object of type %s.',
),
'Run these %d command(s):' => array(
'Run this command:',
'Run these commands:',
),
'Install these %d PHP extension(s):' => array(
'Install this PHP extension:',
'Install these PHP extensions:',
),
'The current Phabricator configuration has these %d value(s):' => array(
'The current Phabricator configuration has this value:',
'The current Phabricator configuration has these values:',
),
'The current MySQL configuration has these %d value(s):' => array(
'The current MySQL configuration has this value:',
'The current MySQL configuration has these values:',
),
'You can update these %d value(s) here:' => array(
'You can update this value here:',
'You can update these values here:',
),
'The current PHP configuration has these %d value(s):' => array(
'The current PHP configuration has this value:',
'The current PHP configuration has these values:',
),
'To update these %d value(s), edit your PHP configuration file.' => array(
'To update this %d value, edit your PHP configuration file.',
'To update these %d values, edit your PHP configuration file.',
),
'To update these %d value(s), edit your PHP configuration file, located '.
'here:' => array(
'To update this value, edit your PHP configuration file, located '.
'here:',
'To update these values, edit your PHP configuration file, located '.
'here:',
),
'PHP also loaded these %s configuration file(s):' => array(
'PHP also loaded this configuration file:',
'PHP also loaded these configuration files:',
),
'You have %d unresolved setup issue(s)...' => array(
'You have an unresolved setup issue...',
'You have %d unresolved setup issues...',
),
'%s added %d inline comment(s).' => array(
array(
'%s added an inline comment.',
'%s added inline comments.',
),
),
'%d comment(s)' => array('%d comment', '%d comments'),
'%d rejection(s)' => array('%d rejection', '%d rejections'),
'%d update(s)' => array('%d update', '%d updates'),
'This configuration value is defined in these %d '.
'configuration source(s): %s.' => array(
'This configuration value is defined in this '.
'configuration source: %2$s.',
'This configuration value is defined in these %d '.
'configuration sources: %s.',
),
'%d Open Pull Request(s)' => array(
'%d Open Pull Request',
'%d Open Pull Requests',
),
'Stale (%s day(s))' => array(
'Stale (%s day)',
'Stale (%s days)',
),
'Old (%s day(s))' => array(
'Old (%s day)',
'Old (%s days)',
),
'%s Commit(s)' => array(
'%s Commit',
'%s Commits',
),
'%s attached %d file(s): %s.' => array(
array(
'%s attached a file: %3$s.',
'%s attached files: %3$s.',
),
),
'%s detached %d file(s): %s.' => array(
array(
'%s detached a file: %3$s.',
'%s detached files: %3$s.',
),
),
'%s changed file(s), attached %d: %s; detached %d: %s.' =>
'%s changed files, attached: %3$s; detached: %5$s.',
'%s added %s dependencie(s): %s.' => array(
array(
'%s added a dependency: %3$s.',
'%s added dependencies: %3$s.',
),
),
'%s removed %s dependencie(s): %s.' => array(
array(
'%s removed a dependency: %3$s.',
'%s removed dependencies: %3$s.',
),
),
'%s added %s dependent revision(s): %s.' => array(
array(
'%s added a dependent revision: %3$s.',
'%s added dependent revisions: %3$s.',
),
),
'%s removed %s dependent revision(s): %s.' => array(
array(
'%s removed a dependent revision: %3$s.',
'%s removed dependent revisions: %3$s.',
),
),
'%s added %s commit(s): %s.' => array(
array(
'%s added a commit: %3$s.',
'%s added commits: %3$s.',
),
),
'%s removed %s commit(s): %s.' => array(
array(
'%s removed a commit: %3$s.',
'%s removed commits: %3$s.',
),
),
'%s edited commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %3$s; removed %5$s.',
'%s added %s reverted commit(s): %s.' => array(
array(
'%s added a reverted commit: %3$s.',
'%s added reverted commits: %3$s.',
),
),
'%s removed %s reverted commit(s): %s.' => array(
array(
'%s removed a reverted commit: %3$s.',
'%s removed reverted commits: %3$s.',
),
),
'%s edited reverted commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverted commits, added %3$s; removed %5$s.',
'%s added %s reverting commit(s): %s.' => array(
array(
'%s added a reverting commit: %3$s.',
'%s added reverting commits: %3$s.',
),
),
'%s removed %s reverting commit(s): %s.' => array(
array(
'%s removed a reverting commit: %3$s.',
'%s removed reverting commits: %3$s.',
),
),
'%s edited reverting commit(s), added %s: %s; removed %s: %s.' =>
'%s edited reverting commits, added %3$s; removed %5$s.',
'%s changed project member(s), added %d: %s; removed %d: %s.' =>
'%s changed project members, added %3$s; removed %5$s.',
'%s added %d project member(s): %s.' => array(
array(
'%s added a member: %3$s.',
'%s added members: %3$s.',
),
),
'%s removed %d project member(s): %s.' => array(
array(
'%s removed a member: %3$s.',
'%s removed members: %3$s.',
),
),
'%d project hashtag(s) are already used: %s.' => array(
'Project hashtag %2$s is already used.',
'%d project hashtags are already used: %2$s.',
),
'%s changed project hashtag(s), added %d: %s; removed %d: %s.' =>
'%s changed project hashtags, added %3$s; removed %5$s.',
'%s added %d project hashtag(s): %s.' => array(
array(
'%s added a hashtag: %3$s.',
'%s added hashtags: %3$s.',
),
),
'%s removed %d project hashtag(s): %s.' => array(
array(
'%s removed a hashtag: %3$s.',
'%s removed hashtags: %3$s.',
),
),
'%d User(s) Need Approval' => array(
'%d User Needs Approval',
'%d Users Need Approval',
),
'%s older changes(s) are hidden.' => array(
'%d older change is hidden.',
'%d older changes are hidden.',
),
'%s, %s line(s)' => array(
'%s, %s line',
'%s, %s lines',
),
'%s pushed %d commit(s) to %s.' => array(
array(
'%s pushed a commit to %3$s.',
'%s pushed %d commits to %s.',
),
),
'%s commit(s)' => array(
'1 commit',
'%s commits',
),
'%s removed %s JIRA issue(s): %s.' => array(
array(
'%s removed a JIRA issue: %3$s.',
'%s removed JIRA issues: %3$s.',
),
),
'%s added %s JIRA issue(s): %s.' => array(
array(
'%s added a JIRA issue: %3$s.',
'%s added JIRA issues: %3$s.',
),
),
'%s added %s required legal document(s): %s.' => array(
array(
'%s added a required legal document: %3$s.',
'%s added required legal documents: %3$s.',
),
),
'%s updated JIRA issue(s): added %s %s; removed %d %s.' =>
'%s updated JIRA issues: added %3$s; removed %5$s.',
'%s edited %s task(s), added %s: %s; removed %s: %s.' =>
'%s edited tasks, added %4$s; removed %6$s.',
'%s added %s task(s) to %s: %s.' => array(
array(
'%s added a task to %3$s: %4$s.',
'%s added tasks to %3$s: %4$s.',
),
),
'%s removed %s task(s) from %s: %s.' => array(
array(
'%s removed a task from %3$s: %4$s.',
'%s removed tasks from %3$s: %4$s.',
),
),
'%s edited %s task(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited tasks for %3$s, added: %5$s; removed %7$s.',
'%s edited %s commit(s), added %s: %s; removed %s: %s.' =>
'%s edited commits, added %4$s; removed %6$s.',
'%s added %s commit(s) to %s: %s.' => array(
array(
'%s added a commit to %3$s: %4$s.',
'%s added commits to %3$s: %4$s.',
),
),
'%s removed %s commit(s) from %s: %s.' => array(
array(
'%s removed a commit from %3$s: %4$s.',
'%s removed commits from %3$s: %4$s.',
),
),
'%s edited %s commit(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited commits for %3$s, added: %5$s; removed %7$s.',
'%s added %s revision(s): %s.' => array(
array(
'%s added a revision: %3$s.',
'%s added revisions: %3$s.',
),
),
'%s removed %s revision(s): %s.' => array(
array(
'%s removed a revision: %3$s.',
'%s removed revisions: %3$s.',
),
),
'%s edited %s revision(s), added %s: %s; removed %s: %s.' =>
'%s edited revisions, added %4$s; removed %6$s.',
'%s added %s revision(s) to %s: %s.' => array(
array(
'%s added a revision to %3$s: %4$s.',
'%s added revisions to %3$s: %4$s.',
),
),
'%s removed %s revision(s) from %s: %s.' => array(
array(
'%s removed a revision from %3$s: %4$s.',
'%s removed revisions from %3$s: %4$s.',
),
),
'%s edited %s revision(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited revisions for %3$s, added: %5$s; removed %7$s.',
'%s edited %s project(s), added %s: %s; removed %s: %s.' =>
'%s edited projects, added %4$s; removed %6$s.',
'%s added %s project(s) to %s: %s.' => array(
array(
'%s added a project to %3$s: %4$s.',
'%s added projects to %3$s: %4$s.',
),
),
'%s removed %s project(s) from %s: %s.' => array(
array(
'%s removed a project from %3$s: %4$s.',
'%s removed projects from %3$s: %4$s.',
),
),
'%s edited %s project(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited projects for %3$s, added: %5$s; removed %7$s.',
'%s added %s panel(s): %s.' => array(
array(
'%s added a panel: %3$s.',
'%s added panels: %3$s.',
),
),
'%s removed %s panel(s): %s.' => array(
array(
'%s removed a panel: %3$s.',
'%s removed panels: %3$s.',
),
),
'%s edited %s panel(s), added %s: %s; removed %s: %s.' =>
'%s edited panels, added %4$s; removed %6$s.',
'%s added %s dashboard(s): %s.' => array(
array(
'%s added a dashboard: %3$s.',
'%s added dashboards: %3$s.',
),
),
'%s removed %s dashboard(s): %s.' => array(
array(
'%s removed a dashboard: %3$s.',
'%s removed dashboards: %3$s.',
),
),
'%s edited %s dashboard(s), added %s: %s; removed %s: %s.' =>
'%s edited dashboards, added %4$s; removed %6$s.',
'%s added %s edge(s): %s.' => array(
array(
'%s added an edge: %3$s.',
'%s added edges: %3$s.',
),
),
'%s added %s edge(s) to %s: %s.' => array(
array(
'%s added an edge to %3$s: %4$s.',
'%s added edges to %3$s: %4$s.',
),
),
'%s removed %s edge(s): %s.' => array(
array(
'%s removed an edge: %3$s.',
'%s removed edges: %3$s.',
),
),
'%s removed %s edge(s) from %s: %s.' => array(
array(
'%s removed an edge from %3$s: %4$s.',
'%s removed edges from %3$s: %4$s.',
),
),
'%s edited edge(s), added %s: %s; removed %s: %s.' =>
'%s edited edges, added: %3$s; removed: %5$s.',
'%s edited %s edge(s) for %s, added %s: %s; removed %s: %s.' =>
'%s edited edges for %3$s, added: %5$s; removed %7$s.',
'%d related link(s):' => array(
'Related link:',
'Related links:',
),
'You have %d unpaid invoice(s).' => array(
'You have an unpaid invoice.',
'You have unpaid invoices.',
),
'The configurations differ in the following %s way(s):' => array(
'The configurations differ:',
'The configurations differ in these ways:',
),
'Phabricator is configured with an email domain whitelist (in %s), so '.
'only users with a verified email address at one of these %s '.
'allowed domain(s) will be able to register an account: %s' => array(
array(
'Phabricator is configured with an email domain whitelist (in %s), '.
'so only users with a verified email address at %3$s will be '.
'allowed to register an account.',
'Phabricator is configured with an email domain whitelist (in %s), '.
'so only users with a verified email address at one of these '.
'allowed domains will be able to register an account: %3$s',
),
),
'Show First %d Line(s)' => array(
'Show First Line',
'Show First %d Lines',
),
"\xE2\x96\xB2 Show %d Line(s)" => array(
- "\xE2\x96\xB2 Show %d Line(s)",
- "\xE2\x96\xB2 Show %d Line(s)",
+ "\xE2\x96\xB2 Show Line",
+ "\xE2\x96\xB2 Show %d Lines",
),
'Show All %d Line(s)' => array(
'Show Line',
'Show All %d Lines',
),
"\xE2\x96\xBC Show %d Line(s)" => array(
"\xE2\x96\xBC Show Line",
"\xE2\x96\xBC Show %d Lines",
),
'Show Last %d Line(s)' => array(
'Show Last Line',
'Show Last %d Lines',
),
);
}
}
diff --git a/webroot/rsrc/js/application/differential/ChangesetViewManager.js b/webroot/rsrc/js/application/differential/ChangesetViewManager.js
index a18eeda8f1..36f097cf49 100644
--- a/webroot/rsrc/js/application/differential/ChangesetViewManager.js
+++ b/webroot/rsrc/js/application/differential/ChangesetViewManager.js
@@ -1,291 +1,365 @@
/**
* @provides changeset-view-manager
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* javelin-workflow
* javelin-router
* javelin-behavior-device
* javelin-vector
*/
JX.install('ChangesetViewManager', {
construct : function(node) {
this._node = node;
var data = this._getNodeData();
this._renderURI = data.renderURI;
this._ref = data.ref;
this._whitespace = data.whitespace;
this._renderer = data.renderer;
this._highlight = data.highlight;
this._encoding = data.encoding;
},
members: {
_node: null,
_loaded: false,
_sequence: 0,
_stabilize: false,
_renderURI: null,
_ref: null,
_whitespace: null,
_renderer: null,
_highlight: null,
_encoding: null,
/**
* Has the content of this changeset been loaded?
*
* This method returns `true` if a request has been fired, even if the
* response has not returned yet.
*
* @return bool True if the content has been loaded.
*/
isLoaded: function() {
return this._loaded;
},
/**
* Configure stabilization of the document position on content load.
*
* When we dump the changeset into the document, we can try to stabilize
* the document scroll position so that the user doesn't feel like they
* are jumping around as things load in. This is generally useful when
* populating initial changes.
*
* However, if a user explicitly requests a content load by clicking a
* "Load" link or using the dropdown menu, this stabilization generally
* feels unnatural, so we don't use it in response to explicit user action.
*
* @param bool True to stabilize the next content fill.
* @return this
*/
setStabilize: function(stabilize) {
this._stabilize = stabilize;
return this;
},
/**
* Should this changeset load immediately when the page loads?
*
* Normally, changes load immediately, but if a diff or commit is very
* large we stop doing this and have the user load files explicitly, or
* choose to load everything.
*
* @return bool True if the changeset should load automatically when the
* page loads.
*/
shouldAutoload: function() {
return this._getNodeData().autoload;
},
/**
* Load this changeset, if it isn't already loading.
*
* This fires a request to fill the content of this changeset, provided
* there isn't already a request in flight. To force a reload, use
* @{method:reload}.
*
* @return this
*/
load: function() {
if (this._loaded) {
return this;
}
return this.reload();
},
/**
* Reload the changeset content.
*
* This method always issues a request, even if the content is already
* loading. To load conditionally, use @{method:load}.
*
* @return this
*/
reload: function() {
this._loaded = true;
this._sequence++;
- var params = {
- ref: this._ref,
- whitespace: this._whitespace || '',
- renderer: this.getRenderer() || '',
- highlight: this._highlight || '',
- encoding: this._encoding || ''
- };
+ var params = this._getViewParameters();
var workflow = new JX.Workflow(this._renderURI, params)
.setHandler(JX.bind(this, this._onresponse, this._sequence));
+ this._startContentWorkflow(workflow);
+
+ JX.DOM.setContent(
+ this._getContentFrame(),
+ JX.$N(
+ 'div',
+ {className: 'differential-loading'},
+ 'Loading...'));
+
+ return this;
+ },
+
+ /**
+ * Load missing context in a changeset.
+ *
+ * We do this when the user clicks "Show X Lines". We also expand all of
+ * the missing context when they "Show Entire File".
+ *
+ * @param string Line range specification, like "0-40/0-20".
+ * @param node Row where the context should be rendered after loading.
+ * @param bool True if this is a bulk load of multiple context blocks.
+ * @return this
+ */
+ loadContext: function(range, target, bulk) {
+ var params = this._getViewParameters();
+ params.range = range;
+
+ var container = JX.DOM.scry(target, 'td')[0];
+ // TODO: pht()
+ JX.DOM.setContent(container, 'Loading...');
+ JX.DOM.alterClass(target, 'differential-show-more-loading', true);
+
+ var workflow = new JX.Workflow(this._renderURI, params)
+ .setHandler(JX.bind(this, this._oncontext, target));
+
+ if (bulk) {
+ // If we're loading a bunch of these because the viewer clicked
+ // "Show Entire File Content" or similar, use lower-priority requests
+ // and draw a progress bar.
+ this._startContentWorkflow(workflow);
+ } else {
+ // If this is a single click on a context link, use a higher priority
+ // load without a chrome change.
+ workflow.start();
+ }
+
+ return this;
+ },
+
+ _startContentWorkflow: function(workflow) {
var routable = workflow.getRoutable();
routable
.setPriority(500)
.setType('content')
.setKey(this._getRoutableKey());
JX.Router.getInstance().queue(routable);
+ },
- JX.DOM.setContent(
- this._getContentFrame(),
- JX.$N(
- 'div',
- {className: 'differential-loading'},
- 'Loading...'));
- return this;
+ /**
+ * Receive a response to a context request.
+ */
+ _oncontext: function(target, response) {
+ var table = JX.$H(response.changeset).getNode();
+ var root = target.parentNode;
+ this._moveRows(table, root, target);
+ root.removeChild(target);
+ },
+
+ _moveRows: function(src, dst, before) {
+ var rows = JX.DOM.scry(src, 'tr');
+ for (var ii = 0; ii < rows.length; ii++) {
+
+ // Find the table this <tr /> belongs to. If it's a sub-table, like a
+ // table in an inline comment, don't copy it.
+ if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
+ continue;
+ }
+
+ if (before) {
+ dst.insertBefore(rows[ii], before);
+ } else {
+ dst.appendChild(rows[ii]);
+ }
+ }
},
+ /**
+ * Get parameters which define the current rendering options.
+ */
+ _getViewParameters: function() {
+ return {
+ ref: this._ref,
+ whitespace: this._whitespace || '',
+ renderer: this.getRenderer() || '',
+ highlight: this._highlight || '',
+ encoding: this._encoding || ''
+ };
+ },
/**
* Get the active @{class:JX.Routable} for this changeset.
*
* After issuing a request with @{method:load} or @{method:reload}, you
* can adjust routable settings (like priority) by querying the routable
* with this method. Note that there may not be a current routable.
*
* @return JX.Routable|null Active routable, if one exists.
*/
getRoutable: function() {
return JX.Router.getInstance().getRoutableByKey(this._getRoutableKey());
},
setRenderer: function(renderer) {
this._renderer = renderer;
return this;
},
getRenderer: function() {
if (this._renderer !== null) {
return this._renderer;
}
// NOTE: If you load the page at one device resolution and then resize to
// a different one we don't re-render the diffs, because it's a
// complicated mess and you could lose inline comments, cursor positions,
// etc.
- var renderer = (JX.Device.getDevice() == 'desktop') ? '2up' : '1up';
-
- return renderer;
+ return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up';
},
setEncoding: function(encoding) {
this._encoding = encoding;
return this;
},
getEncoding: function() {
return this._encoding;
},
setHighlight: function(highlight) {
this._highlight = highlight;
return this;
},
getHighlight: function() {
return this._highlight;
},
_getNodeData: function() {
return JX.Stratcom.getData(this._node);
},
_onresponse: function(sequence, response) {
if (sequence != this._sequence) {
// If this isn't the most recent request, ignore it. This normally
// means the user changed view settings between the time the page loaded
// and the content filled.
return;
}
// As we populate the changeset list, we try to hold the document scroll
// position steady, so that, e.g., users who want to leave a comment on a
// diff with a large number of changes don't constantly have the text
// area scrolled off the bottom of the screen until the entire diff loads.
//
// There are two three major cases here:
//
// - If we're near the top of the document, never scroll.
// - If we're near the bottom of the document, always scroll.
// - Otherwise, scroll if the changes were above the midline of the
// viewport.
var target = this._node;
var old_pos = JX.Vector.getScroll();
var old_view = JX.Vector.getViewport();
var old_dim = JX.Vector.getDocument();
// Number of pixels away from the top or bottom of the document which
// count as "nearby".
var sticky = 480;
var near_top = (old_pos.y <= sticky);
var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky));
var target_pos = JX.Vector.getPos(target);
var target_dim = JX.Vector.getDim(target);
var target_mid = (target_pos.y + (target_dim.y / 2));
var view_mid = (old_pos.y + (old_view.y / 2));
var above_mid = (target_mid < view_mid);
var frame = this._getContentFrame();
JX.DOM.setContent(frame, JX.$H(response.changeset));
if (this._stabilize) {
if (!near_top) {
if (near_bot || above_mid) {
// Figure out how much taller the document got.
var delta = (JX.Vector.getDocument().y - old_dim.y);
JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta);
}
}
this._stabilize = false;
}
if (response.coverage) {
for (var k in response.coverage) {
try {
JX.DOM.replace(JX.$(k), JX.$H(response.coverage[k]));
} catch (ignored) {
// Not terribly important.
}
}
}
},
_getContentFrame: function() {
return JX.DOM.find(this._node, 'div', 'changeset-view-content');
},
_getRoutableKey: function() {
return 'changeset-view.' + this._ref + '.' + this._sequence;
}
},
statics: {
getForNode: function(node) {
var data = JX.Stratcom.getData(node);
if (!data.changesetViewManager) {
data.changesetViewManager = new JX.ChangesetViewManager(node);
}
return data.changesetViewManager;
}
}
});
diff --git a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
index 7d24af836f..d47ec93b13 100644
--- a/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
+++ b/webroot/rsrc/js/application/differential/DifferentialInlineCommentEditor.js
@@ -1,275 +1,294 @@
/**
* @provides differential-inline-comment-editor
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* javelin-request
* javelin-workflow
*/
JX.install('DifferentialInlineCommentEditor', {
construct : function(uri) {
this._uri = uri;
},
events : ['done'],
members : {
_uri : null,
_undoText : null,
_skipOverInlineCommentRows : function(node) {
// TODO: Move this semantic information out of class names.
while (node && node.className.indexOf('inline') !== -1) {
node = node.nextSibling;
}
return node;
},
_buildRequestData : function() {
return {
op : this.getOperation(),
on_right : this.getOnRight(),
id : this.getID(),
number : this.getLineNumber(),
is_new : this.getIsNew(),
length : this.getLength(),
changeset : this.getChangeset(),
text : this.getText() || ''
};
},
_draw : function(content, exact_row) {
var row = this.getRow();
var table = this.getTable();
var target = exact_row ? row : this._skipOverInlineCommentRows(row);
+ function copyRows(dst, src, before) {
+ var rows = JX.DOM.scry(src, 'tr');
+ for (var ii = 0; ii < rows.length; ii++) {
+
+ // Find the table this <tr /> belongs to. If it's a sub-table, like a
+ // table in an inline comment, don't copy it.
+ if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
+ continue;
+ }
+
+ if (before) {
+ dst.insertBefore(rows[ii], before);
+ } else {
+ dst.appendChild(rows[ii]);
+ }
+ }
+ return rows;
+ }
+
return copyRows(table, content, target);
},
_removeUndoLink : function() {
var rows = JX.DifferentialInlineCommentEditor._undoRows;
if (rows) {
for (var ii = 0; ii < rows.length; ii++) {
JX.DOM.remove(rows[ii]);
}
}
},
_undo : function() {
this._removeUndoLink();
this.setText(this._undoText);
this.start();
},
_registerUndoListener : function() {
if (!JX.DifferentialInlineCommentEditor._activeEditor) {
JX.Stratcom.listen(
'click',
'differential-inline-comment-undo',
function(e) {
JX.DifferentialInlineCommentEditor._activeEditor._undo();
e.kill();
});
}
JX.DifferentialInlineCommentEditor._activeEditor = this;
},
_setRowState : function(state) {
var is_hidden = (state == 'hidden');
var is_loading = (state == 'loading');
var row = this.getRow();
JX.DOM.alterClass(row, 'differential-inline-hidden', is_hidden);
JX.DOM.alterClass(row, 'differential-inline-loading', is_loading);
},
_didContinueWorkflow : function(response) {
var drawn = this._draw(JX.$H(response).getNode());
var op = this.getOperation();
if (op == 'edit') {
this._setRowState('hidden');
}
JX.DOM.find(
drawn[0],
'textarea',
'differential-inline-comment-edit-textarea').focus();
var oncancel = JX.bind(this, function(e) {
e.kill();
this._didCancelWorkflow();
if (op == 'edit') {
this._setRowState('visible');
}
JX.DOM.remove(drawn[0]);
});
JX.DOM.listen(drawn[0], 'click', 'inline-edit-cancel', oncancel);
var onsubmit = JX.bind(this, function(e) {
e.kill();
JX.Workflow.newFromForm(e.getTarget())
.setHandler(JX.bind(this, function(response) {
JX.DOM.remove(drawn[0]);
if (op == 'edit') {
this._setRowState('visible');
}
this._didCompleteWorkflow(response);
}))
.start();
JX.DOM.alterClass(drawn[0], 'differential-inline-loading', true);
});
JX.DOM.listen(
drawn[0],
['submit', 'didSyntheticSubmit'],
'inline-edit-form',
onsubmit);
},
_didCompleteWorkflow : function(response) {
var op = this.getOperation();
// We don't get any markup back if the user deletes a comment, or saves
// an empty comment (which effects a delete).
if (response.markup) {
this._draw(JX.$H(response.markup).getNode());
}
// These operations remove the old row (edit adds a new row first).
var remove_old = (op == 'edit' || op == 'delete');
if (remove_old) {
JX.DOM.remove(this.getRow());
var other_rows = this.getOtherRows();
for(var i = 0; i < other_rows.length; ++i) {
JX.DOM.remove(other_rows[i]);
}
}
// Once the user saves something, get rid of the 'undo' option. A
// particular case where we need this is saving a delete, when we might
// otherwise leave around an 'undo' for an earlier edit to the same
// comment.
this._removeUndoLink();
JX.Stratcom.invoke('differential-inline-comment-update');
this.invoke('done');
},
_didCancelWorkflow : function() {
this.invoke('done');
var op = this.getOperation();
if (op == 'delete') {
// No undo for delete, we prompt the user explicitly.
return;
}
var textarea;
try {
textarea = JX.DOM.find(
document.body, // TODO: use getDialogRootNode() when available
'textarea',
'differential-inline-comment-edit-textarea');
} catch (ex) {
// The close handler is called whenever the dialog closes, even if the
// user closed it by completing the workflow with "Save". The
// JX.Workflow API should probably be refined to allow programmatic
// distinction of close caused by 'cancel' vs 'submit'. Testing for
// presence of the textarea serves as a proxy for detecting a 'cancel'.
return;
}
var text = textarea.value;
// If the user hasn't edited the text (i.e., no change from original for
// 'edit' or no text at all), don't offer them an undo.
if (text == this.getOriginalText() || text === '') {
return;
}
// Save the text so we can 'undo' back to it.
this._undoText = text;
var templates = this.getTemplates();
var template = this.getOnRight() ? templates.r : templates.l;
template = JX.$H(template).getNode();
// NOTE: Operation order matters here; we can't remove anything until
// after we draw the new rows because _draw uses the old rows to figure
// out where to place the comment.
// We use 'exact_row' to put the "undo" text directly above the affected
// comment.
var exact_row = true;
var rows = this._draw(template, exact_row);
this._removeUndoLink();
JX.DifferentialInlineCommentEditor._undoRows = rows;
},
start : function() {
this._registerUndoListener();
var data = this._buildRequestData();
var op = this.getOperation();
if (op == 'delete') {
this._setRowState('loading');
var oncomplete = JX.bind(this, this._didCompleteWorkflow);
var onclose = JX.bind(this, function() {
this._setRowState('visible');
this._didCancelWorkflow();
});
new JX.Workflow(this._uri, data)
.setHandler(oncomplete)
.setCloseHandler(onclose)
.start();
} else {
var handler = JX.bind(this, this._didContinueWorkflow);
if (op == 'edit') {
this._setRowState('loading');
}
new JX.Request(this._uri, handler)
.setData(data)
.send();
}
return this;
}
},
statics : {
/**
* Global refernece to the 'undo' rows currently rendered in the document.
*/
_undoRows : null,
/**
* Global listener for the 'undo' click associated with the currently
* displayed 'undo' link. When an editor is start()ed, it becomes the active
* editor.
*/
_activeEditor : null
},
properties : {
operation : null,
row : null,
otherRows: [],
table : null,
onRight : null,
ID : null,
lineNumber : null,
changeset : null,
length : null,
isNew : null,
text : null,
templates : null,
originalText : null
}
});
diff --git a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js
index bc2e015d53..434a057c07 100644
--- a/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js
+++ b/webroot/rsrc/js/application/differential/behavior-dropdown-menus.js
@@ -1,255 +1,254 @@
/**
* @provides javelin-behavior-differential-dropdown-menus
* @requires javelin-behavior
* javelin-dom
* javelin-util
* javelin-stratcom
* javelin-workflow
* phuix-dropdown-menu
* phuix-action-list-view
* phuix-action-view
* phabricator-phtize
* changeset-view-manager
*/
JX.behavior('differential-dropdown-menus', function(config) {
var pht = JX.phtize(config.pht);
function show_more(container) {
+ var view = JX.ChangesetViewManager.getForNode(container);
+
var nodes = JX.DOM.scry(container, 'tr', 'context-target');
for (var ii = 0; ii < nodes.length; ii++) {
var show = JX.DOM.scry(nodes[ii], 'a', 'show-more');
for (var jj = 0; jj < show.length; jj++) {
- if (JX.Stratcom.getData(show[jj]).type != 'all') {
+ var data = JX.Stratcom.getData(show[jj]);
+ if (data.type != 'all') {
continue;
}
- var event_data = {
- context : nodes[ii],
- show : show[jj]
- };
- JX.Stratcom.invoke('differential-reveal-context', null, event_data);
+ view.loadContext(data.range, nodes[ii], true);
}
}
}
JX.Stratcom.listen(
'click',
'differential-reveal-all',
function(e) {
var containers = JX.DOM.scry(
JX.$('differential-review-stage'),
'div',
'differential-changeset');
for (var i=0; i < containers.length; i++) {
show_more(containers[i]);
}
e.kill();
});
var buildmenu = function(e) {
var button = e.getNode('differential-view-options');
var data = JX.Stratcom.getData(button);
if (data.menu) {
return;
}
e.prevent();
var changeset = JX.DOM.findAbove(
button,
'div',
'differential-changeset');
var view = JX.ChangesetViewManager.getForNode(changeset);
var menu = new JX.PHUIXDropdownMenu(button);
var list = new JX.PHUIXActionListView();
var add_link = function(icon, name, href, local) {
if (!href) {
return;
}
var link = new JX.PHUIXActionView()
.setIcon(icon)
.setName(name)
.setHref(href)
.setHandler(function(e) {
if (local) {
window.location.assign(href);
} else {
window.open(href);
}
menu.close();
e.prevent();
});
list.addItem(link);
return link;
};
var reveal_item = new JX.PHUIXActionView()
.setIcon('fa-eye');
list.addItem(reveal_item);
var visible_item = new JX.PHUIXActionView()
.setHandler(function(e) {
var diff = JX.DOM.scry(
JX.$(data.containerID),
'table',
'differential-diff');
JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff});
e.prevent();
menu.close();
});
list.addItem(visible_item);
add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI);
add_link('fa-file-o', pht('View Standalone'), data.standaloneURI);
var up_item = new JX.PHUIXActionView()
.setHandler(function(e) {
if (view.isLoaded()) {
var renderer = view.getRenderer();
if (renderer == '1up') {
renderer = '2up';
} else {
renderer = '1up';
}
view.setRenderer(renderer);
}
view.reload();
e.prevent();
menu.close();
});
list.addItem(up_item);
var encoding_item = new JX.PHUIXActionView()
.setIcon('fa-font')
.setName(pht('Change Text Encoding...'))
.setHandler(function(e) {
var params = {
encoding: view.getEncoding()
};
new JX.Workflow('/services/encoding/', params)
.setHandler(function(r) {
view.setEncoding(r.encoding);
view.reload();
})
.start();
e.prevent();
menu.close();
});
list.addItem(encoding_item);
var highlight_item = new JX.PHUIXActionView()
.setIcon('fa-sun-o')
.setName(pht('Highlight As...'))
.setHandler(function(e) {
var params = {
highlight: view.getHighlight()
};
new JX.Workflow('/services/highlight/', params)
.setHandler(function(r) {
view.setHighlight(r.highlight);
view.reload();
})
.start();
e.prevent();
menu.close();
});
list.addItem(highlight_item);
add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI);
add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI);
add_link('fa-pencil', pht('Open in Editor'), data.editor, true);
add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure);
menu.setContent(list.getNode());
menu.listen('open', function() {
// When the user opens the menu, check if there are any "Show More"
// links in the changeset body. If there aren't, disable the "Show
// Entire File" menu item since it won't change anything.
var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more');
if (nodes.length) {
reveal_item
.setDisabled(false)
.setName(pht('Show Entire File'))
.setIcon('fa-file-o')
.setHandler(function(e) {
show_more(JX.$(data.containerID));
e.prevent();
menu.close();
});
} else {
reveal_item
.setDisabled(true)
.setIcon('fa-file')
.setName(pht('Entire File Shown'))
.setHandler(function(e) { e.prevent(); });
}
encoding_item.setDisabled(!view.isLoaded());
highlight_item.setDisabled(!view.isLoaded());
if (view.isLoaded()) {
if (view.getRenderer() == '2up') {
up_item
.setIcon('fa-list-alt')
.setName(pht('View Unified'));
} else {
up_item
.setIcon('fa-files-o')
.setName(pht('View Side-by-Side'));
}
} else {
up_item
.setIcon('fa-refresh')
.setName(pht('Load Changes'));
}
visible_item
.setDisabled(true)
.setIcon('fa-expand')
.setName(pht('Can\'t Toggle Unloaded File'));
var diffs = JX.DOM.scry(
JX.$(data.containerID),
'table',
'differential-diff');
if (diffs.length > 1) {
JX.$E(
'More than one node with sigil "differential-diff" was found in "'+
data.containerID+'."');
} else if (diffs.length == 1) {
var diff = diffs[0];
visible_item.setDisabled(false);
if (JX.Stratcom.getData(diff).hidden) {
visible_item
.setName(pht('Expand File'))
.setIcon('fa-expand');
} else {
visible_item
.setName(pht('Collapse File'))
.setIcon('fa-compress');
}
} else {
// Do nothing when there is no diff shown in the table. For example,
// the file is binary.
}
});
data.menu = menu;
menu.open();
};
JX.Stratcom.listen('click', 'differential-view-options', buildmenu);
});
diff --git a/webroot/rsrc/js/application/differential/behavior-show-more.js b/webroot/rsrc/js/application/differential/behavior-show-more.js
index 3d85674542..4ef4964f7c 100644
--- a/webroot/rsrc/js/application/differential/behavior-show-more.js
+++ b/webroot/rsrc/js/application/differential/behavior-show-more.js
@@ -1,71 +1,27 @@
/**
* @provides javelin-behavior-differential-show-more
* @requires javelin-behavior
* javelin-dom
* javelin-workflow
* javelin-util
* javelin-stratcom
+ * changeset-view-manager
*/
-JX.behavior('differential-show-more', function(config) {
-
- function onresponse(context, response) {
- var table = JX.$H(response.changeset).getNode();
- var root = context.parentNode;
- copyRows(root, table, context);
- root.removeChild(context);
- }
+JX.behavior('differential-show-more', function() {
JX.Stratcom.listen(
'click',
'show-more',
function(e) {
- var event_data = {
- context : e.getNodes()['context-target'],
- show : e.getNodes()['show-more']
- };
-
- JX.Stratcom.invoke('differential-reveal-context', null, event_data);
e.kill();
- });
-
- JX.Stratcom.listen(
- 'differential-reveal-context',
- null,
- function(e) {
- var context = e.getData().context;
- var data = JX.Stratcom.getData(e.getData().show);
-
- var container = JX.DOM.scry(context, 'td')[0];
- JX.DOM.setContent(container, 'Loading...');
- JX.DOM.alterClass(context, 'differential-show-more-loading', true);
- if (!data.whitespace) {
- data.whitespace = config.whitespace;
- }
+ var changeset = e.getNode('differential-changeset');
+ var view = JX.ChangesetViewManager.getForNode(changeset);
+ var data = e.getNodeData('show-more');
+ var target = e.getNode('context-target');
- new JX.Workflow(config.uri, data)
- .setHandler(JX.bind(null, onresponse, context))
- .start();
+ view.loadContext(data.range, target);
});
});
-
-function copyRows(dst, src, before) {
- var rows = JX.DOM.scry(src, 'tr');
- for (var ii = 0; ii < rows.length; ii++) {
-
- // Find the table this <tr /> belongs to. If it's a sub-table, like a
- // table in an inline comment, don't copy it.
- if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
- continue;
- }
-
- if (before) {
- dst.insertBefore(rows[ii], before);
- } else {
- dst.appendChild(rows[ii]);
- }
- }
- return rows;
-}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 16:26 (2 w, 6 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126461
Default Alt Text
(273 KB)

Event Timeline