Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892898
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
254 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 7194b5c1ed..3f852d6048 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2442 +1,2442 @@
<?php
/**
* This file is automatically generated. Use 'bin/celerity map' to rebuild it.
*
* @generated
*/
return array(
'names' => array(
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'a560707d',
'core.pkg.js' => '845355f4',
'dark-console.pkg.js' => '187792c2',
- 'differential.pkg.css' => 'b042ee8b',
- 'differential.pkg.js' => 'e0600220',
+ 'differential.pkg.css' => '319dca29',
+ 'differential.pkg.js' => 'ccf7bdca',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => 'c9308721',
'rsrc/audio/basic/alert.mp3' => '17889334',
'rsrc/audio/basic/bing.mp3' => 'a817a0c3',
'rsrc/audio/basic/pock.mp3' => '0fa843d0',
'rsrc/audio/basic/tap.mp3' => '02d16994',
'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2',
'rsrc/css/aphront/dialog-view.css' => '6f4ea703',
'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2',
'rsrc/css/aphront/panel-view.css' => '46923d46',
'rsrc/css/aphront/phabricator-nav-view.css' => '423f92cc',
'rsrc/css/aphront/table-view.css' => '0bb61df1',
'rsrc/css/aphront/tokenizer.css' => '34e2a838',
'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
'rsrc/css/aphront/typeahead.css' => '8779483d',
'rsrc/css/application/almanac/almanac.css' => '2e050f4f',
'rsrc/css/application/auth/auth.css' => 'c2f23d74',
'rsrc/css/application/base/main-menu-view.css' => 'bcec20f0',
'rsrc/css/application/base/notification-menu.css' => '4df1ee30',
'rsrc/css/application/base/phui-theme.css' => '35883b37',
'rsrc/css/application/base/standard-page-view.css' => 'a374f94c',
'rsrc/css/application/chatlog/chatlog.css' => 'abdc76ee',
'rsrc/css/application/conduit/conduit-api.css' => 'ce2cfc41',
'rsrc/css/application/config/config-options.css' => '16c920ae',
'rsrc/css/application/config/config-template.css' => '20babf50',
'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
'rsrc/css/application/conpherence/color.css' => 'b17746b0',
'rsrc/css/application/conpherence/durable-column.css' => '2d57072b',
'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e',
'rsrc/css/application/conpherence/menu.css' => '67f4680d',
'rsrc/css/application/conpherence/message-pane.css' => 'd244db1e',
'rsrc/css/application/conpherence/notification.css' => '6a3d4e58',
'rsrc/css/application/conpherence/participant-pane.css' => '69e0058a',
'rsrc/css/application/conpherence/transaction.css' => '3a3f5e7e',
'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579',
'rsrc/css/application/countdown/timer.css' => 'bff8012f',
'rsrc/css/application/daemon/bulk-job.css' => '73af99f5',
'rsrc/css/application/dashboard/dashboard.css' => '5a205b9d',
'rsrc/css/application/diff/diff-tree-view.css' => 'e2d3e222',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => 'df3afa61',
'rsrc/css/application/differential/core.css' => '7300a73e',
- 'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
+ 'rsrc/css/application/differential/phui-inline-comment.css' => 'd5749acc',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
'rsrc/css/application/differential/revision-history.css' => '8aa3eac5',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9',
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0',
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
'rsrc/css/application/flag/flag.css' => '2b77be8d',
'rsrc/css/application/harbormaster/harbormaster.css' => '8dfe16b2',
'rsrc/css/application/herald/herald-test.css' => 'e004176f',
'rsrc/css/application/herald/herald.css' => '648d39e2',
'rsrc/css/application/maniphest/report.css' => '3d53188b',
'rsrc/css/application/maniphest/task-edit.css' => '272daa84',
'rsrc/css/application/maniphest/task-summary.css' => '61d1667e',
'rsrc/css/application/objectselector/object-selector.css' => 'ee77366f',
'rsrc/css/application/owners/owners-path-editor.css' => 'fa7c13ef',
'rsrc/css/application/paste/paste.css' => 'b37bcd38',
'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf',
'rsrc/css/application/people/people-profile.css' => '2ea2daa1',
'rsrc/css/application/phame/phame.css' => 'bb442327',
'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2',
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
'rsrc/css/application/phortune/phortune.css' => '508a1a5e',
'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
'rsrc/css/application/policy/policy.css' => 'ceb56a08',
'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
'rsrc/css/application/project/project-card-view.css' => '4e7371cd',
'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9',
'rsrc/css/application/project/project-view.css' => '567858b3',
'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db',
'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07',
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '0ac1ea31',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => 'bce37359',
'rsrc/css/application/search/application-search-view.css' => '0f7c06d8',
'rsrc/css/application/search/search-results.css' => '9ea70ace',
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
'rsrc/css/application/uiexample/example.css' => 'b4795059',
'rsrc/css/core/core.css' => '1b29ed61',
'rsrc/css/core/remarkup.css' => 'c286eaef',
'rsrc/css/core/syntax.css' => '548567f6',
'rsrc/css/core/z-index.css' => '612e9522',
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
'rsrc/css/font/font-awesome.css' => '3883938a',
'rsrc/css/font/font-lato.css' => '23631304',
'rsrc/css/font/phui-font-icon-base.css' => '303c9b87',
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
'rsrc/css/phui/button/phui-button.css' => 'ea704902',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '9597d706',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35',
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/phui-action-list.css' => '1b0085b2',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf',
'rsrc/css/phui/phui-curtain-object-ref-view.css' => '12404744',
'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6',
'rsrc/css/phui/phui-document-pro.css' => 'b9613a10',
'rsrc/css/phui/phui-document-summary.css' => 'b068eed1',
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '1f177cb7',
'rsrc/css/phui/phui-formation-view.css' => 'd2dec8ed',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
'rsrc/css/phui/phui-info-view.css' => 'a10a909b',
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '2f253c22',
'rsrc/css/phui/phui-object-box.css' => 'b8d7eea0',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
'rsrc/css/phui/phui-property-list-view.css' => '5adf7078',
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
'rsrc/css/phui/phui-status.css' => 'e5ff8be0',
'rsrc/css/phui/phui-tag-view.css' => '8519160a',
'rsrc/css/phui/phui-timeline-view.css' => '2d32d7a9',
'rsrc/css/phui/phui-two-column-view.css' => 'f96d319f',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '913441b6',
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
'rsrc/css/sprite-login.css' => '18b368a6',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/externals/d3/d3.min.js' => '9d068042',
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '70983df0',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'cd02f93b',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '351fd46a',
'rsrc/externals/font/lato/lato-bold.eot' => '7367aa5e',
'rsrc/externals/font/lato/lato-bold.svg' => '681aa4f5',
'rsrc/externals/font/lato/lato-bold.ttf' => '66d3c296',
'rsrc/externals/font/lato/lato-bold.woff' => '89d9fba7',
'rsrc/externals/font/lato/lato-bold.woff2' => '389fcdb1',
'rsrc/externals/font/lato/lato-bolditalic.eot' => '03eeb4da',
'rsrc/externals/font/lato/lato-bolditalic.svg' => 'f56fa11c',
'rsrc/externals/font/lato/lato-bolditalic.ttf' => '9c3aec21',
'rsrc/externals/font/lato/lato-bolditalic.woff' => 'bfbd0616',
'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'bc7d1274',
'rsrc/externals/font/lato/lato-italic.eot' => '7db5b247',
'rsrc/externals/font/lato/lato-italic.svg' => 'b1ae496f',
'rsrc/externals/font/lato/lato-italic.ttf' => '43eed813',
'rsrc/externals/font/lato/lato-italic.woff' => 'c28975e1',
'rsrc/externals/font/lato/lato-italic.woff2' => 'fffc0d8c',
'rsrc/externals/font/lato/lato-regular.eot' => '06e0c291',
'rsrc/externals/font/lato/lato-regular.svg' => '3ad95f53',
'rsrc/externals/font/lato/lato-regular.ttf' => 'e2e9c398',
'rsrc/externals/font/lato/lato-regular.woff' => '0b13d332',
'rsrc/externals/font/lato/lato-regular.woff2' => '8f846797',
'rsrc/externals/javelin/core/Event.js' => 'c03f2fb4',
'rsrc/externals/javelin/core/Stratcom.js' => '0889b835',
'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '048472d2',
'rsrc/externals/javelin/core/__tests__/install.js' => '14a7e671',
'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'a28464bb',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e29a4354',
'rsrc/externals/javelin/core/init.js' => '98e6504a',
'rsrc/externals/javelin/core/init_node.js' => '16961339',
'rsrc/externals/javelin/core/install.js' => '5902260c',
'rsrc/externals/javelin/core/util.js' => 'edb4d8c9',
'rsrc/externals/javelin/docs/Base.js' => '5a401d7d',
'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62',
'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9',
'rsrc/externals/javelin/ext/fx/FX.js' => '34450586',
'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => '202a2e85',
'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '1c850a26',
'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '72960bc1',
'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '225bbb98',
'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => '6cfa0008',
'rsrc/externals/javelin/ext/view/HTMLView.js' => 'f8c4e135',
'rsrc/externals/javelin/ext/view/View.js' => '289bf236',
'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '876506b6',
'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => 'a9942052',
'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '9aae2b66',
'rsrc/externals/javelin/ext/view/ViewVisitor.js' => '308f9fe4',
'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => '6e50a13f',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'd284be5d',
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => 'a9f35511',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '3a1b81f6',
'rsrc/externals/javelin/lib/Cookie.js' => '05d290ef',
'rsrc/externals/javelin/lib/DOM.js' => '94681e22',
'rsrc/externals/javelin/lib/History.js' => '030b4f7a',
'rsrc/externals/javelin/lib/JSON.js' => '541f81c3',
'rsrc/externals/javelin/lib/Leader.js' => '0d2490ce',
'rsrc/externals/javelin/lib/Mask.js' => '7c4d8998',
'rsrc/externals/javelin/lib/Quicksand.js' => 'd3799cb4',
'rsrc/externals/javelin/lib/Request.js' => '84e6891f',
'rsrc/externals/javelin/lib/Resource.js' => '740956e1',
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
'rsrc/externals/javelin/lib/Sound.js' => 'd4cc2d2a',
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
'rsrc/externals/javelin/lib/Workflow.js' => '945ff654',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb',
'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '22ee68a5',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '23387297',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '5a79f6c3',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '8badee71',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '80bff3af',
'rsrc/favicons/favicon-16x16.png' => '4c51a03a',
'rsrc/favicons/mask-icon.svg' => 'db699fe1',
'rsrc/image/BFCFDA.png' => '74b5c88b',
'rsrc/image/actions/edit.png' => 'fd987dff',
'rsrc/image/avatar.png' => '0d17c6c4',
'rsrc/image/checker_dark.png' => '7fc8fa7b',
'rsrc/image/checker_light.png' => '3157a202',
'rsrc/image/checker_lighter.png' => 'c45928c1',
'rsrc/image/chevron-in.png' => '1aa2f88f',
'rsrc/image/chevron-out.png' => 'c815e272',
'rsrc/image/controls/checkbox-checked.png' => '1770d7a0',
'rsrc/image/controls/checkbox-unchecked.png' => 'e1deba0a',
'rsrc/image/d5d8e1.png' => '6764616e',
'rsrc/image/darkload.gif' => '5bd41a89',
'rsrc/image/divot.png' => '0fbe2453',
'rsrc/image/examples/hero.png' => '5d8c4b21',
'rsrc/image/grippy_texture.png' => 'a7d222b5',
'rsrc/image/icon/fatcow/arrow_branch.png' => '98149d9f',
'rsrc/image/icon/fatcow/arrow_merge.png' => 'e142f4f8',
'rsrc/image/icon/fatcow/calendar_edit.png' => '5ff44a08',
'rsrc/image/icon/fatcow/document_black.png' => 'd3515fa5',
'rsrc/image/icon/fatcow/flag_blue.png' => '54db2e5c',
'rsrc/image/icon/fatcow/flag_finish.png' => '2953a51b',
'rsrc/image/icon/fatcow/flag_ghost.png' => '7d9ada92',
'rsrc/image/icon/fatcow/flag_green.png' => '010f7161',
'rsrc/image/icon/fatcow/flag_orange.png' => '6c384ca5',
'rsrc/image/icon/fatcow/flag_pink.png' => '11ac6b12',
'rsrc/image/icon/fatcow/flag_purple.png' => 'c4f423a4',
'rsrc/image/icon/fatcow/flag_red.png' => '9e6d8817',
'rsrc/image/icon/fatcow/flag_yellow.png' => '906733f4',
'rsrc/image/icon/fatcow/key_question.png' => 'c10c26db',
'rsrc/image/icon/fatcow/link.png' => '8edbf327',
'rsrc/image/icon/fatcow/page_white_edit.png' => '17ef5625',
'rsrc/image/icon/fatcow/page_white_put.png' => '82430c91',
'rsrc/image/icon/fatcow/source/conduit.png' => '5b55130c',
'rsrc/image/icon/fatcow/source/email.png' => '8a32b77f',
'rsrc/image/icon/fatcow/source/fax.png' => '8bc2a49b',
'rsrc/image/icon/fatcow/source/mobile.png' => '0a918412',
'rsrc/image/icon/fatcow/source/tablet.png' => 'fc50b050',
'rsrc/image/icon/fatcow/source/web.png' => '70433af3',
'rsrc/image/icon/subscribe.png' => '07ef454e',
'rsrc/image/icon/tango/attachment.png' => 'bac9032d',
'rsrc/image/icon/tango/edit.png' => 'e6296206',
'rsrc/image/icon/tango/go-down.png' => '0b903712',
'rsrc/image/icon/tango/log.png' => '86b6a6f4',
'rsrc/image/icon/tango/upload.png' => '3fe6b92d',
'rsrc/image/icon/unsubscribe.png' => 'db04378a',
'rsrc/image/lightblue-header.png' => 'e6d483c6',
'rsrc/image/logo/light-eye.png' => '72337472',
'rsrc/image/main_texture.png' => '894d03c4',
'rsrc/image/menu_texture.png' => '896c9ade',
'rsrc/image/people/harding.png' => '95b2db63',
'rsrc/image/people/jefferson.png' => 'e883a3a2',
'rsrc/image/people/lincoln.png' => 'be2c07c5',
'rsrc/image/people/mckinley.png' => '6af510a0',
'rsrc/image/people/taft.png' => 'b15ab07e',
'rsrc/image/people/user0.png' => '4bc64b40',
'rsrc/image/people/user1.png' => '8063f445',
'rsrc/image/people/user2.png' => 'd28246c0',
'rsrc/image/people/user3.png' => 'fb1ac12d',
'rsrc/image/people/user4.png' => 'fe4fac8f',
'rsrc/image/people/user5.png' => '3d07065c',
'rsrc/image/people/user6.png' => 'e4bd47c8',
'rsrc/image/people/user7.png' => '71d8fe8b',
'rsrc/image/people/user8.png' => '85f86bf7',
'rsrc/image/people/user9.png' => '523db8aa',
'rsrc/image/people/washington.png' => '86159e68',
'rsrc/image/phrequent_active.png' => 'de66dc50',
'rsrc/image/phrequent_inactive.png' => '79c61baf',
'rsrc/image/resize.png' => '9cc83373',
'rsrc/image/sprite-login-X2.png' => '604545f6',
'rsrc/image/sprite-login.png' => '7a001a9a',
'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
'rsrc/image/sprite-tokens.png' => 'bede2580',
'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
'rsrc/image/texture/dark-menu-hover.png' => '390a4fa1',
'rsrc/image/texture/dark-menu.png' => '542f699c',
'rsrc/image/texture/grip.png' => 'bc80753a',
'rsrc/image/texture/panel-header-gradient.png' => '65004dbf',
'rsrc/image/texture/phlnx-bg.png' => '6c9cd31d',
'rsrc/image/texture/pholio-background.gif' => '84910bfc',
'rsrc/image/texture/table_header.png' => '7652d1ad',
'rsrc/image/texture/table_header_hover.png' => '12ea5236',
'rsrc/image/texture/table_header_tall.png' => '5cc420c4',
'rsrc/js/application/aphlict/Aphlict.js' => '022516b4',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'e9a2940f',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4e61fa88',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'c3703a16',
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '070679fe',
'rsrc/js/application/calendar/behavior-day-view.js' => '727a5a61',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '0b1bc990',
'rsrc/js/application/calendar/behavior-month-view.js' => '158c64e0',
'rsrc/js/application/config/behavior-reorder-fields.js' => '2539f834',
'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'aec8e38c',
'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '91befbcc',
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'fa6f30b2',
'rsrc/js/application/conpherence/behavior-menu.js' => '8c2ed2bf',
'rsrc/js/application/conpherence/behavior-participant-pane.js' => '43ba89a2',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '4ae58b5a',
'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '5a6f6a06',
'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0',
'rsrc/js/application/countdown/timer.js' => '6a162524',
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '9c01e364',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
- 'rsrc/js/application/diff/DiffChangeset.js' => 'd721533b',
- 'rsrc/js/application/diff/DiffChangesetList.js' => '8b0eab21',
- 'rsrc/js/application/diff/DiffInline.js' => '734d3c33',
+ 'rsrc/js/application/diff/DiffChangeset.js' => 'bfdae878',
+ 'rsrc/js/application/diff/DiffChangesetList.js' => 'a00bf62d',
+ 'rsrc/js/application/diff/DiffInline.js' => 'b00168c1',
'rsrc/js/application/diff/DiffPathView.js' => '8207abf9',
'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b',
'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd',
'rsrc/js/application/differential/behavior-populate.js' => 'b86ef6c2',
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89',
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
'rsrc/js/application/fact/Chart.js' => '52e3ff03',
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
'rsrc/js/application/herald/HeraldRuleEditor.js' => '2633bef7',
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '48fe33d0',
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '3eed1f2b',
'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '5aa1544e',
'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '02cb4398',
'rsrc/js/application/phortune/behavior-test-payment-form.js' => '4a7fb02b',
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
'rsrc/js/application/projects/WorkboardBoard.js' => 'b46d88c5',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '84f82dad',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
'rsrc/js/application/projects/behavior-project-boards.js' => '58cb6a88',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c',
'rsrc/js/application/repository/repository-crossreference.js' => '6337cf26',
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => '4842f137',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => '0ad8d31f',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8b5c7d65',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e',
'rsrc/js/application/trigger/TriggerRule.js' => '41b7b4f6',
'rsrc/js/application/trigger/TriggerRuleControl.js' => '5faf27b9',
'rsrc/js/application/trigger/TriggerRuleEditor.js' => 'b49fd60c',
'rsrc/js/application/trigger/TriggerRuleType.js' => '4feea7d3',
'rsrc/js/application/trigger/trigger-rule-editor.js' => '398fdf13',
'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '70245195',
'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '7b139193',
'rsrc/js/application/uiexample/gesture-example.js' => '242dedd0',
'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
'rsrc/js/core/Busy.js' => '5202e831',
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/Hovercard.js' => '074f0783',
'rsrc/js/core/KeyboardShortcut.js' => '1a844c06',
'rsrc/js/core/KeyboardShortcutManager.js' => '81debc48',
'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
'rsrc/js/core/Notification.js' => 'a9b91e3f',
'rsrc/js/core/Prefab.js' => '5793d835',
'rsrc/js/core/ShapedRequest.js' => '995f5102',
'rsrc/js/core/TextAreaUtils.js' => 'f340a484',
'rsrc/js/core/Title.js' => '43bc9360',
'rsrc/js/core/ToolTip.js' => '83754533',
'rsrc/js/core/behavior-audio-source.js' => '3dc5ad43',
'rsrc/js/core/behavior-autofocus.js' => '65bb0011',
'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6',
'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308',
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
'rsrc/js/core/behavior-device.js' => '0cf79f45',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb',
'rsrc/js/core/behavior-form.js' => '55d7b788',
'rsrc/js/core/behavior-gesture.js' => 'b58d1a2a',
'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a',
'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b',
'rsrc/js/core/behavior-history-install.js' => '6a1583a8',
'rsrc/js/core/behavior-hovercard.js' => '6c379000',
'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf',
'rsrc/js/core/behavior-line-linker.js' => '590e6527',
'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4',
'rsrc/js/core/behavior-object-selector.js' => '98ef467f',
'rsrc/js/core/behavior-oncopy.js' => 'da8f5259',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '54262396',
'rsrc/js/core/behavior-read-only-warning.js' => 'b9109f8f',
'rsrc/js/core/behavior-redirect.js' => '407ee861',
'rsrc/js/core/behavior-refresh-csrf.js' => '46116c01',
'rsrc/js/core/behavior-remarkup-load-image.js' => '202bfa3f',
'rsrc/js/core/behavior-remarkup-preview.js' => 'd8a86cfb',
'rsrc/js/core/behavior-reorder-applications.js' => 'aa371860',
'rsrc/js/core/behavior-reveal-content.js' => 'b105a3a6',
'rsrc/js/core/behavior-scrollbar.js' => '92388bae',
'rsrc/js/core/behavior-search-typeahead.js' => '1cb7d027',
'rsrc/js/core/behavior-select-content.js' => 'e8240b50',
'rsrc/js/core/behavior-select-on-click.js' => '66365ee2',
'rsrc/js/core/behavior-setup-check-https.js' => '01384686',
'rsrc/js/core/behavior-time-typeahead.js' => '5803b9e7',
'rsrc/js/core/behavior-toggle-class.js' => '32db8374',
'rsrc/js/core/behavior-tokenizer.js' => '3b4899b0',
'rsrc/js/core/behavior-tooltip.js' => '73ecc1f8',
'rsrc/js/core/behavior-user-menu.js' => '60cd9241',
'rsrc/js/core/behavior-watch-anchor.js' => 'a77e2cbd',
'rsrc/js/core/behavior-workflow.js' => '9623adc1',
'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402',
'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73',
'rsrc/js/core/darkconsole/behavior-dark-console.js' => '457f4d16',
'rsrc/js/core/phtize.js' => '2f1db1ed',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '5cf0501a',
'rsrc/js/phui/behavior-phui-file-upload.js' => 'e150bd50',
'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4',
'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
'rsrc/js/phuix/PHUIXActionView.js' => 'a8f573a9',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '2fbe234d',
'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'b557770a',
'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
'rsrc/js/phuix/PHUIXFormationColumnView.js' => '4bcc1f78',
'rsrc/js/phuix/PHUIXFormationFlankView.js' => '6648270a',
'rsrc/js/phuix/PHUIXFormationView.js' => 'cef53b3e',
'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
),
'symbols' => array(
'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => '6f4ea703',
'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46',
'aphront-table-view-css' => '0bb61df1',
'aphront-tokenizer-control-css' => '34e2a838',
'aphront-tooltip-css' => 'e3f2412f',
'aphront-typeahead-control-css' => '8779483d',
'application-search-view-css' => '0f7c06d8',
'auth-css' => 'c2f23d74',
'bulk-job-css' => '73af99f5',
'conduit-api-css' => 'ce2cfc41',
'config-options-css' => '16c920ae',
'conpherence-color-css' => 'b17746b0',
'conpherence-durable-column-view' => '2d57072b',
'conpherence-header-pane-css' => 'c9a3db8e',
'conpherence-menu-css' => '67f4680d',
'conpherence-message-pane-css' => 'd244db1e',
'conpherence-notification-css' => '6a3d4e58',
'conpherence-participant-pane-css' => '69e0058a',
'conpherence-thread-manager' => 'aec8e38c',
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => '9d068042',
'diff-tree-view-css' => 'e2d3e222',
'differential-changeset-view-css' => 'df3afa61',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
'differential-revision-history-css' => '8aa3eac5',
'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => 'bba788b9',
'diffusion-css' => 'b54c77b0',
'diffusion-icons-css' => '23b31a1b',
'diffusion-readme-css' => 'b68a76e4',
'diffusion-repository-css' => 'b89e8c6c',
'diviner-shared-css' => '4bd263b0',
'font-fontawesome' => '3883938a',
'font-lato' => '23631304',
'global-drag-and-drop-css' => '1d2713a4',
'harbormaster-css' => '8dfe16b2',
'herald-css' => '648d39e2',
'herald-rule-editor' => '2633bef7',
'herald-test-css' => 'e004176f',
'inline-comment-summary-css' => '81eb368d',
'javelin-aphlict' => '022516b4',
'javelin-behavior' => '1b6acc2a',
'javelin-behavior-aphlict-dropdown' => 'e9a2940f',
'javelin-behavior-aphlict-listen' => '4e61fa88',
'javelin-behavior-aphlict-status' => 'c3703a16',
'javelin-behavior-aphront-basic-tokenizer' => '3b4899b0',
'javelin-behavior-aphront-drag-and-drop-textarea' => '7ad020a5',
'javelin-behavior-aphront-form-disable-on-submit' => '55d7b788',
'javelin-behavior-aphront-more' => '506aa3f4',
'javelin-behavior-audio-source' => '3dc5ad43',
'javelin-behavior-audit-preview' => 'b7b73831',
'javelin-behavior-badge-view' => '92cdd7b6',
'javelin-behavior-bulk-editor' => 'aa6d2308',
'javelin-behavior-bulk-job-reload' => '3829a3cf',
'javelin-behavior-calendar-month-view' => '158c64e0',
'javelin-behavior-choose-control' => '04f8a1e3',
'javelin-behavior-comment-actions' => '4dffaeb2',
'javelin-behavior-config-reorder-fields' => '2539f834',
'javelin-behavior-conpherence-menu' => '8c2ed2bf',
'javelin-behavior-conpherence-participant-pane' => '43ba89a2',
'javelin-behavior-conpherence-pontificate' => '4ae58b5a',
'javelin-behavior-conpherence-search' => '91befbcc',
'javelin-behavior-countdown-timer' => '6a162524',
'javelin-behavior-dark-console' => '457f4d16',
'javelin-behavior-dashboard-async-panel' => '9c01e364',
'javelin-behavior-dashboard-move-panels' => 'a2ab19be',
'javelin-behavior-dashboard-query-panel-select' => '1e413dc9',
'javelin-behavior-dashboard-tab-panel' => '0116d3e8',
'javelin-behavior-day-view' => '727a5a61',
'javelin-behavior-desktop-notifications-control' => '070679fe',
'javelin-behavior-detect-timezone' => '78bc5d94',
'javelin-behavior-device' => '0cf79f45',
'javelin-behavior-differential-diff-radios' => '925fe8cd',
'javelin-behavior-differential-populate' => 'b86ef6c2',
'javelin-behavior-diffusion-commit-branches' => '4b671572',
'javelin-behavior-diffusion-commit-graph' => 'ef836bf2',
'javelin-behavior-diffusion-locate-file' => '87428eb2',
'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
'javelin-behavior-document-engine' => '243d6c22',
'javelin-behavior-doorkeeper-tag' => '6a85bc5a',
'javelin-behavior-drydock-live-operation-status' => '47a0728b',
'javelin-behavior-durable-column' => 'fa6f30b2',
'javelin-behavior-editengine-reorder-configs' => '4842f137',
'javelin-behavior-editengine-reorder-fields' => '0ad8d31f',
'javelin-behavior-event-all-day' => '0b1bc990',
'javelin-behavior-fancy-datepicker' => '956f3eeb',
'javelin-behavior-global-drag-and-drop' => '1cab0e9a',
'javelin-behavior-harbormaster-log' => 'b347a301',
'javelin-behavior-herald-rule-editor' => '0922e81d',
'javelin-behavior-high-security-warning' => 'dae2d55b',
'javelin-behavior-history-install' => '6a1583a8',
'javelin-behavior-icon-composer' => '38a6cedb',
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
'javelin-behavior-line-chart' => 'ad258e28',
'javelin-behavior-linked-container' => '74446546',
'javelin-behavior-maniphest-batch-selector' => '139ef688',
'javelin-behavior-maniphest-list-editor' => 'c687e867',
'javelin-behavior-owners-path-editor' => 'ff688a7a',
'javelin-behavior-passphrase-credential-control' => '48fe33d0',
'javelin-behavior-phabricator-autofocus' => '65bb0011',
'javelin-behavior-phabricator-clipboard-copy' => 'cf32921f',
'javelin-behavior-phabricator-gesture' => 'b58d1a2a',
'javelin-behavior-phabricator-gesture-example' => '242dedd0',
'javelin-behavior-phabricator-keyboard-pager' => '1325b731',
'javelin-behavior-phabricator-keyboard-shortcuts' => '42c44e8b',
'javelin-behavior-phabricator-line-linker' => '590e6527',
'javelin-behavior-phabricator-notification-example' => '29819b75',
'javelin-behavior-phabricator-object-selector' => '98ef467f',
'javelin-behavior-phabricator-oncopy' => 'da8f5259',
'javelin-behavior-phabricator-remarkup-assist' => '54262396',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
'javelin-behavior-phabricator-search-typeahead' => '1cb7d027',
'javelin-behavior-phabricator-show-older-transactions' => '8b5c7d65',
'javelin-behavior-phabricator-tooltips' => '73ecc1f8',
'javelin-behavior-phabricator-transaction-comment-form' => '2bdadf1a',
'javelin-behavior-phabricator-transaction-list' => '9cec214e',
'javelin-behavior-phabricator-watch-anchor' => 'a77e2cbd',
'javelin-behavior-pholio-mock-edit' => '3eed1f2b',
'javelin-behavior-pholio-mock-view' => '5aa1544e',
'javelin-behavior-phui-dropdown-menu' => '5cf0501a',
'javelin-behavior-phui-file-upload' => 'e150bd50',
'javelin-behavior-phui-hovercards' => '6c379000',
'javelin-behavior-phui-selectable-list' => 'b26a41e4',
'javelin-behavior-phui-submenu' => 'b5e9bff9',
'javelin-behavior-phui-tab-group' => '242aa08b',
'javelin-behavior-phui-timer-control' => 'f84bcbf4',
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
'javelin-behavior-redirect' => '407ee861',
'javelin-behavior-refresh-csrf' => '46116c01',
'javelin-behavior-releeph-preview-branch' => '75184d68',
'javelin-behavior-releeph-request-state-change' => '9f081f05',
'javelin-behavior-releeph-request-typeahead' => 'aa3a100c',
'javelin-behavior-remarkup-load-image' => '202bfa3f',
'javelin-behavior-remarkup-preview' => 'd8a86cfb',
'javelin-behavior-reorder-applications' => 'aa371860',
'javelin-behavior-reorder-columns' => '8ac32fd9',
'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
'javelin-behavior-repository-crossreference' => '6337cf26',
'javelin-behavior-scrollbar' => '92388bae',
'javelin-behavior-search-reorder-queries' => 'b86f297f',
'javelin-behavior-select-content' => 'e8240b50',
'javelin-behavior-select-on-click' => '66365ee2',
'javelin-behavior-setup-check-https' => '01384686',
'javelin-behavior-stripe-payment-form' => '02cb4398',
'javelin-behavior-test-payment-form' => '4a7fb02b',
'javelin-behavior-time-typeahead' => '5803b9e7',
'javelin-behavior-toggle-class' => '32db8374',
'javelin-behavior-toggle-widget' => '8f959ad0',
'javelin-behavior-trigger-rule-editor' => '398fdf13',
'javelin-behavior-typeahead-browse' => '70245195',
'javelin-behavior-typeahead-search' => '7b139193',
'javelin-behavior-user-menu' => '60cd9241',
'javelin-behavior-view-placeholder' => 'a9942052',
'javelin-behavior-workflow' => '9623adc1',
'javelin-chart' => '52e3ff03',
'javelin-chart-curtain-view' => '86954222',
'javelin-chart-function-label' => '81de1dab',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
'javelin-dom' => '94681e22',
'javelin-dynval' => '202a2e85',
'javelin-event' => 'c03f2fb4',
'javelin-fx' => '34450586',
'javelin-history' => '030b4f7a',
'javelin-install' => '5902260c',
'javelin-json' => '541f81c3',
'javelin-leader' => '0d2490ce',
'javelin-magical-init' => '98e6504a',
'javelin-mask' => '7c4d8998',
'javelin-quicksand' => 'd3799cb4',
'javelin-reactor' => '1c850a26',
'javelin-reactor-dom' => '6cfa0008',
'javelin-reactor-node-calmer' => '225bbb98',
'javelin-reactornode' => '72960bc1',
'javelin-request' => '84e6891f',
'javelin-resource' => '740956e1',
'javelin-routable' => '6a18c42e',
'javelin-router' => '32755edb',
'javelin-scrollbar' => 'a43ae2ae',
'javelin-sound' => 'd4cc2d2a',
'javelin-stratcom' => '0889b835',
'javelin-tokenizer' => '89a1ae3a',
'javelin-typeahead' => 'a4356cde',
'javelin-typeahead-composite-source' => '22ee68a5',
'javelin-typeahead-normalizer' => 'a241536a',
'javelin-typeahead-ondemand-source' => '23387297',
'javelin-typeahead-preloaded-source' => '5a79f6c3',
'javelin-typeahead-source' => '8badee71',
'javelin-typeahead-static-source' => '80bff3af',
'javelin-uri' => '2e255291',
'javelin-util' => 'edb4d8c9',
'javelin-vector' => 'e9c80beb',
'javelin-view' => '289bf236',
'javelin-view-html' => 'f8c4e135',
'javelin-view-interpreter' => '876506b6',
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => 'b46d88c5',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '945ff654',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e',
'multirow-row-manager' => '5b54c823',
'owners-path-editor' => '2a8b62d9',
'owners-path-editor-css' => 'fa7c13ef',
'paste-css' => 'b37bcd38',
'path-typeahead' => 'ad486db3',
'people-picture-menu-item-css' => 'fe8e07cf',
'people-profile-css' => '2ea2daa1',
'phabricator-action-list-view-css' => '1b0085b2',
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',
'phabricator-core-css' => '1b29ed61',
'phabricator-countdown-css' => 'bff8012f',
'phabricator-darklog' => '3b869402',
'phabricator-darkmessage' => '26cd4b73',
'phabricator-dashboard-css' => '5a205b9d',
- 'phabricator-diff-changeset' => 'd721533b',
- 'phabricator-diff-changeset-list' => '8b0eab21',
- 'phabricator-diff-inline' => '734d3c33',
+ 'phabricator-diff-changeset' => 'bfdae878',
+ 'phabricator-diff-changeset-list' => 'a00bf62d',
+ 'phabricator-diff-inline' => 'b00168c1',
'phabricator-diff-path-view' => '8207abf9',
'phabricator-diff-tree-view' => '5d83623b',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => '0169e425',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
'phabricator-file-upload' => 'ab85e184',
'phabricator-flag-css' => '2b77be8d',
'phabricator-keyboard-shortcut' => '1a844c06',
'phabricator-keyboard-shortcut-manager' => '81debc48',
'phabricator-main-menu-view' => 'bcec20f0',
'phabricator-nav-view-css' => '423f92cc',
'phabricator-notification' => 'a9b91e3f',
'phabricator-notification-css' => '30240bd2',
'phabricator-notification-menu-css' => '4df1ee30',
'phabricator-object-selector-css' => 'ee77366f',
'phabricator-phtize' => '2f1db1ed',
'phabricator-prefab' => '5793d835',
'phabricator-remarkup-css' => 'c286eaef',
'phabricator-search-results-css' => '9ea70ace',
'phabricator-shaped-request' => '995f5102',
'phabricator-slowvote-css' => '1694baed',
'phabricator-source-code-view-css' => '03d7ac28',
'phabricator-standard-page-view' => 'a374f94c',
'phabricator-textareautils' => 'f340a484',
'phabricator-title' => '43bc9360',
'phabricator-tooltip' => '83754533',
'phabricator-ui-example-css' => 'b4795059',
'phabricator-zindex-css' => '612e9522',
'phame-css' => 'bb442327',
'pholio-css' => '88ef5ef1',
'pholio-edit-css' => '4df55b3b',
'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',
'phui-action-panel-css' => '6c386cbf',
'phui-badge-view-css' => '666e25ad',
'phui-basic-nav-view-css' => '56ebd66d',
'phui-big-info-view-css' => '362ad37b',
'phui-box-css' => '5ed3b8cb',
'phui-bulk-editor-css' => '374d5e30',
'phui-button-bar-css' => 'a4aa75c4',
'phui-button-css' => 'ea704902',
'phui-button-simple-css' => '1ff278aa',
'phui-calendar-css' => 'f11073aa',
'phui-calendar-day-css' => '9597d706',
'phui-calendar-list-css' => 'ccd7e4e2',
'phui-calendar-month-css' => 'cb758c42',
'phui-chart-css' => '14df9ae3',
'phui-cms-css' => '8c05c41e',
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
'phui-crumbs-view-css' => '614f43cf',
'phui-curtain-object-ref-view-css' => '12404744',
'phui-curtain-view-css' => '68c5efb6',
'phui-document-summary-view-css' => 'b068eed1',
'phui-document-view-css' => '52b748a5',
'phui-document-view-pro-css' => 'b9613a10',
'phui-feed-story-css' => 'a0c05029',
'phui-font-icon-base-css' => '303c9b87',
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => '1f177cb7',
'phui-form-view-css' => '01b796c0',
'phui-formation-view-css' => 'd2dec8ed',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
'phui-hovercard' => '074f0783',
'phui-hovercard-view-css' => '6ca90fa0',
'phui-icon-set-selector-css' => '7aa5f3ec',
'phui-icon-view-css' => '4cbc684a',
'phui-image-mask-css' => '62c7f4d2',
'phui-info-view-css' => 'a10a909b',
- 'phui-inline-comment-view-css' => '48acce5b',
+ 'phui-inline-comment-view-css' => 'd5749acc',
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '2f253c22',
'phui-object-box-css' => 'b8d7eea0',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
'phui-oi-list-view-css' => 'd7723ecc',
'phui-oi-simple-ui-css' => '6a30fa46',
'phui-pager-css' => 'd022c7ad',
'phui-pinboard-view-css' => '1f08f5d8',
'phui-policy-section-view-css' => '139fdc64',
'phui-property-list-view-css' => '5adf7078',
'phui-remarkup-preview-css' => '91767007',
'phui-segment-bar-view-css' => '5166b370',
'phui-spacing-css' => 'b05cadc3',
'phui-status-list-view-css' => 'e5ff8be0',
'phui-tag-view-css' => '8519160a',
'phui-theme-css' => '35883b37',
'phui-timeline-view-css' => '2d32d7a9',
'phui-two-column-view-css' => 'f96d319f',
'phui-workboard-color-css' => 'e86de308',
'phui-workboard-view-css' => '74fc9d98',
'phui-workcard-view-css' => '913441b6',
'phui-workpanel-view-css' => '3ae89b20',
'phuix-action-list-view' => 'c68f183f',
'phuix-action-view' => 'a8f573a9',
'phuix-autocomplete' => '2fbe234d',
'phuix-button-view' => '55a24e84',
'phuix-dropdown-menu' => 'b557770a',
'phuix-form-control-view' => '38c1f3fb',
'phuix-formation-column-view' => '4bcc1f78',
'phuix-formation-flank-view' => '6648270a',
'phuix-formation-view' => 'cef53b3e',
'phuix-icon-view' => 'a5257c4e',
'policy-css' => 'ceb56a08',
'policy-edit-css' => '8794e2ed',
'policy-transaction-detail-css' => 'c02b8384',
'ponder-view-css' => '05a09d0a',
'project-card-view-css' => '4e7371cd',
'project-triggers-css' => 'cd9c8bb9',
'project-view-css' => '567858b3',
'releeph-core' => 'f81ff2db',
'releeph-preview-branch' => '22db5c07',
'releeph-request-differential-create-dialog' => '0ac1ea31',
'releeph-request-typeahead-css' => 'bce37359',
'setup-issue-css' => '5eed85b2',
'sprite-login-css' => '18b368a6',
'sprite-tokens-css' => 'f1896dc5',
'syntax-default-css' => '055fc231',
'syntax-highlighting-css' => '548567f6',
'tokens-css' => 'ce5a50bd',
'trigger-rule' => '41b7b4f6',
'trigger-rule-control' => '5faf27b9',
'trigger-rule-editor' => 'b49fd60c',
'trigger-rule-type' => '4feea7d3',
'typeahead-browse-css' => 'b7ed02d2',
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
'0116d3e8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'01384686' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'0169e425' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'022516b4' => array(
'javelin-install',
'javelin-util',
'javelin-websocket',
'javelin-leader',
'javelin-json',
),
'02cb4398' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'030b4f7a' => array(
'javelin-stratcom',
'javelin-install',
'javelin-uri',
'javelin-util',
),
'0392a5d8' => array(
'javelin-install',
),
'03e8891f' => array(
'javelin-install',
),
'04f8a1e3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'05d290ef' => array(
'javelin-install',
'javelin-util',
),
'070679fe' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-uri',
'phabricator-notification',
),
'074f0783' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'0889b835' => array(
'javelin-install',
'javelin-event',
'javelin-util',
'javelin-magical-init',
),
'0922e81d' => array(
'herald-rule-editor',
'javelin-behavior',
),
'0ad8d31f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'0cf79f45' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'0d2490ce' => array(
'javelin-install',
),
'0eaa33a9' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'javelin-workflow',
'phuix-icon-view',
),
'111bfd2d' => array(
'javelin-install',
),
'1325b731' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'139ef688' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'1a844c06' => array(
'javelin-install',
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1b6acc2a' => array(
'javelin-magical-init',
'javelin-util',
),
'1c850a26' => array(
'javelin-install',
'javelin-util',
),
'1cab0e9a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'1cb7d027' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
'phuix-icon-view',
),
'1e413dc9' => array(
'javelin-behavior',
'javelin-dom',
),
'1ff278aa' => array(
'phui-button-css',
),
'202a2e85' => array(
'javelin-install',
'javelin-reactornode',
'javelin-util',
'javelin-reactor',
),
'202bfa3f' => array(
'javelin-behavior',
'javelin-request',
),
'225bbb98' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
),
'22ee68a5' => array(
'javelin-install',
'javelin-typeahead-source',
'javelin-util',
),
23387297 => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
23631304 => array(
'phui-fontkit-css',
),
'242aa08b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'242dedd0' => array(
'javelin-stratcom',
'javelin-behavior',
'javelin-vector',
'javelin-dom',
),
'243d6c22' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'2539f834' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-json',
'phabricator-draggable-list',
),
'2633bef7' => array(
'multirow-row-manager',
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-json',
'phabricator-prefab',
),
'289bf236' => array(
'javelin-install',
'javelin-util',
),
'29819b75' => array(
'phabricator-notification',
'javelin-stratcom',
'javelin-behavior',
),
'2a8b62d9' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'phuix-form-control-view',
),
'2bdadf1a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
'phabricator-shaped-request',
),
'2e255291' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
),
'2f1db1ed' => array(
'javelin-util',
),
'2fbe234d' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'308f9fe4' => array(
'javelin-install',
'javelin-util',
),
'32755edb' => array(
'javelin-install',
'javelin-util',
),
'32db8374' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
34450586 => array(
'javelin-color',
'javelin-install',
'javelin-util',
),
'34c53422' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
),
'34e2a838' => array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
'3829a3cf' => array(
'javelin-behavior',
'javelin-uri',
),
'38a6cedb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'38c1f3fb' => array(
'javelin-install',
'javelin-dom',
),
'398fdf13' => array(
'javelin-behavior',
'trigger-rule-editor',
'trigger-rule',
'trigger-rule-type',
),
'3ae89b20' => array(
'phui-workcard-view-css',
),
'3b4899b0' => array(
'javelin-behavior',
'phabricator-prefab',
),
'3dc5ad43' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
),
'3eed1f2b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
'javelin-quicksand',
'phabricator-phtize',
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
'407ee861' => array(
'javelin-behavior',
'javelin-uri',
),
'42c44e8b' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'4370900d' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'43ba89a2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'conpherence-thread-manager',
),
'43bc9360' => array(
'javelin-install',
),
'457f4d16' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-util',
'javelin-dom',
'javelin-request',
'phabricator-keyboard-shortcut',
'phabricator-darklog',
'phabricator-darkmessage',
),
'46116c01' => array(
'javelin-request',
'javelin-behavior',
'javelin-dom',
'javelin-router',
'javelin-util',
'phabricator-busy',
),
'47a0728b' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
),
'4842f137' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'48fe33d0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'javelin-uri',
),
'490e2e2e' => array(
'phui-oi-list-view-css',
),
'4a7fb02b' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'4ae58b5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
'conpherence-thread-manager',
),
'4b671572' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
),
'4bcc1f78' => array(
'javelin-install',
'javelin-dom',
),
'4dffaeb2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'4e61fa88' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'javelin-leader',
'javelin-sound',
'phabricator-notification',
),
'4feea7d3' => array(
'trigger-rule-control',
),
'506aa3f4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5202e831' => array(
'javelin-install',
'javelin-dom',
'javelin-fx',
),
'52e3ff03' => array(
'phui-chart-css',
'd3',
'javelin-chart-curtain-view',
'javelin-chart-function-label',
),
'541f81c3' => array(
'javelin-install',
),
54262396 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
'phuix-autocomplete',
'javelin-mask',
),
'548567f6' => array(
'syntax-default-css',
),
'55a24e84' => array(
'javelin-install',
'javelin-dom',
),
'55d7b788' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5793d835' => 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',
),
'5803b9e7' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
'javelin-typeahead-static-source',
),
'58cb6a88' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
'javelin-workboard-drop-effect',
),
'5902260c' => array(
'javelin-util',
'javelin-magical-init',
),
'590e6527' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
),
'5a6f6a06' => array(
'javelin-behavior',
'javelin-quicksand',
),
'5a79f6c3' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'5aa1544e' => 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',
),
'5b54c823' => array(
'javelin-install',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
),
'5cf0501a' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'5d83623b' => array(
'javelin-dom',
),
'5faf27b9' => array(
'phuix-form-control-view',
),
'60cd9241' => array(
'javelin-behavior',
),
'6337cf26' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'65bb0011' => array(
'javelin-behavior',
'javelin-dom',
),
'66365ee2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'6648270a' => array(
'javelin-install',
'javelin-dom',
),
'6a1583a8' => array(
'javelin-behavior',
'javelin-history',
),
'6a162524' => array(
'javelin-behavior',
'javelin-dom',
),
'6a18c42e' => array(
'javelin-install',
),
'6a30fa46' => array(
'phui-oi-list-view-css',
),
'6a85bc5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-magical-init',
),
'6c379000' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'phui-hovercard',
),
'6cfa0008' => array(
'javelin-dom',
'javelin-dynval',
'javelin-reactor',
'javelin-reactornode',
'javelin-install',
'javelin-util',
),
70245195 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'727a5a61' => array(
'phuix-icon-view',
),
'72960bc1' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
'javelin-reactor-node-calmer',
),
- '734d3c33' => array(
- 'javelin-dom',
- ),
'73ecc1f8' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'phabricator-tooltip',
),
'740956e1' => array(
'javelin-util',
'javelin-uri',
'javelin-install',
),
74446546 => array(
'javelin-behavior',
'javelin-dom',
),
'75184d68' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-request',
),
'78bc5d94' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'78f811c9' => array(
'javelin-install',
),
'7930776a' => array(
'javelin-install',
'javelin-dom',
),
'7ad020a5' => array(
'javelin-behavior',
'javelin-dom',
'phabricator-drag-and-drop-file-upload',
'phabricator-textareautils',
),
'7b139193' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'7c4d8998' => array(
'javelin-install',
'javelin-dom',
),
'80bff3af' => array(
'javelin-install',
'javelin-typeahead-source',
),
'81debc48' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'8207abf9' => array(
'javelin-dom',
),
83754533 => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'84e6891f' => array(
'javelin-install',
'javelin-stratcom',
'javelin-util',
'javelin-behavior',
'javelin-json',
'javelin-dom',
'javelin-resource',
'javelin-routable',
),
'84f82dad' => array(
'javelin-install',
),
'87428eb2' => array(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
'javelin-dom',
'javelin-typeahead',
'javelin-uri',
),
'876506b6' => array(
'javelin-view',
'javelin-install',
'javelin-dom',
),
'89a1ae3a' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'8ac32fd9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
- '8b0eab21' => array(
- 'javelin-install',
- 'phuix-button-view',
- 'phabricator-diff-tree-view',
- ),
'8b5c7d65' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'8badee71' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'8c2ed2bf' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'javelin-behavior-device',
'javelin-history',
'javelin-vector',
'javelin-scrollbar',
'phabricator-title',
'phabricator-shaped-request',
'conpherence-thread-manager',
),
'8e0aa661' => array(
'javelin-install',
'javelin-dom',
),
'8f959ad0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'91befbcc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'92388bae' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'925fe8cd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'92cdd7b6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'9347f172' => array(
'javelin-behavior',
'multirow-row-manager',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'javelin-json',
),
'94243d89' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'94681e22' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'956f3eeb' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-router',
),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'995f5102' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-router',
),
'9aae2b66' => array(
'javelin-install',
'javelin-util',
),
'9c01e364' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'9cec214e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-uri',
'phabricator-textareautils',
),
'9f081f05' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-keyboard-shortcut',
),
+ 'a00bf62d' => array(
+ 'javelin-install',
+ 'phuix-button-view',
+ 'phabricator-diff-tree-view',
+ ),
'a17b84f1' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'a241536a' => array(
'javelin-install',
),
'a2ab19be' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'a4356cde' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'a43ae2ae' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'a4aa75c4' => array(
'phui-button-css',
'phui-button-simple-css',
),
'a5257c4e' => array(
'javelin-install',
'javelin-dom',
),
'a77e2cbd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'a8f573a9' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'a9942052' => array(
'javelin-behavior',
'javelin-dom',
'javelin-view-renderer',
'javelin-install',
),
'a9b91e3f' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'phabricator-notification-css',
),
'aa371860' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'aa3a100c' => array(
'javelin-behavior',
'javelin-dom',
'javelin-typeahead',
'javelin-typeahead-ondemand-source',
'javelin-dom',
),
'aa6d2308' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'multirow-row-manager',
'javelin-json',
'phuix-form-control-view',
),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'ad258e28' => array(
'javelin-behavior',
'javelin-dom',
'javelin-chart',
),
'ad486db3' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'aec8e38c' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-aphlict',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
+ 'b00168c1' => array(
+ 'javelin-dom',
+ ),
'b105a3a6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b26a41e4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b347a301' => array(
'javelin-behavior',
),
'b46d88c5' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
),
'b49fd60c' => array(
'multirow-row-manager',
'trigger-rule',
),
'b517bfa0' => array(
'phui-oi-list-view-css',
),
'b557770a' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'b58d1a2a' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-magical-init',
),
'b5e9bff9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b7b73831' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'b86ef6c2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-tooltip',
'phabricator-diff-changeset-list',
'phabricator-diff-changeset',
'phuix-formation-view',
),
'b86f297f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'b9109f8f' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'b9d0c2f3' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'bcec20f0' => array(
'phui-theme-css',
),
+ 'bfdae878' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-workflow',
+ 'javelin-router',
+ 'javelin-behavior-device',
+ 'javelin-vector',
+ 'phabricator-diff-inline',
+ 'phabricator-diff-path-view',
+ 'phuix-button-view',
+ ),
'c03f2fb4' => array(
'javelin-install',
),
'c2c500a7' => array(
'javelin-install',
'javelin-dom',
'phuix-button-view',
),
'c3703a16' => array(
'javelin-behavior',
'javelin-aphlict',
'phabricator-phtize',
'javelin-dom',
),
'c3d24e63' => array(
'javelin-install',
'javelin-workboard-card',
'javelin-workboard-header',
),
'c687e867' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-fx',
'javelin-util',
),
'c68f183f' => array(
'javelin-install',
'javelin-dom',
),
'c715c123' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'c7e748bf' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phuix-icon-view',
'phabricator-busy',
),
'cef53b3e' => array(
'javelin-install',
'javelin-dom',
'phuix-formation-column-view',
'phuix-formation-flank-view',
),
'cf32921f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd12d214f' => array(
'javelin-install',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-util',
),
'd3799cb4' => array(
'javelin-install',
),
'd4cc2d2a' => array(
'javelin-install',
),
- 'd721533b' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-workflow',
- 'javelin-router',
- 'javelin-behavior-device',
- 'javelin-vector',
- 'phabricator-diff-inline',
- 'phabricator-diff-path-view',
- 'phuix-button-view',
- ),
'd8a86cfb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'da15d3dc' => array(
'phui-oi-list-view-css',
),
'da8f5259' => array(
'javelin-behavior',
'javelin-dom',
),
'dae2d55b' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'df3afa61' => array(
'phui-inline-comment-view-css',
),
'e150bd50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'e5bdb730' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'e8240b50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e9a2940f' => array(
'javelin-behavior',
'javelin-request',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-uri',
'javelin-behavior-device',
'phabricator-title',
'phabricator-favicon',
),
'e9c80beb' => array(
'javelin-install',
'javelin-event',
),
'ebe83a6b' => array(
'javelin-install',
),
'ec4e31c0' => array(
'phui-timeline-view-css',
),
'ee77366f' => array(
'aphront-dialog-view-css',
),
'ef836bf2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'f340a484' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
),
'f84bcbf4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'f8c4e135' => array(
'javelin-install',
'javelin-dom',
'javelin-view-visitor',
'javelin-util',
),
'fa6f30b2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-behavior-device',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
'conpherence-thread-manager',
),
'fa74cc35' => array(
'phui-oi-list-view-css',
),
'fdc13e4e' => array(
'javelin-install',
),
'ff688a7a' => array(
'owners-path-editor',
'javelin-behavior',
),
),
'packages' => array(
'conpherence.pkg.css' => array(
'conpherence-menu-css',
'conpherence-color-css',
'conpherence-message-pane-css',
'conpherence-notification-css',
'conpherence-transaction-css',
'conpherence-participant-pane-css',
'conpherence-header-pane-css',
),
'conpherence.pkg.js' => array(
'javelin-behavior-conpherence-menu',
'javelin-behavior-conpherence-participant-pane',
'javelin-behavior-conpherence-pontificate',
'javelin-behavior-toggle-widget',
),
'core.pkg.css' => array(
'phabricator-core-css',
'phabricator-zindex-css',
'phui-button-css',
'phui-button-simple-css',
'phui-theme-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',
'application-search-view-css',
'phabricator-remarkup-css',
'syntax-highlighting-css',
'syntax-default-css',
'phui-pager-css',
'aphront-tooltip-css',
'phabricator-flag-css',
'phui-info-view-css',
'phabricator-main-menu-view',
'phabricator-notification-css',
'phabricator-notification-menu-css',
'phui-lightbox-css',
'phui-comment-panel-css',
'phui-header-view-css',
'phabricator-nav-view-css',
'phui-basic-nav-view-css',
'phui-crumbs-view-css',
'phui-oi-list-view-css',
'phui-oi-color-css',
'phui-oi-big-ui-css',
'phui-oi-drag-ui-css',
'phui-oi-simple-ui-css',
'phui-oi-flush-ui-css',
'global-drag-and-drop-css',
'phui-spacing-css',
'phui-form-css',
'phui-icon-view-css',
'phabricator-action-list-view-css',
'phui-property-list-view-css',
'phui-tag-view-css',
'phui-list-view-css',
'font-fontawesome',
'font-lato',
'phui-font-icon-base-css',
'phui-fontkit-css',
'phui-box-css',
'phui-object-box-css',
'phui-timeline-view-css',
'phui-two-column-view-css',
'phui-curtain-view-css',
'sprite-login-css',
'sprite-tokens-css',
'tokens-css',
'auth-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-curtain-object-ref-view-css',
'phui-comment-form-css',
'phui-head-thing-view-css',
'conpherence-durable-column-view',
'phui-button-bar-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',
'phuix-icon-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-sound',
'javelin-aphlict',
'phabricator-notification',
'javelin-behavior-aphlict-listen',
'javelin-behavior-phabricator-search-typeahead',
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phui-hovercard',
'javelin-behavior-phui-hovercards',
'javelin-color',
'javelin-fx',
'phabricator-draggable-list',
'javelin-behavior-phabricator-transaction-list',
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
'javelin-quicksand',
'javelin-behavior-quicksand-blacklist',
'javelin-behavior-high-security-warning',
'javelin-behavior-read-only-warning',
'javelin-scrollbar',
'javelin-behavior-scrollbar',
'javelin-behavior-durable-column',
'conpherence-thread-manager',
'javelin-behavior-detect-timezone',
'javelin-behavior-setup-check-https',
'javelin-behavior-aphlict-status',
'javelin-behavior-user-menu',
'phabricator-favicon',
'javelin-behavior-phui-tab-group',
'javelin-behavior-phui-submenu',
'phuix-button-view',
'javelin-behavior-comment-actions',
'phuix-form-control-view',
'phuix-autocomplete',
),
'dark-console.pkg.js' => array(
'javelin-behavior-dark-console',
'phabricator-darklog',
'phabricator-darkmessage',
),
'differential.pkg.css' => array(
'differential-core-view-css',
'differential-changeset-view-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',
'phui-inline-comment-view-css',
'diff-tree-view-css',
'phui-formation-view-css',
),
'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload',
'phabricator-shaped-request',
'javelin-behavior-differential-populate',
'javelin-behavior-differential-diff-radios',
'javelin-behavior-aphront-drag-and-drop-textarea',
'javelin-behavior-phabricator-object-selector',
'javelin-behavior-repository-crossreference',
'javelin-behavior-aphront-more',
'phabricator-diff-inline',
'phabricator-diff-changeset',
'phabricator-diff-changeset-list',
'phabricator-diff-tree-view',
'phabricator-diff-path-view',
'phuix-formation-view',
'phuix-formation-column-view',
'phuix-formation-flank-view',
),
'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-list-editor',
),
),
);
diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
index 424fec142f..be039772c1 100644
--- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
+++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php
@@ -1,251 +1,253 @@
<?php
final class CelerityDefaultPostprocessor
extends CelerityPostprocessor {
const POSTPROCESSOR_KEY = 'default';
public function getPostprocessorKey() {
return self::POSTPROCESSOR_KEY;
}
public function getPostprocessorName() {
return pht('Use Standard Colors');
}
public function buildDefaultPostprocessor() {
return null;
}
public function buildVariables() {
return array(
// Fonts
'basefont' => "13px 'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
'fontfamily' => "'Segoe UI', 'Segoe UI Emoji', ".
"'Segoe UI Symbol', 'Lato', 'Helvetica Neue', ".
"Helvetica, Arial, sans-serif",
// Drop Shadow
'dropshadow' => '0 2px 12px rgba(0, 0, 0, .20)',
'whitetextshadow' => '0 1px 0 rgba(255, 255, 255, 1)',
// Anchors
'anchor' => '#136CB2',
// Font Sizes
'biggestfontsize' => '15px',
'biggerfontsize' => '14px',
'normalfontsize' => '13px',
'smallerfontsize' => '12px',
'smallestfontsize' => '11px',
// Base Colors
'red' => '#c0392b',
'lightred' => '#f4dddb',
'orange' => '#e67e22',
'lightorange' => '#f7e2d4',
'yellow' => '#f1c40f',
'lightyellow' => '#fdf5d4',
'green' => '#139543',
'lightgreen' => '#d7eddf',
'blue' => '#2980b9',
'lightblue' => '#daeaf3',
'sky' => '#3498db',
'lightsky' => '#ddeef9',
'fire' => '#e62f17',
'indigo' => '#6e5cb6',
'lightindigo' => '#eae6f7',
'pink' => '#da49be',
'lightpink' => '#fbeaf8',
'violet' => '#8e44ad',
'lightviolet' => '#ecdff1',
'charcoal' => '#4b4d51',
'backdrop' => '#c4cde0',
'hoverwhite' => 'rgba(255,255,255,.6)',
'hovergrey' => '#c5cbcf',
'hoverblue' => '#eceff5',
'hoverborder' => '#dfe1e9',
'hoverselectedgrey' => '#bbc4ca',
'hoverselectedblue' => '#e6e9ee',
'borderinset' => 'inset 0 0 0 1px rgba(55,55,55,.15)',
'timeline' => '#d5d8e1',
'timeline.icon.background' => '#E6E9F1',
'bluepropertybackground' => '#eff3fc',
// Alphas
'alphawhite' => '255,255,255',
'alphagrey' => '55,55,55',
'alphablue' => '71,87,120',
'alphablack' => '0,0,0',
// Base Greys
+ 'thingreyborder' => '#dadee8',
'lightgreyborder' => '#C7CCD9',
'greyborder' => '#A1A6B0',
'darkgreyborder' => '#676A70',
'lightgreytext' => '#92969D',
'greytext' => '#74777D',
'darkgreytext' => '#4B4D51',
'lightgreybackground' => '#F7F7F7',
'greybackground' => '#EBECEE',
'darkgreybackground' => '#DFE0E2',
// Base Blues
'thinblueborder' => '#DDE8EF',
'lightblueborder' => '#BFCFDA',
'blueborder' => '#8C98B8',
'darkblueborder' => '#626E82',
'lightbluebackground' => '#F8F9FC',
'bluebackground' => '#ECEEF4',
'lightbluetext' => '#8C98B8',
'bluetext' => '#6B748C',
'darkbluetext' => '#464C5C',
'blacktext' => '#000',
// Base Greens
'lightgreenborder' => '#bfdac1',
'greenborder' => '#8cb89c',
'greentext' => '#3e6d35',
'lightgreenbackground' => '#e6f2e4',
// Base Red
'lightredborder' => '#f4c6c6',
'redborder' => '#eb9797',
'redtext' => '#802b2b',
'lightredbackground' => '#f5e1e1',
// Base Violet
'lightvioletborder' => '#cfbddb',
'violetborder' => '#b589ba',
'violettext' => '#603c73',
'lightvioletbackground' => '#e9dfee',
// Shades are a more muted set of our base colors
// better suited to blending into other UIs.
// Shade Red
'sh-lightredborder' => '#efcfcf',
'sh-redborder' => '#d1abab',
'sh-redicon' => '#c85a5a',
'sh-redtext' => '#a53737',
'sh-redbackground' => '#f7e6e6',
// Shade Orange
'sh-lightorangeborder' => '#f8dcc3',
'sh-orangeborder' => '#dbb99e',
'sh-orangeicon' => '#e78331',
'sh-orangetext' => '#ba6016',
'sh-orangebackground' => '#fbede1',
// Shade Yellow
'sh-lightyellowborder' => '#e9dbcd',
'sh-yellowborder' => '#c9b8a8',
'sh-yellowicon' => '#9b946e',
'sh-yellowtext' => '#726f56',
'sh-yellowbackground' => '#fdf3da',
// Shade Green
'sh-lightgreenborder' => '#c6e6c7',
'sh-greenborder' => '#a0c4a1',
'sh-greenicon' => '#4ca74e',
'sh-greentext' => '#326d34',
'sh-greenbackground' => '#ddefdd',
// Shade Blue
'sh-lightblueborder' => '#cfdbe3',
'sh-blueborder' => '#a7b5bf',
'sh-blueicon' => '#6b748c',
'sh-bluetext' => '#464c5c',
'sh-bluebackground' => '#dee7f8',
// Shade Indigo
'sh-lightindigoborder' => '#d1c9ee',
'sh-indigoborder' => '#bcb4da',
'sh-indigoicon' => '#8672d4',
'sh-indigotext' => '#6e5cb6',
'sh-indigobackground' => '#eae6f7',
// Shade Violet
'sh-lightvioletborder' => '#e0d1e7',
'sh-violetborder' => '#bcabc5',
'sh-violeticon' => '#9260ad',
'sh-violettext' => '#69427f',
'sh-violetbackground' => '#efe8f3',
// Shade Pink
'sh-lightpinkborder' => '#f6d5ef',
'sh-pinkborder' => '#d5aecd',
'sh-pinkicon' => '#e26fcb',
'sh-pinktext' => '#da49be',
'sh-pinkbackground' => '#fbeaf8',
// Shade Grey
'sh-lightgreyborder' => '#e3e4e8',
'sh-greyborder' => '#b2b2b2',
'sh-greyicon' => '#757575',
'sh-greytext' => '#555555',
'sh-greybackground' => '#edeef2',
// Shade Disabled
'sh-lightdisabledborder' => '#e5e5e5',
'sh-disabledborder' => '#cbcbcb',
'sh-disabledicon' => '#bababa',
'sh-disabledtext' => '#a6a6a6',
'sh-disabledbackground' => '#f3f3f3',
// Diffs
'diff.background' => '#fff',
'new-background' => 'rgba(151, 234, 151, .3)',
'new-bright' => 'rgba(151, 234, 151, .6)',
'old-background' => 'rgba(251, 175, 175, .3)',
'old-bright' => 'rgba(251, 175, 175, .7)',
'move-background' => '#fdf5d4',
'copy-background' => '#f1c40f',
// Usually light yellow
'gentle.highlight' => '#fdf3da',
'gentle.highlight.border' => '#c9b8a8',
+ 'gentle.highlight.background' => '#fffdf6',
'highlight.bright' => '#fdf320',
'paste.content' => '#fffef5',
'paste.border' => '#e9dbcd',
'paste.highlight' => '#fdf3da',
// Background color for "most" themes.
'page.background' => '#f3f5f7',
'page.sidenav' => '#eaedf1',
'page.content' => '#fff',
'menu.profile.text' => 'rgba(255,255,255,.8)',
'menu.profile.text.selected' => 'rgba(255,255,255,1)',
'menu.profile.icon.disabled' => 'rgba(255,255,255,.4)',
'menu.main.height' => '44px',
'menu.profile.width' => '240px',
// Buttons
'blue.button.color' => '#2980b9',
'blue.button.gradient' => 'linear-gradient(to bottom, #3498db, #2980b9)',
'blue.button.hover' => 'linear-gradient(to bottom, #3498db, #1b6ba0)',
'green.button.color' => '#139543',
'green.button.gradient' => 'linear-gradient(to bottom, #23BB5B, #139543)',
'green.button.hover' => 'linear-gradient(to bottom, #23BB5B, #178841)',
'red.button.color' => '#b33225',
'red.button.gradient' => 'linear-gradient(to bottom, #d25454, #b33225)',
'red.button.hover' => 'linear-gradient(to bottom, #d25454, #982115)',
'grey.button.color' => '#F7F7F9',
'grey.button.gradient' => 'linear-gradient(to bottom, #ffffff, #f1f0f1)',
'grey.button.hover' => 'linear-gradient(to bottom, #ffffff, #eeebec)',
'document.border' => '#dedee1',
'delete-color' => '#c0392b',
'create-color' => '#139543',
);
}
}
diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
index 404ff40194..900491e00b 100644
--- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
+++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php
@@ -1,495 +1,495 @@
<?php
final class PHUIDiffInlineCommentDetailView
extends PHUIDiffInlineCommentView {
private $handles;
private $markupEngine;
private $editable;
private $preview;
private $allowReply;
private $canMarkDone;
private $objectOwnerPHID;
public function isHidden() {
return $this->getInlineComment()->isHidden();
}
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 setEditable($editable) {
$this->editable = $editable;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setAllowReply($allow_reply) {
$this->allowReply = $allow_reply;
return $this;
}
public function setCanMarkDone($can_mark_done) {
$this->canMarkDone = $can_mark_done;
return $this;
}
public function getCanMarkDone() {
return $this->canMarkDone;
}
public function setObjectOwnerPHID($phid) {
$this->objectOwnerPHID = $phid;
return $this;
}
public function getObjectOwnerPHID() {
return $this->objectOwnerPHID;
}
public function getAnchorName() {
$inline = $this->getInlineComment();
if ($inline->getID()) {
return 'inline-'.$inline->getID();
}
return null;
}
public function getScaffoldCellID() {
$anchor = $this->getAnchorName();
if ($anchor) {
return 'anchor-'.$anchor;
}
return null;
}
public function render() {
require_celerity_resource('phui-inline-comment-view-css');
$inline = $this->getInlineComment();
- $classes = array(
- 'differential-inline-comment',
- );
-
$is_synthetic = false;
if ($inline->getSyntheticAuthor()) {
$is_synthetic = true;
}
$is_preview = $this->preview;
$metadata = $this->getInlineCommentMetadata();
+ $classes = array(
+ 'differential-inline-comment',
+ );
+
$sigil = 'differential-inline-comment';
if ($is_preview) {
$sigil = $sigil.' differential-inline-comment-preview';
- }
- $classes = array(
- 'differential-inline-comment',
- );
+ $classes[] = 'inline-comment-preview';
+ } else {
+ $classes[] = 'inline-comment-element';
+ }
$content = $inline->getContent();
$handles = $this->handles;
$links = array();
$draft_text = null;
if (!$is_synthetic) {
// This display is controlled by CSS
$draft_text = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setName(pht('Unsubmitted'))
->setSlimShady(true)
->setColor(PHUITagView::COLOR_RED)
->addClass('mml inline-draft-text');
}
$ghost_tag = null;
$ghost = $inline->getIsGhost();
$ghost_id = null;
if ($ghost) {
if ($ghost['new']) {
$ghosticon = 'fa-fast-forward';
$reason = pht('View on forward revision');
} else {
$ghosticon = 'fa-fast-backward';
$reason = pht('View on previous revision');
}
$ghost_icon = id(new PHUIIconView())
->setIcon($ghosticon)
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $reason,
'size' => 300,
));
$ghost_tag = phutil_tag(
'a',
array(
'class' => 'ghost-icon',
'href' => $ghost['href'],
'target' => '_blank',
),
$ghost_icon);
$classes[] = 'inline-comment-ghost';
}
if ($inline->getReplyToCommentPHID()) {
$classes[] = 'inline-comment-is-reply';
}
$viewer_phid = $this->getUser()->getPHID();
$owner_phid = $this->getObjectOwnerPHID();
if ($viewer_phid) {
if ($viewer_phid == $owner_phid) {
$classes[] = 'viewer-is-object-owner';
}
}
$anchor_name = $this->getAnchorName();
$action_buttons = array();
$menu_items = array();
if ($this->editable && !$is_preview) {
$menu_items[] = array(
'label' => pht('Edit Comment'),
'icon' => 'fa-pencil',
'action' => 'edit',
'key' => 'e',
);
} else if ($is_preview) {
$links[] = javelin_tag(
'a',
array(
'class' => 'inline-button-divider pml msl',
'meta' => array(
'inlineCommentID' => $inline->getID(),
),
'sigil' => 'differential-inline-preview-jump',
),
pht('View'));
$action_buttons[] = id(new PHUIButtonView())
->setTag('a')
->setTooltip(pht('Delete'))
->setIcon('fa-trash-o')
->addSigil('differential-inline-delete')
->setMustCapture(true)
->setAuralLabel(pht('Delete'));
}
if (!$is_preview && $this->canHide()) {
$menu_items[] = array(
'label' => pht('Collapse'),
'icon' => 'fa-times',
'action' => 'collapse',
'key' => 'q',
);
}
$can_reply =
(!$this->editable) &&
(!$is_preview) &&
($this->allowReply) &&
// NOTE: No product reason why you can't reply to synthetic comments,
// but the reply mechanism currently sends the inline comment ID to the
// server, not file/line information, and synthetic comments don't have
// an inline comment ID.
(!$is_synthetic);
if ($can_reply) {
$menu_items[] = array(
'label' => pht('Reply to Comment'),
'icon' => 'fa-reply',
'action' => 'reply',
'key' => 'r',
);
$menu_items[] = array(
'label' => pht('Quote Comment'),
'icon' => 'fa-quote-left',
'action' => 'quote',
'key' => 'R',
);
}
if (!$is_preview) {
$xaction_phid = $inline->getTransactionPHID();
$storage = $inline->getStorageObject();
if ($xaction_phid) {
$menu_items[] = array(
'label' => pht('View Raw Remarkup'),
'icon' => 'fa-code',
'action' => 'raw',
'uri' => $storage->getRawRemarkupURI(),
);
}
}
if ($this->editable && !$is_preview) {
$menu_items[] = array(
'label' => pht('Delete Comment'),
'icon' => 'fa-trash-o',
'action' => 'delete',
);
}
$done_button = null;
$mark_done = $this->getCanMarkDone();
// Allow users to mark their own draft inlines as "Done".
if ($viewer_phid == $inline->getAuthorPHID()) {
if ($inline->isDraft()) {
$mark_done = true;
}
}
if (!$is_synthetic) {
$draft_state = false;
switch ($inline->getFixedState()) {
case PhabricatorInlineComment::STATE_DRAFT:
$is_done = $mark_done;
$draft_state = true;
break;
case PhabricatorInlineComment::STATE_UNDRAFT:
$is_done = !$mark_done;
$draft_state = true;
break;
case PhabricatorInlineComment::STATE_DONE:
$is_done = true;
break;
default:
case PhabricatorInlineComment::STATE_UNDONE:
$is_done = false;
break;
}
// If you don't have permission to mark the comment as "Done", you also
// can not see the draft state.
if (!$mark_done) {
$draft_state = false;
}
if ($is_done) {
$classes[] = 'inline-is-done';
}
if ($draft_state) {
$classes[] = 'inline-state-is-draft';
}
if ($mark_done && !$is_preview) {
$done_input = javelin_tag(
'input',
array(
'type' => 'checkbox',
'checked' => ($is_done ? 'checked' : null),
'class' => 'differential-inline-done',
'sigil' => 'differential-inline-done',
));
$done_button = phutil_tag(
'label',
array(
'class' => 'differential-inline-done-label ',
),
array(
$done_input,
pht('Done'),
));
} else {
if ($is_done) {
$icon = id(new PHUIIconView())->setIcon('fa-check sky msr');
$label = pht('Done');
$class = 'button-done';
} else {
$icon = null;
$label = pht('Not Done');
$class = 'button-not-done';
}
$done_button = phutil_tag(
'div',
array(
'class' => 'done-label '.$class,
),
array(
$icon,
$label,
));
}
}
$content = $this->markupEngine->getOutput(
$inline,
PhabricatorInlineComment::MARKUP_FIELD_BODY);
if ($is_preview) {
$anchor = null;
} else {
$anchor = phutil_tag(
'a',
array(
'name' => $anchor_name,
'id' => $anchor_name,
'class' => 'differential-inline-comment-anchor',
),
'');
}
if ($inline->isDraft() && !$is_synthetic) {
$classes[] = 'inline-state-is-draft';
}
if ($is_synthetic) {
$classes[] = 'differential-inline-comment-synthetic';
}
$classes = implode(' ', $classes);
$author_owner = null;
if ($is_synthetic) {
$author = $inline->getSyntheticAuthor();
} else {
$author = $handles[$inline->getAuthorPHID()]->getName();
if ($inline->getAuthorPHID() == $this->objectOwnerPHID) {
$author_owner = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setName(pht('Author'))
->setSlimShady(true)
->setColor(PHUITagView::COLOR_YELLOW)
->addClass('mml');
}
}
$actions = null;
if ($action_buttons || $menu_items) {
$actions = new PHUIButtonBarView();
$actions->setBorderless(true);
$actions->addClass('inline-button-divider');
foreach ($action_buttons as $button) {
$actions->addButton($button);
}
if (!$is_preview) {
$menu_button = id(new PHUIButtonView())
->setTag('a')
->setColor(PHUIButtonView::GREY)
->setDropdown(true)
->setAuralLabel(pht('Inline Actions'))
->addSigil('inline-action-dropdown');
$actions->addButton($menu_button);
}
}
$group_left = phutil_tag(
'div',
array(
'class' => 'inline-head-left',
),
array(
$author,
$author_owner,
$draft_text,
$ghost_tag,
));
$group_right = phutil_tag(
'div',
array(
'class' => 'inline-head-right',
),
array(
$done_button,
$links,
$actions,
));
$snippet = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(96)
->truncateString($inline->getContent());
$metadata['snippet'] = pht('%s: %s', $author, $snippet);
$metadata['menuItems'] = $menu_items;
$markup = javelin_tag(
'div',
array(
'class' => $classes,
'sigil' => $sigil,
'meta' => $metadata,
),
array(
javelin_tag(
'div',
array(
'class' => 'differential-inline-comment-head grouped',
'sigil' => 'differential-inline-header',
),
array(
$group_left,
$group_right,
)),
phutil_tag_div(
'differential-inline-comment-content',
phutil_tag_div('phabricator-remarkup', $content)),
));
$summary = phutil_tag(
'div',
array(
'class' => 'differential-inline-summary',
),
array(
phutil_tag('strong', array(), pht('%s:', $author)),
' ',
$snippet,
));
return array(
$anchor,
$markup,
$summary,
);
}
private function canHide() {
$inline = $this->getInlineComment();
if ($inline->isDraft()) {
return false;
}
if (!$inline->getID()) {
return false;
}
$viewer = $this->getUser();
if (!$viewer->isLoggedIn()) {
return false;
}
if (!$inline->supportsHiding()) {
return false;
}
return true;
}
}
diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css
index 2738a64749..ce5b87effd 100644
--- a/webroot/rsrc/css/application/differential/phui-inline-comment.css
+++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css
@@ -1,429 +1,438 @@
/**
* @provides phui-inline-comment-view-css
*/
.differential-diff td.anchor-target {
background: {$lightyellow};
}
/* In the document, the anchor is positioned inside the inline comment, but
this makes the browser jump into the comment so the top isn't visible.
Instead, artificially position it a bit above the comment so we'll jump a
bit before the comment. This allows us to see the entire comment (and
generally the commented-on lines, at least in the case of one or two-line
comments) after the jump.
*/
.differential-inline-comment-anchor {
position: absolute;
display: block;
margin-top: -72px;
}
.differential-inline-comment-content {
overflow: auto;
}
.differential-inline-comment,
.differential-inline-comment-edit {
- background: {$page.content};
- border: 1px solid {$gentle.highlight.border};
font: {$basefont};
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
white-space: normal;
border-radius: 3px;
margin: 8px 12px;
+ background: {$page.content};
+ border: 1px solid {$blueborder};
}
.device .differential-inline-comment {
margin: 4px;
}
.inline-state-is-draft {
border: 1px dashed {$greyborder};
}
.differential-inline-comment-head {
font-weight: bold;
color: {$darkbluetext};
- border-bottom: 1px solid {$gentle.highlight.border};
padding: 4px 5px 4px 8px;
- background-color: {$gentle.highlight};
+
+ background: {$lightbluebackground};
+ border-bottom: 1px solid {$blueborder};
}
.differential-inline-comment-content {
padding: 12px;
}
.inline-state-is-draft .differential-inline-comment-head {
border-bottom: 1px dashed {$lightgreyborder};
background-color: {$lightgreybackground};
}
/* Tighten up spacing on replies */
.differential-inline-comment.inline-comment-is-reply {
margin-top: 0;
}
.differential-inline-comment .inline-head-right {
float: right;
padding-right: 4px;
}
.differential-inline-comment .inline-head-right .button {
vertical-align: top;
}
.differential-inline-comment .inline-head-left {
float: left;
padding: 4px;
}
.device-phone .differential-inline-comment .inline-head-left {
float: none;
}
.device-phone .differential-inline-comment .inline-head-right {
margin: 12px 0 4px 4px;
}
.device-phone .differential-inline-comment .inline-head-right .mml {
margin: 0 4px 0 0;
}
/* - Sythetic Comment ---------------------------------------------------------
Comments left by our robot overlords.
*/
.differential-inline-comment.differential-inline-comment-synthetic {
border: 1px solid {$blue};
}
.differential-inline-comment.differential-inline-comment-synthetic
.differential-inline-comment-head {
border-bottom: 1px solid {$blueborder};
background-color: {$lightblue};
}
.differential-inline-comment.differential-inline-comment-synthetic
.differential-inline-comment-head {
padding-bottom: 4px;
}
/* - Ghost Comment ------------------------------------------------------------
Comments from older or newer versions of the changeset.
*/
.differential-inline-comment.inline-comment-ghost {
border: 1px solid {$lightgreyborder};
opacity: 0.75;
}
.differential-inline-comment.inline-comment-ghost
.differential-inline-comment-head {
border-bottom: 1px solid {$lightgreyborder};
background-color: {$lightgreybackground};
}
/* - New/Edit Inline Comment --------------------------------------------------
Styles for when you are creating or editing an inline comment.
*/
.differential-inline-comment .done-label {
display: inline-block;
color: {$sh-yellowicon};
padding: 4px;
}
.differential-inline-comment.inline-state-is-draft .done-label,
.differential-inline-comment.inline-comment-ghost .done-label {
color: {$lightgreytext};
}
/* - New/Edit Inline Comment --------------------------------------------------
Styles for when you are creating or editing an inline comment.
*/
.differential-inline-comment-edit-body .aphront-form-input {
margin: 0;
width: 100%;
}
.differential-inline-comment-edit {
padding: 8px;
}
.differential-inline-comment-edit-buttons {
padding: 8px 0 0 0;
}
.differential-inline-comment-edit-buttons button {
float: right;
margin-left: 6px;
}
.differential-inline-comment-edit-title {
font-weight: bold;
color: {$darkbluetext};
padding: 4px 0 12px;
font-size: {$biggerfontsize};
}
.differential-inline-comment-edit {
background-color: {$lightgreybackground};
border: 1px solid {$lightgreyborder};
}
.differential-inline-comment-edit .remarkup-assist-textarea {
border-left-color: {$lightgreyborder};
border-right-color: {$lightgreyborder};
border-bottom-color: {$greyborder};
}
.differential-inline-comment-edit .remarkup-assist-bar {
border-left-color: {$lightgreyborder};
border-right-color: {$lightgreyborder};
border-top-color: {$lightgreyborder};
}
.differential-inline-comment-edit .aphront-form-control-textarea {
padding: 0;
}
/* - Action Buttons -----------------------------------------------------------
Reply, Edit, Delete, View, Button Bars...
*/
.differential-inline-comment .differential-inline-done-label {
border-color: {$gentle.highlight.border};
color: {$bluetext};
}
.differential-inline-comment.inline-state-is-draft
.differential-inline-done-label,
.differential-inline-comment.inline-state-is-draft
.button.simple,
.differential-inline-comment.inline-comment-ghost
.button.simple {
color: {$lightgreytext};
}
/* - Done Button --------------------------------------------------------------
Default colors, hovers, checked styles for the Done Button.
*/
.differential-inline-done-label {
border: 1px solid {$sh-yellowborder};
border-radius: 3px;
display: inline-block;
padding: 3px 8px 4px;
cursor: pointer;
}
.differential-inline-done-label .differential-inline-done {
margin: 0 6px 0 0;
display: inline;
cursor: pointer;
}
.differential-inline-comment.inline-is-done
.differential-inline-done-label {
background-color: {$page.content};
border-color: {$lightblueborder};
color: {$sky};
opacity: 1;
}
.device-desktop .differential-inline-comment.inline-is-done
.differential-inline-done-label:hover {
background-color: {$page.content};
color: {$sky};
}
.differential-inline-comment.inline-is-done .differential-inline-comment-head
.button-done {
color: {$sky};
}
-
-/* - Inline Is Done -----------------------------------------------------------
-
- Is Done for Diff Author = grey, for Diff Viewer = yellow.
-
-*/
-
.differential-inline-comment.inline-is-done {
- border-color: {$lightgreyborder};
+ border-color: {$thingreyborder};
}
.differential-inline-comment.inline-is-done
.differential-inline-comment-head {
background-color: {$lightgreybackground};
- border-bottom-color: {$lightgreyborder};
+ border-bottom-color: {$thingreyborder};
}
.differential-inline-comment.inline-is-done .differential-inline-comment-head
.button.simple {
border-color: {$lightgreyborder};
color: {$lightgreytext};
}
.differential-inline-comment.inline-is-done .differential-inline-comment-head
.differential-inline-done-label {
color: {$sky};
background-color: {$page.content};
border-color: {$sky};
}
/* - Inline State is Draft ----------------------------------------------------
The Unsubmitted state of the comment / done checkbox styles.
*/
.differential-inline-comment .inline-draft-text {
display: none;
}
.differential-inline-comment.inline-state-is-draft .inline-draft-text {
display: inline-block;
}
.inline-state-is-draft .differential-inline-done-label {
border-style: dashed;
}
/* - Undo ---------------------------------------------------------------------
A wild undo box appears!
*/
.differential-inline-undo {
padding: 8px;
margin: 4px 12px;
text-align: center;
background: {$sh-yellowbackground};
border: 1px solid {$sh-yellowborder};
color: {$darkgreytext};
font: {$basefont};
font-size: {$normalfontsize};
border-radius: 3px;
}
.differential-inline-undo a {
font-weight: bold;
}
/* - Spooky Ghost UI -----------------------------------------------------------
Hide your codez.
*/
.inline-comment-ghost .differential-inline-comment-head {
padding-left: 40px;
}
.ghost-icon {
background: rgba({$alphagrey},.07);
float: left;
padding: 2px 4px 2px 2px;
position: absolute;
top: 0;
left: 0;
}
.ghost-icon .phui-icon-view {
padding: 8px 7px;
font-size: 16px;
color: {$lightbluetext};
}
.device-desktop .ghost-icon .phui-icon-view:hover {
color: {$fire};
}
.differential-inline-comment.inline-comment-ghost
.differential-inline-comment-head {
position: relative;
}
.differential-inline-comment.inline-comment-ghost
.differential-inline-done-label,
.differential-inline-comment.inline-comment-ghost {
border-color: {$lightgreyborder};
color: {$lightgreytext};
}
/* - Hiding Inlines ------------------------------------------------------------
*/
.reveal-inline {
color: {$lightbluetext};
margin: 4px 0;
display: none;
}
.inline-hidden .reveal-inline {
display: block;
}
.inline-hidden .differential-inline-comment {
display: none;
}
.differential-inline-summary {
background: {$lightgreybackground};
padding: 2px 16px;
color: {$lightgreytext};
display: none;
font: {$basefont};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.device .differential-inline-summary {
padding-left: 4px;
padding-right: 4px;
}
.inline-hidden .differential-inline-summary {
display: block;
}
.reveal-inline span.phui-icon-view {
color: {$lightbluetext};
}
.reveal-inline:hover span.phui-icon-view {
color: {$darkbluetext};
}
.inline-button-divider {
border-left: 1px solid rgba({$alphagrey},.25);
margin-left: 8px;
}
.differential-inline-comment-synthetic .inline-button-divider {
border: none;
}
+
+.inline-comment-element .differential-inline-comment-head {
+ cursor: pointer;
+}
+
+.inline-comment-selected .inline-comment-element {
+ border-color: {$yellow};
+ background: {$gentle.highlight.background};
+}
+
+.inline-comment-selected .inline-comment-element
+ .differential-inline-comment-head {
+ background: {$lightyellow};
+ border-color: {$yellow};
+}
diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js
index 8957827ccf..9251f21932 100644
--- a/webroot/rsrc/js/application/diff/DiffChangeset.js
+++ b/webroot/rsrc/js/application/diff/DiffChangeset.js
@@ -1,1057 +1,1070 @@
/**
* @provides phabricator-diff-changeset
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* javelin-workflow
* javelin-router
* javelin-behavior-device
* javelin-vector
* phabricator-diff-inline
* phabricator-diff-path-view
* phuix-button-view
* @javelin
*/
JX.install('DiffChangeset', {
construct : function(node) {
this._node = node;
var data = this._getNodeData();
this._renderURI = data.renderURI;
this._ref = data.ref;
this._loaded = data.loaded;
this._treeNodeID = data.treeNodeID;
this._leftID = data.left;
this._rightID = data.right;
this._displayPath = JX.$H(data.displayPath);
this._pathParts = data.pathParts;
this._icon = data.icon;
this._editorURI = data.editorURI;
this._editorConfigureURI = data.editorConfigureURI;
this._showPathURI = data.showPathURI;
this._showDirectoryURI = data.showDirectoryURI;
this._pathIconIcon = data.pathIconIcon;
this._pathIconColor = data.pathIconColor;
this._isLowImportance = data.isLowImportance;
this._isOwned = data.isOwned;
this._isLoading = true;
this._inlines = [];
if (data.changesetState) {
this._loadChangesetState(data.changesetState);
}
+ JX.enableDispatch(window, 'selectstart');
+
var onselect = JX.bind(this, this._onClickHeader);
- JX.DOM.listen(this._node, 'mousedown', 'changeset-header', onselect);
+ JX.DOM.listen(
+ this._node,
+ ['mousedown', 'selectstart'],
+ 'changeset-header',
+ onselect);
},
members: {
_node: null,
_loaded: false,
_sequence: 0,
_stabilize: false,
_renderURI: null,
_ref: null,
_rendererKey: null,
_highlight: null,
_requestDocumentEngineKey: null,
_responseDocumentEngineKey: null,
_availableDocumentEngineKeys: null,
_characterEncoding: null,
_undoTemplates: null,
_leftID: null,
_rightID: null,
_inlines: null,
_visible: true,
_displayPath: null,
_changesetList: null,
_icon: null,
_editorURI: null,
_editorConfigureURI: null,
_showPathURI: null,
_showDirectoryURI: null,
_pathView: null,
_pathIconIcon: null,
_pathIconColor: null,
_isLowImportance: null,
_isOwned: null,
_isHidden: null,
_isSelected: false,
_viewMenu: null,
getEditorURI: function() {
return this._editorURI;
},
getEditorConfigureURI: function() {
return this._editorConfigureURI;
},
getShowPathURI: function() {
return this._showPathURI;
},
getShowDirectoryURI: function() {
return this._showDirectoryURI;
},
getLeftChangesetID: function() {
return this._leftID;
},
getRightChangesetID: function() {
return this._rightID;
},
setChangesetList: function(list) {
this._changesetList = list;
return this;
},
setViewMenu: function(menu) {
this._viewMenu = menu;
return this;
},
getIcon: function() {
if (!this._visible) {
return 'fa-file-o';
}
return this._icon;
},
getColor: function() {
if (!this._visible) {
return 'grey';
}
return 'blue';
},
getChangesetList: function() {
return this._changesetList;
},
/**
* 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(state) {
this._loaded = true;
this._sequence++;
var workflow = this._newReloadWorkflow(state)
.setHandler(JX.bind(this, this._onresponse, this._sequence));
this._startContentWorkflow(workflow);
var pht = this.getChangesetList().getTranslations();
JX.DOM.setContent(
this._getContentFrame(),
JX.$N(
'div',
{className: 'differential-loading'},
pht('Loading...')));
return this;
},
_newReloadWorkflow: function(state) {
var params = this._getViewParameters(state);
return new JX.Workflow(this._renderURI, params);
},
/**
* 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 All Context".
*
* @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 pht = this.getChangesetList().getTranslations();
var container = JX.DOM.scry(target, 'td')[0];
JX.DOM.setContent(container, pht('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 All Context" 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;
},
loadAllContext: function() {
var nodes = JX.DOM.scry(this._node, '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++) {
var data = JX.Stratcom.getData(show[jj]);
if (data.type != 'all') {
continue;
}
this.loadContext(data.range, nodes[ii], true);
}
}
},
_startContentWorkflow: function(workflow) {
var routable = workflow.getRoutable();
routable
.setPriority(500)
.setType('content')
.setKey(this._getRoutableKey());
JX.Router.getInstance().queue(routable);
},
getDisplayPath: function() {
return this._displayPath;
},
/**
* Receive a response to a context request.
*/
_oncontext: function(target, response) {
// TODO: This should be better structured.
// If the response comes back with several top-level nodes, the last one
// is the actual context; the others are headers. Add any headers first,
// then copy the new rows into the document.
var markup = JX.$H(response.changeset).getFragment();
var len = markup.childNodes.length;
var diff = JX.DOM.findAbove(target, 'table', 'differential-diff');
for (var ii = 0; ii < len - 1; ii++) {
diff.parentNode.insertBefore(markup.firstChild, diff);
}
var table = markup.firstChild;
var root = target.parentNode;
this._moveRows(table, root, target);
root.removeChild(target);
this._onchangesetresponse(response);
},
_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(state) {
var parameters = {
ref: this._ref,
device: this._getDefaultDeviceRenderer()
};
if (state) {
JX.copy(parameters, state);
}
return parameters;
},
/**
* 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());
},
getRendererKey: function() {
return this._rendererKey;
},
_getDefaultDeviceRenderer: function() {
// 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.
return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up';
},
getUndoTemplates: function() {
return this._undoTemplates;
},
getCharacterEncoding: function() {
return this._characterEncoding;
},
getHighlight: function() {
return this._highlight;
},
getRequestDocumentEngineKey: function() {
return this._requestDocumentEngineKey;
},
getResponseDocumentEngineKey: function() {
return this._responseDocumentEngineKey;
},
getAvailableDocumentEngineKeys: function() {
return this._availableDocumentEngineKeys;
},
getSelectableItems: function() {
var items = [];
items.push({
type: 'file',
changeset: this,
target: this,
nodes: {
begin: this._node,
end: null
}
});
if (!this._visible) {
return items;
}
var rows = JX.DOM.scry(this._node, 'tr');
var blocks = [];
var block;
var ii;
for (ii = 0; ii < rows.length; ii++) {
var type = this._getRowType(rows[ii]);
if (!block || (block.type !== type)) {
block = {
type: type,
items: []
};
blocks.push(block);
}
block.items.push(rows[ii]);
}
var last_inline = null;
var last_inline_item = null;
for (ii = 0; ii < blocks.length; ii++) {
block = blocks[ii];
if (block.type == 'change') {
items.push({
type: block.type,
changeset: this,
target: block.items[0],
nodes: {
begin: block.items[0],
end: block.items[block.items.length - 1]
}
});
}
if (block.type == 'comment') {
for (var jj = 0; jj < block.items.length; jj++) {
var inline = this.getInlineForRow(block.items[jj]);
// When comments are being edited, they have a hidden row with
// the actual comment and then a visible row with the editor.
// In this case, we only want to generate one item, but it should
// use the editor as a scroll target. To accomplish this, check if
// this row has the same inline as the previous row. If so, update
// the last item to use this row's nodes.
if (inline === last_inline) {
last_inline_item.nodes.begin = block.items[jj];
last_inline_item.nodes.end = block.items[jj];
continue;
} else {
last_inline = inline;
}
var is_saved = (!inline.isDraft() && !inline.isEditing());
last_inline_item = {
type: block.type,
changeset: this,
target: inline,
hidden: inline.isHidden(),
collapsed: inline.isCollapsed(),
deleted: !inline.getID() && !inline.isEditing(),
nodes: {
begin: block.items[jj],
end: block.items[jj]
},
attributes: {
unsaved: inline.isEditing(),
anyDraft: inline.isDraft() || inline.isDraftDone(),
undone: (is_saved && !inline.isDone()),
done: (is_saved && inline.isDone())
}
};
items.push(last_inline_item);
}
}
}
return items;
},
_getRowType: function(row) {
// NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy
// magic.
if (row.className.indexOf('inline') !== -1) {
return 'comment';
}
var cells = JX.DOM.scry(row, 'td');
for (var ii = 0; ii < cells.length; ii++) {
if (cells[ii].className.indexOf('old') !== -1 ||
cells[ii].className.indexOf('new') !== -1) {
return 'change';
}
}
},
_getNodeData: function() {
return JX.Stratcom.getData(this._node);
},
getVectors: function() {
return {
pos: JX.$V(this._node),
dim: JX.Vector.getDim(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 several 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, unless
// we have an anchor.
// - Otherwise, scroll if the changes were above (or, at least,
// almost entirely above) the viewport.
//
// We don't scroll if the changes were just near the top of the viewport
// because this makes us scroll incorrectly when an anchored change is
// visible. See T12779.
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));
// If we have an anchor in the URL, never stick to the bottom of the
// page. See T11784 for discussion.
if (window.location.hash) {
near_bot = false;
}
var target_pos = JX.Vector.getPos(target);
var target_dim = JX.Vector.getDim(target);
var target_bot = (target_pos.y + target_dim.y);
// Detect if the changeset is entirely (or, at least, almost entirely)
// above us. The height here is roughly the height of the persistent
// banner.
var above_screen = (target_bot < old_pos.y + 64);
// If we have a URL anchor and are currently nearby, stick to it
// no matter what.
var on_target = null;
if (window.location.hash) {
try {
var anchor = JX.$(window.location.hash.replace('#', ''));
if (anchor) {
var anchor_pos = JX.$V(anchor);
if ((anchor_pos.y > old_pos.y) &&
(anchor_pos.y < old_pos.y + 96)) {
on_target = anchor;
}
}
} catch (ignored) {
// If we have a bogus anchor, just ignore it.
}
}
var frame = this._getContentFrame();
JX.DOM.setContent(frame, JX.$H(response.changeset));
if (this._stabilize) {
if (on_target) {
JX.DOM.scrollToPosition(old_pos.x, JX.$V(on_target).y - 60);
} else if (!near_top) {
if (near_bot || above_screen) {
// 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;
}
this._onchangesetresponse(response);
},
_onchangesetresponse: function(response) {
// Code shared by autoload and context responses.
this._loadChangesetState(response);
JX.Stratcom.invoke('differential-inline-comment-refresh');
this._rebuildAllInlines();
JX.Stratcom.invoke('resize');
},
_loadChangesetState: function(state) {
if (state.coverage) {
for (var k in state.coverage) {
try {
JX.DOM.replace(JX.$(k), JX.$H(state.coverage[k]));
} catch (ignored) {
// Not terribly important.
}
}
}
if (state.undoTemplates) {
this._undoTemplates = state.undoTemplates;
}
this._rendererKey = state.rendererKey;
this._highlight = state.highlight;
this._characterEncoding = state.characterEncoding;
this._requestDocumentEngineKey = state.requestDocumentEngineKey;
this._responseDocumentEngineKey = state.responseDocumentEngineKey;
this._availableDocumentEngineKeys = state.availableDocumentEngineKeys;
this._isHidden = state.isHidden;
var is_hidden = !this.isVisible();
if (this._isHidden != is_hidden) {
this.setVisible(!this._isHidden);
}
this._isLoading = false;
this.getPathView().setIsLoading(this._isLoading);
},
_getContentFrame: function() {
return JX.DOM.find(this._node, 'div', 'changeset-view-content');
},
_getRoutableKey: function() {
return 'changeset-view.' + this._ref + '.' + this._sequence;
},
getInlineForRow: function(node) {
var data = JX.Stratcom.getData(node);
if (!data.inline) {
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToRow(node);
this._inlines.push(inline);
}
return data.inline;
},
newInlineForRange: function(origin, target, options) {
var list = this.getChangesetList();
var src = list.getLineNumberFromHeader(origin);
var dst = list.getLineNumberFromHeader(target);
var changeset_id = null;
var side = list.getDisplaySideFromHeader(origin);
if (side == 'right') {
changeset_id = this.getRightChangesetID();
} else {
changeset_id = this.getLeftChangesetID();
}
var is_new = false;
if (side == 'right') {
is_new = true;
} else if (this.getRightChangesetID() != this.getLeftChangesetID()) {
is_new = true;
}
var data = {
origin: origin,
target: target,
number: src,
length: dst - src,
changesetID: changeset_id,
displaySide: side,
isNewFile: is_new
};
JX.copy(data, options || {});
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToRange(data);
this._inlines.push(inline);
inline.create();
return inline;
},
newInlineReply: function(original, text) {
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToReply(original);
this._inlines.push(inline);
inline.create(text);
return inline;
},
getInlineByID: function(id) {
return this._queryInline('id', id);
},
getInlineByPHID: function(phid) {
return this._queryInline('phid', phid);
},
_queryInline: function(field, value) {
// First, look for the inline in the objects we've already built.
var inline = this._findInline(field, value);
if (inline) {
return inline;
}
// If we haven't found a matching inline yet, rebuild all the inlines
// present in the document, then look again.
this._rebuildAllInlines();
return this._findInline(field, value);
},
_findInline: function(field, value) {
for (var ii = 0; ii < this._inlines.length; ii++) {
var inline = this._inlines[ii];
var target;
switch (field) {
case 'id':
target = inline.getID();
break;
case 'phid':
target = inline.getPHID();
break;
}
if (target == value) {
return inline;
}
}
return null;
},
getInlines: function() {
this._rebuildAllInlines();
return this._inlines;
},
_rebuildAllInlines: function() {
var rows = JX.DOM.scry(this._node, 'tr');
var ii;
for (ii = 0; ii < rows.length; ii++) {
var row = rows[ii];
if (this._getRowType(row) != 'comment') {
continue;
}
// As a side effect, this builds any missing inline objects and adds
// them to this Changeset's list of inlines.
this.getInlineForRow(row);
}
},
redrawFileTree: function() {
var inlines = this._inlines;
var done = [];
var undone = [];
var inline;
for (var ii = 0; ii < inlines.length; ii++) {
inline = inlines[ii];
if (inline.isDeleted()) {
continue;
}
if (inline.isUndo()) {
continue;
}
if (inline.isSynthetic()) {
continue;
}
if (inline.isEditing()) {
continue;
}
if (!inline.getID()) {
// These are new comments which have been cancelled, and do not
// count as anything.
continue;
}
if (inline.isDraft()) {
continue;
}
if (!inline.isDone()) {
undone.push(inline);
} else {
done.push(inline);
}
}
var total = done.length + undone.length;
var hint;
var is_visible;
var is_completed;
if (total) {
if (done.length) {
hint = [done.length, '/', total];
} else {
hint = total;
}
is_visible = true;
is_completed = (done.length == total);
} else {
hint = '-';
is_visible = false;
is_completed = false;
}
var node = this.getPathView().getInlineNode();
JX.DOM.setContent(node, hint);
JX.DOM.alterClass(node, 'diff-tree-path-inlines-visible', is_visible);
JX.DOM.alterClass(node, 'diff-tree-path-inlines-completed', is_completed);
},
_onClickHeader: function(e) {
// If the user clicks the actual path name text, don't count this as
// a selection action: we want to let them select the path.
var path_name = e.getNode('changeset-header-path-name');
if (path_name) {
return;
}
+ // Don't allow repeatedly clicking a header to begin a "select word" or
+ // "select line" operation.
+ if (e.getType() === 'selectstart') {
+ e.kill();
+ return;
+ }
+
// NOTE: Don't prevent or kill the event. If the user has text selected,
// clicking a header should clear the selection (and dismiss any inline
// context menu, if one exists) as clicking elsewhere in the document
// normally would.
if (this._isSelected) {
this.getChangesetList().selectChangeset(null);
} else {
this.select(false);
}
},
toggleVisibility: function() {
this.setVisible(!this._visible);
var attrs = {
hidden: this.isVisible() ? 0 : 1,
discard: 1
};
var workflow = this._newReloadWorkflow(attrs)
.setHandler(JX.bag);
this._startContentWorkflow(workflow);
},
setVisible: function(visible) {
this._visible = visible;
var diff = this._getDiffNode();
var options = this._getViewButtonNode();
var show = this._getShowButtonNode();
if (this._visible) {
JX.DOM.show(diff);
JX.DOM.show(options);
JX.DOM.hide(show);
} else {
JX.DOM.hide(diff);
JX.DOM.hide(options);
JX.DOM.show(show);
if (this._viewMenu) {
this._viewMenu.close();
}
}
JX.Stratcom.invoke('resize');
var node = this._node;
JX.DOM.alterClass(node, 'changeset-content-hidden', !this._visible);
this.getPathView().setIsHidden(!this._visible);
},
setIsSelected: function(is_selected) {
this._isSelected = !!is_selected;
var node = this._node;
JX.DOM.alterClass(node, 'changeset-selected', this._isSelected);
return this;
},
_getDiffNode: function() {
if (!this._diffNode) {
this._diffNode = JX.DOM.find(this._node, 'table', 'differential-diff');
}
return this._diffNode;
},
_getViewButtonNode: function() {
if (!this._viewButtonNode) {
this._viewButtonNode = JX.DOM.find(
this._node,
'a',
'differential-view-options');
}
return this._viewButtonNode;
},
_getShowButtonNode: function() {
if (!this._showButtonNode) {
var pht = this.getChangesetList().getTranslations();
var show_button = new JX.PHUIXButtonView()
.setIcon('fa-angle-double-down')
.setText(pht('Show Changeset'))
.setColor('grey');
var button_node = show_button.getNode();
this._getViewButtonNode().parentNode.appendChild(button_node);
var onshow = JX.bind(this, this._onClickShowButton);
JX.DOM.listen(button_node, 'click', null, onshow);
this._showButtonNode = button_node;
}
return this._showButtonNode;
},
_onClickShowButton: function(e) {
e.prevent();
// We're always showing the changeset, but want to make sure the state
// change is persisted on the server.
this.toggleVisibility();
},
isVisible: function() {
return this._visible;
},
getPathView: function() {
if (!this._pathView) {
var view = new JX.DiffPathView()
.setChangeset(this)
.setPath(this._pathParts)
.setIsLowImportance(this._isLowImportance)
.setIsOwned(this._isOwned)
.setIsLoading(this._isLoading);
view.getIcon()
.setIcon(this._pathIconIcon)
.setColor(this._pathIconColor);
this._pathView = view;
}
return this._pathView;
},
select: function(scroll) {
this.getChangesetList().selectChangeset(this, scroll);
return this;
}
},
statics: {
getForNode: function(node) {
var data = JX.Stratcom.getData(node);
if (!data.changesetViewManager) {
data.changesetViewManager = new JX.DiffChangeset(node);
}
return data.changesetViewManager;
}
}
});
diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js
index f380de4e75..24c520dc9a 100644
--- a/webroot/rsrc/js/application/diff/DiffChangesetList.js
+++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js
@@ -1,2746 +1,2771 @@
/**
* @provides phabricator-diff-changeset-list
* @requires javelin-install
* phuix-button-view
* phabricator-diff-tree-view
* @javelin
*/
JX.install('DiffChangesetList', {
construct: function() {
this._changesets = [];
var onload = JX.bind(this, this._ifawake, this._onload);
JX.Stratcom.listen('click', 'differential-load', onload);
var onmore = JX.bind(this, this._ifawake, this._onmore);
JX.Stratcom.listen('click', 'show-more', onmore);
var onmenu = JX.bind(this, this._ifawake, this._onmenu);
JX.Stratcom.listen('click', 'differential-view-options', onmenu);
var onexpand = JX.bind(this, this._ifawake, this._oncollapse, false);
JX.Stratcom.listen('click', 'reveal-inline', onexpand);
var onresize = JX.bind(this, this._ifawake, this._onresize);
JX.Stratcom.listen('resize', null, onresize);
var onscroll = JX.bind(this, this._ifawake, this._onscroll);
JX.Stratcom.listen('scroll', null, onscroll);
+ JX.enableDispatch(window, 'selectstart');
+
var onselect = JX.bind(this, this._ifawake, this._onselect);
JX.Stratcom.listen(
- 'mousedown',
+ ['mousedown', 'selectstart'],
['differential-inline-comment', 'differential-inline-header'],
onselect);
var onhover = JX.bind(this, this._ifawake, this._onhover);
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'differential-inline-comment',
onhover);
var onrangedown = JX.bind(this, this._ifawake, this._onrangedown);
JX.Stratcom.listen(
'mousedown',
['differential-changeset', 'tag:td'],
onrangedown);
var onrangemove = JX.bind(this, this._ifawake, this._onrangemove);
JX.Stratcom.listen(
['mouseover', 'mouseout'],
['differential-changeset', 'tag:td'],
onrangemove);
var onrangeup = JX.bind(this, this._ifawake, this._onrangeup);
JX.Stratcom.listen(
'mouseup',
null,
onrangeup);
var onrange = JX.bind(this, this._ifawake, this._onSelectRange);
JX.enableDispatch(window, 'selectionchange');
JX.Stratcom.listen('selectionchange', null, onrange);
this._setupInlineCommentListeners();
},
properties: {
translations: null,
inlineURI: null,
inlineListURI: null,
isStandalone: false,
formationView: null
},
members: {
_initialized: false,
_asleep: true,
_changesets: null,
_cursorItem: null,
_focusNode: null,
_focusStart: null,
_focusEnd: null,
_hoverNode: null,
_hoverInline: null,
_hoverOrigin: null,
_hoverTarget: null,
_rangeActive: false,
_rangeOrigin: null,
_rangeTarget: null,
_bannerNode: null,
_unsavedButton: null,
_unsubmittedButton: null,
_doneButton: null,
_doneMode: null,
_dropdownMenu: null,
_menuButton: null,
_menuItems: null,
_selectedChangeset: null,
sleep: function() {
this._asleep = true;
this._redrawFocus();
this._redrawSelection();
this.resetHover();
this._bannerChangeset = null;
this._redrawBanner();
},
wake: function() {
this._asleep = false;
this._redrawFocus();
this._redrawSelection();
this._bannerChangeset = null;
this._redrawBanner();
this._redrawFiletree();
if (this._initialized) {
return;
}
this._initialized = true;
var pht = this.getTranslations();
// We may be viewing the normal "/D123" view (with all the changesets)
// or the standalone view (with just one changeset). In the standalone
// view, some options (like jumping to next or previous file) do not
// make sense and do not function.
var standalone = this.getIsStandalone();
var label;
if (!standalone) {
label = pht('Jump to the table of contents.');
this._installKey('t', 'diff-nav', label, this._ontoc);
label = pht('Jump to the comment area.');
this._installKey('x', 'diff-nav', label, this._oncomments);
}
label = pht('Jump to next change.');
this._installJumpKey('j', label, 1);
label = pht('Jump to previous change.');
this._installJumpKey('k', label, -1);
if (!standalone) {
label = pht('Jump to next file.');
this._installJumpKey('J', label, 1, 'file');
label = pht('Jump to previous file.');
this._installJumpKey('K', label, -1, 'file');
}
label = pht('Jump to next inline comment.');
this._installJumpKey('n', label, 1, 'comment');
label = pht('Jump to previous inline comment.');
this._installJumpKey('p', label, -1, 'comment');
label = pht('Jump to next inline comment, including collapsed comments.');
this._installJumpKey('N', label, 1, 'comment', true);
label = pht(
'Jump to previous inline comment, including collapsed comments.');
this._installJumpKey('P', label, -1, 'comment', true);
var formation = this.getFormationView();
if (formation) {
var filetree = formation.getColumn(0);
var toggletree = JX.bind(filetree, filetree.toggleVisibility);
label = pht('Hide or show the paths panel.');
this._installKey('f', 'diff-vis', label, toggletree);
}
if (!standalone) {
label = pht('Hide or show the current changeset.');
this._installKey('h', 'diff-vis', label, this._onkeytogglefile);
}
label = pht('Reply to selected inline comment or change.');
this._installKey('r', 'inline', label,
JX.bind(this, this._onkeyreply, false));
label = pht('Reply and quote selected inline comment.');
this._installKey('R', 'inline', label,
JX.bind(this, this._onkeyreply, true));
label = pht('Add new inline comment on selected source text.');
this._installKey('c', 'inline', label,
JX.bind(this, this._onKeyCreate));
label = pht('Edit selected inline comment.');
this._installKey('e', 'inline', label, this._onkeyedit);
label = pht('Mark or unmark selected inline comment as done.');
this._installKey('w', 'inline', label, this._onkeydone);
label = pht('Collapse or expand inline comment.');
this._installKey('q', 'diff-vis', label, this._onkeycollapse);
label = pht('Hide or show all inline comments.');
this._installKey('A', 'diff-vis', label, this._onkeyhideall);
label = pht('Show path in repository.');
this._installKey('d', 'diff-nav', label, this._onkeyshowpath);
label = pht('Show directory in repository.');
this._installKey('D', 'diff-nav', label, this._onkeyshowdirectory);
label = pht('Open file in external editor.');
this._installKey('\\', 'diff-nav', label, this._onkeyopeneditor);
},
isAsleep: function() {
return this._asleep;
},
newChangesetForNode: function(node) {
var changeset = JX.DiffChangeset.getForNode(node);
this._changesets.push(changeset);
changeset.setChangesetList(this);
return changeset;
},
getChangesetForNode: function(node) {
return JX.DiffChangeset.getForNode(node);
},
getInlineByID: function(id) {
var inline = null;
for (var ii = 0; ii < this._changesets.length; ii++) {
inline = this._changesets[ii].getInlineByID(id);
if (inline) {
break;
}
}
return inline;
},
_ifawake: function(f) {
// This function takes another function and only calls it if the
// changeset list is awake, so we basically just ignore events when we
// are asleep. This may move up the stack at some point as we do more
// with Quicksand/Sheets.
if (this.isAsleep()) {
return;
}
return f.apply(this, [].slice.call(arguments, 1));
},
_onload: function(e) {
var data = e.getNodeData('differential-load');
// NOTE: We can trigger a load from either an explicit "Load" link on
// the changeset, or by clicking a link in the table of contents. If
// the event was a table of contents link, we let the anchor behavior
// run normally.
if (data.kill) {
e.kill();
}
var node = JX.$(data.id);
var changeset = this.getChangesetForNode(node);
changeset.load();
// TODO: Move this into Changeset.
var routable = changeset.getRoutable();
if (routable) {
routable.setPriority(2000);
}
},
_installKey: function(key, group, label, handler) {
handler = JX.bind(this, this._ifawake, handler);
return new JX.KeyboardShortcut(key, label)
.setHandler(handler)
.setGroup(group)
.register();
},
_installJumpKey: function(key, label, delta, filter, show_collapsed) {
filter = filter || null;
var options = {
filter: filter,
collapsed: show_collapsed
};
var handler = JX.bind(this, this._onjumpkey, delta, options);
return this._installKey(key, 'diff-nav', label, handler);
},
_ontoc: function(manager) {
var toc = JX.$('toc');
manager.scrollTo(toc);
},
_oncomments: function(manager) {
var reply = JX.$('reply');
manager.scrollTo(reply);
},
getSelectedInline: function() {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
return cursor.target;
}
}
return null;
},
_onkeyreply: function(is_quote) {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.target;
if (inline.canReply()) {
this.setFocus(null);
inline.reply(true);
return;
}
}
// If the keyboard cursor is selecting a range of lines, we may have
// a mixture of old and new changes on the selected rows. It is not
// entirely unambiguous what the user means when they say they want
// to reply to this, but we use this logic: reply on the new file if
// there are any new lines. Otherwise (if there are only removed
// lines) reply on the old file.
if (cursor.type == 'change') {
var origin = cursor.nodes.begin;
var target = cursor.nodes.end;
// The "origin" and "target" are entire rows, but we need to find
// a range of "<th />" nodes to actually create an inline, so go
// fishing.
var old_list = [];
var new_list = [];
var row = origin;
while (row) {
var header = row.firstChild;
while (header) {
if (this.getLineNumberFromHeader(header)) {
if (header.className.indexOf('old') !== -1) {
old_list.push(header);
} else if (header.className.indexOf('new') !== -1) {
new_list.push(header);
}
}
header = header.nextSibling;
}
if (row == target) {
break;
}
row = row.nextSibling;
}
var use_list;
if (new_list.length) {
use_list = new_list;
} else {
use_list = old_list;
}
var src = use_list[0];
var dst = use_list[use_list.length - 1];
cursor.changeset.newInlineForRange(src, dst);
this.setFocus(null);
return;
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment or change to reply to.'));
},
_onkeyedit: function() {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.target;
if (inline.canEdit()) {
this.setFocus(null);
inline.edit();
return;
}
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment to edit.'));
},
_onKeyCreate: function() {
var start = this._sourceSelectionStart;
var end = this._sourceSelectionEnd;
if (!this._sourceSelectionStart) {
var pht = this.getTranslations();
this._warnUser(
pht(
'You must select source text to create a new inline comment.'));
return;
}
this._setSourceSelection(null, null);
var config = {
startOffset: start.offset,
endOffset: end.offset
};
var changeset = start.changeset;
changeset.newInlineForRange(start.targetNode, end.targetNode, config);
},
_onkeydone: function() {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.target;
if (inline.canDone()) {
this.setFocus(null);
inline.toggleDone();
return;
}
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment to mark done.'));
},
_onkeytogglefile: function() {
var pht = this.getTranslations();
var changeset = this._getChangesetForKeyCommand();
if (!changeset) {
this._warnUser(pht('You must select a file to hide or show.'));
return;
}
changeset.toggleVisibility();
},
_getChangesetForKeyCommand: function() {
var cursor = this._cursorItem;
var changeset;
if (cursor) {
changeset = cursor.changeset;
}
if (!changeset) {
changeset = this._getVisibleChangeset();
}
return changeset;
},
_onkeyopeneditor: function() {
var pht = this.getTranslations();
var changeset = this._getChangesetForKeyCommand();
if (!changeset) {
this._warnUser(pht('You must select a file to edit.'));
return;
}
var editor_uri = changeset.getEditorURI();
if (editor_uri === null) {
this._warnUser(pht('No external editor is configured.'));
return;
}
JX.$U(editor_uri).go();
},
_onkeyshowpath: function() {
this._onrepositorykey(false);
},
_onkeyshowdirectory: function() {
this._onrepositorykey(true);
},
_onrepositorykey: function(is_directory) {
var pht = this.getTranslations();
var changeset = this._getChangesetForKeyCommand();
if (!changeset) {
this._warnUser(pht('You must select a file to open.'));
return;
}
var show_uri;
if (is_directory) {
show_uri = changeset.getShowDirectoryURI();
} else {
show_uri = changeset.getShowPathURI();
}
if (show_uri === null) {
return;
}
window.open(show_uri);
},
_onkeycollapse: function() {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.target;
if (inline.canCollapse()) {
this.setFocus(null);
inline.setCollapsed(!inline.isCollapsed());
return;
}
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment to hide.'));
},
_onkeyhideall: function() {
var inlines = this._getInlinesByType();
if (inlines.visible.length) {
this._toggleInlines('all');
} else {
this._toggleInlines('show');
}
},
_warnUser: function(message) {
new JX.Notification()
.setContent(message)
.alterClassName('jx-notification-alert', true)
.setDuration(3000)
.show();
},
_onjumpkey: function(delta, options) {
var state = this._getSelectionState();
var filter = options.filter || null;
var collapsed = options.collapsed || false;
var wrap = options.wrap || false;
var attribute = options.attribute || null;
var show = options.show || false;
var cursor = state.cursor;
var items = state.items;
// If there's currently no selection and the user tries to go back,
// don't do anything.
if ((cursor === null) && (delta < 0)) {
return;
}
var did_wrap = false;
while (true) {
if (cursor === null) {
cursor = 0;
} else {
cursor = cursor + delta;
}
// If we've gone backward past the first change, bail out.
if (cursor < 0) {
return;
}
// If we've gone forward off the end of the list, figure out where we
// should end up.
if (cursor >= items.length) {
if (!wrap) {
// If we aren't wrapping around, we're done.
return;
}
if (did_wrap) {
// If we're already wrapped around, we're done.
return;
}
// Otherwise, wrap the cursor back to the top.
cursor = 0;
did_wrap = true;
}
// If we're selecting things of a particular type (like only files)
// and the next item isn't of that type, move past it.
if (filter !== null) {
if (items[cursor].type !== filter) {
continue;
}
}
// If the item is collapsed, don't select it when iterating with jump
// keys. It can still potentially be selected in other ways.
if (!collapsed) {
if (items[cursor].collapsed) {
continue;
}
}
// If the item has been deleted, don't select it when iterating. The
// cursor may remain on it until it is removed.
if (items[cursor].deleted) {
continue;
}
// If we're selecting things with a particular attribute, like
// "unsaved", skip items without the attribute.
if (attribute !== null) {
if (!(items[cursor].attributes || {})[attribute]) {
continue;
}
}
// If this item is a hidden inline but we're clicking a button which
// selects inlines of a particular type, make it visible again.
if (items[cursor].hidden) {
if (!show) {
continue;
}
items[cursor].target.setHidden(false);
}
// Otherwise, we've found a valid item to select.
break;
}
this._setSelectionState(items[cursor], true);
},
_getSelectionState: function() {
var items = this._getSelectableItems();
var cursor = null;
if (this._cursorItem !== null) {
for (var ii = 0; ii < items.length; ii++) {
var item = items[ii];
if (this._cursorItem.target === item.target) {
cursor = ii;
break;
}
}
}
return {
cursor: cursor,
items: items
};
},
selectChangeset: function(changeset, scroll) {
var items = this._getSelectableItems();
var cursor = null;
for (var ii = 0; ii < items.length; ii++) {
var item = items[ii];
if (changeset === item.target) {
cursor = ii;
break;
}
}
if (cursor !== null) {
this._setSelectionState(items[cursor], scroll);
} else {
this._setSelectionState(null, false);
}
return this;
},
_setSelectionState: function(item, scroll) {
+ var old = this._cursorItem;
+
+ if (old) {
+ if (old.type === 'comment') {
+ old.target.setIsSelected(false);
+ }
+ }
+
this._cursorItem = item;
+
+ if (item) {
+ if (item.type === 'comment') {
+ item.target.setIsSelected(true);
+ }
+ }
+
this._redrawSelection(scroll);
return this;
},
_redrawSelection: function(scroll) {
var cursor = this._cursorItem;
if (!cursor) {
this.setFocus(null);
return;
}
// If this item has been removed from the document (for example: create
// a new empty comment, then use the "Unsaved" button to select it, then
// cancel it), we can still keep the cursor here but do not want to show
// a selection reticle over an invisible node.
if (cursor.deleted) {
this.setFocus(null);
return;
}
var changeset = cursor.changeset;
var tree = this._getTreeView();
if (changeset) {
tree.setSelectedPath(cursor.changeset.getPathView());
} else {
tree.setSelectedPath(null);
}
this._selectChangeset(changeset);
this.setFocus(cursor.nodes.begin, cursor.nodes.end);
if (scroll) {
var pos = JX.$V(cursor.nodes.begin);
JX.DOM.scrollToPosition(0, pos.y - 60);
}
return this;
},
redrawCursor: function() {
// NOTE: This is setting the cursor to the current cursor. Usually, this
// would have no effect.
// However, if the old cursor pointed at an inline and the inline has
// been edited so the rows have changed, this updates the cursor to point
// at the new inline with the proper rows for the current state, and
// redraws the reticle correctly.
var state = this._getSelectionState();
if (state.cursor !== null) {
this._setSelectionState(state.items[state.cursor], false);
}
},
_getSelectableItems: function() {
var result = [];
for (var ii = 0; ii < this._changesets.length; ii++) {
var items = this._changesets[ii].getSelectableItems();
for (var jj = 0; jj < items.length; jj++) {
result.push(items[jj]);
}
}
return result;
},
_onhover: function(e) {
if (e.getIsTouchEvent()) {
return;
}
var inline;
if (e.getType() == 'mouseout') {
inline = null;
} else {
inline = this._getInlineForEvent(e);
}
this._setHoverInline(inline);
},
_onmore: function(e) {
e.kill();
var node = e.getNode('differential-changeset');
var changeset = this.getChangesetForNode(node);
var data = e.getNodeData('show-more');
var target = e.getNode('context-target');
changeset.loadContext(data.range, target);
},
_onmenu: function(e) {
var button = e.getNode('differential-view-options');
var data = JX.Stratcom.getData(button);
if (data.menu) {
// We've already built this menu, so we can let the menu itself handle
// the event.
return;
}
e.prevent();
var pht = this.getTranslations();
var node = JX.DOM.findAbove(
button,
'div',
'differential-changeset');
var changeset_list = this;
var changeset = this.getChangesetForNode(node);
var menu = new JX.PHUIXDropdownMenu(button)
.setWidth(240);
var list = new JX.PHUIXActionListView();
var add_link = function(icon, name, href, local) {
var link = new JX.PHUIXActionView()
.setIcon(icon)
.setName(name)
.setHandler(function(e) {
if (local) {
window.location.assign(href);
} else {
window.open(href);
}
menu.close();
e.prevent();
});
if (href) {
link.setHref(href);
} else {
link
.setDisabled(true)
.setUnresponsive(true);
}
list.addItem(link);
return link;
};
var visible_item = new JX.PHUIXActionView()
.setKeyCommand('h')
.setHandler(function(e) {
e.prevent();
menu.close();
changeset.select(false);
changeset.toggleVisibility();
});
list.addItem(visible_item);
var reveal_item = new JX.PHUIXActionView()
.setIcon('fa-eye');
list.addItem(reveal_item);
list.addItem(
new JX.PHUIXActionView()
.setDivider(true));
var up_item = new JX.PHUIXActionView()
.setHandler(function(e) {
if (changeset.isLoaded()) {
// Don't let the user swap display modes if a comment is being
// edited, since they might lose their work. See PHI180.
var inlines = changeset.getInlines();
for (var ii = 0; ii < inlines.length; ii++) {
if (inlines[ii].isEditing()) {
changeset_list._warnUser(
pht(
'Finish editing inline comments before changing display ' +
'modes.'));
e.prevent();
menu.close();
return;
}
}
var renderer = changeset.getRendererKey();
if (renderer == '1up') {
renderer = '2up';
} else {
renderer = '1up';
}
changeset.reload({renderer: renderer});
} else {
changeset.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: changeset.getCharacterEncoding()
};
new JX.Workflow('/services/encoding/', params)
.setHandler(function(r) {
changeset.reload({encoding: r.encoding});
})
.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: changeset.getHighlight()
};
new JX.Workflow('/services/highlight/', params)
.setHandler(function(r) {
changeset.reload({highlight: r.highlight});
})
.start();
e.prevent();
menu.close();
});
list.addItem(highlight_item);
var engine_item = new JX.PHUIXActionView()
.setIcon('fa-file-image-o')
.setName(pht('View As Document Type...'))
.setHandler(function(e) {
var options = changeset.getAvailableDocumentEngineKeys() || [];
options = options.join(',');
var params = {
engine: changeset.getResponseDocumentEngineKey(),
options: options
};
new JX.Workflow('/services/viewas/', params)
.setHandler(function(r) {
changeset.reload({engine: r.engine});
})
.start();
e.prevent();
menu.close();
});
list.addItem(engine_item);
list.addItem(
new JX.PHUIXActionView()
.setDivider(true));
add_link('fa-external-link', pht('View Standalone'), data.standaloneURI);
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-folder-open-o',
pht('Show Directory in Repository'),
changeset.getShowDirectoryURI())
.setKeyCommand('D');
add_link(
'fa-file-text-o',
pht('Show Path in Repository'),
changeset.getShowPathURI())
.setKeyCommand('d');
var editor_uri = changeset.getEditorURI();
if (editor_uri !== null) {
add_link('fa-i-cursor', pht('Open in Editor'), editor_uri, true)
.setKeyCommand('\\');
} else {
var configure_uri = changeset.getEditorConfigureURI();
if (configure_uri !== null) {
add_link('fa-wrench', pht('Configure Editor'), configure_uri);
}
}
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 All Context'))
.setIcon('fa-arrows-v')
.setHandler(function(e) {
changeset.loadAllContext();
e.prevent();
menu.close();
});
} else {
reveal_item
.setDisabled(true)
.setUnresponsive(true)
.setIcon('fa-file')
.setName(pht('All Context Shown'))
.setHref(null);
}
encoding_item.setDisabled(!changeset.isLoaded());
highlight_item.setDisabled(!changeset.isLoaded());
engine_item.setDisabled(!changeset.isLoaded());
if (changeset.isLoaded()) {
if (changeset.getRendererKey() == '2up') {
up_item
.setIcon('fa-list-alt')
.setName(pht('View Unified Diff'));
} else {
up_item
.setIcon('fa-columns')
.setName(pht('View Side-by-Side Diff'));
}
} else {
up_item
.setIcon('fa-refresh')
.setName(pht('Load Changes'));
}
visible_item
.setDisabled(true)
.setIcon('fa-eye-slash')
.setName(pht('Hide Changeset'));
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) {
visible_item.setDisabled(false);
} else {
// Do nothing when there is no diff shown in the table. For example,
// the file is binary.
}
});
data.menu = menu;
changeset.setViewMenu(menu);
menu.open();
},
_oncollapse: function(is_collapse, e) {
e.kill();
var inline = this._getInlineForEvent(e);
inline.setCollapsed(is_collapse);
},
_onresize: function() {
this._redrawFocus();
this._redrawSelection();
this._redrawHover();
// Force a banner redraw after a resize event. Particularly, this makes
// sure the inline state updates immediately after an inline edit
// operation, even if the changeset itself has not changed.
this._bannerChangeset = null;
this._redrawBanner();
var changesets = this._changesets;
for (var ii = 0; ii < changesets.length; ii++) {
changesets[ii].redrawFileTree();
}
},
_onscroll: function() {
this._redrawBanner();
},
_onselect: function(e) {
// If the user clicked some element inside the header, like an action
// icon, ignore the event. They have to click the header element itself.
if (e.getTarget() !== e.getNode('differential-inline-header')) {
return;
}
+ // If the user has double-clicked or triple-clicked a header, we want to
+ // toggle the inline selection mode, not select text. Kill select events
+ // originating with this element as the target.
+ if (e.getType() === 'selectstart') {
+ e.kill();
+ return;
+ }
+
var inline = this._getInlineForEvent(e);
if (!inline) {
return;
}
// NOTE: Don't kill or prevent the event. In particular, we want this
// click to clear any text selection as it normally would.
this.selectInline(inline);
},
selectInline: function(inline, force, scroll) {
var selection = this._getSelectionState();
var item;
if (!force) {
// If the comment the user clicked is currently selected, deselect it.
// This makes it easy to undo things if you clicked by mistake.
if (selection.cursor !== null) {
item = selection.items[selection.cursor];
if (item.target === inline) {
this._setSelectionState(null, false);
return;
}
}
}
// Otherwise, select the item that the user clicked. This makes it
// easier to resume keyboard operations after using the mouse to do
// something else.
var items = selection.items;
for (var ii = 0; ii < items.length; ii++) {
item = items[ii];
if (item.target === inline) {
this._setSelectionState(item, scroll);
}
}
},
redrawPreview: function() {
// TODO: This isn't the cleanest way to find the preview form, but
// rendering no longer has direct access to it.
var forms = JX.DOM.scry(document.body, 'form', 'transaction-append');
if (forms.length) {
JX.DOM.invoke(forms[0], 'shouldRefresh');
}
// Clear the mouse hover reticle after a substantive edit: we don't get
// a "mouseout" event if the row vanished because of row being removed
// after an edit.
this.resetHover();
},
setFocus: function(node, extended_node) {
if (!node) {
var tree = this._getTreeView();
tree.setSelectedPath(null);
this._selectChangeset(null);
}
this._focusStart = node;
this._focusEnd = extended_node;
this._redrawFocus();
},
_selectChangeset: function(changeset) {
if (this._selectedChangeset === changeset) {
return;
}
if (this._selectedChangeset !== null) {
this._selectedChangeset.setIsSelected(false);
this._selectedChangeset = null;
}
this._selectedChangeset = changeset;
if (this._selectedChangeset !== null) {
this._selectedChangeset.setIsSelected(true);
}
},
_redrawFocus: function() {
var node = this._focusStart;
var extended_node = this._focusEnd || node;
var reticle = this._getFocusNode();
if (!node || this.isAsleep()) {
JX.DOM.remove(reticle);
return;
}
// Outset the reticle some pixels away from the element, so there's some
// space between the focused element and the outline.
var p = JX.Vector.getPos(node);
var s = JX.Vector.getAggregateScrollForNode(node);
var d = JX.Vector.getDim(node);
p.add(s).add(d.x + 1, 4).setPos(reticle);
// Compute the size we need to extend to the full extent of the focused
// nodes.
JX.Vector.getPos(extended_node)
.add(-p.x, -p.y)
.add(0, JX.Vector.getDim(extended_node).y)
.add(10, -4)
.setDim(reticle);
JX.DOM.getContentFrame().appendChild(reticle);
},
_getFocusNode: function() {
if (!this._focusNode) {
var node = JX.$N('div', {className : 'keyboard-focus-focus-reticle'});
this._focusNode = node;
}
return this._focusNode;
},
_setHoverInline: function(inline) {
if (inline && (this._hoverInline === inline)) {
return;
}
this._hoverInline = inline;
if (inline) {
var changeset = inline.getChangeset();
var changeset_id;
var side = inline.getDisplaySide();
if (side == 'right') {
changeset_id = changeset.getRightChangesetID();
} else {
changeset_id = changeset.getLeftChangesetID();
}
var new_part;
if (inline.isNewFile()) {
new_part = 'N';
} else {
new_part = 'O';
}
var prefix = 'C' + changeset_id + new_part + 'L';
var number = inline.getLineNumber();
var length = inline.getLineLength();
try {
var origin = JX.$(prefix + number);
var target = JX.$(prefix + (number + length));
this._hoverOrigin = origin;
this._hoverTarget = target;
} catch (error) {
// There may not be any nodes present in the document. A case where
// this occurs is when you reply to a ghost inline which was made
// on lines near the bottom of "long.txt" in an earlier diff, and
// the file was later shortened so those lines no longer exist. For
// more details, see T11662.
this._hoverOrigin = null;
this._hoverTarget = null;
}
} else {
this._hoverOrigin = null;
this._hoverTarget = null;
}
this._redrawHover();
},
_setHoverRange: function(origin, target) {
this._hoverOrigin = origin;
this._hoverTarget = target;
this._redrawHover();
},
resetHover: function() {
this._setHoverInline(null);
this._hoverOrigin = null;
this._hoverTarget = null;
},
_redrawHover: function() {
var ii;
var map = this._hoverMap;
if (map) {
for (ii = 0; ii < map.length; ii++) {
JX.DOM.alterClass(map[ii].cellNode, 'inline-hover', false);
if (map[ii].bright) {
JX.DOM.alterClass(map[ii].cellNode, 'inline-hover-bright', false);
}
if (map[ii].hoverNode) {
JX.DOM.remove(map[ii].hoverNode);
}
}
this._hoverMap = null;
}
var reticle = this._getHoverNode();
JX.DOM.remove(reticle);
if (!this._hoverOrigin || this.isAsleep()) {
return;
}
var top = this._hoverOrigin;
var bot = this._hoverTarget;
if (JX.$V(top).y > JX.$V(bot).y) {
var tmp = top;
top = bot;
bot = tmp;
}
// Find the leftmost cell that we're going to highlight. This is the
// next sibling with a "data-copy-mode" attribute, which is a marker
// for the cell with actual content in it.
var content_cell = top;
while (content_cell && !this._isContentCell(content_cell)) {
content_cell = content_cell.nextSibling;
}
// If we didn't find a cell to highlight, don't highlight anything.
if (!content_cell) {
return;
}
var inline = this._hoverInline;
if (!inline) {
var pos = JX.$V(content_cell)
.add(JX.Vector.getAggregateScrollForNode(content_cell));
var dim = JX.$V(content_cell)
.add(JX.Vector.getAggregateScrollForNode(content_cell))
.add(-pos.x, -pos.y)
.add(JX.Vector.getDim(content_cell));
var bpos = JX.$V(bot)
.add(JX.Vector.getAggregateScrollForNode(bot));
dim.y = (bpos.y - pos.y) + JX.Vector.getDim(bot).y;
pos.setPos(reticle);
dim.setDim(reticle);
JX.DOM.getContentFrame().appendChild(reticle);
JX.DOM.show(reticle);
return;
}
if (!inline.hoverMap) {
inline.hoverMap = this._newHoverMap(top, bot, content_cell, inline);
}
map = inline.hoverMap;
for (ii = 0; ii < map.length; ii++) {
JX.DOM.alterClass(map[ii].cellNode, 'inline-hover', true);
if (map[ii].bright) {
JX.DOM.alterClass(map[ii].cellNode, 'inline-hover-bright', true);
}
if (map[ii].hoverNode) {
map[ii].cellNode.insertBefore(
map[ii].hoverNode,
map[ii].cellNode.firstChild);
}
}
this._hoverMap = map;
},
_newHoverMap: function(top, bot, content_cell, inline) {
var start = inline.getStartOffset();
var end = inline.getEndOffset();
var head_row = JX.DOM.findAbove(top, 'tr');
var last_row = JX.DOM.findAbove(bot, 'tr');
var cursor = head_row;
var rows = [];
var idx = null;
var ii;
do {
for (ii = 0; ii < cursor.childNodes.length; ii++) {
var child = cursor.childNodes[ii];
if (!JX.DOM.isType(child, 'td')) {
continue;
}
if (child === content_cell) {
idx = ii;
}
if (ii === idx) {
if (!this._isContentCell(child)) {
break;
}
rows.push({
cellNode: child
});
}
}
if (cursor === last_row) {
break;
}
cursor = cursor.nextSibling;
} while (cursor);
var info;
var content;
for (ii = 0; ii < rows.length; ii++) {
info = this._getSelectionOffset(rows[ii].cellNode, null);
content = info.content;
content = content.replace(/\n+$/, '');
rows[ii].content = content;
}
var attr_dull = {
className: 'inline-hover-text'
};
var attr_bright = {
className: 'inline-hover-text inline-hover-text-bright'
};
var attr_container = {
className: 'inline-hover-container'
};
var min = 0;
var max = rows.length - 1;
var offset_min;
var offset_max;
var len;
var node;
var text;
var any_highlight = false;
for (ii = 0; ii < rows.length; ii++) {
content = rows[ii].content;
len = content.length;
if (ii === min && (start !== null)) {
offset_min = start;
} else {
offset_min = 0;
}
if (ii === max && (end !== null)) {
offset_max = Math.min(end, len);
} else {
offset_max = len;
}
var has_min = (offset_min > 0);
var has_max = (offset_max < len);
if (has_min || has_max) {
any_highlight = true;
}
rows[ii].min = offset_min;
rows[ii].max = offset_max;
rows[ii].hasMin = has_min;
rows[ii].hasMax = has_max;
}
for (ii = 0; ii < rows.length; ii++) {
content = rows[ii].content;
offset_min = rows[ii].min;
offset_max = rows[ii].max;
var has_highlight = (rows[ii].hasMin || rows[ii].hasMax);
if (any_highlight) {
var parts = [];
if (offset_min > 0) {
text = content.substring(0, offset_min);
node = JX.$N('span', attr_dull, text);
parts.push(node);
}
if (len) {
text = content.substring(offset_min, offset_max);
node = JX.$N('span', attr_bright, text);
parts.push(node);
}
if (offset_max < len) {
text = content.substring(offset_max, len);
node = JX.$N('span', attr_dull, text);
parts.push(node);
}
rows[ii].hoverNode = JX.$N('div', attr_container, parts);
} else {
rows[ii].hoverNode = null;
}
rows[ii].bright = (any_highlight && !has_highlight);
}
return rows;
},
_getHoverNode: function() {
if (!this._hoverNode) {
var attributes = {
className: 'differential-reticle'
};
this._hoverNode = JX.$N('div', attributes);
}
return this._hoverNode;
},
_deleteInlineByID: function(id) {
var uri = this.getInlineURI();
var data = {
op: 'refdelete',
id: id
};
var handler = JX.bind(this, this.redrawPreview);
new JX.Workflow(uri, data)
.setHandler(handler)
.start();
},
_getInlineForEvent: function(e) {
var node = e.getNode('differential-changeset');
if (!node) {
return null;
}
var changeset = this.getChangesetForNode(node);
var inline_row = e.getNode('inline-row');
return changeset.getInlineForRow(inline_row);
},
getLineNumberFromHeader: function(node) {
var n = parseInt(node.getAttribute('data-n'));
if (!n) {
return null;
}
// If this is a line number that's part of a row showing more context,
// we don't want to let users leave inlines here.
try {
JX.DOM.findAbove(node, 'tr', 'context-target');
return null;
} catch (ex) {
// Ignore.
}
return n;
},
getDisplaySideFromHeader: function(th) {
return (th.parentNode.firstChild != th) ? 'right' : 'left';
},
_onrangedown: function(e) {
// NOTE: We're allowing "mousedown" from a touch event through so users
// can leave inlines on a single line.
// See PHI985. We want to exclude both right-mouse and middle-mouse
// clicks from continuing.
if (!e.isLeftButton()) {
return;
}
if (this._rangeActive) {
return;
}
var target = e.getTarget();
var number = this.getLineNumberFromHeader(target);
if (!number) {
return;
}
e.kill();
this._rangeActive = true;
this._rangeOrigin = target;
this._rangeTarget = target;
this._setHoverRange(this._rangeOrigin, this._rangeTarget);
},
_onrangemove: function(e) {
if (e.getIsTouchEvent()) {
return;
}
var is_out = (e.getType() == 'mouseout');
var target = e.getTarget();
this._updateRange(target, is_out);
},
_updateRange: function(target, is_out) {
// Don't update the range if this target doesn't correspond to a line
// number. For instance, this may be a dead line number, like the empty
// line numbers on the left hand side of a newly added file.
var number = this.getLineNumberFromHeader(target);
if (!number) {
return;
}
if (this._rangeActive) {
var origin = this._hoverOrigin;
// Don't update the reticle if we're selecting a line range and the
// "<th />" under the cursor is on the wrong side of the file. You can
// only leave inline comments on the left or right side of a file, not
// across lines on both sides.
var origin_side = this.getDisplaySideFromHeader(origin);
var target_side = this.getDisplaySideFromHeader(target);
if (origin_side != target_side) {
return;
}
// Don't update the reticle if we're selecting a line range and the
// "<th />" under the cursor corresponds to a different file. You can
// only leave inline comments on lines in a single file, not across
// multiple files.
var origin_table = JX.DOM.findAbove(origin, 'table');
var target_table = JX.DOM.findAbove(target, 'table');
if (origin_table != target_table) {
return;
}
}
if (is_out) {
if (this._rangeActive) {
// If we're dragging a range, just leave the state as it is. This
// allows you to drag over something invalid while selecting a
// range without the range flickering or getting lost.
} else {
// Otherwise, clear the current range.
this.resetHover();
}
return;
}
if (this._rangeActive) {
this._rangeTarget = target;
} else {
this._rangeOrigin = target;
this._rangeTarget = target;
}
this._setHoverRange(this._rangeOrigin, this._rangeTarget);
},
_onrangeup: function(e) {
if (!this._rangeActive) {
return;
}
e.kill();
var origin = this._rangeOrigin;
var target = this._rangeTarget;
// If the user dragged a range from the bottom to the top, swap the node
// order around.
if (JX.$V(origin).y > JX.$V(target).y) {
var tmp = target;
target = origin;
origin = tmp;
}
var node = JX.DOM.findAbove(origin, null, 'differential-changeset');
var changeset = this.getChangesetForNode(node);
changeset.newInlineForRange(origin, target);
this._rangeActive = false;
this._rangeOrigin = null;
this._rangeTarget = null;
this.resetHover();
},
_redrawBanner: function() {
// If the inline comment menu is open and we've done a redraw, close it.
// In particular, this makes it close when you scroll the document:
// otherwise, it stays open but the banner moves underneath it.
if (this._dropdownMenu) {
this._dropdownMenu.close();
}
var node = this._getBannerNode();
var changeset = this._getVisibleChangeset();
var tree = this._getTreeView();
var formation = this.getFormationView();
if (!changeset) {
this._bannerChangeset = null;
JX.DOM.remove(node);
tree.setFocusedPath(null);
if (formation) {
formation.repaint();
}
return;
}
// Don't do anything if nothing has changed. This seems to avoid some
// flickering issues in Safari, at least.
if (this._bannerChangeset === changeset) {
return;
}
this._bannerChangeset = changeset;
var paths = tree.getPaths();
for (var ii = 0; ii < paths.length; ii++) {
var path = paths[ii];
if (path.getChangeset() === changeset) {
tree.setFocusedPath(path);
}
}
var inlines = this._getInlinesByType();
var unsaved = inlines.unsaved;
var unsubmitted = inlines.unsubmitted;
var undone = inlines.undone;
var done = inlines.done;
var draft_done = inlines.draftDone;
JX.DOM.alterClass(
node,
'diff-banner-has-unsaved',
!!unsaved.length);
JX.DOM.alterClass(
node,
'diff-banner-has-unsubmitted',
!!unsubmitted.length);
JX.DOM.alterClass(
node,
'diff-banner-has-draft-done',
!!draft_done.length);
var pht = this.getTranslations();
var unsaved_button = this._getUnsavedButton();
var unsubmitted_button = this._getUnsubmittedButton();
var done_button = this._getDoneButton();
var menu_button = this._getMenuButton();
if (unsaved.length) {
unsaved_button.setText(unsaved.length + ' ' + pht('Unsaved'));
JX.DOM.show(unsaved_button.getNode());
} else {
JX.DOM.hide(unsaved_button.getNode());
}
if (unsubmitted.length || draft_done.length) {
var any_draft_count = unsubmitted.length + draft_done.length;
unsubmitted_button.setText(any_draft_count + ' ' + pht('Unsubmitted'));
JX.DOM.show(unsubmitted_button.getNode());
} else {
JX.DOM.hide(unsubmitted_button.getNode());
}
if (done.length || undone.length) {
// If you haven't marked any comments as "Done", we just show text
// like "3 Comments". If you've marked at least one done, we show
// "1 / 3 Comments".
var done_text;
if (done.length) {
done_text = [
done.length,
' / ',
(done.length + undone.length),
' ',
pht('Comments')
];
} else {
done_text = [
undone.length,
' ',
pht('Comments')
];
}
done_button.setText(done_text);
JX.DOM.show(done_button.getNode());
// If any comments are not marked "Done", this cycles through the
// missing comments. Otherwise, it cycles through all the saved
// comments.
if (undone.length) {
this._doneMode = 'undone';
} else {
this._doneMode = 'done';
}
} else {
JX.DOM.hide(done_button.getNode());
}
var path_view = [icon, ' ', changeset.getDisplayPath()];
var buttons_attrs = {
className: 'diff-banner-buttons'
};
var buttons_list = [
unsaved_button.getNode(),
unsubmitted_button.getNode(),
done_button.getNode(),
menu_button.getNode()
];
var buttons_view = JX.$N('div', buttons_attrs, buttons_list);
var icon = new JX.PHUIXIconView()
.setIcon(changeset.getIcon())
.getNode();
JX.DOM.setContent(node, [buttons_view, path_view]);
document.body.appendChild(node);
if (formation) {
formation.repaint();
}
},
_getInlinesByType: function() {
var changesets = this._changesets;
var unsaved = [];
var unsubmitted = [];
var undone = [];
var done = [];
var draft_done = [];
var visible_done = [];
var visible_collapsed = [];
var visible_ghosts = [];
var visible = [];
var hidden = [];
for (var ii = 0; ii < changesets.length; ii++) {
var inlines = changesets[ii].getInlines();
var inline;
var jj;
for (jj = 0; jj < inlines.length; jj++) {
inline = inlines[jj];
if (inline.isDeleted()) {
continue;
}
if (inline.isSynthetic()) {
continue;
}
if (inline.isEditing()) {
unsaved.push(inline);
} else if (!inline.getID()) {
// These are new comments which have been cancelled, and do not
// count as anything.
continue;
} else if (inline.isDraft()) {
unsubmitted.push(inline);
} else {
// NOTE: Unlike other states, an inline may be marked with a
// draft checkmark and still be a "done" or "undone" comment.
if (inline.isDraftDone()) {
draft_done.push(inline);
}
if (!inline.isDone()) {
undone.push(inline);
} else {
done.push(inline);
}
}
}
for (jj = 0; jj < inlines.length; jj++) {
inline = inlines[jj];
if (inline.isDeleted()) {
continue;
}
if (inline.isEditing()) {
continue;
}
if (inline.isHidden()) {
hidden.push(inline);
continue;
}
visible.push(inline);
if (inline.isDone()) {
visible_done.push(inline);
}
if (inline.isCollapsed()) {
visible_collapsed.push(inline);
}
if (inline.isGhost()) {
visible_ghosts.push(inline);
}
}
}
return {
unsaved: unsaved,
unsubmitted: unsubmitted,
undone: undone,
done: done,
draftDone: draft_done,
visibleDone: visible_done,
visibleGhosts: visible_ghosts,
visibleCollapsed: visible_collapsed,
visible: visible,
hidden: hidden
};
},
_getUnsavedButton: function() {
if (!this._unsavedButton) {
var button = new JX.PHUIXButtonView()
.setIcon('fa-commenting-o')
.setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE);
var node = button.getNode();
var onunsaved = JX.bind(this, this._onunsavedclick);
JX.DOM.listen(node, 'click', null, onunsaved);
this._unsavedButton = button;
}
return this._unsavedButton;
},
_getUnsubmittedButton: function() {
if (!this._unsubmittedButton) {
var button = new JX.PHUIXButtonView()
.setIcon('fa-comment-o')
.setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE);
var node = button.getNode();
var onunsubmitted = JX.bind(this, this._onunsubmittedclick);
JX.DOM.listen(node, 'click', null, onunsubmitted);
this._unsubmittedButton = button;
}
return this._unsubmittedButton;
},
_getDoneButton: function() {
if (!this._doneButton) {
var button = new JX.PHUIXButtonView()
.setIcon('fa-comment')
.setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE);
var node = button.getNode();
var ondone = JX.bind(this, this._ondoneclick);
JX.DOM.listen(node, 'click', null, ondone);
this._doneButton = button;
}
return this._doneButton;
},
_getMenuButton: function() {
if (!this._menuButton) {
var pht = this.getTranslations();
var button = new JX.PHUIXButtonView()
.setIcon('fa-bars')
.setButtonType(JX.PHUIXButtonView.BUTTONTYPE_SIMPLE)
.setAuralLabel(pht('Display Options'));
var dropdown = new JX.PHUIXDropdownMenu(button.getNode());
this._menuItems = {};
var list = new JX.PHUIXActionListView();
dropdown.setContent(list.getNode());
var map = {
hideDone: {
type: 'done'
},
hideCollapsed: {
type: 'collapsed'
},
hideGhosts: {
type: 'ghosts'
},
hideAll: {
type: 'all'
},
showAll: {
type: 'show'
}
};
for (var k in map) {
var spec = map[k];
var handler = JX.bind(this, this._onhideinlines, spec.type);
var item = new JX.PHUIXActionView()
.setHandler(handler);
list.addItem(item);
this._menuItems[k] = item;
}
dropdown.listen('open', JX.bind(this, this._ondropdown));
if (this.getInlineListURI()) {
list.addItem(
new JX.PHUIXActionView()
.setDivider(true));
list.addItem(
new JX.PHUIXActionView()
.setIcon('fa-external-link')
.setName(pht('List Inline Comments'))
.setHref(this.getInlineListURI()));
}
this._menuButton = button;
this._dropdownMenu = dropdown;
}
return this._menuButton;
},
_ondropdown: function() {
var inlines = this._getInlinesByType();
var items = this._menuItems;
var pht = this.getTranslations();
items.hideDone
.setName(pht('Hide "Done" Inlines'))
.setDisabled(!inlines.visibleDone.length);
items.hideCollapsed
.setName(pht('Hide Collapsed Inlines'))
.setDisabled(!inlines.visibleCollapsed.length);
items.hideGhosts
.setName(pht('Hide Older Inlines'))
.setDisabled(!inlines.visibleGhosts.length);
items.hideAll
.setName(pht('Hide All Inlines'))
.setDisabled(!inlines.visible.length);
items.showAll
.setName(pht('Show All Inlines'))
.setDisabled(!inlines.hidden.length);
},
_onhideinlines: function(type, e) {
this._dropdownMenu.close();
e.prevent();
this._toggleInlines(type);
},
_toggleInlines: function(type) {
var inlines = this._getInlinesByType();
// Clear the selection state since we end up in a weird place if the
// user hides the selected inline.
this._setSelectionState(null);
var targets;
var mode = true;
switch (type) {
case 'done':
targets = inlines.visibleDone;
break;
case 'collapsed':
targets = inlines.visibleCollapsed;
break;
case 'ghosts':
targets = inlines.visibleGhosts;
break;
case 'all':
targets = inlines.visible;
break;
case 'show':
targets = inlines.hidden;
mode = false;
break;
}
for (var ii = 0; ii < targets.length; ii++) {
targets[ii].setHidden(mode);
}
},
_onunsavedclick: function(e) {
e.kill();
var options = {
filter: 'comment',
wrap: true,
show: true,
attribute: 'unsaved'
};
this._onjumpkey(1, options);
},
_onunsubmittedclick: function(e) {
e.kill();
var options = {
filter: 'comment',
wrap: true,
show: true,
attribute: 'anyDraft'
};
this._onjumpkey(1, options);
},
_ondoneclick: function(e) {
e.kill();
var options = {
filter: 'comment',
wrap: true,
show: true,
attribute: this._doneMode
};
this._onjumpkey(1, options);
},
_getBannerNode: function() {
if (!this._bannerNode) {
var attributes = {
className: 'diff-banner',
id: 'diff-banner'
};
this._bannerNode = JX.$N('div', attributes);
}
return this._bannerNode;
},
_getVisibleChangeset: function() {
if (this.isAsleep()) {
return null;
}
if (JX.Device.getDevice() != 'desktop') {
return null;
}
// Never show the banner if we're very near the top of the page.
var margin = 480;
var s = JX.Vector.getScroll();
if (s.y < margin) {
return null;
}
// We're going to find the changeset which spans an invisible line a
// little underneath the bottom of the banner. This makes the header
// tick over from "A.txt" to "B.txt" just as "A.txt" scrolls completely
// offscreen.
var detect_height = 64;
for (var ii = 0; ii < this._changesets.length; ii++) {
var changeset = this._changesets[ii];
var c = changeset.getVectors();
// If the changeset starts above the line...
if (c.pos.y <= (s.y + detect_height)) {
// ...and ends below the line, this is the current visible changeset.
if ((c.pos.y + c.dim.y) >= (s.y + detect_height)) {
return changeset;
}
}
}
return null;
},
_getTreeView: function() {
if (!this._treeView) {
var tree = new JX.DiffTreeView();
for (var ii = 0; ii < this._changesets.length; ii++) {
var changeset = this._changesets[ii];
tree.addPath(changeset.getPathView());
}
this._treeView = tree;
}
return this._treeView;
},
_redrawFiletree : function() {
var formation = this.getFormationView();
if (!formation) {
return;
}
var filetree = formation.getColumn(0);
var flank = filetree.getFlank();
var flank_body = flank.getBodyNode();
var tree = this._getTreeView();
JX.DOM.setContent(flank_body, tree.getNode());
},
_setupInlineCommentListeners: function() {
var onsave = JX.bind(this, this._onInlineEvent, 'save');
JX.Stratcom.listen(
['submit', 'didSyntheticSubmit'],
'inline-edit-form',
onsave);
var oncancel = JX.bind(this, this._onInlineEvent, 'cancel');
JX.Stratcom.listen(
'click',
'inline-edit-cancel',
oncancel);
var onundo = JX.bind(this, this._onInlineEvent, 'undo');
JX.Stratcom.listen(
'click',
'differential-inline-comment-undo',
onundo);
var ondone = JX.bind(this, this._onInlineEvent, 'done');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-done'],
ondone);
var ondelete = JX.bind(this, this._onInlineEvent, 'delete');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-delete'],
ondelete);
var onmenu = JX.bind(this, this._onInlineEvent, 'menu');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'inline-action-dropdown'],
onmenu);
var ondraft = JX.bind(this, this._onInlineEvent, 'draft');
JX.Stratcom.listen(
'keydown',
['differential-inline-comment', 'tag:textarea'],
ondraft);
var on_preview_view = JX.bind(this, this._onPreviewEvent, 'view');
JX.Stratcom.listen(
'click',
'differential-inline-preview-jump',
on_preview_view);
},
_onPreviewEvent: function(action, e) {
if (this.isAsleep()) {
return;
}
var data = e.getNodeData('differential-inline-preview-jump');
var inline = this.getInlineByID(data.inlineCommentID);
if (!inline) {
return;
}
e.kill();
switch (action) {
case 'view':
this.selectInline(inline, true, true);
break;
}
},
_onInlineEvent: function(action, e) {
if (this.isAsleep()) {
return;
}
if (action !== 'draft' && action !== 'menu') {
e.kill();
}
var inline = this._getInlineForEvent(e);
var is_ref = false;
// If we don't have a natural inline object, the user may have clicked
// an action (like "Delete") inside a preview element at the bottom of
// the page.
// If they did, try to find an associated normal inline to act on, and
// pretend they clicked that instead. This makes the overall state of
// the page more consistent.
// However, there may be no normal inline (for example, because it is
// on a version of the diff which is not visible). In this case, we
// act by reference.
if (inline === null) {
var data = e.getNodeData('differential-inline-comment');
inline = this.getInlineByID(data.id);
if (inline) {
is_ref = true;
} else {
switch (action) {
case 'delete':
this._deleteInlineByID(data.id);
return;
}
}
}
// TODO: For normal operations, highlight the inline range here.
switch (action) {
case 'save':
inline.save(e.getTarget());
break;
case 'cancel':
inline.cancel();
break;
case 'undo':
inline.undo();
break;
case 'done':
inline.toggleDone();
break;
case 'delete':
inline.delete(is_ref);
break;
case 'draft':
inline.triggerDraft();
break;
case 'menu':
var node = e.getNode('inline-action-dropdown');
inline.activateMenu(node, e);
break;
}
},
_onSelectRange: function(e) {
this._updateSourceSelection();
},
_updateSourceSelection: function() {
var ranges = this._getSelectedRanges();
// In Firefox, selecting multiple rows gives us multiple ranges. In
// Safari and Chrome, we get a single range.
if (!ranges.length) {
this._setSourceSelection(null, null);
return;
}
var min = 0;
var max = ranges.length - 1;
var head = ranges[min].startContainer;
var last = ranges[max].endContainer;
var head_loc = this._getFragmentLocation(head);
var last_loc = this._getFragmentLocation(last);
if (head_loc === null || last_loc === null) {
this._setSourceSelection(null, null);
return;
}
if (head_loc.changesetID !== last_loc.changesetID) {
this._setSourceSelection(null, null);
return;
}
head_loc.offset += ranges[min].startOffset;
last_loc.offset += ranges[max].endOffset;
this._setSourceSelection(head_loc, last_loc);
},
_setSourceSelection: function(start, end) {
var start_updated =
!this._isSameSourceSelection(this._sourceSelectionStart, start);
var end_updated =
!this._isSameSourceSelection(this._sourceSelectionEnd, end);
if (!start_updated && !end_updated) {
return;
}
this._sourceSelectionStart = start;
this._sourceSelectionEnd = end;
if (!start) {
this._closeSourceSelectionMenu();
return;
}
var menu;
if (this._sourceSelectionMenu) {
menu = this._sourceSelectionMenu;
} else {
menu = this._newSourceSelectionMenu();
this._sourceSelectionMenu = menu;
}
var pos = JX.$V(start.node)
.add(0, -menu.getMenuNodeDimensions().y)
.add(0, -24);
menu.setPosition(pos);
menu.open();
},
_newSourceSelectionMenu: function() {
var pht = this.getTranslations();
var menu = new JX.PHUIXDropdownMenu(null)
.setWidth(240);
// We need to disable autofocus for this menu, since it operates on the
// text selection in the document. If we leave this enabled, opening the
// menu immediately discards the selection.
menu.setDisableAutofocus(true);
var list = new JX.PHUIXActionListView();
menu.setContent(list.getNode());
var oncreate = JX.bind(this, this._onSourceSelectionMenuAction, 'create');
var comment_item = new JX.PHUIXActionView()
.setIcon('fa-comment-o')
.setName(pht('New Inline Comment'))
.setKeyCommand('c')
.setHandler(oncreate);
list.addItem(comment_item);
return menu;
},
_onSourceSelectionMenuAction: function(action, e) {
e.kill();
this._closeSourceSelectionMenu();
switch (action) {
case 'create':
this._onKeyCreate();
break;
}
},
_closeSourceSelectionMenu: function() {
if (this._sourceSelectionMenu) {
this._sourceSelectionMenu.close();
}
},
_isSameSourceSelection: function(u, v) {
if (u === null && v === null) {
return true;
}
if (u === null && v !== null) {
return false;
}
if (u !== null && v === null) {
return false;
}
return (
(u.changesetID === v.changesetID) &&
(u.line === v.line) &&
(u.displayColumn === v.displayColumn) &&
(u.offset === v.offset)
);
},
_getFragmentLocation: function(fragment) {
// Find the changeset containing the fragment.
var changeset = null;
try {
var node = JX.DOM.findAbove(
fragment,
'div',
'differential-changeset');
changeset = this.getChangesetForNode(node);
if (!changeset) {
return null;
}
} catch (ex) {
return null;
}
// Find the line number and display column for the fragment.
var line = null;
var column_count = -1;
var has_new = false;
var has_old = false;
var offset = null;
var target_node = null;
var td;
try {
// NOTE: In Safari, you can carefully select an entire line and then
// move your mouse down slightly, causing selection of an empty
// document fragment which is an immediate child of the next "<tr />".
// If the fragment is a direct child of a "<tr />" parent, assume the
// user has done this and select the last child of the previous row
// instead. It's possible there are other ways to do this, so this may
// not always be the right rule.
// Otherwise, select the containing "<td />".
var is_end;
if (JX.DOM.isType(fragment.parentNode, 'tr')) {
// Assume this is Safari, and that the user has carefully selected a
// row and then moved their mouse down a few pixels to select the
// invisible fragment at the beginning of the next row.
var cells = fragment.parentNode.previousSibling.childNodes;
td = cells[cells.length - 1];
is_end = true;
} else {
td = JX.DOM.findAbove(fragment, 'td');
is_end = false;
}
var cursor = td;
while (cursor) {
if (cursor.getAttribute('data-copy-mode')) {
column_count++;
} else {
// In unified mode, the content column isn't currently marked
// with an attribute, and we can't count content columns anyway.
// Keep track of whether or not we see a "NL" (New Line) column
// and/or an "OL" (Old Line) column to try to puzzle out which
// side of the display change we're on.
if (cursor.id.match(/NL/)) {
has_new = true;
} else if (cursor.id.match(/OL/)) {
has_old = true;
}
}
var n = parseInt(cursor.getAttribute('data-n'));
if (n) {
if (line === null) {
target_node = cursor;
line = n;
}
}
cursor = cursor.previousSibling;
}
if (!line) {
return null;
}
if (column_count < 0) {
if (has_new || has_old) {
if (has_new) {
column_count = 1;
} else {
column_count = 0;
}
} else {
return null;
}
}
var info = this._getSelectionOffset(td, fragment);
if (info.found) {
offset = info.offset;
} else {
if (is_end) {
offset = info.offset;
} else {
offset = 0;
}
}
} catch (ex) {
return null;
}
var changeset_id;
if (column_count > 0) {
changeset_id = changeset.getRightChangesetID();
} else {
changeset_id = changeset.getLeftChangesetID();
}
return {
node: td,
changeset: changeset,
changesetID: changeset_id,
line: line,
displayColumn: column_count,
offset: offset,
targetNode: target_node
};
},
_getSelectionOffset: function(node, target) {
if (!node.childNodes || !node.childNodes.length) {
return {
offset: node.textContent.length,
content: node.textContent,
found: false
};
}
var found = false;
var offset = 0;
var content = '';
for (var ii = 0; ii < node.childNodes.length; ii++) {
var child = node.childNodes[ii];
if (child === target) {
found = true;
}
var spec = this._getSelectionOffset(child, target);
content += spec.content;
if (!found) {
offset += spec.offset;
}
found = found || spec.found;
}
return {
offset: offset,
content: content,
found: found
};
},
_getSelectedRanges: function() {
var ranges = [];
if (!window.getSelection) {
return ranges;
}
var selection = window.getSelection();
for (var ii = 0; ii < selection.rangeCount; ii++) {
var range = selection.getRangeAt(ii);
if (range.collapsed) {
continue;
}
ranges.push(range);
}
return ranges;
},
_isContentCell: function(node) {
return !!node.getAttribute('data-copy-mode');
}
}
});
diff --git a/webroot/rsrc/js/application/diff/DiffInline.js b/webroot/rsrc/js/application/diff/DiffInline.js
index dd669b2fc1..cfec020544 100644
--- a/webroot/rsrc/js/application/diff/DiffInline.js
+++ b/webroot/rsrc/js/application/diff/DiffInline.js
@@ -1,1018 +1,1042 @@
/**
* @provides phabricator-diff-inline
* @requires javelin-dom
* @javelin
*/
JX.install('DiffInline', {
construct : function() {
},
members: {
_id: null,
_phid: null,
_changesetID: null,
_row: null,
_number: null,
_length: null,
_displaySide: null,
_isNewFile: null,
_replyToCommentPHID: null,
_originalText: null,
_snippet: null,
_menuItems: null,
_documentEngineKey: null,
_isDeleted: false,
_isInvisible: false,
_isLoading: false,
_changeset: null,
_isCollapsed: false,
_isDraft: null,
_isDraftDone: null,
_isFixed: null,
_isEditing: false,
_isNew: false,
_isSynthetic: false,
_isHidden: false,
_editRow: null,
_undoRow: null,
_undoType: null,
_undoText: null,
_draftRequest: null,
_skipFocus: false,
_menu: null,
_startOffset: null,
_endOffset: null,
+ _isSelected: false,
bindToRow: function(row) {
this._row = row;
var row_data = JX.Stratcom.getData(row);
row_data.inline = this;
this._isCollapsed = row_data.hidden || false;
// TODO: Get smarter about this once we do more editing, this is pretty
// hacky.
var comment = JX.DOM.find(row, 'div', 'differential-inline-comment');
var data = JX.Stratcom.getData(comment);
this._readInlineState(data);
this._phid = data.phid;
if (data.on_right) {
this._displaySide = 'right';
} else {
this._displaySide = 'left';
}
this._number = parseInt(data.number, 10);
this._length = parseInt(data.length, 10);
var original = '' + data.original;
if (original.length) {
this._originalText = original;
} else {
this._originalText = null;
}
this._isNewFile = data.isNewFile;
this._replyToCommentPHID = data.replyToCommentPHID;
this._isDraft = data.isDraft;
this._isFixed = data.isFixed;
this._isGhost = data.isGhost;
this._isSynthetic = data.isSynthetic;
this._isDraftDone = data.isDraftDone;
this._changesetID = data.changesetID;
this._isNew = false;
this._snippet = data.snippet;
this._menuItems = data.menuItems;
this._documentEngineKey = data.documentEngineKey;
this._startOffset = data.startOffset;
this._endOffset = data.endOffset;
this._isEditing = data.isEditing;
if (this._isEditing) {
// NOTE: The "original" shipped down in the DOM may reflect a draft
// which we're currently editing. This flow is a little clumsy, but
// reasonable until some future change moves away from "send down
// the inline, then immediately click edit".
this.edit(null, true);
} else {
this.setInvisible(false);
}
this._startDrafts();
return this;
},
isDraft: function() {
return this._isDraft;
},
isDone: function() {
return this._isFixed;
},
isEditing: function() {
return this._isEditing;
},
isUndo: function() {
return !!this._undoRow;
},
isDeleted: function() {
return this._isDeleted;
},
isSynthetic: function() {
return this._isSynthetic;
},
isDraftDone: function() {
return this._isDraftDone;
},
isHidden: function() {
return this._isHidden;
},
isGhost: function() {
return this._isGhost;
},
getStartOffset: function() {
return this._startOffset;
},
getEndOffset: function() {
return this._endOffset;
},
+ setIsSelected: function(is_selected) {
+ this._isSelected = is_selected;
+
+ if (this._row) {
+ JX.DOM.alterClass(
+ this._row,
+ 'inline-comment-selected',
+ this._isSelected);
+ }
+
+ return this;
+ },
+
bindToRange: function(data) {
this._displaySide = data.displaySide;
this._number = parseInt(data.number, 10);
this._length = parseInt(data.length, 10);
this._isNewFile = data.isNewFile;
this._changesetID = data.changesetID;
this._isNew = true;
- this._startOffset = null;
- this._endOffset = null;
+
+ if (data.hasOwnProperty('startOffset')) {
+ this._startOffset = data.startOffset;
+ } else {
+ this._startOffset = null;
+ }
+
+ if (data.hasOwnProperty('endOffset')) {
+ this._endOffset = data.endOffset;
+ } else {
+ this._endOffset = null;
+ }
// Insert the comment after any other comments which already appear on
// the same row.
var parent_row = JX.DOM.findAbove(data.target, 'tr');
var target_row = parent_row.nextSibling;
while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) {
target_row = target_row.nextSibling;
}
var row = this._newRow();
parent_row.parentNode.insertBefore(row, target_row);
this.setInvisible(true);
this._startDrafts();
return this;
},
bindToReply: function(inline) {
this._displaySide = inline._displaySide;
this._number = inline._number;
this._length = inline._length;
this._isNewFile = inline._isNewFile;
this._changesetID = inline._changesetID;
this._isNew = true;
this._documentEngineKey = inline._documentEngineKey;
this._replyToCommentPHID = inline._phid;
var changeset = this.getChangeset();
// We're going to figure out where in the document to position the new
// inline. Normally, it goes after any existing inline rows (so if
// several inlines reply to the same line, they appear in chronological
// order).
// However: if inlines are threaded, we want to put the new inline in
// the right place in the thread. This might be somewhere in the middle,
// so we need to do a bit more work to figure it out.
// To find the right place in the thread, we're going to look for any
// inline which is at or above the level of the comment we're replying
// to. This means we've reached a new fork of the thread, and should
// put our new inline before the comment we found.
var ancestor_map = {};
var ancestor = inline;
var reply_phid;
while (ancestor) {
reply_phid = ancestor.getReplyToCommentPHID();
if (!reply_phid) {
break;
}
ancestor_map[reply_phid] = true;
ancestor = changeset.getInlineByPHID(reply_phid);
}
var parent_row = inline._row;
var target_row = parent_row.nextSibling;
while (target_row && JX.Stratcom.hasSigil(target_row, 'inline-row')) {
var target = changeset.getInlineForRow(target_row);
reply_phid = target.getReplyToCommentPHID();
// If we found an inline which is replying directly to some ancestor
// of this new comment, this is where the new rows go.
if (ancestor_map.hasOwnProperty(reply_phid)) {
break;
}
target_row = target_row.nextSibling;
}
var row = this._newRow();
parent_row.parentNode.insertBefore(row, target_row);
this.setInvisible(true);
this._startDrafts();
return this;
},
setChangeset: function(changeset) {
this._changeset = changeset;
return this;
},
getChangeset: function() {
return this._changeset;
},
setEditing: function(editing) {
this._isEditing = editing;
return this;
},
setHidden: function(hidden) {
this._isHidden = hidden;
this._redraw();
return this;
},
canReply: function() {
return this._hasMenuAction('reply');
},
canEdit: function() {
return this._hasMenuAction('edit');
},
canDone: function() {
if (!JX.DOM.scry(this._row, 'input', 'differential-inline-done').length) {
return false;
}
return true;
},
canCollapse: function() {
return this._hasMenuAction('collapse');
},
getRawText: function() {
return this._originalText;
},
_newRow: function() {
var attributes = {
sigil: 'inline-row'
};
var row = JX.$N('tr', attributes);
JX.Stratcom.getData(row).inline = this;
this._row = row;
this._id = null;
this._phid = null;
this._isCollapsed = false;
this._originalText = null;
return row;
},
setCollapsed: function(collapsed) {
this._closeMenu();
this._isCollapsed = collapsed;
var op;
if (collapsed) {
op = 'hide';
} else {
op = 'show';
}
var inline_uri = this._getInlineURI();
var comment_id = this._id;
new JX.Workflow(inline_uri, {op: op, ids: comment_id})
.setHandler(JX.bag)
.start();
this._redraw();
this._didUpdate(true);
},
isCollapsed: function() {
return this._isCollapsed;
},
toggleDone: function() {
var uri = this._getInlineURI();
var data = {
op: 'done',
id: this._id
};
var ondone = JX.bind(this, this._ondone);
new JX.Workflow(uri, data)
.setHandler(ondone)
.start();
},
_ondone: function(response) {
var checkbox = JX.DOM.find(
this._row,
'input',
'differential-inline-done');
checkbox.checked = (response.isChecked ? 'checked' : null);
var comment = JX.DOM.findAbove(
checkbox,
'div',
'differential-inline-comment');
JX.DOM.alterClass(comment, 'inline-is-done', response.isChecked);
// NOTE: This is marking the inline as having an unsubmitted checkmark,
// as opposed to a submitted checkmark. This is different from the
// top-level "draft" state of unsubmitted comments.
JX.DOM.alterClass(comment, 'inline-state-is-draft', response.draftState);
this._isFixed = response.isChecked;
this._isDraftDone = !!response.draftState;
this._didUpdate();
},
create: function(text) {
var changeset = this.getChangeset();
if (!this._documentEngineKey) {
this._documentEngineKey = changeset.getResponseDocumentEngineKey();
}
var uri = this._getInlineURI();
var handler = JX.bind(this, this._oncreateresponse);
var data = this._newRequestData('new', text);
this.setLoading(true);
new JX.Request(uri, handler)
.setData(data)
.send();
},
reply: function(with_quote) {
this._closeMenu();
var text;
if (with_quote) {
text = this.getRawText();
text = '> ' + text.replace(/\n/g, '\n> ') + '\n\n';
} else {
text = '';
}
var changeset = this.getChangeset();
return changeset.newInlineReply(this, text);
},
edit: function(text, skip_focus) {
this._closeMenu();
this._skipFocus = !!skip_focus;
// If you edit an inline ("A"), modify the text ("AB"), cancel, and then
// edit it again: discard the undo state ("AB"). Otherwise we end up
// with an open editor and an active "Undo" link, which is weird.
if (this._undoRow) {
JX.DOM.remove(this._undoRow);
this._undoRow = null;
this._undoType = null;
this._undoText = null;
}
var uri = this._getInlineURI();
var handler = JX.bind(this, this._oneditresponse);
var data = this._newRequestData('edit', text || null);
this.setLoading(true);
new JX.Request(uri, handler)
.setData(data)
.send();
},
delete: function(is_ref) {
var uri = this._getInlineURI();
var handler = JX.bind(this, this._ondeleteresponse);
// NOTE: This may be a direct delete (the user clicked on the inline
// itself) or a "refdelete" (the user clicked somewhere else, like the
// preview, but the inline is present on the page).
// For a "refdelete", we prompt the user to confirm that they want to
// delete the comment, because they can not undo deletions from the
// preview. We could jump the user to the inline instead, but this would
// be somewhat disruptive and make deleting several comments more
// difficult.
var op;
if (is_ref) {
op = 'refdelete';
} else {
op = 'delete';
}
var data = this._newRequestData(op);
this.setLoading(true);
new JX.Workflow(uri, data)
.setHandler(handler)
.start();
},
getDisplaySide: function() {
return this._displaySide;
},
getLineNumber: function() {
return this._number;
},
getLineLength: function() {
return this._length;
},
isNewFile: function() {
return this._isNewFile;
},
getID: function() {
return this._id;
},
getPHID: function() {
return this._phid;
},
getChangesetID: function() {
return this._changesetID;
},
getReplyToCommentPHID: function() {
return this._replyToCommentPHID;
},
setDeleted: function(deleted) {
this._isDeleted = deleted;
this._redraw();
return this;
},
setInvisible: function(invisible) {
this._isInvisible = invisible;
this._redraw();
return this;
},
setLoading: function(loading) {
this._isLoading = loading;
this._redraw();
return this;
},
_newRequestData: function(operation, text) {
var data = {
op: operation,
is_new: this.isNewFile(),
on_right: ((this.getDisplaySide() == 'right') ? 1 : 0),
renderer: this.getChangeset().getRendererKey(),
text: text || null
};
if (operation === 'new') {
var create_data = {
changesetID: this.getChangesetID(),
documentEngineKey: this._documentEngineKey,
replyToCommentPHID: this.getReplyToCommentPHID(),
startOffset: this._startOffset,
endOffset: this._endOffset,
number: this.getLineNumber(),
length: this.getLineLength()
};
JX.copy(data, create_data);
} else {
var edit_data = {
id: this._id
};
JX.copy(data, edit_data);
}
return data;
},
_oneditresponse: function(response) {
var rows = JX.$H(response.view).getNode();
this._readInlineState(response.inline);
this._drawEditRows(rows);
this.setLoading(false);
this.setInvisible(true);
},
_oncreateresponse: function(response) {
var rows = JX.$H(response.view).getNode();
this._readInlineState(response.inline);
this._drawEditRows(rows);
},
_readInlineState: function(state) {
this._id = state.id;
},
_ondeleteresponse: function() {
// If there's an existing "unedit" undo element, remove it.
if (this._undoRow) {
JX.DOM.remove(this._undoRow);
this._undoRow = null;
}
// If there's an existing editor, remove it. This happens when you
// delete a comment from the comment preview area. In this case, we
// read and preserve the text so "Undo" restores it.
var text;
if (this._editRow) {
text = this._readText(this._editRow);
JX.DOM.remove(this._editRow);
this._editRow = null;
}
this._drawUndeleteRows(text);
this.setLoading(false);
this.setDeleted(true);
this._didUpdate();
},
_drawUndeleteRows: function(text) {
this._undoType = 'undelete';
this._undoText = text || null;
return this._drawUndoRows('undelete', this._row);
},
_drawUneditRows: function(text) {
this._undoType = 'unedit';
this._undoText = text;
return this._drawUndoRows('unedit', null, text);
},
_drawUndoRows: function(mode, cursor, text) {
var templates = this.getChangeset().getUndoTemplates();
var template;
if (this.getDisplaySide() == 'right') {
template = templates.r;
} else {
template = templates.l;
}
template = JX.$H(template).getNode();
this._undoRow = this._drawRows(template, cursor, mode, text);
},
_drawContentRows: function(rows) {
return this._drawRows(rows, null, 'content');
},
_drawEditRows: function(rows) {
this.setEditing(true);
this._editRow = this._drawRows(rows, null, 'edit');
},
_drawRows: function(rows, cursor, type, text) {
var first_row = JX.DOM.scry(rows, 'tr')[0];
var row = first_row;
var anchor = cursor || this._row;
cursor = cursor || this._row.nextSibling;
var result_row;
var next_row;
while (row) {
// Grab this first, since it's going to change once we insert the row
// into the document.
next_row = row.nextSibling;
// Bind edit and undo rows to this DiffInline object so that
// interactions like hovering work properly.
JX.Stratcom.getData(row).inline = this;
anchor.parentNode.insertBefore(row, cursor);
cursor = row;
if (!result_row) {
result_row = row;
}
if (!this._skipFocus) {
// If the row has a textarea, focus it. This allows the user to start
// typing a comment immediately after a "new", "edit", or "reply"
// action.
// (When simulating an "edit" on page load, we don't do this.)
var textareas = JX.DOM.scry(
row,
'textarea',
'differential-inline-comment-edit-textarea');
if (textareas.length) {
var area = textareas[0];
area.focus();
var length = area.value.length;
JX.TextAreaUtils.setSelectionRange(area, length, length);
}
}
row = next_row;
}
JX.Stratcom.invoke('resize');
return result_row;
},
save: function(form) {
var handler = JX.bind(this, this._onsubmitresponse);
this.setLoading(true);
JX.Workflow.newFromForm(form)
.setHandler(handler)
.start();
},
undo: function() {
JX.DOM.remove(this._undoRow);
this._undoRow = null;
if (this._undoType === 'undelete') {
var uri = this._getInlineURI();
var data = this._newRequestData('undelete');
var handler = JX.bind(this, this._onundelete);
this.setDeleted(false);
this.setLoading(true);
new JX.Request(uri, handler)
.setData(data)
.send();
}
if (this._undoText !== null) {
this.edit(this._undoText);
}
},
_onundelete: function() {
this.setLoading(false);
this._didUpdate();
},
cancel: function() {
var text = this._readText(this._editRow);
JX.DOM.remove(this._editRow);
this._editRow = null;
if (text && text.length && (text != this._originalText)) {
this._drawUneditRows(text);
}
// If this was an empty box and we typed some text and then hit cancel,
// don't show the empty concrete inline.
if (!this._originalText) {
this.setInvisible(true);
} else {
this.setInvisible(false);
}
// If you "undo" to restore text ("AB") and then "Cancel", we put you
// back in the original text state ("A"). We also send the original
// text ("A") to the server as the current persistent state.
var uri = this._getInlineURI();
var data = this._newRequestData('cancel', this._originalText);
var handler = JX.bind(this, this._onCancelResponse);
this.setLoading(true);
new JX.Request(uri, handler)
.setData(data)
.send();
this._didUpdate(true);
},
_onCancelResponse: function(response) {
this.setEditing(false);
this.setLoading(false);
// If the comment was empty when we started editing it (there's no
// original text) and empty when we finished editing it (there's no
// undo row), just delete the comment.
if (!this._originalText && !this.isUndo()) {
this.setDeleted(true);
JX.DOM.remove(this._row);
this._row = null;
this._didUpdate();
}
},
_readText: function(row) {
var textarea;
try {
textarea = JX.DOM.find(
row,
'textarea',
'differential-inline-comment-edit-textarea');
} catch (ex) {
return null;
}
return textarea.value;
},
_onsubmitresponse: function(response) {
if (this._editRow) {
JX.DOM.remove(this._editRow);
this._editRow = null;
}
this.setLoading(false);
this.setInvisible(false);
this.setEditing(false);
this._onupdate(response);
},
_onupdate: function(response) {
var new_row;
if (response.view) {
new_row = this._drawContentRows(JX.$H(response.view).getNode());
}
// TODO: Save the old row so the action it's undo-able if it was a
// delete.
var remove_old = true;
if (remove_old) {
JX.DOM.remove(this._row);
}
// If you delete the content on a comment and save it, it acts like a
// delete: the server does not return a new row.
if (new_row) {
this.bindToRow(new_row);
} else {
this.setDeleted(true);
this._row = null;
}
this._didUpdate();
},
_didUpdate: function(local_only) {
// After making changes to inline comments, refresh the transaction
// preview at the bottom of the page.
if (!local_only) {
this.getChangeset().getChangesetList().redrawPreview();
}
this.getChangeset().getChangesetList().redrawCursor();
this.getChangeset().getChangesetList().resetHover();
// Emit a resize event so that UI elements like the keyboard focus
// reticle can redraw properly.
JX.Stratcom.invoke('resize');
},
_redraw: function() {
var is_invisible =
(this._isInvisible || this._isDeleted || this._isHidden);
var is_loading = this._isLoading;
var is_collapsed = (this._isCollapsed && !this._isHidden);
var row = this._row;
JX.DOM.alterClass(row, 'differential-inline-hidden', is_invisible);
JX.DOM.alterClass(row, 'differential-inline-loading', is_loading);
JX.DOM.alterClass(row, 'inline-hidden', is_collapsed);
},
_getInlineURI: function() {
var changeset = this.getChangeset();
var list = changeset.getChangesetList();
return list.getInlineURI();
},
_startDrafts: function() {
if (this._draftRequest) {
return;
}
var onresponse = JX.bind(this, this._onDraftResponse);
var draft = JX.bind(this, this._getDraftState);
var uri = this._getInlineURI();
var request = new JX.PhabricatorShapedRequest(uri, onresponse, draft);
// The main transaction code uses a 500ms delay on desktop and a
// 10s delay on mobile. Perhaps this should be standardized.
request.setRateLimit(2000);
this._draftRequest = request;
request.start();
},
_onDraftResponse: function() {
// For now, do nothing.
},
_getDraftState: function() {
if (this.isDeleted()) {
return null;
}
if (!this.isEditing()) {
return null;
}
var text = this._readText(this._editRow);
if (text === null) {
return null;
}
return {
op: 'draft',
id: this.getID(),
text: text
};
},
triggerDraft: function() {
if (this._draftRequest) {
this._draftRequest.trigger();
}
},
activateMenu: function(button, e) {
// If we already have a menu for this button, let the menu handle the
// event.
var data = JX.Stratcom.getData(button);
if (data.menu) {
return;
}
e.prevent();
var menu = new JX.PHUIXDropdownMenu(button)
.setWidth(240);
var list = new JX.PHUIXActionListView();
var items = this._newMenuItems(menu);
for (var ii = 0; ii < items.length; ii++) {
list.addItem(items[ii]);
}
menu.setContent(list.getNode());
data.menu = menu;
this._menu = menu;
menu.listen('open', JX.bind(this, function() {
var changeset_list = this.getChangeset().getChangesetList();
changeset_list.selectInline(this, true);
}));
menu.open();
},
_newMenuItems: function(menu) {
var items = [];
for (var ii = 0; ii < this._menuItems.length; ii++) {
var spec = this._menuItems[ii];
var onmenu = JX.bind(this, this._onMenuItem, menu, spec.action, spec);
var item = new JX.PHUIXActionView()
.setIcon(spec.icon)
.setName(spec.label)
.setHandler(onmenu);
if (spec.key) {
item.setKeyCommand(spec.key);
}
items.push(item);
}
return items;
},
_onMenuItem: function(menu, action, spec, e) {
e.prevent();
menu.close();
switch (action) {
case 'reply':
this.reply();
break;
case 'quote':
this.reply(true);
break;
case 'collapse':
this.setCollapsed(true);
break;
case 'delete':
this.delete();
break;
case 'edit':
this.edit();
break;
case 'raw':
new JX.Workflow(spec.uri)
.start();
break;
}
},
_hasMenuAction: function(action) {
for (var ii = 0; ii < this._menuItems.length; ii++) {
var spec = this._menuItems[ii];
if (spec.action === action) {
return true;
}
}
return false;
},
_closeMenu: function() {
if (this._menu) {
this._menu.close();
}
}
}
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:31 (1 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126987
Default Alt Text
(254 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment