mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-15 10:00:55 +01:00
(stable) Promote 2017 Week 35
This commit is contained in:
commit
007fb4c30e
57 changed files with 1196 additions and 113 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => 'e68cf1fa',
|
||||
'conpherence.pkg.js' => 'b5b51108',
|
||||
'core.pkg.css' => '291cbd98',
|
||||
'core.pkg.css' => '4ac857bf',
|
||||
'core.pkg.js' => '6c085267',
|
||||
'darkconsole.pkg.js' => '1f9a31bc',
|
||||
'differential.pkg.css' => '45951e9e',
|
||||
|
@ -29,10 +29,10 @@ return array(
|
|||
'rsrc/css/aphront/dialog-view.css' => '6bfc244b',
|
||||
'rsrc/css/aphront/list-filter-view.css' => '5d6f0526',
|
||||
'rsrc/css/aphront/multi-column.css' => '84cc6640',
|
||||
'rsrc/css/aphront/notification.css' => '3f6c89c9',
|
||||
'rsrc/css/aphront/notification.css' => '457861ec',
|
||||
'rsrc/css/aphront/panel-view.css' => '8427b78d',
|
||||
'rsrc/css/aphront/phabricator-nav-view.css' => 'faf6a6fc',
|
||||
'rsrc/css/aphront/table-view.css' => 'a3aa6910',
|
||||
'rsrc/css/aphront/table-view.css' => '8c9bbafe',
|
||||
'rsrc/css/aphront/tokenizer.css' => '15d5ff71',
|
||||
'rsrc/css/aphront/tooltip.css' => '173b9431',
|
||||
'rsrc/css/aphront/typeahead-browse.css' => 'f2818435',
|
||||
|
@ -40,7 +40,7 @@ return array(
|
|||
'rsrc/css/application/almanac/almanac.css' => 'dbb9b3af',
|
||||
'rsrc/css/application/auth/auth.css' => '0877ed6e',
|
||||
'rsrc/css/application/base/main-menu-view.css' => '1802a242',
|
||||
'rsrc/css/application/base/notification-menu.css' => '73fefdfa',
|
||||
'rsrc/css/application/base/notification-menu.css' => '10685bd4',
|
||||
'rsrc/css/application/base/phui-theme.css' => '9f261c6b',
|
||||
'rsrc/css/application/base/standard-page-view.css' => '34ee718b',
|
||||
'rsrc/css/application/chatlog/chatlog.css' => 'd295b020',
|
||||
|
@ -74,8 +74,8 @@ return array(
|
|||
'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e',
|
||||
'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6',
|
||||
'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec',
|
||||
'rsrc/css/application/diffusion/diffusion-source.css' => '47db8a7c',
|
||||
'rsrc/css/application/diffusion/diffusion.css' => 'ceacf994',
|
||||
'rsrc/css/application/diffusion/diffusion-source.css' => '69ac9399',
|
||||
'rsrc/css/application/diffusion/diffusion.css' => '45727264',
|
||||
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
|
||||
'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948',
|
||||
'rsrc/css/application/flag/flag.css' => 'bba8f811',
|
||||
|
@ -110,7 +110,7 @@ return array(
|
|||
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
|
||||
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
|
||||
'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
|
||||
'rsrc/css/application/search/application-search-view.css' => '66ee5d46',
|
||||
'rsrc/css/application/search/application-search-view.css' => '787f5b76',
|
||||
'rsrc/css/application/search/search-results.css' => '505dd8cf',
|
||||
'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230',
|
||||
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
||||
|
@ -127,7 +127,7 @@ return array(
|
|||
'rsrc/css/layout/phabricator-source-code-view.css' => 'aea41829',
|
||||
'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494',
|
||||
'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68',
|
||||
'rsrc/css/phui/button/phui-button.css' => 'a37aa3a8',
|
||||
'rsrc/css/phui/button/phui-button.css' => '1863cc6e',
|
||||
'rsrc/css/phui/calendar/phui-calendar-day.css' => '572b1893',
|
||||
'rsrc/css/phui/calendar/phui-calendar-list.css' => '576be600',
|
||||
'rsrc/css/phui/calendar/phui-calendar-month.css' => '21154caf',
|
||||
|
@ -141,7 +141,7 @@ return array(
|
|||
'rsrc/css/phui/phui-action-list.css' => 'e7eba156',
|
||||
'rsrc/css/phui/phui-action-panel.css' => 'b4798122',
|
||||
'rsrc/css/phui/phui-badge.css' => '22c0cf4f',
|
||||
'rsrc/css/phui/phui-basic-nav-view.css' => 'a0705f53',
|
||||
'rsrc/css/phui/phui-basic-nav-view.css' => '98c11ab3',
|
||||
'rsrc/css/phui/phui-big-info-view.css' => 'acc3492c',
|
||||
'rsrc/css/phui/phui-box.css' => '745e881d',
|
||||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
|
@ -178,7 +178,7 @@ return array(
|
|||
'rsrc/css/phui/phui-status.css' => 'd5263e49',
|
||||
'rsrc/css/phui/phui-tag-view.css' => 'b4719c50',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => 'f21db7ca',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => '76dcd3d4',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'bf86c483',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => '783cdff5',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => '3bc85455',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => 'cca5fa92',
|
||||
|
@ -529,7 +529,7 @@ return array(
|
|||
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
|
||||
'rsrc/js/phuix/PHUIXActionView.js' => '442efd08',
|
||||
'rsrc/js/phuix/PHUIXAutocomplete.js' => '4b7430ab',
|
||||
'rsrc/js/phuix/PHUIXButtonView.js' => 'a37126bd',
|
||||
'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac',
|
||||
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '8018ee50',
|
||||
'rsrc/js/phuix/PHUIXExample.js' => '68af71ca',
|
||||
'rsrc/js/phuix/PHUIXFormControl.js' => '83e03671',
|
||||
|
@ -543,11 +543,11 @@ return array(
|
|||
'aphront-list-filter-view-css' => '5d6f0526',
|
||||
'aphront-multi-column-view-css' => '84cc6640',
|
||||
'aphront-panel-view-css' => '8427b78d',
|
||||
'aphront-table-view-css' => 'a3aa6910',
|
||||
'aphront-table-view-css' => '8c9bbafe',
|
||||
'aphront-tokenizer-control-css' => '15d5ff71',
|
||||
'aphront-tooltip-css' => '173b9431',
|
||||
'aphront-typeahead-control-css' => 'a4a21016',
|
||||
'application-search-view-css' => '66ee5d46',
|
||||
'application-search-view-css' => '787f5b76',
|
||||
'auth-css' => '0877ed6e',
|
||||
'bulk-job-css' => 'df9c1d4a',
|
||||
'conduit-api-css' => '7bc725c4',
|
||||
|
@ -570,11 +570,11 @@ return array(
|
|||
'differential-revision-history-css' => '0e8eb855',
|
||||
'differential-revision-list-css' => 'f3c47d33',
|
||||
'differential-table-of-contents-css' => 'ae4b7a55',
|
||||
'diffusion-css' => 'ceacf994',
|
||||
'diffusion-css' => '45727264',
|
||||
'diffusion-icons-css' => '0c15255e',
|
||||
'diffusion-readme-css' => '419dd5b6',
|
||||
'diffusion-repository-css' => 'ee6f20ec',
|
||||
'diffusion-source-css' => '47db8a7c',
|
||||
'diffusion-source-css' => '69ac9399',
|
||||
'diviner-shared-css' => '896f1d43',
|
||||
'font-fontawesome' => 'e838e088',
|
||||
'font-lato' => 'c7ccd872',
|
||||
|
@ -792,8 +792,8 @@ return array(
|
|||
'phabricator-main-menu-view' => '1802a242',
|
||||
'phabricator-nav-view-css' => 'faf6a6fc',
|
||||
'phabricator-notification' => '5c3349b2',
|
||||
'phabricator-notification-css' => '3f6c89c9',
|
||||
'phabricator-notification-menu-css' => '73fefdfa',
|
||||
'phabricator-notification-css' => '457861ec',
|
||||
'phabricator-notification-menu-css' => '10685bd4',
|
||||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
'phabricator-phtize' => 'd254d646',
|
||||
'phabricator-prefab' => 'c5af80a2',
|
||||
|
@ -820,11 +820,11 @@ return array(
|
|||
'phriction-document-css' => '4282e4ad',
|
||||
'phui-action-panel-css' => 'b4798122',
|
||||
'phui-badge-view-css' => '22c0cf4f',
|
||||
'phui-basic-nav-view-css' => 'a0705f53',
|
||||
'phui-basic-nav-view-css' => '98c11ab3',
|
||||
'phui-big-info-view-css' => 'acc3492c',
|
||||
'phui-box-css' => '745e881d',
|
||||
'phui-button-bar-css' => 'f1ff5494',
|
||||
'phui-button-css' => 'a37aa3a8',
|
||||
'phui-button-css' => '1863cc6e',
|
||||
'phui-button-simple-css' => '8e1baf68',
|
||||
'phui-calendar-css' => 'f1ddf11c',
|
||||
'phui-calendar-day-css' => '572b1893',
|
||||
|
@ -874,7 +874,7 @@ return array(
|
|||
'phui-tag-view-css' => 'b4719c50',
|
||||
'phui-theme-css' => '9f261c6b',
|
||||
'phui-timeline-view-css' => 'f21db7ca',
|
||||
'phui-two-column-view-css' => '76dcd3d4',
|
||||
'phui-two-column-view-css' => 'bf86c483',
|
||||
'phui-workboard-color-css' => '783cdff5',
|
||||
'phui-workboard-view-css' => '3bc85455',
|
||||
'phui-workcard-view-css' => 'cca5fa92',
|
||||
|
@ -882,7 +882,7 @@ return array(
|
|||
'phuix-action-list-view' => 'b5c256b8',
|
||||
'phuix-action-view' => '442efd08',
|
||||
'phuix-autocomplete' => '4b7430ab',
|
||||
'phuix-button-view' => 'a37126bd',
|
||||
'phuix-button-view' => '8a91e1ac',
|
||||
'phuix-dropdown-menu' => '8018ee50',
|
||||
'phuix-form-control-view' => '83e03671',
|
||||
'phuix-icon-view' => 'bff6884b',
|
||||
|
@ -1557,6 +1557,10 @@ return array(
|
|||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'8a91e1ac' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'8ce821c5' => array(
|
||||
'phabricator-notification',
|
||||
'javelin-stratcom',
|
||||
|
@ -1696,10 +1700,6 @@ return array(
|
|||
'javelin-sound',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'a37126bd' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
),
|
||||
'a3a63478' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
|
|
9
resources/sql/autopatches/20170828.ferret.01.taskdoc.sql
Normal file
9
resources/sql/autopatches/20170828.ferret.01.taskdoc.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_fdocument (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
isClosed BOOL NOT NULL,
|
||||
authorPHID VARBINARY(64),
|
||||
ownerPHID VARBINARY(64),
|
||||
epochCreated INT UNSIGNED NOT NULL,
|
||||
epochModified INT UNSIGNED NOT NULL
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
documentID INT UNSIGNED NOT NULL,
|
||||
fieldKey VARCHAR(4) NOT NULL COLLATE {$COLLATE_TEXT},
|
||||
rawCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
|
||||
normalCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,5 @@
|
|||
CREATE TABLE {$NAMESPACE}_maniphest.maniphest_task_fngrams (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
documentID INT UNSIGNED NOT NULL,
|
||||
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
4
resources/sql/autopatches/20170830.ferret.01.unique.sql
Normal file
4
resources/sql/autopatches/20170830.ferret.01.unique.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
TRUNCATE TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield;
|
||||
|
||||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield
|
||||
ADD UNIQUE KEY `key_documentfield` (documentID, fieldKey);
|
2
resources/sql/autopatches/20170830.ferret.02.term.sql
Normal file
2
resources/sql/autopatches/20170830.ferret.02.term.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_maniphest.maniphest_task_ffield
|
||||
ADD termCorpus LONGTEXT NOT NULL COLLATE {$COLLATE_SORT};
|
|
@ -1533,6 +1533,10 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskEditBulkJobType' => 'applications/maniphest/bulk/ManiphestTaskEditBulkJobType.php',
|
||||
'ManiphestTaskEditController' => 'applications/maniphest/controller/ManiphestTaskEditController.php',
|
||||
'ManiphestTaskEditEngineLock' => 'applications/maniphest/editor/ManiphestTaskEditEngineLock.php',
|
||||
'ManiphestTaskFerretDocument' => 'applications/maniphest/storage/ManiphestTaskFerretDocument.php',
|
||||
'ManiphestTaskFerretEngine' => 'applications/maniphest/search/ManiphestTaskFerretEngine.php',
|
||||
'ManiphestTaskFerretField' => 'applications/maniphest/storage/ManiphestTaskFerretField.php',
|
||||
'ManiphestTaskFerretNgrams' => 'applications/maniphest/storage/ManiphestTaskFerretNgrams.php',
|
||||
'ManiphestTaskFulltextEngine' => 'applications/maniphest/search/ManiphestTaskFulltextEngine.php',
|
||||
'ManiphestTaskGraph' => 'infrastructure/graph/ManiphestTaskGraph.php',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'applications/maniphest/edge/ManiphestTaskHasCommitEdgeType.php',
|
||||
|
@ -2828,6 +2832,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryNotification' => 'applications/notification/storage/PhabricatorFeedStoryNotification.php',
|
||||
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
|
||||
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
|
||||
'PhabricatorFerretDocument' => 'applications/search/ferret/PhabricatorFerretDocument.php',
|
||||
'PhabricatorFerretEngine' => 'applications/search/ferret/PhabricatorFerretEngine.php',
|
||||
'PhabricatorFerretField' => 'applications/search/ferret/PhabricatorFerretField.php',
|
||||
'PhabricatorFerretFulltextEngineExtension' => 'applications/search/engineextension/PhabricatorFerretFulltextEngineExtension.php',
|
||||
'PhabricatorFerretInterface' => 'applications/search/ferret/PhabricatorFerretInterface.php',
|
||||
'PhabricatorFerretNgrams' => 'applications/search/ferret/PhabricatorFerretNgrams.php',
|
||||
'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
|
||||
'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php',
|
||||
'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
|
||||
|
@ -3195,6 +3205,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNamedQueryQuery' => 'applications/search/query/PhabricatorNamedQueryQuery.php',
|
||||
'PhabricatorNavigationRemarkupRule' => 'infrastructure/markup/rule/PhabricatorNavigationRemarkupRule.php',
|
||||
'PhabricatorNeverTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorNeverTriggerClock.php',
|
||||
'PhabricatorNgramEngine' => 'applications/search/ngrams/PhabricatorNgramEngine.php',
|
||||
'PhabricatorNgramEngineTestCase' => 'applications/search/ngrams/__tests__/PhabricatorNgramEngineTestCase.php',
|
||||
'PhabricatorNgramsIndexEngineExtension' => 'applications/search/engineextension/PhabricatorNgramsIndexEngineExtension.php',
|
||||
'PhabricatorNgramsInterface' => 'applications/search/interface/PhabricatorNgramsInterface.php',
|
||||
'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
|
||||
|
@ -6659,6 +6671,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorSpacesInterface',
|
||||
'PhabricatorConduitResultInterface',
|
||||
'PhabricatorFulltextInterface',
|
||||
'PhabricatorFerretInterface',
|
||||
'DoorkeeperBridgedObjectInterface',
|
||||
'PhabricatorEditEngineSubtypeInterface',
|
||||
'PhabricatorEditEngineLockableInterface',
|
||||
|
@ -6682,6 +6695,10 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskEditBulkJobType' => 'PhabricatorWorkerBulkJobType',
|
||||
'ManiphestTaskEditController' => 'ManiphestController',
|
||||
'ManiphestTaskEditEngineLock' => 'PhabricatorEditEngineLock',
|
||||
'ManiphestTaskFerretDocument' => 'PhabricatorFerretDocument',
|
||||
'ManiphestTaskFerretEngine' => 'PhabricatorFerretEngine',
|
||||
'ManiphestTaskFerretField' => 'PhabricatorFerretField',
|
||||
'ManiphestTaskFerretNgrams' => 'PhabricatorFerretNgrams',
|
||||
'ManiphestTaskFulltextEngine' => 'PhabricatorFulltextEngine',
|
||||
'ManiphestTaskGraph' => 'PhabricatorObjectGraph',
|
||||
'ManiphestTaskHasCommitEdgeType' => 'PhabricatorEdgeType',
|
||||
|
@ -8147,6 +8164,11 @@ phutil_register_library_map(array(
|
|||
'PhabricatorFeedStoryNotification' => 'PhabricatorFeedDAO',
|
||||
'PhabricatorFeedStoryPublisher' => 'Phobject',
|
||||
'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
|
||||
'PhabricatorFerretDocument' => 'PhabricatorSearchDAO',
|
||||
'PhabricatorFerretEngine' => 'Phobject',
|
||||
'PhabricatorFerretField' => 'PhabricatorSearchDAO',
|
||||
'PhabricatorFerretFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension',
|
||||
'PhabricatorFerretNgrams' => 'PhabricatorSearchDAO',
|
||||
'PhabricatorFile' => array(
|
||||
'PhabricatorFileDAO',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
|
@ -8565,6 +8587,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorNamedQueryQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorNavigationRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorNeverTriggerClock' => 'PhabricatorTriggerClock',
|
||||
'PhabricatorNgramEngine' => 'Phobject',
|
||||
'PhabricatorNgramEngineTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorNgramsIndexEngineExtension' => 'PhabricatorIndexEngineExtension',
|
||||
'PhabricatorNgramsInterface' => 'PhabricatorIndexableInterface',
|
||||
'PhabricatorNotificationBuilder' => 'Phobject',
|
||||
|
|
|
@ -57,6 +57,7 @@ final class DiffusionBranchTableController extends DiffusionController {
|
|||
$content = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($repository->getName())
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setTable($list)
|
||||
->setPager($pager);
|
||||
}
|
||||
|
|
|
@ -371,6 +371,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
->setHeader($browse_header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($browse_table)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
$path = $drequest->getPath();
|
||||
|
@ -595,6 +596,8 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
),
|
||||
$rows);
|
||||
|
||||
$corpus_table = phutil_tag_div('diffusion-source-wrap', $corpus_table);
|
||||
|
||||
if ($this->getRequest()->isAjax()) {
|
||||
return $corpus_table;
|
||||
}
|
||||
|
@ -654,6 +657,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($corpus)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setCollapsed(true);
|
||||
|
||||
$messages = array();
|
||||
|
@ -730,6 +734,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
->setText($blame_text)
|
||||
->setIcon($blame_icon)
|
||||
->setUser($viewer)
|
||||
->setSelected(!$blame_value)
|
||||
->setColor(PHUIButtonView::GREY);
|
||||
|
||||
if ($viewer->isLoggedIn()) {
|
||||
|
@ -859,6 +864,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$view = id(new PHUIObjectBoxView())
|
||||
->setHeaderText(pht('Owner Packages'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setObjectList($ownership);
|
||||
}
|
||||
|
||||
|
@ -1134,7 +1140,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$before_link = null;
|
||||
$commit_date = null;
|
||||
|
||||
$style = 'border-right: 2px solid '.$line['color'].';';
|
||||
$style = 'border-right: 3px solid '.$line['color'].';';
|
||||
|
||||
if ($identifier && !$line['duplicate']) {
|
||||
if (isset($commit_links[$identifier])) {
|
||||
|
@ -1340,6 +1346,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->addPropertyList($properties);
|
||||
}
|
||||
|
||||
|
@ -1360,6 +1367,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->appendChild($text);
|
||||
|
||||
return $box;
|
||||
|
@ -1692,15 +1700,20 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Recently Open Revisions'));
|
||||
|
||||
$view = id(new DifferentialRevisionListView())
|
||||
$list = id(new DifferentialRevisionListView())
|
||||
->setRevisions($revisions)
|
||||
->setUser($viewer)
|
||||
->setNoBox(true);
|
||||
|
||||
$phids = $list->getRequiredHandlePHIDs();
|
||||
$handles = $this->loadViewerHandles($phids);
|
||||
$list->setHandles($handles);
|
||||
|
||||
$view = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setRevisions($revisions)
|
||||
->setUser($viewer);
|
||||
|
||||
$phids = $view->getRequiredHandlePHIDs();
|
||||
$handles = $this->loadViewerHandles($phids);
|
||||
$view->setHandles($handles);
|
||||
->addClass('diffusion-mobile-view')
|
||||
->appendChild($list);
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
@ -1837,6 +1850,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$corpus = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setCollapsed(true);
|
||||
|
||||
if ($messages) {
|
||||
|
@ -1921,6 +1935,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setTable($history_table);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ final class DiffusionCompareController extends DiffusionController {
|
|||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
require_celerity_resource('diffusion-css');
|
||||
|
||||
if (!$repository->supportsBranchComparison()) {
|
||||
return $this->newDialog()
|
||||
|
@ -315,6 +316,7 @@ final class DiffusionCompareController extends DiffusionController {
|
|||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($history_table)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ final class DiffusionGraphController extends DiffusionController {
|
|||
->setHeaderText(pht('History Graph'))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($graph)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
|
||||
$tabs = $this->buildTabsView('graph');
|
||||
|
|
|
@ -420,7 +420,8 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$history_table->setIsHead(true);
|
||||
|
||||
$panel = id(new PHUIObjectBoxView())
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view');
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Recent Commits'));
|
||||
$panel->setHeader($header);
|
||||
|
@ -583,6 +584,7 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
->setHeaderText($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($browse_table)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setPager($pager);
|
||||
}
|
||||
|
||||
|
|
|
@ -86,8 +86,11 @@ final class DiffusionRepositoryBranchesManagementPanel
|
|||
$repository->getHumanReadableDetail('close-commits-filter', array()),
|
||||
phutil_tag('em', array(), pht('Autoclose On All Branches')));
|
||||
|
||||
$autoclose_disabled = false;
|
||||
if ($repository->getDetail('disable-autoclose')) {
|
||||
$autoclose_only = phutil_tag('em', array(), pht('Disabled'));
|
||||
$autoclose_disabled = true;
|
||||
$autoclose_only =
|
||||
phutil_tag('em', array(), pht('Autoclose has been disabled'));
|
||||
}
|
||||
|
||||
$view->addProperty(pht('Autoclose Only'), $autoclose_only);
|
||||
|
@ -133,12 +136,18 @@ final class DiffusionRepositoryBranchesManagementPanel
|
|||
$status = pht('Open');
|
||||
}
|
||||
|
||||
if ($autoclose_disabled) {
|
||||
$autoclose_status = pht('Disabled (Repository)');
|
||||
} else {
|
||||
$autoclose_status = pht('Off');
|
||||
}
|
||||
|
||||
$rows[] = array(
|
||||
$icon,
|
||||
$branch_name,
|
||||
$status,
|
||||
$tracking ? pht('Tracking') : pht('Off'),
|
||||
$autoclosing ? pht('Autoclose On') : pht('Off'),
|
||||
$autoclosing ? pht('Autoclose On') : $autoclose_status,
|
||||
);
|
||||
}
|
||||
$branch_table = new AphrontTableView($rows);
|
||||
|
|
|
@ -134,7 +134,7 @@ final class DiffusionBrowseTableView extends DiffusionView {
|
|||
array(
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
));
|
||||
|
|
|
@ -40,6 +40,7 @@ final class DiffusionHistoryListView extends DiffusionHistoryView {
|
|||
$view[] = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->setObjectList($list);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ final class DiffusionReadmeView extends DiffusionView {
|
|||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->addClass('diffusion-mobile-view')
|
||||
->appendChild($document)
|
||||
->addClass('diffusion-readme-view');
|
||||
}
|
||||
|
|
|
@ -49,6 +49,8 @@ final class ManiphestTaskSearchEngine
|
|||
$subtype_map = id(new ManiphestTask())->newEditEngineSubtypeMap();
|
||||
$hide_subtypes = (count($subtype_map) == 1);
|
||||
|
||||
$hide_ferret = !PhabricatorEnv::getEnvConfig('phabricator.show-prototypes');
|
||||
|
||||
return array(
|
||||
id(new PhabricatorOwnersSearchField())
|
||||
->setLabel(pht('Assigned To'))
|
||||
|
@ -89,6 +91,10 @@ final class ManiphestTaskSearchEngine
|
|||
id(new PhabricatorSearchTextField())
|
||||
->setLabel(pht('Contains Words'))
|
||||
->setKey('fulltext'),
|
||||
id(new PhabricatorSearchTextField())
|
||||
->setLabel(pht('Query (Prototype)'))
|
||||
->setKey('query')
|
||||
->setIsHidden($hide_ferret),
|
||||
id(new PhabricatorSearchThreeStateField())
|
||||
->setLabel(pht('Open Parents'))
|
||||
->setKey('hasParents')
|
||||
|
@ -144,6 +150,7 @@ final class ManiphestTaskSearchEngine
|
|||
'statuses',
|
||||
'priorities',
|
||||
'subtypes',
|
||||
'query',
|
||||
'fulltext',
|
||||
'hasParents',
|
||||
'hasSubtasks',
|
||||
|
@ -224,6 +231,27 @@ final class ManiphestTaskSearchEngine
|
|||
$query->withFullTextSearch($map['fulltext']);
|
||||
}
|
||||
|
||||
if (strlen($map['query'])) {
|
||||
$raw_query = $map['query'];
|
||||
|
||||
$compiler = id(new PhutilSearchQueryCompiler())
|
||||
->setEnableFunctions(true);
|
||||
|
||||
$raw_tokens = $compiler->newTokens($raw_query);
|
||||
|
||||
$fulltext_tokens = array();
|
||||
foreach ($raw_tokens as $raw_token) {
|
||||
$fulltext_token = id(new PhabricatorFulltextToken())
|
||||
->setToken($raw_token);
|
||||
|
||||
$fulltext_tokens[] = $fulltext_token;
|
||||
}
|
||||
|
||||
$query->withFerretConstraint(
|
||||
id(new ManiphestTask())->newFerretEngine(),
|
||||
$fulltext_tokens);
|
||||
}
|
||||
|
||||
if ($map['parentIDs']) {
|
||||
$query->withParentTaskIDs($map['parentIDs']);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskFerretEngine
|
||||
extends PhabricatorFerretEngine {
|
||||
|
||||
public function newNgramsObject() {
|
||||
return new ManiphestTaskFerretNgrams();
|
||||
}
|
||||
|
||||
public function newDocumentObject() {
|
||||
return new ManiphestTaskFerretDocument();
|
||||
}
|
||||
|
||||
public function newFieldObject() {
|
||||
return new ManiphestTaskFerretField();
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@ final class ManiphestTask extends ManiphestDAO
|
|||
PhabricatorSpacesInterface,
|
||||
PhabricatorConduitResultInterface,
|
||||
PhabricatorFulltextInterface,
|
||||
PhabricatorFerretInterface,
|
||||
DoorkeeperBridgedObjectInterface,
|
||||
PhabricatorEditEngineSubtypeInterface,
|
||||
PhabricatorEditEngineLockableInterface {
|
||||
|
@ -603,4 +604,12 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return new ManiphestTaskEditEngineLock();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorFerretInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function newFerretEngine() {
|
||||
return new ManiphestTaskFerretEngine();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskFerretDocument
|
||||
extends PhabricatorFerretDocument {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'maniphest';
|
||||
}
|
||||
|
||||
public function getIndexKey() {
|
||||
return 'task';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskFerretField
|
||||
extends PhabricatorFerretField {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'maniphest';
|
||||
}
|
||||
|
||||
public function getIndexKey() {
|
||||
return 'task';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskFerretNgrams
|
||||
extends PhabricatorFerretNgrams {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'maniphest';
|
||||
}
|
||||
|
||||
public function getIndexKey() {
|
||||
return 'task';
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,17 @@ final class PhabricatorNotificationClearController
|
|||
$chrono_key = $request->getStr('chronoKey');
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
$should_clear = true;
|
||||
} else {
|
||||
try {
|
||||
$request->validateCSRF();
|
||||
$should_clear = true;
|
||||
} catch (AphrontMalformedRequestException $ex) {
|
||||
$should_clear = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($should_clear) {
|
||||
$table = new PhabricatorFeedStoryNotification();
|
||||
|
||||
queryfx(
|
||||
|
|
|
@ -9,7 +9,7 @@ final class PhabricatorNotificationPanelController
|
|||
$query = id(new PhabricatorNotificationQuery())
|
||||
->setViewer($viewer)
|
||||
->withUserPHIDs(array($viewer->getPHID()))
|
||||
->setLimit(15);
|
||||
->setLimit(10);
|
||||
|
||||
$stories = $query->execute();
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ final class PhabricatorProjectsFulltextEngineExtension
|
|||
return pht('Projects');
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
return ($object instanceof PhabricatorProjectInterface);
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
|
|
|
@ -609,7 +609,8 @@ final class PhabricatorProjectQuery
|
|||
}
|
||||
|
||||
if ($this->nameTokens !== null) {
|
||||
foreach ($this->nameTokens as $key => $token) {
|
||||
$name_tokens = $this->getNameTokensForQuery($this->nameTokens);
|
||||
foreach ($name_tokens as $key => $token) {
|
||||
$token_table = 'token_'.$key;
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
|
@ -797,4 +798,22 @@ final class PhabricatorProjectQuery
|
|||
}
|
||||
}
|
||||
|
||||
private function getNameTokensForQuery(array $tokens) {
|
||||
// When querying for projects by name, only actually search for the five
|
||||
// longest tokens. MySQL can get grumpy with a large number of JOINs
|
||||
// with LIKEs and queries for more than 5 tokens are essentially never
|
||||
// legitimate searches for projects, but users copy/pasting nonsense.
|
||||
// See also PHI47.
|
||||
|
||||
$length_map = array();
|
||||
foreach ($tokens as $token) {
|
||||
$length_map[$token] = strlen($token);
|
||||
}
|
||||
arsort($length_map);
|
||||
|
||||
$length_map = array_slice($length_map, 0, 5, true);
|
||||
|
||||
return array_keys($length_map);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,5 +5,7 @@ final class PhabricatorSearchDocumentFieldType extends Phobject {
|
|||
const FIELD_TITLE = 'titl';
|
||||
const FIELD_BODY = 'body';
|
||||
const FIELD_COMMENT = 'cmnt';
|
||||
const FIELD_ALL = 'full';
|
||||
const FIELD_CORE = 'core';
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFerretFulltextEngineExtension
|
||||
extends PhabricatorFulltextEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'ferret';
|
||||
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Ferret Fulltext Engine');
|
||||
}
|
||||
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
return ($object instanceof PhabricatorFerretInterface);
|
||||
}
|
||||
|
||||
|
||||
public function indexFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
$phid = $document->getPHID();
|
||||
$engine = $object->newFerretEngine();
|
||||
|
||||
$ferret_document = $engine->newDocumentObject()
|
||||
->setObjectPHID($phid)
|
||||
->setIsClosed(0)
|
||||
->setEpochCreated(0)
|
||||
->setEpochModified(0);
|
||||
|
||||
$stemmer = new PhutilSearchStemmer();
|
||||
$ngram_engine = id(new PhabricatorNgramEngine());
|
||||
|
||||
// Copy all of the "title" and "body" fields to create new "core" fields.
|
||||
// This allows users to search "in title or body" with the "core:" prefix.
|
||||
$document_fields = $document->getFieldData();
|
||||
$virtual_fields = array();
|
||||
foreach ($document_fields as $field) {
|
||||
$virtual_fields[] = $field;
|
||||
|
||||
list($key, $raw_corpus) = $field;
|
||||
switch ($key) {
|
||||
case PhabricatorSearchDocumentFieldType::FIELD_TITLE:
|
||||
case PhabricatorSearchDocumentFieldType::FIELD_BODY:
|
||||
$virtual_fields[] = array(
|
||||
PhabricatorSearchDocumentFieldType::FIELD_CORE,
|
||||
$raw_corpus,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$key_all = PhabricatorSearchDocumentFieldType::FIELD_ALL;
|
||||
|
||||
$empty_template = array(
|
||||
'raw' => array(),
|
||||
'term' => array(),
|
||||
'normal' => array(),
|
||||
);
|
||||
|
||||
$ferret_corpus_map = array(
|
||||
$key_all => $empty_template,
|
||||
);
|
||||
|
||||
foreach ($virtual_fields as $field) {
|
||||
list($key, $raw_corpus) = $field;
|
||||
if (!strlen($raw_corpus)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$term_corpus = $ngram_engine->newTermsCorpus($raw_corpus);
|
||||
|
||||
$normal_corpus = $stemmer->stemCorpus($raw_corpus);
|
||||
$normal_coprus = $ngram_engine->newTermsCorpus($normal_corpus);
|
||||
|
||||
if (!isset($ferret_corpus_map[$key])) {
|
||||
$ferret_corpus_map[$key] = $empty_template;
|
||||
}
|
||||
|
||||
$ferret_corpus_map[$key]['raw'][] = $raw_corpus;
|
||||
$ferret_corpus_map[$key]['term'][] = $term_corpus;
|
||||
$ferret_corpus_map[$key]['normal'][] = $normal_corpus;
|
||||
|
||||
$ferret_corpus_map[$key_all]['raw'][] = $raw_corpus;
|
||||
$ferret_corpus_map[$key_all]['term'][] = $term_corpus;
|
||||
$ferret_corpus_map[$key_all]['normal'][] = $normal_corpus;
|
||||
}
|
||||
|
||||
$ferret_fields = array();
|
||||
$ngrams_source = array();
|
||||
foreach ($ferret_corpus_map as $key => $fields) {
|
||||
$raw_corpus = $fields['raw'];
|
||||
$raw_corpus = implode("\n", $raw_corpus);
|
||||
$ngrams_source[] = $raw_corpus;
|
||||
|
||||
$normal_corpus = $fields['normal'];
|
||||
$normal_corpus = implode(' ', $normal_corpus);
|
||||
if (strlen($normal_corpus)) {
|
||||
$ngrams_source[] = $normal_corpus;
|
||||
$normal_corpus = ' '.$normal_corpus.' ';
|
||||
}
|
||||
|
||||
$term_corpus = $fields['term'];
|
||||
$term_corpus = implode(' ', $term_corpus);
|
||||
if (strlen($term_corpus)) {
|
||||
$ngrams_source[] = $term_corpus;
|
||||
$term_corpus = ' '.$term_corpus.' ';
|
||||
}
|
||||
|
||||
$ferret_fields[] = $engine->newFieldObject()
|
||||
->setFieldKey($key)
|
||||
->setRawCorpus($raw_corpus)
|
||||
->setTermCorpus($term_corpus)
|
||||
->setNormalCorpus($normal_corpus);
|
||||
}
|
||||
$ngrams_source = implode(' ', $ngrams_source);
|
||||
|
||||
$ngrams = $ngram_engine->getNgramsFromString($ngrams_source, 'index');
|
||||
|
||||
$ferret_document->openTransaction();
|
||||
|
||||
try {
|
||||
$this->deleteOldDocument($engine, $object, $document);
|
||||
|
||||
$ferret_document->save();
|
||||
|
||||
$document_id = $ferret_document->getID();
|
||||
foreach ($ferret_fields as $ferret_field) {
|
||||
$ferret_field
|
||||
->setDocumentID($document_id)
|
||||
->save();
|
||||
}
|
||||
|
||||
$ferret_ngrams = $engine->newNgramsObject();
|
||||
$conn = $ferret_ngrams->establishConnection('w');
|
||||
|
||||
$sql = array();
|
||||
foreach ($ngrams as $ngram) {
|
||||
$sql[] = qsprintf(
|
||||
$conn,
|
||||
'(%d, %s)',
|
||||
$document_id,
|
||||
$ngram);
|
||||
}
|
||||
|
||||
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
|
||||
queryfx(
|
||||
$conn,
|
||||
'INSERT INTO %T (documentID, ngram) VALUES %Q',
|
||||
$ferret_ngrams->getTableName(),
|
||||
$chunk);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
$ferret_document->killTransaction();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$ferret_document->saveTransaction();
|
||||
}
|
||||
|
||||
|
||||
private function deleteOldDocument(
|
||||
PhabricatorFerretEngine $engine,
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
$old_document = $engine->newDocumentObject()->loadOneWhere(
|
||||
'objectPHID = %s',
|
||||
$document->getPHID());
|
||||
if (!$old_document) {
|
||||
return;
|
||||
}
|
||||
|
||||
$conn = $old_document->establishConnection('w');
|
||||
$old_id = $old_document->getID();
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'DELETE FROM %T WHERE id = %d',
|
||||
$engine->newDocumentObject()->getTableName(),
|
||||
$old_id);
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'DELETE FROM %T WHERE documentID = %d',
|
||||
$engine->newFieldObject()->getTableName(),
|
||||
$old_id);
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'DELETE FROM %T WHERE documentID = %d',
|
||||
$engine->newNgramsObject()->getTableName(),
|
||||
$old_id);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@ final class PhabricatorLiskFulltextEngineExtension
|
|||
return pht('Lisk Builtin Properties');
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
if (!($object instanceof PhabricatorLiskDAO)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ final class PhabricatorLiskFulltextEngineExtension
|
|||
return true;
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
|
|
40
src/applications/search/ferret/PhabricatorFerretDocument.php
Normal file
40
src/applications/search/ferret/PhabricatorFerretDocument.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFerretDocument
|
||||
extends PhabricatorSearchDAO {
|
||||
|
||||
protected $objectPHID;
|
||||
protected $isClosed;
|
||||
protected $authorPHID;
|
||||
protected $ownerPHID;
|
||||
protected $epochCreated;
|
||||
protected $epochModified;
|
||||
|
||||
abstract public function getIndexKey();
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'isClosed' => 'bool',
|
||||
'authorPHID' => 'phid?',
|
||||
'ownerPHID' => 'phid?',
|
||||
'epochCreated' => 'epoch',
|
||||
'epochModified' => 'epoch',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_object' => array(
|
||||
'columns' => array('objectPHID'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getTableName() {
|
||||
$application = $this->getApplicationName();
|
||||
$key = $this->getIndexKey();
|
||||
return "{$application}_{$key}_fdocument";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFerretEngine extends Phobject {
|
||||
|
||||
abstract public function newNgramsObject();
|
||||
abstract public function newDocumentObject();
|
||||
abstract public function newFieldObject();
|
||||
|
||||
}
|
39
src/applications/search/ferret/PhabricatorFerretField.php
Normal file
39
src/applications/search/ferret/PhabricatorFerretField.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFerretField
|
||||
extends PhabricatorSearchDAO {
|
||||
|
||||
protected $documentID;
|
||||
protected $fieldKey;
|
||||
protected $rawCorpus;
|
||||
protected $termCorpus;
|
||||
protected $normalCorpus;
|
||||
|
||||
abstract public function getIndexKey();
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'documentID' => 'uint32',
|
||||
'fieldKey' => 'text4',
|
||||
'rawCorpus' => 'sort',
|
||||
'termCorpus' => 'sort',
|
||||
'normalCorpus' => 'sort',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_documentfield' => array(
|
||||
'columns' => array('documentID', 'fieldKey'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getTableName() {
|
||||
$application = $this->getApplicationName();
|
||||
$key = $this->getIndexKey();
|
||||
return "{$application}_{$key}_ffield";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
interface PhabricatorFerretInterface {
|
||||
|
||||
public function newFerretEngine();
|
||||
|
||||
}
|
35
src/applications/search/ferret/PhabricatorFerretNgrams.php
Normal file
35
src/applications/search/ferret/PhabricatorFerretNgrams.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorFerretNgrams
|
||||
extends PhabricatorSearchDAO {
|
||||
|
||||
protected $documentID;
|
||||
protected $ngram;
|
||||
|
||||
abstract public function getIndexKey();
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'documentID' => 'uint32',
|
||||
'ngram' => 'char3',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_ngram' => array(
|
||||
'columns' => array('ngram', 'documentID'),
|
||||
),
|
||||
'key_object' => array(
|
||||
'columns' => array('documentID'),
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function getTableName() {
|
||||
$application = $this->getApplicationName();
|
||||
$key = $this->getIndexKey();
|
||||
return "{$application}_{$key}_fngrams";
|
||||
}
|
||||
|
||||
}
|
|
@ -26,9 +26,16 @@ abstract class PhabricatorFulltextEngine
|
|||
$object = $this->getObject();
|
||||
|
||||
$extensions = PhabricatorFulltextEngineExtension::getAllExtensions();
|
||||
|
||||
$enrich_extensions = array();
|
||||
$index_extensions = array();
|
||||
foreach ($extensions as $key => $extension) {
|
||||
if (!$extension->shouldIndexFulltextObject($object)) {
|
||||
unset($extensions[$key]);
|
||||
if ($extension->shouldEnrichFulltextObject($object)) {
|
||||
$enrich_extensions[] = $extension;
|
||||
}
|
||||
|
||||
if ($extension->shouldIndexFulltextObject($object)) {
|
||||
$index_extensions[] = $extension;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +43,11 @@ abstract class PhabricatorFulltextEngine
|
|||
|
||||
$this->buildAbstractDocument($document, $object);
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
foreach ($enrich_extensions as $extension) {
|
||||
$extension->enrichFulltextObject($object, $document);
|
||||
}
|
||||
|
||||
foreach ($index_extensions as $extension) {
|
||||
$extension->indexFulltextObject($object, $document);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,25 @@ abstract class PhabricatorFulltextEngineExtension extends Phobject {
|
|||
|
||||
abstract public function getExtensionName();
|
||||
|
||||
abstract public function shouldIndexFulltextObject($object);
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document);
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
return;
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
return;
|
||||
}
|
||||
|
||||
final public static function getAllExtensions() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
|
|
95
src/applications/search/ngrams/PhabricatorNgramEngine.php
Normal file
95
src/applications/search/ngrams/PhabricatorNgramEngine.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorNgramEngine extends Phobject {
|
||||
|
||||
public function tokenizeString($value) {
|
||||
$value = trim($value, ' ');
|
||||
$value = preg_split('/ +/', $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function getNgramsFromString($value, $mode) {
|
||||
$tokens = $this->tokenizeString($value);
|
||||
|
||||
$ngrams = array();
|
||||
foreach ($tokens as $token) {
|
||||
$token = phutil_utf8_strtolower($token);
|
||||
|
||||
switch ($mode) {
|
||||
case 'query':
|
||||
break;
|
||||
case 'index':
|
||||
$token = ' '.$token.' ';
|
||||
break;
|
||||
case 'prefix':
|
||||
$token = ' '.$token;
|
||||
break;
|
||||
}
|
||||
|
||||
$token_v = phutil_utf8v($token);
|
||||
$len = (count($token_v) - 2);
|
||||
for ($ii = 0; $ii < $len; $ii++) {
|
||||
$ngram = array_slice($token_v, $ii, 3);
|
||||
$ngram = implode('', $ngram);
|
||||
$ngrams[$ngram] = $ngram;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($ngrams);
|
||||
|
||||
return array_keys($ngrams);
|
||||
}
|
||||
|
||||
public function newTermsCorpus($raw_corpus) {
|
||||
$term_corpus = strtr(
|
||||
$raw_corpus,
|
||||
array(
|
||||
'!' => ' ',
|
||||
'"' => ' ',
|
||||
'#' => ' ',
|
||||
'$' => ' ',
|
||||
'%' => ' ',
|
||||
'&' => ' ',
|
||||
'(' => ' ',
|
||||
')' => ' ',
|
||||
'*' => ' ',
|
||||
'+' => ' ',
|
||||
',' => ' ',
|
||||
'-' => ' ',
|
||||
'/' => ' ',
|
||||
':' => ' ',
|
||||
';' => ' ',
|
||||
'<' => ' ',
|
||||
'=' => ' ',
|
||||
'>' => ' ',
|
||||
'?' => ' ',
|
||||
'@' => ' ',
|
||||
'[' => ' ',
|
||||
'\\' => ' ',
|
||||
']' => ' ',
|
||||
'^' => ' ',
|
||||
'`' => ' ',
|
||||
'{' => ' ',
|
||||
'|' => ' ',
|
||||
'}' => ' ',
|
||||
'~' => ' ',
|
||||
'.' => ' ',
|
||||
'_' => ' ',
|
||||
"\n" => ' ',
|
||||
"\r" => ' ',
|
||||
"\t" => ' ',
|
||||
));
|
||||
|
||||
// NOTE: Single quotes divide terms only if they're at a word boundary.
|
||||
// In contractions, like "whom'st've", the entire word is a single term.
|
||||
$term_corpus = preg_replace('/(^| )[\']+/', ' ', $term_corpus);
|
||||
$term_corpus = preg_replace('/[\']+( |$)/', ' ', $term_corpus);
|
||||
|
||||
$term_corpus = preg_replace('/\s+/u', ' ', $term_corpus);
|
||||
$term_corpus = trim($term_corpus, ' ');
|
||||
|
||||
return $term_corpus;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorNgramEngineTestCase
|
||||
extends PhabricatorTestCase {
|
||||
|
||||
public function testTermsCorpus() {
|
||||
$map = array(
|
||||
'Hear ye, hear ye!' => 'Hear ye hear ye',
|
||||
"Thou whom'st've art worthy." => "Thou whom'st've art worthy",
|
||||
'Guaranteed to contain "food".' => 'Guaranteed to contain food',
|
||||
'http://example.org/path/to/file.jpg' =>
|
||||
'http example org path to file jpg',
|
||||
);
|
||||
|
||||
$engine = new PhabricatorNgramEngine();
|
||||
foreach ($map as $input => $expect) {
|
||||
$actual = $engine->newTermsCorpus($input);
|
||||
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
$actual,
|
||||
pht('Terms corpus for: %s', $input));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -205,7 +205,10 @@ final class PhabricatorSettingsMainController
|
|||
if ($panel->getPanelGroupKey() != $group_key) {
|
||||
$group_key = $panel->getPanelGroupKey();
|
||||
$group = $panel->getPanelGroup();
|
||||
$nav->addLabel($group->getPanelGroupName());
|
||||
$panel_name = $group->getPanelGroupName();
|
||||
if ($panel_name) {
|
||||
$nav->addLabel($panel_name);
|
||||
}
|
||||
}
|
||||
|
||||
$nav->addFilter($panel->getPanelKey(), $panel->getPanelName());
|
||||
|
|
|
@ -6,7 +6,7 @@ final class PhabricatorSettingsAccountPanelGroup
|
|||
const PANELGROUPKEY = 'account';
|
||||
|
||||
public function getPanelGroupName() {
|
||||
return pht('Account');
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getPanelGroupOrder() {
|
||||
|
|
|
@ -9,11 +9,11 @@ final class PhabricatorSubscriptionsFulltextEngineExtension
|
|||
return pht('Subscribers');
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
return ($object instanceof PhabricatorSubscribableInterface);
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ final class PhabricatorTransactionsFulltextEngineExtension
|
|||
return pht('Comments');
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
return ($object instanceof PhabricatorApplicationTransactionInterface);
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
|
|
|
@ -83,9 +83,11 @@ final class PHUIButtonExample extends PhabricatorUIExample {
|
|||
),
|
||||
array(
|
||||
'icon' => 'fa-upload',
|
||||
'disabled' => true,
|
||||
),
|
||||
array(
|
||||
'icon' => 'fa-street-view',
|
||||
'selected' => true,
|
||||
),
|
||||
array(
|
||||
'text' => pht('Copy "Quack" to Clipboard'),
|
||||
|
@ -99,6 +101,8 @@ final class PHUIButtonExample extends PhabricatorUIExample {
|
|||
->setColor(PHUIButtonView::GREY)
|
||||
->setIcon(idx($spec, 'icon'))
|
||||
->setText(idx($spec, 'text'))
|
||||
->setSelected(idx($spec, 'selected'))
|
||||
->setDisabled(idx($spec, 'disabled'))
|
||||
->addClass(PHUI::MARGIN_SMALL_RIGHT)
|
||||
->setDropdown(idx($spec, 'dropdown'));
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ final class PhabricatorCustomFieldFulltextEngineExtension
|
|||
return pht('Custom Fields');
|
||||
}
|
||||
|
||||
public function shouldIndexFulltextObject($object) {
|
||||
public function shouldEnrichFulltextObject($object) {
|
||||
return ($object instanceof PhabricatorCustomFieldInterface);
|
||||
}
|
||||
|
||||
public function indexFulltextObject(
|
||||
public function enrichFulltextObject(
|
||||
$object,
|
||||
PhabricatorSearchAbstractDocument $document) {
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
private $spacePHIDs;
|
||||
private $spaceIsArchived;
|
||||
private $ngrams = array();
|
||||
private $ferretEngine;
|
||||
private $ferretTokens;
|
||||
private $ferretTables;
|
||||
|
||||
protected function getPageCursors(array $page) {
|
||||
return array(
|
||||
|
@ -270,6 +273,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
$joins[] = $this->buildEdgeLogicJoinClause($conn);
|
||||
$joins[] = $this->buildApplicationSearchJoinClause($conn);
|
||||
$joins[] = $this->buildNgramsJoinClause($conn);
|
||||
$joins[] = $this->buildFerretJoinClause($conn);
|
||||
return $joins;
|
||||
}
|
||||
|
||||
|
@ -292,6 +296,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
$where[] = $this->buildEdgeLogicWhereClause($conn);
|
||||
$where[] = $this->buildSpacesWhereClause($conn);
|
||||
$where[] = $this->buildNgramsWhereClause($conn);
|
||||
$where[] = $this->buildFerretWhereClause($conn);
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
@ -346,6 +351,10 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
return true;
|
||||
}
|
||||
|
||||
if ($this->shouldGroupFerretResultRows()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1373,6 +1382,332 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
|
|||
}
|
||||
|
||||
|
||||
/* -( Ferret )------------------------------------------------------------- */
|
||||
|
||||
|
||||
public function withFerretConstraint(
|
||||
PhabricatorFerretEngine $engine,
|
||||
array $fulltext_tokens) {
|
||||
|
||||
if ($this->ferretEngine) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Query may not have multiple fulltext constraints.'));
|
||||
}
|
||||
|
||||
if (!$fulltext_tokens) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->ferretEngine = $engine;
|
||||
$this->ferretTokens = $fulltext_tokens;
|
||||
|
||||
|
||||
$function_map = array(
|
||||
'all' => PhabricatorSearchDocumentFieldType::FIELD_ALL,
|
||||
'title' => PhabricatorSearchDocumentFieldType::FIELD_TITLE,
|
||||
'body' => PhabricatorSearchDocumentFieldType::FIELD_BODY,
|
||||
'core' => PhabricatorSearchDocumentFieldType::FIELD_CORE,
|
||||
);
|
||||
|
||||
$current_function = 'all';
|
||||
$table_map = array();
|
||||
$idx = 1;
|
||||
foreach ($this->ferretTokens as $fulltext_token) {
|
||||
$raw_token = $fulltext_token->getToken();
|
||||
$function = $raw_token->getFunction();
|
||||
|
||||
if ($function === null) {
|
||||
$function = $current_function;
|
||||
}
|
||||
|
||||
if (!isset($function_map[$function])) {
|
||||
throw new PhutilSearchQueryCompilerSyntaxException(
|
||||
pht(
|
||||
'Unknown search function "%s".',
|
||||
$function));
|
||||
}
|
||||
|
||||
if (!isset($table_map[$function])) {
|
||||
$alias = 'ftfield'.$idx++;
|
||||
$table_map[$function] = array(
|
||||
'alias' => $alias,
|
||||
'key' => $function_map[$function],
|
||||
);
|
||||
}
|
||||
|
||||
$current_function = $function;
|
||||
}
|
||||
|
||||
$this->ferretTables = $table_map;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function buildFerretJoinClause(AphrontDatabaseConnection $conn) {
|
||||
if (!$this->ferretEngine) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
|
||||
$op_not = PhutilSearchQueryCompiler::OPERATOR_NOT;
|
||||
|
||||
$engine = $this->ferretEngine;
|
||||
$ngram_engine = new PhabricatorNgramEngine();
|
||||
$stemmer = new PhutilSearchStemmer();
|
||||
|
||||
$ngram_table = $engine->newNgramsObject();
|
||||
$ngram_table_name = $ngram_table->getTableName();
|
||||
|
||||
$flat = array();
|
||||
foreach ($this->ferretTokens as $fulltext_token) {
|
||||
$raw_token = $fulltext_token->getToken();
|
||||
|
||||
// If this is a negated term like "-pomegranate", don't join the ngram
|
||||
// table since we aren't looking for documents with this term. (We could
|
||||
// LEFT JOIN the table and require a NULL row, but this is probably more
|
||||
// trouble than it's worth.)
|
||||
if ($raw_token->getOperator() == $op_not) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $raw_token->getValue();
|
||||
|
||||
$length = count(phutil_utf8v($value));
|
||||
|
||||
if ($raw_token->getOperator() == $op_sub) {
|
||||
$is_substring = true;
|
||||
} else {
|
||||
$is_substring = false;
|
||||
}
|
||||
|
||||
// If the user specified a substring query for a substring which is
|
||||
// shorter than the ngram length, we can't use the ngram index, so
|
||||
// don't do a join. We'll fall back to just doing LIKE on the full
|
||||
// corpus.
|
||||
if ($is_substring) {
|
||||
if ($length < 3) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($raw_token->isQuoted()) {
|
||||
$is_stemmed = false;
|
||||
} else {
|
||||
$is_stemmed = true;
|
||||
}
|
||||
|
||||
if ($is_substring) {
|
||||
$ngrams = $ngram_engine->getNgramsFromString($value, 'query');
|
||||
} else {
|
||||
$ngrams = $ngram_engine->getNgramsFromString($value, 'index');
|
||||
|
||||
// If this is a stemmed term, only look for ngrams present in both the
|
||||
// unstemmed and stemmed variations.
|
||||
if ($is_stemmed) {
|
||||
$stem_value = $stemmer->stemToken($value);
|
||||
$stem_ngrams = $ngram_engine->getNgramsFromString(
|
||||
$stem_value,
|
||||
'index');
|
||||
|
||||
$ngrams = array_intersect($ngrams, $stem_ngrams);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ngrams as $ngram) {
|
||||
$flat[] = array(
|
||||
'table' => $ngram_table_name,
|
||||
'ngram' => $ngram,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL only allows us to join a maximum of 61 tables per query. Each
|
||||
// ngram is going to cost us a join toward that limit, so if the user
|
||||
// specified a very long query string, just pick 16 of the ngrams
|
||||
// at random.
|
||||
if (count($flat) > 16) {
|
||||
shuffle($flat);
|
||||
$flat = array_slice($flat, 0, 16);
|
||||
}
|
||||
|
||||
$alias = $this->getPrimaryTableAlias();
|
||||
if ($alias) {
|
||||
$phid_column = qsprintf($conn, '%T.%T', $alias, 'phid');
|
||||
} else {
|
||||
$phid_column = qsprintf($conn, '%T', 'phid');
|
||||
}
|
||||
|
||||
$document_table = $engine->newDocumentObject();
|
||||
$field_table = $engine->newFieldObject();
|
||||
|
||||
$joins = array();
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T ftdoc ON ftdoc.objectPHID = %Q',
|
||||
$document_table->getTableName(),
|
||||
$phid_column);
|
||||
|
||||
$idx = 1;
|
||||
foreach ($flat as $spec) {
|
||||
$table = $spec['table'];
|
||||
$ngram = $spec['ngram'];
|
||||
|
||||
$alias = 'ft'.$idx++;
|
||||
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T %T ON %T.documentID = ftdoc.id AND %T.ngram = %s',
|
||||
$table,
|
||||
$alias,
|
||||
$alias,
|
||||
$alias,
|
||||
$ngram);
|
||||
}
|
||||
|
||||
foreach ($this->ferretTables as $table) {
|
||||
$alias = $table['alias'];
|
||||
|
||||
$joins[] = qsprintf(
|
||||
$conn,
|
||||
'JOIN %T %T ON ftdoc.id = %T.documentID
|
||||
AND %T.fieldKey = %s',
|
||||
$field_table->getTableName(),
|
||||
$alias,
|
||||
$alias,
|
||||
$alias,
|
||||
$table['key']);
|
||||
}
|
||||
|
||||
return $joins;
|
||||
}
|
||||
|
||||
protected function buildFerretWhereClause(AphrontDatabaseConnection $conn) {
|
||||
if (!$this->ferretEngine) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$ngram_engine = new PhabricatorNgramEngine();
|
||||
$stemmer = new PhutilSearchStemmer();
|
||||
$table_map = $this->ferretTables;
|
||||
|
||||
$op_sub = PhutilSearchQueryCompiler::OPERATOR_SUBSTRING;
|
||||
$op_not = PhutilSearchQueryCompiler::OPERATOR_NOT;
|
||||
|
||||
$where = array();
|
||||
$current_function = 'all';
|
||||
foreach ($this->ferretTokens as $fulltext_token) {
|
||||
$raw_token = $fulltext_token->getToken();
|
||||
$value = $raw_token->getValue();
|
||||
|
||||
$function = $raw_token->getFunction();
|
||||
if ($function === null) {
|
||||
$function = $current_function;
|
||||
}
|
||||
$current_function = $function;
|
||||
|
||||
$table_alias = $table_map[$function]['alias'];
|
||||
|
||||
$is_not = ($raw_token->getOperator() == $op_not);
|
||||
|
||||
if ($raw_token->getOperator() == $op_sub) {
|
||||
$is_substring = true;
|
||||
} else {
|
||||
$is_substring = false;
|
||||
}
|
||||
|
||||
// If we're doing substring search, we just match against the raw corpus
|
||||
// and we're done.
|
||||
if ($is_substring) {
|
||||
if ($is_not) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.rawCorpus NOT LIKE %~)',
|
||||
$table_alias,
|
||||
$value);
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.rawCorpus LIKE %~)',
|
||||
$table_alias,
|
||||
$value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, we need to match against the term corpus and the normal
|
||||
// corpus, so that searching for "raw" does not find "strawberry".
|
||||
if ($raw_token->isQuoted()) {
|
||||
$is_quoted = true;
|
||||
$is_stemmed = false;
|
||||
} else {
|
||||
$is_quoted = false;
|
||||
$is_stemmed = true;
|
||||
}
|
||||
|
||||
// Never stem negated queries, since this can exclude results users
|
||||
// did not mean to exclude and generally confuse things.
|
||||
if ($is_not) {
|
||||
$is_stemmed = false;
|
||||
}
|
||||
|
||||
$term_constraints = array();
|
||||
|
||||
$term_value = ' '.$ngram_engine->newTermsCorpus($value).' ';
|
||||
if ($is_not) {
|
||||
$term_constraints[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.termCorpus NOT LIKE %~)',
|
||||
$table_alias,
|
||||
$term_value);
|
||||
} else {
|
||||
$term_constraints[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.termCorpus LIKE %~)',
|
||||
$table_alias,
|
||||
$term_value);
|
||||
}
|
||||
|
||||
if ($is_stemmed) {
|
||||
$stem_value = $stemmer->stemToken($value);
|
||||
$stem_value = $ngram_engine->newTermsCorpus($stem_value);
|
||||
$stem_value = ' '.$stem_value.' ';
|
||||
|
||||
$term_constraints[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.normalCorpus LIKE %~)',
|
||||
$table_alias,
|
||||
$stem_value);
|
||||
}
|
||||
|
||||
if ($is_not) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'(%Q)',
|
||||
implode(' AND ', $term_constraints));
|
||||
} else if ($is_quoted) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'(%T.rawCorpus LIKE %~ AND (%Q))',
|
||||
$table_alias,
|
||||
$value,
|
||||
implode(' OR ', $term_constraints));
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'(%Q)',
|
||||
implode(' OR ', $term_constraints));
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function shouldGroupFerretResultRows() {
|
||||
return (bool)$this->ferretTokens;
|
||||
}
|
||||
|
||||
|
||||
/* -( Ngrams )------------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ final class PHUIButtonView extends AphrontTagView {
|
|||
private $href = null;
|
||||
private $title = null;
|
||||
private $disabled;
|
||||
private $selected;
|
||||
private $name;
|
||||
private $tooltip;
|
||||
private $noCSS;
|
||||
|
@ -74,6 +75,11 @@ final class PHUIButtonView extends AphrontTagView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setSelected($selected) {
|
||||
$this->selected = $selected;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setTag($tag) {
|
||||
$this->tag = $tag;
|
||||
return $this;
|
||||
|
@ -189,6 +195,10 @@ final class PHUIButtonView extends AphrontTagView {
|
|||
$classes[] = 'disabled';
|
||||
}
|
||||
|
||||
if ($this->selected) {
|
||||
$classes[] = 'selected';
|
||||
}
|
||||
|
||||
switch ($this->getButtonType()) {
|
||||
case self::BUTTONTYPE_DEFAULT:
|
||||
$classes[] = 'phui-button-default';
|
||||
|
|
|
@ -150,13 +150,25 @@ final class PHUIFeedStoryView extends AphrontView {
|
|||
if ($this->getShowTimestamp()) {
|
||||
if ($this->epoch) {
|
||||
if ($user) {
|
||||
$foot = phabricator_datetime($this->epoch, $user);
|
||||
$marker = id(new PHUIIconView())
|
||||
->setIcon('fa-circle')
|
||||
->addClass('phabricator-notification-status');
|
||||
$date = phabricator_datetime($this->epoch, $user);
|
||||
$foot = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phabricator-notification-date',
|
||||
),
|
||||
$foot);
|
||||
$date);
|
||||
$foot = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'phabricator-notification-foot',
|
||||
),
|
||||
array(
|
||||
$marker,
|
||||
$date,
|
||||
));
|
||||
} else {
|
||||
$foot = null;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
.jx-notification {
|
||||
width: 240px;
|
||||
padding: 8px 16px;
|
||||
padding: 8px 12px;
|
||||
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
.aphront-table-wrap {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.aphront-table-view {
|
||||
|
@ -128,14 +129,8 @@ th.aphront-table-view-sortable-selected {
|
|||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.device-tablet .aphront-table-view td,
|
||||
.device-phone .aphront-table-view td {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.device-tablet .aphront-table-view th,
|
||||
.device-phone .aphront-table-view th {
|
||||
padding: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
.phabricator-notification-menu {
|
||||
background: {$page.content};
|
||||
font-size: {$smallestfontsize};
|
||||
font-size: {$smallerfontsize};
|
||||
line-height: 18px;
|
||||
word-wrap: break-word;
|
||||
overflow-y: auto;
|
||||
box-shadow: {$dropshadow};
|
||||
|
@ -12,9 +13,8 @@
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.phabricator-notification .phabricator-notification-date {
|
||||
margin-left: 8px;
|
||||
color: {$lightgreytext};
|
||||
.phabricator-notification {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.phabricator-notification-menu-loading {
|
||||
|
@ -44,11 +44,7 @@
|
|||
}
|
||||
|
||||
.phabricator-notification-list .phabricator-notification {
|
||||
padding: 10px 4px;
|
||||
}
|
||||
|
||||
.phabricator-notification {
|
||||
padding: 6px 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.phabricator-notification-menu .phabricator-notification {
|
||||
|
@ -56,7 +52,7 @@
|
|||
}
|
||||
|
||||
.device-desktop .phabricator-notification-menu .phabricator-notification:hover {
|
||||
background: {$hoverblue};
|
||||
background: {$lightgreybackground};
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-notification-menu
|
||||
|
@ -81,9 +77,34 @@
|
|||
color: {$lightgreytext};
|
||||
}
|
||||
|
||||
.phabricator-notification-foot {
|
||||
color: {$lightgreytext};
|
||||
font-size: {$smallestfontsize};
|
||||
line-height: 18px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phabricator-notification-unread .phabricator-notification-foot {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.phabricator-notification-foot .phabricator-notification-status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.phabricator-notification-unread .phabricator-notification-foot
|
||||
.phabricator-notification-status {
|
||||
font-size: 7px;
|
||||
color: {$lightgreytext};
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
top: 6px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.phabricator-notification-header {
|
||||
font-weight: bold;
|
||||
padding: 10px 8px;
|
||||
padding: 10px 12px;
|
||||
font-size: {$smallerfontsize};
|
||||
border-bottom: 1px solid {$thinblueborder};
|
||||
}
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
.diffusion-source {
|
||||
width: 100%;
|
||||
background: {$page.content};
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.device-phone .diffusion-source-wrap {
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.diffusion-source tr.phabricator-source-highlight th,
|
||||
|
@ -27,6 +33,11 @@
|
|||
word-break: break-all;
|
||||
}
|
||||
|
||||
.device .diffusion-source td {
|
||||
word-break: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.diffusion-browse-type-form {
|
||||
float: right;
|
||||
}
|
||||
|
|
|
@ -207,16 +207,6 @@
|
|||
border-color: {$thinblueborder};
|
||||
}
|
||||
|
||||
.device-phone.diffusion-history-view .phui-two-column-view
|
||||
.phui-two-column-footer .phui-header-view {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.device-phone.diffusion-history-view .phui-two-column-content {
|
||||
padding: 0;
|
||||
margin: 0 -4px;
|
||||
}
|
||||
|
||||
.device-phone.diffusion-history-view .phui-oi-attribute-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
@ -240,12 +230,10 @@
|
|||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.device-phone.diffusion-history-view .diffusion-history-list .button.has-icon
|
||||
.phui-button-text {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.device-phone.diffusion-history-view .diffusion-history-list .button.has-icon
|
||||
.phui-icon-view {
|
||||
display: none;
|
||||
.device-phone .phui-two-column-view .phui-two-column-content
|
||||
.phui-object-box.diffusion-mobile-view {
|
||||
margin: 0 -12px 20px;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-color: {$thinblueborder};
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.device-phone .application-search-results
|
||||
.device-phone.application-search-view .application-search-results
|
||||
.phui-profile-header.phui-header-shell {
|
||||
padding: 12px 0 12px 4px;
|
||||
}
|
||||
|
|
|
@ -105,6 +105,19 @@ button[disabled] {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
button.button-grey.selected,
|
||||
a.button.button-grey.selected,
|
||||
button.button-grey.selected:hover,
|
||||
a.button.button-grey.selected:hover {
|
||||
border-color: {$sh-orangetext};
|
||||
color: {$sh-orangetext};
|
||||
}
|
||||
|
||||
button.button-grey.selected .phui-icon-view,
|
||||
a.button-grey.selected .phui-icon-view {
|
||||
color: {$sh-orangetext};
|
||||
}
|
||||
|
||||
a.phuix-dropdown-open {
|
||||
color: {$greytext};
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-href {
|
||||
display: block;
|
||||
padding: 6px 8px 6px 20px;
|
||||
padding: 6px 8px 6px 12px;
|
||||
color: {$darkbluetext};
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
|
@ -72,13 +72,8 @@
|
|||
text-overflow: ellipsis
|
||||
}
|
||||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-has-icon
|
||||
.phui-list-item-href {
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-icon {
|
||||
margin-left: -4px;
|
||||
margin-left: -8px;
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
}
|
||||
|
@ -100,7 +95,6 @@
|
|||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-selected {
|
||||
background-color: rgba({$alphablack},.05);
|
||||
border-left: 4px solid {$sky};
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
font-weight: bold;
|
||||
|
@ -109,12 +103,7 @@
|
|||
.device-desktop .phui-basic-nav .phabricator-side-menu
|
||||
.phui-list-item-selected
|
||||
a.phui-list-item-href:hover {
|
||||
background-color: rgba({$alphablack},.05);
|
||||
}
|
||||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-selected
|
||||
.phui-list-item-href {
|
||||
margin-left: -4px;
|
||||
background-color: rgba({$alphablack},.05);
|
||||
}
|
||||
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-type-label {
|
||||
|
@ -124,6 +113,7 @@
|
|||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
border-style: solid;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.device-desktop .phui-basic-nav .phabricator-side-menu
|
||||
|
|
|
@ -281,9 +281,7 @@
|
|||
.phui-two-column-fixed.phui-two-column-view
|
||||
.phui-basic-nav .phabricator-side-menu .phui-list-item-selected {
|
||||
border-radius: 3px;
|
||||
background-color: #f5f9ff;
|
||||
border: 1px solid {$sky};
|
||||
padding-left: 3px;
|
||||
background-color: {$sky};
|
||||
}
|
||||
|
||||
.phui-two-column-fixed.phui-two-column-view .phui-basic-nav
|
||||
|
@ -291,6 +289,16 @@
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.phui-two-column-fixed.phui-two-column-view .phui-basic-nav
|
||||
.phabricator-side-menu .phui-list-item-selected a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.phui-two-column-fixed.phui-two-column-view .phui-basic-nav
|
||||
.phabricator-side-menu .phui-list-item-selected a .phui-icon-view {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.phui-two-column-fixed.phui-two-column-view .phui-header-action-links
|
||||
.phui-mobile-menu {
|
||||
display: block;
|
||||
|
|
|
@ -16,6 +16,7 @@ JX.install('PHUIXButtonView', {
|
|||
|
||||
_iconView: null,
|
||||
_color: null,
|
||||
_selected: null,
|
||||
_buttonType: null,
|
||||
|
||||
setIcon: function(icon) {
|
||||
|
@ -43,6 +44,13 @@ JX.install('PHUIXButtonView', {
|
|||
return this;
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
var node = this.getNode();
|
||||
this._selected = selected;
|
||||
JX.DOM.alterClass(node, 'selected', this._selected);
|
||||
return this;
|
||||
},
|
||||
|
||||
setButtonType: function(button_type) {
|
||||
var self = JX.PHUIXButtonView;
|
||||
|
||||
|
|
Loading…
Reference in a new issue