From 498cb5c096379e4cecd62792f24afb767399e0e6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jul 2016 05:17:05 -0700 Subject: [PATCH 01/34] Fix an XSS issue where Diffusion files exceeding the highlighting byte limit were not properly escaped Fixes T11257. Auditors: chad --- .../controller/DiffusionBrowseController.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 30c2b8265d..2d0ea7770b 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -682,17 +682,21 @@ final class DiffusionBrowseController extends DiffusionController { $blame_commits, $show_blame); } else { - if ($can_highlight) { - require_celerity_resource('syntax-highlighting-css'); + require_celerity_resource('syntax-highlighting-css'); + if (!$can_highlight) { $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $file_corpus); - $lines = phutil_split_lines($highlighted); } else { - $lines = phutil_split_lines($file_corpus); + // Highlight as plain text to escape the content properly. + $highlighted = PhabricatorSyntaxHighlighter::highlightWithLanguage( + 'txt', + $file_corpus); } + $lines = phutil_split_lines($highlighted); + $rows = $this->buildDisplayRows( $lines, $blame_list, From d7b4c50941fada3b5550ed8a795be2c479f4973b Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 2 Jul 2016 05:22:55 -0700 Subject: [PATCH 02/34] Fix a flipped higlight vs no-highlight condition Ref T11257. Auditors: chad --- .../diffusion/controller/DiffusionBrowseController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php index 2d0ea7770b..3ae076a5d0 100644 --- a/src/applications/diffusion/controller/DiffusionBrowseController.php +++ b/src/applications/diffusion/controller/DiffusionBrowseController.php @@ -684,7 +684,7 @@ final class DiffusionBrowseController extends DiffusionController { } else { require_celerity_resource('syntax-highlighting-css'); - if (!$can_highlight) { + if ($can_highlight) { $highlighted = PhabricatorSyntaxHighlighter::highlightWithFilename( $path, $file_corpus); From fa6d3e2de37103a2c83520bba8ba5eb3059f3c4c Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 3 Jul 2016 14:43:34 -0700 Subject: [PATCH 03/34] Implement a "pro" EditEngine for dashboard panels Summary: Ref T10855. This can't replace the old edit flow yet, but get the basics in place. (This is actually much closer to just being able to swap than I anticipated since CustomFields sort of just work, but the exiting flow has some "clone existing panel" / "place directly on dashboard" stuff that this doesn't yet.) Test Plan: Created and edited a panel by manually using the "editpro" flow. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10855 Differential Revision: https://secure.phabricator.com/D16226 --- src/__phutil_library_map__.php | 7 +- .../PhabricatorDashboardApplication.php | 2 + ...atorDashboardPanelEditConduitAPIMethod.php | 20 ++++ ...habricatorDashboardPanelEditController.php | 19 ---- ...ricatorDashboardPanelEditproController.php | 105 ++++++++++++++++++ ...abricatorDashboardPanelCoreCustomField.php | 4 + .../PhabricatorDashboardPanelEditEngine.php | 96 ++++++++++++++++ .../storage/PhabricatorDashboardPanel.php | 5 +- 8 files changed, 237 insertions(+), 21 deletions(-) create mode 100644 src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php create mode 100644 src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php create mode 100644 src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 051ab9c41c..e8c4ab5e87 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2295,7 +2295,10 @@ phutil_register_library_map(array( 'PhabricatorDashboardPanelArchiveController' => 'applications/dashboard/controller/PhabricatorDashboardPanelArchiveController.php', 'PhabricatorDashboardPanelCoreCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php', 'PhabricatorDashboardPanelCustomField' => 'applications/dashboard/customfield/PhabricatorDashboardPanelCustomField.php', + 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php', 'PhabricatorDashboardPanelEditController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditController.php', + 'PhabricatorDashboardPanelEditEngine' => 'applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php', + 'PhabricatorDashboardPanelEditproController' => 'applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php', 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'applications/dashboard/edge/PhabricatorDashboardPanelHasDashboardEdgeType.php', 'PhabricatorDashboardPanelListController' => 'applications/dashboard/controller/PhabricatorDashboardPanelListController.php', 'PhabricatorDashboardPanelPHIDType' => 'applications/dashboard/phid/PhabricatorDashboardPanelPHIDType.php', @@ -6942,7 +6945,6 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorCustomFieldInterface', 'PhabricatorFlaggableInterface', - 'PhabricatorProjectInterface', 'PhabricatorDestructibleInterface', ), 'PhabricatorDashboardPanelArchiveController' => 'PhabricatorDashboardController', @@ -6951,7 +6953,10 @@ phutil_register_library_map(array( 'PhabricatorStandardCustomFieldInterface', ), 'PhabricatorDashboardPanelCustomField' => 'PhabricatorCustomField', + 'PhabricatorDashboardPanelEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhabricatorDashboardPanelEditController' => 'PhabricatorDashboardController', + 'PhabricatorDashboardPanelEditEngine' => 'PhabricatorEditEngine', + 'PhabricatorDashboardPanelEditproController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelHasDashboardEdgeType' => 'PhabricatorEdgeType', 'PhabricatorDashboardPanelListController' => 'PhabricatorDashboardController', 'PhabricatorDashboardPanelPHIDType' => 'PhabricatorPHIDType', diff --git a/src/applications/dashboard/application/PhabricatorDashboardApplication.php b/src/applications/dashboard/application/PhabricatorDashboardApplication.php index 3e67f76f07..a79cf133a3 100644 --- a/src/applications/dashboard/application/PhabricatorDashboardApplication.php +++ b/src/applications/dashboard/application/PhabricatorDashboardApplication.php @@ -40,6 +40,8 @@ final class PhabricatorDashboardApplication extends PhabricatorApplication { '(?:query/(?P[^/]+)/)?' => 'PhabricatorDashboardPanelListController', 'create/' => 'PhabricatorDashboardPanelEditController', + $this->getEditRoutePattern('editpro/') + => 'PhabricatorDashboardPanelEditproController', 'edit/(?:(?P\d+)/)?' => 'PhabricatorDashboardPanelEditController', 'render/(?P\d+)/' => 'PhabricatorDashboardPanelRenderController', 'archive/(?P\d+)/' diff --git a/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php b/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php new file mode 100644 index 0000000000..5fa80da5a6 --- /dev/null +++ b/src/applications/dashboard/conduit/PhabricatorDashboardPanelEditConduitAPIMethod.php @@ -0,0 +1,20 @@ +getPHID(), - PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); - $v_projects = array_reverse($v_projects); if ($dashboard) { $can_edit = PhabricatorPolicyFilter::hasCapability( @@ -84,7 +80,6 @@ final class PhabricatorDashboardPanelEditController if (empty($types[$type])) { return $this->processPanelTypeRequest($request); } - $v_projects = array(); $panel->setPanelType($type); } @@ -135,7 +130,6 @@ final class PhabricatorDashboardPanelEditController $v_name = $request->getStr('name'); $v_view_policy = $request->getStr('viewPolicy'); $v_edit_policy = $request->getStr('editPolicy'); - $v_projects = $request->getArr('projects'); $type_name = PhabricatorDashboardPanelTransaction::TYPE_NAME; $type_view_policy = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -155,12 +149,6 @@ final class PhabricatorDashboardPanelEditController ->setTransactionType($type_edit_policy) ->setNewValue($v_edit_policy); - $proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; - $xactions[] = id(new PhabricatorDashboardPanelTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) - ->setMetadataValue('edge:type', $proj_edge_type) - ->setNewValue(array('=' => array_fuse($v_projects))); - $field_xactions = $field_list->buildFieldTransactionsFromRequest( new PhabricatorDashboardPanelTransaction(), $request); @@ -238,13 +226,6 @@ final class PhabricatorDashboardPanelEditController ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicies($policies)); - $form->appendControl( - id(new AphrontFormTokenizerControl()) - ->setLabel(pht('Tags')) - ->setName('projects') - ->setValue($v_projects) - ->setDatasource(new PhabricatorProjectDatasource())); - $field_list->appendFieldsToForm($form); $crumbs = $this->buildApplicationCrumbs(); diff --git a/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php new file mode 100644 index 0000000000..b40dfd85b3 --- /dev/null +++ b/src/applications/dashboard/controller/PhabricatorDashboardPanelEditproController.php @@ -0,0 +1,105 @@ +setController($this); + + $id = $request->getURIData('id'); + if (!$id) { + $list_uri = $this->getApplicationURI('panel/'); + + $panel_type = $request->getStr('panelType'); + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + if (empty($panel_types[$panel_type])) { + return $this->buildPanelTypeResponse($list_uri); + } + + $engine + ->addContextParameter('panelType', $panel_type) + ->setPanelType($panel_type); + } + + return $engine->buildResponse(); + } + + private function buildPanelTypeResponse($cancel_uri) { + $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); + + $viewer = $this->getViewer(); + $request = $this->getRequest(); + + $e_type = null; + $errors = array(); + if ($request->isFormPost()) { + $e_type = pht('Required'); + $errors[] = pht( + 'To create a new dashboard panel, you must select a panel type.'); + } + + $type_control = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Panel Type')) + ->setName('panelType') + ->setError($e_type); + + foreach ($panel_types as $key => $type) { + $type_control->addButton( + $key, + $type->getPanelTypeName(), + $type->getPanelTypeDescription()); + } + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendRemarkupInstructions( + pht('Choose the type of dashboard panel to create:')) + ->appendChild($type_control); + + if ($request->isAjax()) { + return $this->newDialog() + ->setTitle(pht('Add New Panel')) + ->setWidth(AphrontDialogView::WIDTH_FORM) + ->setErrors($errors) + ->appendForm($form) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Continue')); + } + + $form->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Continue')) + ->addCancelButton($cancel_uri)); + + $title = pht('Create Dashboard Panel'); + $header_icon = 'fa-plus-square'; + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + pht('Panels'), + $this->getApplicationURI('panel/')); + $crumbs->addTextCrumb(pht('New Panel')); + $crumbs->setBorder(true); + + $box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Panel')) + ->setFormErrors($errors) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setForm($form); + + $header = id(new PHUIHeaderView()) + ->setHeader($title) + ->setHeaderIcon($header_icon); + + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setFooter($box); + + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild($view); + } + +} diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php index cf045265b9..6657e9cc41 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php @@ -9,6 +9,10 @@ final class PhabricatorDashboardPanelCoreCustomField } public function createFields($object) { + if (!$object->getPanelType()) { + return array(); + } + $impl = $object->requireImplementation(); $specs = $impl->getFieldSpecifications(); return PhabricatorStandardCustomField::buildStandardFields($this, $specs); diff --git a/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php new file mode 100644 index 0000000000..48940dde60 --- /dev/null +++ b/src/applications/dashboard/editor/PhabricatorDashboardPanelEditEngine.php @@ -0,0 +1,96 @@ +panelType = $panel_type; + return $this; + } + + public function getPanelType() { + return $this->panelType; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Dashboard Panels'); + } + + public function getSummaryHeader() { + return pht('Edit Dashboard Panels'); + } + + public function getSummaryText() { + return pht('This engine is used to modify dashboard panels.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorSearchApplication'; + } + + protected function newEditableObject() { + $viewer = $this->getViewer(); + $panel = PhabricatorDashboardPanel::initializeNewPanel($viewer); + + if ($this->panelType) { + $panel->setPanelType($this->panelType); + } + + return $panel; + } + + protected function newObjectQuery() { + return new PhabricatorDashboardPanelQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Dashboard Panel'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Panel'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Panel: %s', $object->getName()); + } + + protected function getObjectEditShortText($object) { + return pht('Edit Panel'); + } + + protected function getObjectCreateShortText() { + return pht('Edit Panel'); + } + + protected function getObjectName() { + return pht('Dashboard Panel'); + } + + protected function getObjectViewURI($object) { + return $object->getURI(); + } + + protected function buildCustomEditFields($object) { + return array( + id(new PhabricatorTextEditField()) + ->setKey('name') + ->setLabel(pht('Name')) + ->setDescription(pht('Name of the panel.')) + ->setConduitDescription(pht('Rename the panel.')) + ->setConduitTypeDescription(pht('New panel name.')) + ->setTransactionType(PhabricatorDashboardPanelTransaction::TYPE_NAME) + ->setIsRequired(true) + ->setValue($object->getName()), + ); + } + +} diff --git a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php index 4e69fba636..5dae421674 100644 --- a/src/applications/dashboard/storage/PhabricatorDashboardPanel.php +++ b/src/applications/dashboard/storage/PhabricatorDashboardPanel.php @@ -10,7 +10,6 @@ final class PhabricatorDashboardPanel PhabricatorPolicyInterface, PhabricatorCustomFieldInterface, PhabricatorFlaggableInterface, - PhabricatorProjectInterface, PhabricatorDestructibleInterface { protected $name; @@ -72,6 +71,10 @@ final class PhabricatorDashboardPanel return 'W'.$this->getID(); } + public function getURI() { + return '/'.$this->getMonogram(); + } + public function getPanelTypes() { $panel_types = PhabricatorDashboardPanelType::getAllPanelTypes(); $panel_types = mpull($panel_types, 'getPanelTypeName', 'getPanelTypeKey'); From 01040e4573077484f6ade320d349d7b11e77152e Mon Sep 17 00:00:00 2001 From: epriestley Date: Sun, 3 Jul 2016 14:13:06 -0700 Subject: [PATCH 04/34] Correctly disinguish between "0 seconds behind master" and "not replicating" Summary: Fixes T11159. We get two different values here (`NULL` and `0`) with different meanings. Test Plan: - Ran `STOP SLAVE;`. - Saw this: {F1710181} - Ran `START SLAVE;`. - Back to normal. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11159 Differential Revision: https://secure.phabricator.com/D16225 --- .../cluster/PhabricatorDatabaseRef.php | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php index f8ca7a79a8..02777fb7a7 100644 --- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php +++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php @@ -12,6 +12,7 @@ final class PhabricatorDatabaseRef const REPLICATION_MASTER_REPLICA = 'master-replica'; const REPLICATION_REPLICA_NONE = 'replica-none'; const REPLICATION_SLOW = 'replica-slow'; + const REPLICATION_NOT_REPLICATING = 'not-replicating'; const KEY_REFS = 'cluster.db.refs'; const KEY_INDIVIDUAL = 'cluster.db.individual'; @@ -196,13 +197,18 @@ final class PhabricatorDatabaseRef self::REPLICATION_REPLICA_NONE => array( 'icon' => 'fa-download', 'color' => 'red', - 'label' => pht('Not Replicating'), + 'label' => pht('Not A Replica'), ), self::REPLICATION_SLOW => array( 'icon' => 'fa-hourglass', 'color' => 'red', 'label' => pht('Slow Replication'), ), + self::REPLICATION_NOT_REPLICATING => array( + 'icon' => 'fa-exclamation-triangle', + 'color' => 'red', + 'label' => pht('Not Replicating'), + ), ); } @@ -330,14 +336,19 @@ final class PhabricatorDatabaseRef } if ($is_replica) { - $latency = (int)idx($replica_status, 'Seconds_Behind_Master'); - $ref->setReplicaDelay($latency); - if ($latency > 30) { - $ref->setReplicaStatus(self::REPLICATION_SLOW); - $ref->setReplicaMessage( - pht( - 'This replica is lagging far behind the master. Data is at '. - 'risk!')); + $latency = idx($replica_status, 'Seconds_Behind_Master'); + if (!strlen($latency)) { + $ref->setReplicaStatus(self::REPLICATION_NOT_REPLICATING); + } else { + $latency = (int)$latency; + $ref->setReplicaDelay($latency); + if ($latency > 30) { + $ref->setReplicaStatus(self::REPLICATION_SLOW); + $ref->setReplicaMessage( + pht( + 'This replica is lagging far behind the master. Data is at '. + 'risk!')); + } } } } From d09094f4fbb2139698e00b346fd77c801a4cccf3 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Sun, 3 Jul 2016 18:32:31 -0700 Subject: [PATCH 05/34] More tokens Summary: Ref T11244. 8 more tokens. Probably need better math on the selector? Test Plan: Award Dat Boi. Reviewers: epriestley Reviewed By: epriestley Subscribers: putnam, Korvin Maniphest Tasks: T11244 Differential Revision: https://secure.phabricator.com/D16228 --- resources/celerity/map.php | 10 +-- resources/sprite/manifest/tokens.json | 40 ++++++++++++ resources/sprite/tokens_1x/emoji-1.png | Bin 0 -> 611 bytes resources/sprite/tokens_1x/emoji-2.png | Bin 0 -> 752 bytes resources/sprite/tokens_1x/emoji-3.png | Bin 0 -> 581 bytes resources/sprite/tokens_1x/emoji-4.png | Bin 0 -> 516 bytes resources/sprite/tokens_1x/emoji-5.png | Bin 0 -> 638 bytes resources/sprite/tokens_1x/emoji-6.png | Bin 0 -> 1522 bytes resources/sprite/tokens_1x/emoji-7.png | Bin 0 -> 1622 bytes resources/sprite/tokens_1x/emoji-8.png | Bin 0 -> 717 bytes resources/sprite/tokens_2x/emoji-1.png | Bin 0 -> 1400 bytes resources/sprite/tokens_2x/emoji-2.png | Bin 0 -> 1704 bytes resources/sprite/tokens_2x/emoji-3.png | Bin 0 -> 1195 bytes resources/sprite/tokens_2x/emoji-4.png | Bin 0 -> 1058 bytes resources/sprite/tokens_2x/emoji-5.png | Bin 0 -> 1334 bytes resources/sprite/tokens_2x/emoji-6.png | Bin 0 -> 1235 bytes resources/sprite/tokens_2x/emoji-7.png | Bin 0 -> 1353 bytes resources/sprite/tokens_2x/emoji-8.png | Bin 0 -> 1497 bytes .../PhabricatorTokenGiveController.php | 2 +- .../tokens/query/PhabricatorTokenQuery.php | 8 +++ webroot/rsrc/css/sprite-tokens.css | 58 ++++++++++++++---- webroot/rsrc/image/sprite-tokens-X2.png | Bin 13542 -> 24355 bytes webroot/rsrc/image/sprite-tokens.png | Bin 6058 -> 10736 bytes 23 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 resources/sprite/tokens_1x/emoji-1.png create mode 100644 resources/sprite/tokens_1x/emoji-2.png create mode 100644 resources/sprite/tokens_1x/emoji-3.png create mode 100644 resources/sprite/tokens_1x/emoji-4.png create mode 100644 resources/sprite/tokens_1x/emoji-5.png create mode 100644 resources/sprite/tokens_1x/emoji-6.png create mode 100644 resources/sprite/tokens_1x/emoji-7.png create mode 100644 resources/sprite/tokens_1x/emoji-8.png create mode 100644 resources/sprite/tokens_2x/emoji-1.png create mode 100644 resources/sprite/tokens_2x/emoji-2.png create mode 100644 resources/sprite/tokens_2x/emoji-3.png create mode 100644 resources/sprite/tokens_2x/emoji-4.png create mode 100644 resources/sprite/tokens_2x/emoji-5.png create mode 100644 resources/sprite/tokens_2x/emoji-6.png create mode 100644 resources/sprite/tokens_2x/emoji-7.png create mode 100644 resources/sprite/tokens_2x/emoji-8.png diff --git a/resources/celerity/map.php b/resources/celerity/map.php index be152c24b9..cd3ef696ae 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '55d9bb83', + 'core.pkg.css' => '2fbe65a2', 'core.pkg.js' => 'f2139810', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3e81ae60', @@ -163,7 +163,7 @@ return array( 'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373', 'rsrc/css/sprite-login.css' => '60e8560e', 'rsrc/css/sprite-menu.css' => '9dd65b92', - 'rsrc/css/sprite-tokens.css' => '72b952bd', + 'rsrc/css/sprite-tokens.css' => '9cdfd599', 'rsrc/css/syntax/syntax-default.css' => '9923583c', 'rsrc/externals/d3/d3.min.js' => 'a11a5ff2', 'rsrc/externals/font/aleo/aleo-bold.eot' => 'd3d3bed7', @@ -344,8 +344,8 @@ return array( 'rsrc/image/sprite-login.png' => '03d5af29', 'rsrc/image/sprite-menu-X2.png' => 'cfd8fca5', 'rsrc/image/sprite-menu.png' => 'd7a99faa', - 'rsrc/image/sprite-tokens-X2.png' => 'e991bb40', - 'rsrc/image/sprite-tokens.png' => 'fe69d6ab', + 'rsrc/image/sprite-tokens-X2.png' => '804a5232', + 'rsrc/image/sprite-tokens.png' => 'b41d03da', 'rsrc/image/texture/card-gradient.png' => '815f26e8', 'rsrc/image/texture/dark-menu-hover.png' => '5fa7ece8', 'rsrc/image/texture/dark-menu.png' => '7e22296e', @@ -888,7 +888,7 @@ return array( 'setup-issue-css' => 'db7e9c40', 'sprite-login-css' => '60e8560e', 'sprite-menu-css' => '9dd65b92', - 'sprite-tokens-css' => '72b952bd', + 'sprite-tokens-css' => '9cdfd599', 'syntax-default-css' => '9923583c', 'syntax-highlighting-css' => '769d3498', 'tokens-css' => '3d0f239e', diff --git a/resources/sprite/manifest/tokens.json b/resources/sprite/manifest/tokens.json index 6096050a30..c9768db11f 100644 --- a/resources/sprite/manifest/tokens.json +++ b/resources/sprite/manifest/tokens.json @@ -21,6 +21,46 @@ "rule": ".tokens-coin-4", "hash": "75832b7e42df9287b3c35c6afed12a93" }, + "tokens-emoji-1": { + "name": "tokens-emoji-1", + "rule": ".tokens-emoji-1", + "hash": "17f57bdeb4078f9c05f1f037ccb1c162" + }, + "tokens-emoji-2": { + "name": "tokens-emoji-2", + "rule": ".tokens-emoji-2", + "hash": "6877c6e0c63522d5819531aaf4aba787" + }, + "tokens-emoji-3": { + "name": "tokens-emoji-3", + "rule": ".tokens-emoji-3", + "hash": "cc67534b0119d4cc385a93ed5aff86e4" + }, + "tokens-emoji-4": { + "name": "tokens-emoji-4", + "rule": ".tokens-emoji-4", + "hash": "f2a6febd638670962dfb5fdd76b23cfb" + }, + "tokens-emoji-5": { + "name": "tokens-emoji-5", + "rule": ".tokens-emoji-5", + "hash": "22bc23d162449fde492e0fd3eccc7301" + }, + "tokens-emoji-6": { + "name": "tokens-emoji-6", + "rule": ".tokens-emoji-6", + "hash": "e3689840f410ff1bbf365f6b06043d3f" + }, + "tokens-emoji-7": { + "name": "tokens-emoji-7", + "rule": ".tokens-emoji-7", + "hash": "a689b9fe7c9f6f300d757b5350e2cc4b" + }, + "tokens-emoji-8": { + "name": "tokens-emoji-8", + "rule": ".tokens-emoji-8", + "hash": "26570ef132caea33307e1e7574d754e8" + }, "tokens-heart-1": { "name": "tokens-heart-1", "rule": ".tokens-heart-1", diff --git a/resources/sprite/tokens_1x/emoji-1.png b/resources/sprite/tokens_1x/emoji-1.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc2ec784aa9587c867c964134554294e09a0f8d GIT binary patch literal 611 zcmV-p0-XJcP) zLyM71gSi9Z1J5>2W;tC-Fu#MpZ7V>c@Os~)^ra| zRX>drXs43LBew%@;Yrk~0SZ^heq7H{@M@;O|^Blt3MHO}C{csq%`fsG+!Lts;_ zI6Q%Oa0?fjsy>2`v5fr~!pi9sW`2*oUc9^5^l9 zT{w&xjNwu$`Fvp8FoCI7Q`HaPjWBtAO!9OT4&Xd~#1Emgi}hhTESOx=RQ16qel^UV z!b8}OPceuQti@#v;IY7V;6N&Qx)pMlg8gccUBPUSOr(;(hm@DeVh zlG~}|o6++iu46QnoMWUNrbwXQF@UENEY->Bk2+ xc84f7e_qG%ukaD(Q^||}<4srjx*)y(@gIMXvq|ACpK$;H002ovPDHLkV1hAkCIJ8d literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_1x/emoji-2.png b/resources/sprite/tokens_1x/emoji-2.png new file mode 100644 index 0000000000000000000000000000000000000000..124b5a84d1c0c9a3e52b233ae1e9c9db79abcafa GIT binary patch literal 752 zcmVC2GNLcxnMTdWHdOGEawiZBq2PdiXrw-~0D@KF{wL zfcDHje1cw$;h+A8;@UIMA;196;GX#d2Tu;(j^kM0w6$_(&L69-kD&!Gm3F4GG1Ox} zjw}miUyIUVca#p3qs?^%`85%`=R@+^iw$^athPQCXwSTl6kgRB-W{dG>TF;2Sav9U z7vJNl^0vbZAY--l8}R|Qp$FUI=tT{VYR^=ZCByS%;J(r3x>oEwcyjO#+|;zSa#x=9 z0NzF;T1z`q=VRD{k1&nv7sWr3-=qtN=J{LDha^7CmE9O_2kn{P@dHHZFc=sPz8Q(m ztuMZG!{p}6_a47<=kV!ypazU1DD6y*ko#Nz(8beX)6ht)ZPiZ|;$o&M2d2=tX-6h? zt$BzoHllPGBns^b0@=jmO4;HGD8yS|b%l>C1&YJhk3RS?!rP9V=)nEhRD4}_a{2h` zqF-<6pC9|p?)P?|{qnbGYUH{L(c98c(;CkYlwwW(`Ds{No-Ge&x?AGaz3kDBV9azv&ln&S8$GN1_S55U_TsL#!%C65_#6ZZ;tDt3__M?>T~|ut-%Vy`5$MdnUV)n{pTE$D zioX*@=`cV)s+Q>UD4s1#W`^dB(dN2W&{N)actN7gOF(+Cw=9|Ig?(>)@j6zwG}JtX zv)H}()AlR^oWlOHWaiUFeuvy~fY0zU`2!iH!!o%GmXNy^FU}?SH;p#e9YV7_=*cJ@ i9*ff9UH@hAwe%O$?B)oxv9j3!0000cuk$6XKnt2M>~7`LJo!4Tg&C zN_PvhGahIQ6eLP~j_=2l=Y40MS1^HYqbh+7pa!f9&sG3jCL>@6*aEi0ddptz6B8tB zz*`k0EBVMZL3tQpg7&Hr0(2%5!PF~Vz`q}{?ntCkrL+w zUphWfXNof;aSn7L<5BsPg$Mm>%K8WMP2jl#HdK&YA%Ty34+;Gqfnx7l z$Z@a0^5O{qs+6FDWX1IKh5~B&+4yq%0+ttl;7^R`7YwUUI`jwXT0B1+V|uzKfpxEx z-e~$0CUe8ybtybs89niRAf!cjwjys!e*dZ2j{;0~nYLDN5Ct5a=vxVu%VgxN9IJ9b z^YaYW338rgaqbrY(TUGN6d-_?;a5EAF6ynsWipb$P8Jt0r2-2x2|90>DGkQ)>8SyJ z!7w+Sj7b27y*r}YsJ>P~^3Gp6`u7C^{lj@oPrs4Cw(04niEp3%E|^%4e77x!F5XPq z<(0H}>3${s8)YWeBcIhq7vTS&kt<~PI&8lI;xPTK TP>mKd00000NkvXXu0mjfd?X8U literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_1x/emoji-4.png b/resources/sprite/tokens_1x/emoji-4.png new file mode 100644 index 0000000000000000000000000000000000000000..92ff339ce2d56b936878246fc6b2ac33b8fb703d GIT binary patch literal 516 zcmV+f0{i`mP)`jI&OP6EzH`sHTsR@9Py8TGGSpg(YWh=D@Inq*C!M%VPiLnB0~ z7DU$4ivmhD`fLnDMGxTsfOZWLy@t+ zNjql$7%QN%tEjFE(oe4{UDMZq4+@;w&%*T$;mf#XyI^VOgk8rHVHppFTxdDlf=W}- za4j#c_Go7bft!Qpp9fx+T=F-z6f+--oXM9F5v;XXYlrcH-^NTfd3QOY(V`QCEEvz! zhktE$Mc*DPQqSj@a65`yYXvUscc91@0000z5z>)RG6kxUH%6Z3(yv-266^s+)Djm`~5)<#I@pgcdF*x^p)T`&+1}ta=Yz6GD6{ z2D-3G}@N2RQe+Y$&y?8~D%4&|CzObP0sAv{KOs)eBK7W>b`S`x#`yLU!3{?%4 zY6s!+3eH^W9q~Lb#>#~IWdKP+@qG_~W!EJLf|4NLbBRPE3&1I05a^Qu_B9@)qq7@b zFT>CKWEPijb6!zkNKp~zFB6N!CWH{9zyqLK0njv!6J6aLYU^NZ!D7 z1n8PhRZT5>n_6jXZbj3ywd^tgrC_o^I2x^?{bUya`FtMV_mLzCMNufCrNY?&q*uo? zzOhP{WmHw&3<5A}+jbbZ3%pnbJ>9Ijm|WNWJvB9T9QX``f!Jsyug+?ZImYmVb6z(t@3H~{E{)DK`7=!?hW|Me5# YFIbAXKBReNYr-7+<9`BoPCg6hB&4hp^n+ue;mZm6HMPlv9uJxCBhc_IBUnF1+1iZ;?AB zMVLg(0l_TH#+uZGVMrTwlt^R>88IclKk|b_`!ZB?3MZ$=_JA}0aNL>Q_kExD`906? z`P%i_S#zVK5~3goineB&?dlpEdLu`v-$mWkx#}`evE(T^l1nM!WB{cJk`usIFINEU zfD?j6T_6R5BG!5wc}kvb0n1BXEf>WU2E0Jw;DC3z=w|~aWY8|F?n7b>fd^HT zLK8AXD$kY;XGk)D^;!eU69fU%jF!+FDN;WZCUHWK;S5ILC{C~h&XPDh{2;0~S#Ys- z^PFK{>dAz-6~)J5SV>8VwuI73asfs#41?h$Mv|zif%=1@!Ua&#KP_y*4E((8@hKij zghLiNr&O$%5S8gr2wtDf_SmrKA5N5-GAzLPFhYxCUT-L_L2bWc2mjM}s9XaLdgSU+e{~_&n1kyqM7o(ahp|@C zuW%v{!oEEr-$PjHnOHVM2Am?v4oO-b?t*N$q)2|ZE4gPTB$ety)-fTVVKDp~e&CR}1ZM6BOGI`+8&+QMS zxjCCF`}%+BombhmZ}H7}t1cdk>*}cAZP66wy!SS>&#}Aa_E%-wcI+SMS~Yh1$0vf7 z)UCeTr}s~yt74ZfD4l)j?)7s$l}$|-x_a*)Sh2OjzU3Ha?A*D8Eq~eFU_8(6F>GHp zYt}yBeO)u%bF%Yt(vsfJV0_NRMtG*!vQ;X-)UcF=Y*wb9?PkWMg7;^!m{~+bv^?ni3XO z^9`r|HQ6_fdq-UTsHN=A*#tKAyOp|v)4GRfJM_`9Z}v1_+4WoKFKo4BnZHbbtKvU6 C+BCKR literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_1x/emoji-7.png b/resources/sprite/tokens_1x/emoji-7.png new file mode 100644 index 0000000000000000000000000000000000000000..cb4780d99bf3ca874ca8d831b0a211a264934470 GIT binary patch literal 1622 zcmaJ>c~BE)6pvI;gE)d{QH!&)NX28by9vo=Qz8u{!KmSg5n@$vuRw%k<7R5{ZHg^Ry;m4e`FC1B7pCOI4b%Oy+eKei3Ws^9ha-X=s*Y0E3;#WK0Y} z7p(f8i4lq3Dz}*}yv4XsMX`1X;nk72?M?wL62-*2odlK5@BqnV+8k=I`|LRou+eHT zO>RVt&UhxvHm{Il78NF%slsdur@`2{K#W@@7_c)u0l4is4wuTU1_$k`guPb`gTSB) zpRERmNLh@@Ks?JafLx-0C=^8j440sCrA#WH14t264kI{>A`pVAP(&p~fZ+!eym7Qu zWzy=0eF-Nun8ou>6%6O+=S%Ws5|+z^Q5?r%L<&o#ke~s%3LHG)h8(U4p9L-BqBxtA zx3LbuYmp$?TwVXB$28mGmy zKCHpv;t2=E_~QOkQ2gAMpC$xQYvIt5*)_U zuViqjQw50-iPJ|&g~<>ZhT=4YSxGsBq8JVlR*Z%y8BHma6va@{QV

!rtb2rI~yp zp+mjfm!lyZUJe)I5GINfMrZu7GDsxyvl_G-v)fa;?Ob-KCbYTZhle-1f7`To=TFm1 zfH7q4Olm9*R`*ovNm_O z-@ki%ZPTcXI}aML&kJPzc^AXt>l$`XZElH6B4bDVUc0<5Hlu38K=J&OMS*9=SY`&m z@00H&_Z$t58=HE%v{QW`qf%E;(#@qv`1r*R0K* z_*9e|R$kT{bou&-xW>+P&1Ysk{o+t)f7kw)Qvv%brrj5psQq*B-lThVVEL*ot+Qr* zSoyD_dGpRGfgk&oSUZoOhf59LX3dEw!07>v$8Qa!+$yc;F`xfsd9^v^#}jA9tw^|; z(Y7Q!Gwf03vfd-f6Y$W535(PBj8q+aG~#ZhC-B8w)}X8oxjoK*BJ4S^MIW#_Mto^n zf38^>E8cpiGT@@RHIi+3JF$A!+8e|GKdH$2y`i|csz(6x=^;jbrM zA8R}^I;X~DH_;)TTf+iUp6NZ+(!dPOjuii*v^hP``-<;n%uJ7AgV>;R7wRAEsMrLF zmnJ<+>5FLk3g`5x?FCVOhMGP5Uo82nX2Is!t9xsMH@E)bcuGB7RQwqIAhAKQO}A2( zaB-j#&(~`__bx~N0-Z}yHBnx_;XqpKH)@g^yKe$w(UAv z=!w;AcT}BSdg@AepL(G2o@Y&)dQ{Wc^oXL7VWRN0f!*4D_4VFAy+N0#Js!8BpMa#a?h zrPM-xbCX!?y{4$zgSsZQc|2Zk-;p5z4yO}ax9wK5)*duw>+U`>q>k;FZ8#hXn*JBG zF9|_5n;|kk%eUm$Mqz7flgRumpFXbEC1H$1g96p+KG|%Ba=A=8oo*Cz8ykkU4#4j2 z?;_AqugmpGI21hH?;B8@E;q7lMiK&#$J+?<`3BH+9f!jOfZu#jl0g&JPNujWXe`tij*x82J+}R1>`e#Q#B$Z08SS*%Nv)RI*S{1w9iI4ZxWbNR4w-xf8O098Fcp200000NkvXXu0mjfgt0*W literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_2x/emoji-1.png b/resources/sprite/tokens_2x/emoji-1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba8dfc6fa347a3d1598d28a7c44339f1ad90a346 GIT binary patch literal 1400 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 literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_2x/emoji-5.png b/resources/sprite/tokens_2x/emoji-5.png new file mode 100644 index 0000000000000000000000000000000000000000..4284870edaec103c353b73005e66d15f67ec1f8e GIT binary patch literal 1334 zcmV-61_xFYax=?1!DKG#@$CxIC7dRPh0F@2ZKXM3f9Y{k9^E5RcsP&Knw7R zbrVz^s6&fJfGxmG;5Exv?*SKqN6ol>z}u$%$g}~OogD)X0UIqo9q2Scvw8&>o^~Kp z=KybkuN&BxH9EBD2+#%8P8CQhA!Y!0-|FblqAyGm7X{R*0^M>Es;cS$?gQ>IKYone zYwLxc2OI>+^Ey$s)sGoDPKr*&)&J5rU5+wEaS;7(@GUr1fOb*(c5d<48Hgou{~ zq^jy-;46${gZ`^mICJ7Cf5y7e^-LMBXvbY!$HJE7gj&~Ty?wxwLWtuf0ja7wANUoR z+1GuE6Gy(G?`n*i>ME+MN?G?s8h1EY+`5vM)enI&mF+@^)1?4?4s1F5%MYA5_AR=u z15i^fQ(fh_ZJ?}t{s0@d?*!wepTpP)X&?#o2_bS*kj+DzuJuIoU;r|iGN$TZ6d$<8 zsUHsmv;n6vGJO}uVLhs<>H$?%y*VIBl4_zCv^*Gq@l={jf$w(J(-i^0ZnvKS&H@o& z1dxGkz*{*WkH>@0ADGaHuIr4BB`N~tsq^9ScnF0;TZ9lxg%FD{PJoZikj*)uKp?=9 zWviJJoIjzVR9a_vEJ1n-(`R!ySohe|G&VNo`GpWE;0@E9n*)+0iKeC|wm!R?mbO*c z9r;IGCap6xl3+BEEE~*K=VjZDJuF&$FS0zb{1!s=Snb@NmL!Sh=4SlQ?4qrG11Ep} zo{N#+8M-l;3r>!wNsgznJ8VdH2X?z1hr?!URhtcT9UZXQC1%bF(7N_PT2`#V@Anr4 zR#i3c?I$D|pU;PH*$NhfmM}OtNbE`%U6D=(`uiA+4=_4BL@JpigR!n;xteOH8?UdP z`a2q!*Sv_knik;odU3nmr3_-;u~ancxm+$>E*A|A4YaOU$=KK!i9~{QI-Qpej8SqR z%QCVoBT2VRq=h>qyPJlsKsT6J^WF0YIGxU0bykY{3+y)Yc^>{rjGdL}sRUhC z6lEl9n5E4g(>!PeI`SU@9V>wO5Ku7csdvCU%QQ_}h>4MmOFV-MF|L6OGB;Xl#A7B(O76`U@qE7wT*~DOd(;Dlkia{@C%|4MC8nuRQlgYZNx8yCN(0X)1&jbZ zFd)aaCV-p};+bSTMj{bM8b(Jutu>~Scv0%s59HXE0elJ^PQ+tII+?)e0;B=sdOqo7 z{J{V@wv`5s2qE6+OQ(p(%r)7Ut2Im|kxi-W8OX7%jlc;hjj{f|Ojqe?mh1Ucsx`8i z6rk4!o$pzI0D8VHlE1##V&^=sNL^ZS@03m69X9z-TlA=w#LvP#xw6CFMkOnnJB9 zmMfIX6|`R6mPi4Bgtfn-+wv1W{_@x8p&*3W%ntOOtNL$ z5CemQJp;9aY5@IIbGv2&1mpW(e5gR5*IbV1yfS>C(fMv?`^)qqcr6r&%)6CzySKn2zVQ|Bm&$Y<3*=A|oe00n~bOtv+dTH`c z-uv)lG;s3!?{GbzZ@)gmj$ON$zA(k?UsrnqYNci%AD~*Tp_D}H=s1`!mNVnw>Zq6JvVvlxxG7YyPiKuT5YZUsS@xpMI$ zH*Z|$*7a*02Y9VDq1K=^h*p&nDb$VQLr`-)&Yqh*+}1tEwl)DX$wXWv;xQJAWr8q7 z8U|7t2q7YXP!onBq1G!3+%N>DGRdS?-bVbuuOG^SxrGwBe4%r^9ozcC5aNA=APmE= zfNy~9z*9g1r~o&Bo4{3ImZod?34g`q1HFi`W4{a zT)yCTVbjq0x>LveAUL~FstiZBj7WxIzR-<8t&54+?bQGP002ovPDHLkV1iDbRKfrN literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_2x/emoji-7.png b/resources/sprite/tokens_2x/emoji-7.png new file mode 100644 index 0000000000000000000000000000000000000000..b7df3d7e950000aa756318e838b287c24ee31af0 GIT binary patch literal 1353 zcmV-P1-AN$P)cLOIbgHq&jCk)dsYIbZ6HxB zT>yRsB=Cn;^=1q>p*+`F2_UP3*bjkwMX_`dKzZ&s@M2t-2et#>#&zp~XMi6?v2>&p zpte>hwy%MAwfC23p3q}j`EB4;;8W$fBi9P(_ZTYC&QoDVQeRU|>L1m%EsUQ^E3Yh& zD3(G|EUEZ)7tm8%H&4)CPeS@*KfpKf^9xZdox3JL3+z6GA}G|6-GQDwo{XQL0kgnI z0LpV8Ql5M0s)3Z}7Ftx@E%Op(D3qa)T|wf%UwLkyD3)e`_kd%{bGHJY1DmcI2>4!k z?jhy5X`J%hL13foP9X?-{iK)$9*N5bM6u)phvRW>UkM-+cm;S~^SOY&D#$)xZ}T*O zD3)g9)UYk~4=T^SW7$B;a|N(IetlsfP?(dcBk1u_R-$(%l;_?Omv;gvGp*aZY#?AG zkcrFJYkG1K%|HnB(*{uvN`dCDD}$i`_9@TJu!JoIG=ZV052bl#JYKb}VI~r^Zdx3` z9_6`5f!#r`PtfNh^AcbHcyPHu+hkS}6w;aR0`>sIx}Zf2Ci4NZr%6k@0V8Se_tz5t zv&#jVkUfDe-u=KQ0D&gr%*a$)u8vijFEj%D1ZZ_F*|r_Zb2oGZ6yI^igWgE2x?Yr_ z2v$Xd7*2pQFpDswUk8~Fm>(~vBCfBgqO7P+Sqgt3UG2v^0s^)pK*sr{I$DWlC!jV~ zNe49ZH4qBBFq<~?3w6p{rYO5pRLAC;I@Nynp6C?lHkl97frI`!dw&R)GfOLV*TBuJa?=Upqq8!LbO#{K8b-Mp&Cv=X3b?5n%P|?J2H*o zH2Tsew6hbSahY#);tX9*Pg7#TmYBIl?#T>a!s@Fu#bnn`17NLcY+cY_PgZklCqTnt zrXgf2Uu!x|0M0;pQKPO67s!oW#2K8SYy5n=g7&Hgs4`WuZJQT$K!!$#Og9U-O|!bI z_|tg+LMH^sT`8{Bmdy}q8LsIBbq)hG2|Kph72{g8|Jj^38*g&C7fa3biA3G~(h1N} zbEct$Ffdz$wQowBlu@IW_;j-wt+;kbY4ht zFncl*p+D7)3YWiaVbw-fBB1QaJVJ`YqF6fBHc)JD*^`+cvVV19qc%xP9lK*}v&m91c1*__}&aa=t8lE9LI=Luox9&htJzc;**GKL|9_x~G z9C$eXD(M_3hROr)D@*OGubsyqox>Tvgxx=juv&kV1~WNoX9`#+dU4L?0wKjgV81Aq z7M=f&fHbfj!0XCXcLjwy{y+_e<}iAy7+D`-goJeqHSeI#_F$ds!kTpIA{0l6K8b#7 zXPmZyG_V_h2Y@?)O<^V+Q&wmL2;Zn0b>mOqC*U~nW%6&y|JMEiVY=+cIlQ}l00000 LNkvXXu0mjfw-b9r literal 0 HcmV?d00001 diff --git a/resources/sprite/tokens_2x/emoji-8.png b/resources/sprite/tokens_2x/emoji-8.png new file mode 100644 index 0000000000000000000000000000000000000000..f468e6112a13bc1142d1bef954e05f5a170fe8a1 GIT binary patch literal 1497 zcmV;~1t$85P)=$K&Y?g(QN(CPdMTVHC;d^JKHPF^pm+nb5uhK9&_VW7%-i zKCn(%GNE0>bhdiR0R6yi;9I|6+}hQ(p7!<*yx!W0-@SX6RO%L2ul_|opT7Zo2z&-e zE@KSKirQCRp@W#maUJlt1+a<5J(qwVfnp>Qm3n(OJA#xY2m)ZtTJ5^La6& zbX~auS$Z}Si4qJxTsZRid~|en0=)UaK*@wA057(+#U2W#thQ)7z^-IMYkMFdpc{yW zLT;z^{?**vj39Uf;PD3q>I7(PY+Ml_fQAM?z}kBCzIqjwJYWVoK@czugKO8WFh4&} zINZvbHESImJ(tTdIeC?0u|!8lC#zOP>`m@IC{PMO*LD1UKf}X+Fgt6eFEcaK=!VWC z>o-(&&~=^j=Lazi^Xf~dQ*7I|9Z3oUP$(1t(ylAuqB($UHiKap6_~QpsarK2W@o1> zFqK)QQUJ`)=O~p*07J_LN+vW3_*s%9g25nOuNO0nEXwZ}Yx;?Pe{Gp)mKla#-r|>+ z4FtR!2n6C=w{9m82qFjq8#ne;d`1*~Y}im6NNmJQkZzW2jJ6W zLThmv)Xj(XwzqfU@hmdOV(qlHt|Fh$(bN>Gx?szW5767&M_1Q+N~I#r&7mrzu~-LV zW0#vsrP6N9yt+zmE7ah0ql==5B!#PBZr+^Y?Ag<0{{SEu4A#JanF)Bk9)Q~Yz6a2$ zRO%m>>P1aXUSnZlfs})j+ch&YgRUDz;OugNKDm86GdntZ3Bxcdh1qO|Y&KKXu(dTp zBod{uF;G>WPN%C@ZF)M*$jC*2FJwi%=`^V8^_@&;2Y>-V5IliEkYcgOojVKk^*u%; z5_Rhez=aD#WHK27!2o)}pisC2X8wNAo};@0B@>zm>;g_uop$B{ee>omQZ#Dbj|&U= z%E}Z91w>J-IQO}8gJd!pooau{U_K~L$%?8w0$DH0FM%yUz}{$yp?=`=?(Xhq)~z!m z^yug%dU_rO;Kq$fT3T9YZVpi@l{j63Fjm6$ z_0-f2=H{{hRsIEF6wU96i7|BDm;w?k$vhntu*M#M#W>`e#ZjFDA2W>N&Y_{fL*Z~M zxm=EskxK{y%;mC((;{PcM#<;%Y2X#>ZFV}D(E5Q>E@Q-Dam&8L<)J7ms?(nmdoZs6 zd$Oei!@x0FQPZ|Qb#Hm#gsiCh%RtS*5#Uu|wL@cHy;E_&vRKa!SgFfG1={tpuy8z-U#Rv~?P{p280$%f`5^_~cTU z8X!v=X8bWQec`o-g;}E4Et`flhPHyX{~P@arenderIcon(), )); - if ((++$ii % 4) == 0) { + if ((++$ii % 6) == 0) { $buttons[] = phutil_tag('br'); } } diff --git a/src/applications/tokens/query/PhabricatorTokenQuery.php b/src/applications/tokens/query/PhabricatorTokenQuery.php index 8992e719fb..07e15d4b03 100644 --- a/src/applications/tokens/query/PhabricatorTokenQuery.php +++ b/src/applications/tokens/query/PhabricatorTokenQuery.php @@ -43,6 +43,14 @@ final class PhabricatorTokenQuery array('misc-2', pht('Evil Spooky Haunted Tree')), array('misc-3', pht('Baby Tequila')), array('misc-4', pht('The World Burns')), + array('emoji-1', pht('100')), + array('emoji-2', pht('Party Time')), + array('emoji-3', pht('Y So Serious')), + array('emoji-4', pht('Dat Boi')), + array('emoji-5', pht('Cup of Joe')), + array('emoji-6', pht('Hungry Hippo')), + array('emoji-7', pht('Burninate')), + array('emoji-8', pht('Pirate Logo')), ); $type = PhabricatorTokenTokenPHIDType::TYPECONST; diff --git a/webroot/rsrc/css/sprite-tokens.css b/webroot/rsrc/css/sprite-tokens.css index 2e51031199..5e61d6c9be 100644 --- a/webroot/rsrc/css/sprite-tokens.css +++ b/webroot/rsrc/css/sprite-tokens.css @@ -14,7 +14,7 @@ only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) { .sprite-tokens { background-image: url(/rsrc/image/sprite-tokens-X2.png); - background-size: 76px 76px; + background-size: 95px 95px; } } @@ -35,50 +35,82 @@ only screen and (min-resolution: 1.5dppx) { background-position: -57px 0px; } -.tokens-heart-1 { +.tokens-emoji-1 { + background-position: -76px 0px; +} + +.tokens-emoji-2 { background-position: 0px -19px; } -.tokens-heart-2 { +.tokens-emoji-3 { background-position: -19px -19px; } -.tokens-like-1 { +.tokens-emoji-4 { background-position: -38px -19px; } -.tokens-like-2 { +.tokens-emoji-5 { background-position: -57px -19px; } -.tokens-medal-1 { +.tokens-emoji-6 { + background-position: -76px -19px; +} + +.tokens-emoji-7 { background-position: 0px -38px; } -.tokens-medal-2 { +.tokens-emoji-8 { background-position: -19px -38px; } -.tokens-medal-3 { +.tokens-heart-1 { background-position: -38px -38px; } -.tokens-medal-4 { +.tokens-heart-2 { background-position: -57px -38px; } -.tokens-misc-1 { +.tokens-like-1 { + background-position: -76px -38px; +} + +.tokens-like-2 { background-position: 0px -57px; } -.tokens-misc-2 { +.tokens-medal-1 { background-position: -19px -57px; } -.tokens-misc-3 { +.tokens-medal-2 { background-position: -38px -57px; } -.tokens-misc-4 { +.tokens-medal-3 { background-position: -57px -57px; } + +.tokens-medal-4 { + background-position: -76px -57px; +} + +.tokens-misc-1 { + background-position: 0px -76px; +} + +.tokens-misc-2 { + background-position: -19px -76px; +} + +.tokens-misc-3 { + background-position: -38px -76px; +} + +.tokens-misc-4 { + background-position: -57px -76px; +} diff --git a/webroot/rsrc/image/sprite-tokens-X2.png b/webroot/rsrc/image/sprite-tokens-X2.png index 3596bf5374d912869b781981138322c0130d1bf2..2833fc937328006e146469296c9b4bcf2826a7ae 100644 GIT binary patch literal 24355 zcmX_IV{{#Dv`(5vZEV|SlQe8>+qP{qI&ovOvC*)>iEUerZ6|lWyVkuw&djViYn^xV z+0WkZ%!yExmq7f2_XPq10#Qm*R2evT{`Y}{0gh3wjoH8n-ceG^1p)%8@4pXZGCdML z1Oy3$l;{st&#cpIA79nI?2pX@wuj+=Jt)GWph!!l_WT9y@*ylSdUZ-|?Mmfua-(f( zZEwr(k@qF`^LnXmVji?M87r3a=8KwEb&ObzmR238jXxK@8VkW;i=4Bk^SCf|3Ffdb zj&WkUt@`SZ+4Q8}@E!14bRKwhKGgJme&3(!G>Tx5(ygLnP%%BC5X+a2r&LSmJHRr; zNwH76#S3ffWqI`CFz3uw`}lf8PU(w4>NodUcB{8{v`bgHYUs6ZFA1wGve4W=$I#d2 zQM0%9R&vaHW$NkJDpjD(qTPznL6+rx6HfnaK7rV0{rZcRIE*3&e2GlYc9NnRyqJ^) zbZ;$-YgC#!YK%5cm?xerdXLi0kKV0})>MJ-JA`>9o%t}2Fpiik&Gl8S^pjrLkh6i! zK_6wdHcX!DInQB%{PtM2*q$x*RXk`PzDp{CL0wFL95m5fd5SdFn%}{0;dPt$<8ZLeQRTN~ny*DAI=6XLDbsaK<=HxuXU_O3xB;RU8Hh``i z=dQtf#NM)=dHRFbLOuEF?{j{lybsHbd6fqVDhc=$kiB%jv^THce4DdME0<^nmqeeW zQEnycpp_@a*khD9mG{GPiiCh|x&TXkr0v*I8u&IzqjZR^B}=(+lCf*H(`fkC_^3`} zyCiT`%3_3^ZQ?K7DTZSvwZyGYJ77CSuXd(u%};E+7H?OFYiujk@{Z#Q4FKb z!3?7Ysu+8sVoG$TJlRaM9B#cVZHT>|<1Ep+S}Av1WftSa8f~_e&Y|VCvPy;MnCX!m zlrx`t|G4(%t+G@RwslW3M8S%a3m59Io+UN$st(uCpzS3~XKGKBR|O*}H=n8UOQ}AsI7VKzdWkPB^e(*AEKi zm4-d%(In`kkWaQ5Q?pejcv$(QH(b=r{)@C{`g(aQa z-?z@AjpCpdHJUgtj>{ali9J6psVy(~%8s;SLv~U4k#53NR@&NZEU0Xvnx2j2^!mkR1PRaH}hDtGQQ%J^&0 z@dSc(TW>f2!waULbcn|kYu!9^H!w>KrJ$!l;)*;LRaQ0Cl44g$D-JU2V{t_!#}b2_ zKe^L4&e-p->9`FqPMmlg8=0QM7_7igUAu4_*62@`)QWd(r654c7afUA^6x_Us@Dwa z4?!#YS?*mJC7bwGW3TJHNgGA{W?jZ%>vMWd7bm0tMG;HV;(3FJkUyp0Rs~`j6p^o^ zs#+$QuoWyMl;12dxSPW4?bVxbB&O*jptrohx1_Rp>GydSiep(efc3IB$wh99H`#7n zP?BfCri=Qq2{0kWmeP#ZlLVNHel=tFuc8MPDUZf$s`}#GGv9=J&8ikuUOAS1b40Jw zid)M6Qy1mEWS=z${HEN`0;{I(qDJ?`i&N%iFnG0k@6XOjU#?es`~`LCWU8S;~lT=}RXnj#!Fh?D=A$Xt)jO z>;+oc@ugnJN2zZGhw~rvF*4_OKL5nSMG6Y&hLNG(VB+(qrIeVOW%G2UUb|`}BxV_e zMa)lz0aF?0UjZro!;nk(b}9%LuFjlS(J0salR>qujX$@^>6a~PaMMWBKD+lZemh*c zzxjyOW|ghY`(m`yO`sse8(^8eUm~q`T~^0`0w>>M7tbo=DK4K7MnWz6S{XDxfw=A5PSuoq&~&1DiL1(-So3^lhe!3Op4IR*<@Say=vce{cb}cHLzFF zr}vFE_=r(fPb4lZLJTHCO!!cnyL-!t1_}GTom6xRv7(HLTdY1~ta#EyN6B_Fkz&Kkq}5|rcyq)gdP%mHY~`v+@~(&L zFv{7;A_=uZF$gAw>jO>%n>ktJ;bg(sM^05SZN+~WT)$NLh=FuC|GbcB>WxA+(%UQE zb8z;+b@l*2<1?`-TSv_3=>yjeY#5`&s(se+txp#0I+H{wgHBR%`jGQ=6keESw+Sn4 z#c!yPkZng0p|DZ{bvhLS0!>=6sA$w3a#$m6$!4cuf?tYj`}HeU$?BQFp(8gPg;u|w zdME|82GJ}iUqbk>PSPDYS}yJQHm}Whh?XwM>QEH+Z=EcZ+$@sV@9DCBy3RBF6mEW| zqr6#CHB%L`J5A+?F^?`?d)zn;akWGf#NL9MK`;wL0c!;>hp9Zz&9%W{sO9r$@pGw2 za#pe$UAf2@HZB|PAy?#+H1syJ%B3fCFMgZYgAw6+?bt4}rOI_ogIvr@V4&ZjDG8|d zwr^T_dEqsg!Q>>+12yW76P$~ZlKY7K#Neo39J9HBc~44Ixk|U1(FLoe?pq?VOp`_g z1jXlB9juUEwzs!087Y}Zy9d@8q5)mP+ziwcaue|OCFEPKG+h(?GT&9z)@jo8r7XNh zg}*+a9$g48hB>o$_*D}jq91)g*p=o!D$~=`o!3iACPdF3RjOm`bw!m##mI-Nl8!1W zC7XJW{_KVf&auyyq~MwzoQy$8za?0}C1622poH7KOe zPVMSI$W@H}01yikJgI{8hN3B?tZ*o->BKfz1`dGof0PZe0A35fa|RN6*MQ<5lHCCM zy$c_p9Jq<%{2&mVATS&~h$AJ?fM8Z{;uJ*6N^;iLMv(vF|EbTb0{yXO4te(U5h+|5 z(pW53WOl&52-IH~QjZ_tQ7%ogiQ`nAC!3x`@tYG|0#>tj0^v{xu{WDK(6;@~M`oUs7feAT3dHDyC*b&n^R00Vq3Dj&daU z04?Q!`2u66*^ntk`~GDtwTZfq3KJg%)JQu5e71o{)SRWKds6h4g?At6k+QBaoD#B1 zQH>IUPJx?kNu5s^Iw(nlaZ=3wwwbZ8u5PuJ9R^ zHC`V%P0Jj8ldCgtkw6z5EQS;Z@p<|=|G)WB9CHqkd znzKPp#;;mvSecL5NdxxX3YqlPUon*ILd??pVqp7mQN@2~qJlQ5M`g7Y2d@2# zXyetNeFXYkgQtG|9HiWK8@;{9Nz$MxEx*Kj8O2y5(n{01(afTN*JWBAJI1j$=aeH= zM+}j3C4Uh%jiQ%PK`M&ma4L3Nm0d-CC3mDhV&%dQTvdMmupil+1*+qBRMZ?B$xShR zs<(U@l9;Q;w4l{8V;p7SH79NcG7}25!bn9YFM}vd{Z==w3@8uPHZrT%dn}4<9hkl*P<<)iIin>B(XiCP6@Y2x5?G(VJSWqQ)T- zQllLC^u^5V3#(q=#-N9(7K~Uj8nQGU8ERs;zmao{C6-~ z=a%7jAKstKZ-u)~hFW1|*G?qHxgVeNAk)dVM#0d41GMD)twznc3yyv9&f(1kw|z$bEz81VGSLLXaGEPw5& zdbSL z$f$@6W>!#XG!D-eBr1uA*^;0Wo}-O4;ZKIQerd6!&=e>BYGus(YoMt|2X)VX!THM! zszhdjkk3LJUQ9D8BjUms2a6wyY8w<`lxE2qoHqW-!@|6GAyMz^&nWC(*qrb;k0Y%I!;C_TtOI8xe)H*`>~sAf_!%7zxE zoDP*n0oe!VkRAN9n-pEI89;P=EY|fuZl9rM|JY9V^&O;K`Iu!PMGXCtDwK?>E!IH@ zlM@y(egFYcU~im(RhO2oFjv;FiLBPd*_HK_Px#}h5$KmMj7s1EsPw=PQ8ClG5qy~A zT7w#cX#)^-lYxUOR(7=iId~lk+eZ&ch!jH^OgogkkIy|r)wbUI(16o)V87dgK(NXAW z8r5IB=QG=ArqG%l3q5N?7jKsT@7Z^NCDnt0D*ptnn6`*ktlkze@!$uD%c+=4E{Y%4 z2PA5Ok5z@=5>0k_x*wy!Ll6tyZHOY3pr~r${rD4Twfg5cWmTw?S+vyS2`4zjutI3r z@s7LaOwx*_G&Hc=s++SK9ItAH%4|KKyJd2nzZXK!tI^d{nBEArvW>}BSYfe__pu~U zDQKlAVmNZ|8Euai8%=Lgr)_X-8#c?!HWTSP&WD)+^zw@Epg#*q%Wk&sLQtVqh#8Zev?7xD^@CWkm>LKn9Zwwdu-?x z!sePHOqy5`Q)Z)RT(%e8`efR+%$JQn(F>yu$-!P5}$Ay8!V7lvB5Q2Ndk$;~&uNDp_VHC*!%i`fHC^<5uUf=Ua*HX1$hPVjUfObMxMl)I%9u{HWVm8#RaxI` zMk6f@r7X4CzPpeR?18ua5qeKQP3ZiwHBYrxs|=r@n+dHJ?u(|Zh!Wy<*Kf62DP>h(Xgt@4 z=jZoQoLWr@Cbo;aDfCcgWeb$($E%5A(_A0$)2On+tzWBG6fyK3HogUPFi07AaEq4b zjmE3CQ%cb^l#}hXD=Zu0U;zM;cEv`Hg~~{Kky+fhZNp_pYyALlR!1^A8r=vQ{DcUm zkb*WSPvZDoUdguIk%%Sea+I7kt9zU8m!`mcqnbv6L;+@@*k;rkwfo0`=eqn)Oo8@u z*VR@Aogp^d1qjs5#7IZcT}K1Z)UU8NGl4G4b#S$B$X-uFCf5Sl&$#_oU8a$d5tRCX zk<d+fJk$ z#!@&77kcfjKNagin@$xE$ra)T%l!bDz^e)8S=rg*ho-AOOpb)9)En)5 z6tDi!%aB7`ldO1Jx;>0_7E*HHWX$Al- zPug$LKp99ZIoqhJ_3vJ7Ik!4;6L=j(_8W`-RtgIX+mSD4Axk{i1YF$0F{=pkpN4;4 z_7p=8=FSov&7EKW;CFdwG>UOXaSRAP@YyR`9EOao(8lYS*l#EI`aUax{Vo|}~ ziz?pkLcwEfQqQee&HXPprA{>NKsY{n{%YSOn&VYK^sxv~_&c5O6^fR6`Ok6+OrF_4 zQ;5N7IkLq9Xn71*DS!bZGd2NI#2`4>^6i~T95Cqe z74WU<{)bb$u9O$pJu|uI( zh;X44v6cpkA{{q|go+NM;z1Iv6ciuXEAk8IegCs!#^29&cA2jc_a``8mM>~SC>C^* z$Av~AhqDn|HQ8W#P_~JLbb^#mF)untS-C=MBrZQapS@A7; z_^s`s>1j650tJDhQquWqAxjqYT_{40D1^a(W6^#^IiVV6N@dfLuc!46cS|+|+JfOz z?Z#($roLAb9A(a+IP&ujJAV{U5JrXcTEg$4P)uFYldqY6;{T5D!Da@3twvY-r>0dl z6+ax^Q=RZ@ip#K_9WF641ZpIh&x`` z-PDL5vh`Ndlo`i{WJg<}Vwgvu~~Q@(wzx(M3sRfaR_Qn;>>{SG5Z?3-+T$ z!XB}IPuql^uFgx<*pKvUynewXeeTt zYX=kQ`2r1o52c^(1n4Stn_6J5vL{45!xWU&p!K*kTWznjPKZ|?eUG2$PQKR&C+Vz0 zG5lLu>8L!>4bgmy`ob>O63zSxkPqqM4ts9z?r?d&+TZSj--2Rnb5YlWk1WkbAn-VD zs}vzW36Yey;lsVSxB%I6%E$LG_#7Jivva?V5j=7o78^H~&K3*u`5}nd7e&_rf;(@! z103>=ACOJI&mN8pwb1Z>W)Si|(x3K{_H(a>OkjCAHvt_}36z@B;TbVS@rgo}ZjnxGK^bMf69}Tu z=WFf8F5S`?t&ZnexC)RKP`O^_FXwjmbvLN;Ws4|Cl@MgxyLMZ%K*;d#oWxM8GwCpp zN2>V2as&N40TQN~lH!@n>1^l1m^A0ss}6L~(kh3N@(Smkjsn`z<~( zOC{X?T0Hw+R!$~l=7sBD1KT{Bo_zG<^K#KXc^_$A_00#_40u9}CV&`%%f;?Y5X#k| z3Xuej?3`_NViCn@G8oR>09|)g`S#cl7PZ49PO!1Q>PZOTL2;;!NSVM;@n(N(CHl#3 znXuPq?1$;#66m+NEamGt#ux}{YZFM*PL*SVuNOLxpLwpORq{wpr#w?M7=@RaOqHJ2 z_c1e>X_AY%C$ia1AdRsaXfA7Y@#LcG$*IZ?3N)>H#nB67mjcs`SCqMTpd&@3h?-*kDGb}+-PfL|#j>*9V;QD%{HP}YSf40&3sR6{Y=!x6> zSe{f7_CgCD1*mhe9(xB?OD_{u67mUTv&Hjv7JY}6^y%64m49lkqN(RRhE{zkLloO@ z?_Y)_f2XC~4?T;CsoE=6>q1|Negd>V-zIL zft@(gZfxHntrHut!7@LQ;6Sgd8kn7vZ{Exu;^V?>%J%QnCK5+9os>E!a;!7)z(kcJ z-J0iaYKjZw3KQXEVT5Nap5i~;F^mgO^?*so06FeN6Y0io^M>QW{Z0v=erdmX$vDBn zv^r4Olo>1#eZS=Sg1t8!oXoZHbODy44^=b8V!^Gef00T#KNMr}TawG^wjb1g;X_!uNj{7kh^2z|DFYK4;%_;$9xn$|(EZoA@SdCOuajyBZn&tr;=m7keB zkP7lXz*(Z)X+9C@x49m*WO66U)=V%k-^759R1nRDD~vN1Sd9;_T=1vBq5jmOE>{)kGVJzs`UtE8 z@)7(E9#S0>x_wPK<(W~^IpwITs?y*3fY_uEt42n=Z+KYeIuvNhof2YZWsEe_l#Gl% zcHFsnD$8*G+$b?lriklGj&Xkz^cT=DGe{?uNiV|>8jljF3C&AxoqLee9s08` zdI1#T1ETVVzzb)vaJxA6O7zqbSL8zB)AkbHf*P_R0P|`g&Mm%6;a3*5b8V1MgN+ad z>p%^YPgJ+DIk6QrC{2eTc0J#oKgcPt*?cKVR!KGWHG?$I_WhpA&8T$r0#R#vG*d*0 zZ8Z*?dBjx_>1^%=&QNFK$?MO>?)8yQnA)*wW>)kGjKYHHImO+{4tF2>~PPdh3U zJSr7R5z!+Yc@9RsV^O{Pv2W)F)#dwEw-O52P|Nn04%6Ilbv&0N5u>RF0{LnAC|@T~ zGaoYRabL^J~bLh{) z!U7^#}XUMr;%=V@85XX*tvbeCLO)5D7GpGh1%oo2ru-9DwjNqekTk@f`YdUGXh*?Ik1|+?kLf7=ka1(Yp0KsAr|nn7 z69iXK#*q0Ss1oZf2E*kDTs9@S?t|EvVor?Udw8y@h_nbczd=(xyn0$NDbLEK`)boO!KL;)TcA+IyNPV8%7x`)}v*INMxM?e+cT6jChXVawjj4)?Zvg_#PENdRsncT4EpQiVi0;Q71fe|#|dQ6n*<*&K4dNQY}J*HNFT}J+JpTsw4 zyA&3E6t)%~sGXQXg6^^C{EoB2KkSf^hmTgiKF?zQyo}lTOD}&Cm6M)E<-s%idTTu6 zsEaw3d$!KpoZPq~`d7}wQ<&LMgPh#B)pEoUZpUv~_}(PN`a>A+uwJ^G533tXx)jo2 zxN|&lwjssIxY2~B=M0DKxA*HQE2%$I(2;0AmVsu_vSNEb4G+j%ke*SuPPM>4 z%x2q)dxAlhT32@CpfZv)J4MhNsh$$UNy!|p7Idj}V4SZOgj$#_leT|YSeY%~cPm~A z+TGSlU){b4@-_hnXY~fD@zRdH=)Lzz>+7*5sZ8WzA=Di}p^7m=O303^$ANJq`2xBo zudgm7k4C+PQ~O}`x&Mv-2Js3BN)tEroJbr&r|WZWCyAg1q?@8j=VUgYS%`^2W4cUk zEh%IP1q2Ab+_w+vxb42^2SFH&~ae606rax#-Z}W`t8B zqJ}np!v#8bsa4UbKR$KjdR&}#NPlBYCSMCMPWez8A+*Z&M}y=5VxAcC_;2%qz9MH0 z`};28C;!CI90SM`WB$&poK-f3=!RIqzI=UTrg=kaB6ETBA$>Yvn++sg%O~8*<$<7H zNquEvVT1KbT*&fnK&4gbDfgqva8)&NF&Jx_VL`-2`Web3c zjzwh001m9b{E1f<1TF}FH`m2SC4Ggu&l#i`@oWh{v%ucIz!Ubf2VK+-9XG$n;B#jBgR#l1 z-lzain81#|yoVQOl!{7_38H3dl-ut!y}>iYN!!RjrJX5r*)PYv~B4cIozElem{5SY|$5xWZITF#SuZgwV_4$el$Kc_?d%LsgfSvQTZ= zwYgK`B8&7qljdIm6w9UVLv;1XAA?yuE@<>1#?8T5&W5vJh8_%mBrSUN9J+=#Yo4*N zF6SHqg0W`3PP=q??$LAeOb&Pnd;=7QEBsi8ns;jQL{CC{N%i4YUt_=lYU3xet7sMb zZq{Hyk1Fl+3+;Y|ARnN=su%zH>AwNfu}$@A!GqVq$=gO2>yrs|$0k0q)J6g`Rsb*t zSpl5pkA&Lvq6N_p$(&_92gMhV zHKGha+_GfEeG-tvuh1zaZHqrx$?$7Z*RmJIV8N0YYHFuQ=~QpQr0lGmjVQTo}>$*JKD~-PY|22`N2#`&`bz27;hvCnOaU# zj4eI#$d0FtOyx-Z8e^P@8mIsEKz@yD|5Y6vU8aZ`*ejvU=iS;JHHa5NBQP=aM|R_3 z(DUD?#8g2uh*g|D_7UVT`Q zhxm}jEiAmDRAQ{OXA3g!<$77NZLb5`-d*~6n2kZAHNX_Rc>17%O0?ci<8b&WZ{R{`-gk92rK55s zE68V^ZgBCy_QwJ(MblbBRfQV!!=-#$%GbV|F#k<;ciW8qAcusR!@Le3=%tv|Rhf6QD3$owoY6Ut zu2cQ=a`-RUnk#f!549WlKs1JTzK(XIC6R;=PK>0FPCfT$t6i}z5+>8USX;3?F8gcT z;w~{fKu79}cKLJh%>AWZ${+~Eqtd8Az9}=IDkB|<4}w60v*zZjN%t)TFh0{a#(B_qFOA*ioG=mw%fF6tM)@S0d^Qc3EJ^|=df!dh}BRxvWf_nYpvAaF^ zf&br#ei<%e&sR;&v79b}0d<6iG;R3tfqdju48{2=z-)2U=l548gc#q8WiM20ix|J z+9mH&$;yU?dH-~N4QMja2gEJbJf4;& z$_pemEEfzOi_h^m7`f~68Q`n%1HQ5{zo<7YLb9ndOyDv^DiQu_-v{nsjy64)OTXD$mJK80 zyp>!hcld#F0tP^sKQY!4n5D1&y~W=Csdq4o{vhjlLoP^PQ--K;I!9Gj z@u4Q~og7MpUT~cxbl!EV7>_ogBl~iz+lRC#?wRL7W_{ye-EQwj;TB6Un`_)Lin!$LrPX4FE&Thc3&v^S6TDvVasue zzN>_za7tjZq#+l5X6az$Y;^b;VjIwS#5Pq-;dv-s;bh;w6`6i5kTJ0r^~E;_MGCM& z?=m3P2dt+-%HH_i9wwJuws&9hY>7fwVbeWml%Bq#6POv9fV+WMdQ1flaHbPVUl9MZ zmzdL7a^(Dd3u~rH^kG{W;3!dgWuI8%v`)*mYm(H`I|aZ{-`NK(eh+{|p#fX$fq#og zARF_@wL4I`_e{#jopsgY9aIas%6x>*&2^d<_HnMe3>rw%WiL;~S<);b^X<5Pm*=}4 z`d%zsHoS5)QP0rUCSdq*{(ysrcPv^-=Mf)3AJA~sHlHiWZ|LFen;L&tBz)gk&_9Uut{i|=i9rhR)Y440?l!Zak{%OJ3U`%pj} zbNPrX7ch?$F;Fl>Seyq4Z|CwKCYIUF*uHDGUC?oJWbrPGTmqHsPU`H>Rm6DJk zyQ(18)C9;w-(Ss<$7xU?NhQNay4gm2Uasj$33N15m>|Ah=u`+?_+-=ZS~zo5bTsK6kaD$Y*}K#ov`t5^)bY>dJ3?MO??4V zs2+;VO4koCm zPt`X79#c!noz)oWh%)pGqd{wT3B}q+l7m|lsi3dCqsmXx(96q}+1==THUk1E$wn)2 zL}}_z@3_fUkGYlGzCi-paQ=MDySFmkENy@Qe=zEdu?wmUcZKk0178*^H*dwZ>JM^P zPmBBPPkF=$fyBB3O;ABJ0P8wQ4B<*1MaO;iHp`g(WgcGu9;LL55 z*uUoIJ<-mnF_EUJa&-4+Px(`=V*0YBfp}*kxXkyOpr~6Rgbu#2ZFV7UUU9vfqN5z% zVyN!=`kB=})8Neui3C#`eStUbLVELx6h5>zG?Sk;I!&J>NHRR5{}fq#YrFP}6GZ!G z@~VJu57j|emtX+YkPfs`DK*<&uc|PGK^htQHE&ds!NEbg-&ecK3Cl8KjDf5C?lWPd zdm((u`Rtb_;tt8Uh_absve6;cm;gF$0`=ud>ObzF4V3R*;G7F1Pt~E(BYEWAAeu2i z_=NL8@LN_v&Df#8dcGi)8h1@C^N+iDarmsBFSu`!({1Etj7F|TejX` z_j;2-o743d{|PLXd__?6hIcU6#?lXuhiy~?zLusvyAb&E-@=JJXq$Pf|^DESXuOwLL$Xf>Q^}zs_o0***_qphh zkr4kVd2FrexMvGTBHX#$X3`Jv<9(yuE8&!r8PlQYgpQUXKS8@H@H^ARLDaJ#KX%B0 zo&v0djM@(?F0-gX+Za5?O}-cz@C^;8$xncdkwM)CzF@W&en8z<=@$cmniR5IrL@V` zAA6X6cl2~3=}ZpFYKvu>BcjhZIENC8Zr(n#!PLmBdYsDBtE+P6e)rW(-3Qx_G#eE8 zifS#N0jKxC?eXhmKVS7kfrVdIv{+$W?SS1vuO`4m^WbvUf)iqK$4?A=_W$J@`r zC&_qFC3h^2+8!Bb6_0(J6T@#LMIdanh`Nd6&OW2w3X>DqhN^*9a<dSb_ERY}Ph$5F{w2TT3PukZZ|k zjanGCRDT0Mi8)9B+Y??z6u!~Fs>4x_Qcnics*znZ7{0XPd|39TnyOp45vLOlP=?2= z*UYHRMrr4C&bF4^L*%Jbh!J%tM_nL^Esl(VpHzxv<>=w{^!P&+{I6~01)eY_xOY_^ z-^?N=l`+?MV>8FsS}mN9%UpQ&l3K00ZRh7Xhw4wwKF44J-uRAs^|7lINIRiRxwN^1;);JR_5Ta)EkOMvVXO`6p*FJuPxYr)9<+3^=_AH}j!X#Ix9AJ8LcOAuTw9 zuv;nKmxQ4|GK!ZxM$O4Qs?(L5;PWR;tFiWg0P5AIj$aG^#!l{v`Ei_p=M%}Edtxnh zkgDyLqfnR!*h1W~h24qjRLVZL>5KKLVcxvS6rI>66!i;Y4fQ|zkH#A#smo?{Lz0e? zi)N`V^JB)A|5%f7{hfkH8Se92GWK7v=>-+}c)DL_#fhO3_4)eWa7hYb4J%^@qtyJpKfao$M?Yt%jWgohQZ^*XT*`iXJ{ zC3@`gx__`tm!PNyDARt+ri$iU*fV&3e)ie)e;NNHZUqJPhJa9bqLG}On8ra1TFu_r zySUu$wfZaM@nE#!eS#Pm-~be$QXEELCD?*slR78r<;ZXcmds(Ac#Kb=0|B2W*!TTOaAxBjjk)uYucq_ac?~NUJeEGl z=5rtM`P0#}wZ*{;-@}@^zW*MjzUMa2e|cV~dHQ33W{@3#Au-XMb4fgESJE__vE3j6 z;kNfhPkRHEFy()iCLCVy-;{}QSoMZE?=#O^MeN!RV!f6Sk%1j50wmNJaWbKRax(k~ z3Nc2x*_k9=6%OQCkzi5|MpS7hc*myfN&#yiZ}`6Y5=kGvQ>pKM1)^vmMoECIt$JJJ zdQCb()a?$HfX9zYAiy36U#kJ^paea$hpNZH)p7&s@5Sq$n8+`Z3KX)O|BikCJU`zftEZzg zh+pGiVrbaX$hnwm??m8k<&MBFKE!GLbH_z8EM&JJ6@lo_TgP`e}D!!VdxI zi;YV8D{=uP0=Qa_H=_Smev3$%t+gI(XYle)b~qLRvnj;{3sqLukrsx|{?#{UDBcce2%@U-1-qHkg_DQ9 zj;*Ds$c>?z70p-D)eiT!*A7b%qN&y&_cL#(-i`H_7awlKWk^2=2%?Vv{uiKD@Z~v! zOKX0j&0D%=N*=fTYyQIRZjO74r%Ev;>avd5sSr=jgKufaL`l#nK#~h&tpS#|EfFHZ zO!*f|bhxrmo$gfHdO1Vb{ZkcqJZ7?@R7Vj~PM*2ea&go>`@g))@{3f1sL%^62X@NeX`YrP^lLb7j+Aq~aY5_=>G8JP#S{>amQYNZ#@j^_`D38ZiIjum=d`4mfI z%M+K+>3e`YL@|2biEdMlVtg>ucvr&du-nubyU>-Na!4@1uVqO}ui+rx=Lq z1z0yk%d&E%%Pn2ZQX!3M_^(m2p>v!BTVrdwgzBcAM{yKeguBsunKHPwcUS9 z#{^v;K3yLb#vtr+0u%V(2Nht=`b{z-Xsn)e6Q`5>QFR(3dT$Wn={$spz5XNc8@v27 zORoObmnb6E{m}>RI=S?(FoC}|?0oVHKX4Vo(|a1p3d~)=%ZRAb2WNivkr8{;<17A^ zh-z9@2v>6aRVb9yX`fe%h-AD7_$Ndoi1*g4-!$JouNskKTj(``zj5q*@``^1_8}JH z^GPo|9<-0G2lmz%+~H+DXGg$z7` zT}fM#!C$5@X|jXzN|myJLRmneyi#S-WC#8-1u3bWUJ5*7&rvkuC;y7^OAY&|8z@ZR z&Ak5{I$N<~oxwtx+fF(+rZ8zuc$$E zuSb@lyh1@y1Vih(#X!G7&taXO!#V@~2D)z7ZB-=7D-=@diha+Xt0=^W{g7n$lL<`V z!^E}NQi0E(WctXENzY-O$dHMyTj;u#b_@jb`}~P)aJM~IQHV4CnD-H#zyylK-MX$f+mM;K*MznlKl3>oH5NA~Pb5vl6O1oZX zkEZfozlWC&1=y>n-K3_r`)_F*f4U@2oOLvFPdbjHrcI?HAj*{Y_7Cvh-u<+{_7?jN zrA0}8NOZx8bux30l{l(Kroyg%(>n<79WZHs$6{YPi;pQ?Ge7%C|IR0`*aaM|Y02Nk z<5SSI{51(YzNCDUGIrO@&puLA@c{{7&H7Cvs{~sdPJRBl&GRWS3%*#?UvtaFz{X?! z2e^3hR*vxH*VFe8`T5nsvv_B)W(ROa+x@qUi(((AYvzm~7tTMHBc|l<9ev<~ULJq? zFYMgi_2JM}6XL?tIyhod{__v?C_KLHNOm6ZjX7QQ*W4n1=`(v@KdG+ocz{5Kg3Bd@ zkkFt(IERw!^~nh{W;ANj-zzLYuXU8a`S2g6ozQmwEd`Z?IO9Ku-RW)|Fs_Ubu;He^ zd*PJw-DjpAQxq#<<}yt%jP$7wYyPIWH@-R+J$G~B7msdy?xf>u)9$ORs7SUpW|;=2 zk-oBZ_lBF!{PbtWm#*f97f!0{OTS;Lgks+>!vrIJXQX@3*>vWm4~-dJ_1E0u1RkAK zrd|B4&+kN*EEGkeqDoEtZklE>6iPa+R8_*`Q*b)PZ)uv)+pS|57N#X}*Dq&sc)0K_9@^YO(z`*YpuRy?{yj;iSN-hMGoP&)W zYi8a#UUcnzz5UKqrdM4)JmuB61Cw$2t8n?Na0e#iDX&I#`4Z!*D!h$5UvD2jx}Ln^ z&QzxNcx9@BDpiwI$}7dIYLZG-P=pO6=<;igJDsU9R-w8CO8gCrqKxz2ou4ePGlSc6?O(< z1cMM3p@Sg_>8zdVTDrEk%pdnvb#-^G-PPIX-}lwmU9aA|b>6G!sfNuwIfLo2pD+1X1+rshb)8FYZ3?;`7t2Z+ild8 z{#RejlS{@OXvXQS%k}j6NV0;{TZcQ)j5HE?b-mq2ZRJ{fVNNbthK42;e_+I0nvbGL z_yfwQ9vHaZZezmaD?LCgzK0c$hUwof6gZQy*f*$?N){ijq>?842KAg1tAD%2ibuo5 z;<-#VWtBgBN|#RNWUAqaj$=5ga}jgq%;$s?-iOauRgu9Qo0oJtf!jeSz29%3s^UVJ zCL|Iv(WpW+s*p&?xn4w7E&Tpb%744vR!!)C^~IcAND?l86S69rw@iafB0?&i|jktf*gc3ZWPYwd-ju2+47io;=N!!|79F@x|vo$x-Lc+AN4qz;Ef zeS{QS7Lg%?y}L6+BL?x9cts;(TV%++CS@WKl}twb zda7#W4}0d>ZPkjLTn?8HSrs*RjZBh6WH*^)979iI=xH*^IElz^jEtBs%Blm$$gpS1 z0NEi!3~;kekAN{MTvI;8wz&xGVEru1223HZUv3=gd{Fzlbk zG)2tLa9q5S2{YG$874fm&fo_tGsH$}fK;sf*|S+&?TJ)~$(a1}mNohjmS2miN-oU) z<93TS3_~IoQ?Lrys4NR&F(o(2%^owhtr}prC84^7eb6*?Qn4__1WU!jxk;`#CfB{z zUM>l|wkgoiEuuq4(d!i*GIEn#uW!^attz?7DSZ~*fCrv3`0jo6Jh8EbonaUGqFKPs zu!|=)w(y-5eja$rKrax|Q;G6td-{uk>nL5MD~V9#Z&2A2Vmoj}Z?OCA&s~09ZsXQ1 z{d62DDwU+uDR%B0e*r4w3=q9{_A#7Fl{X5dQW9RTI7U@1bY04|RU^&-P*g|Gqo}9i zKq-r{g`SGz^wx{AqpAZ#Pfv+l4#&tUrr}|;pwj20vcx`tIt57rhf^XWY~~fqHD2j= z6CNraBoOe=z>sF~$Od?1!;F`YYLB0LwYJ#_uq@P(-n7H_FmNI=_NP@ z`&wZ{`E%AD6`(iRy$2|pA$LjNniZ5+`Xq1%@crIk_c&+5jvZSW8XCmy_7aKg%jGhk z0PP^mmRW2iE=m4Aijq_OT2||52auGJ%Vg^1>)hqg#ghJL>`M)?1fEZq2hLU2w&XA3)?~J)^OHE06wPc5|$-QbS{@kI$gRxW7e8uS)+zM6Ru`nOfExDlNHS2@?oSCB@fD3 zb1W-6zMm4gG8rp(y*ysIq`e%-S#vCF)UapLa!Hc39jaIQEMt|wp?)@!BoU46%(Y#L zN>`>|P-OeFz?G$xehOI);s;5m#c8Ojh1XkL6t8#0?3qr>`4hX)~4|kv$#pyQKCMHqDmTy^*7#1-xc?&&O+HKT|-!n<2&yuJ7zprHf zio$@G^sQON=yCpsdxPCiRxRBWV8_|~68K_vTh=)x%Okyfb`qS&O5nzGOECz1v!l0v+0;t~{ja`Qm0X<*Puo9ot-Y{1 zxz1U&U*y_f9I}NW3bA=frytRieFl*oE5`|L?OPTLyJRIrRwX_dZsclSjDUhtv={G)T$PlcWFJR z0}qk60!L94{66oZSbX>v!!Z87OY8aMy3p!3YAt9r1Vfr1cmg=K%&wmWyupz6$v!Pq z>j=}=Tux4wBsNffNGSs!0Y2jM@KQX%Hbf9$)wH(G#_OG2+LnouPA3@{*nB{~yILw% z{4T9$4)8FxlcTkziOVng3}>8r5)OxhOeVvw=YBHZwezu8=3UgWOOo&-V(@nXuOVvR zK3ad(=C`NdJjU`p$}4@AZs2m@?pg{ke>P9aGE1`W4HKeqsPltdpg)WbgHwf>Eu3tc z-0|~|hbFWpv$OLAX3acu;(||tp`pEppW*|zU@QIXnKQWM+uxur;Lm;Na5(5xUPbmt zRYt;?>kwI(PsLugIJB(e1w@_F`|7XSJn6U(dq{Hua00^pz8zRRcE4;gheFAwfUi?S z#SezG6A|G6Iihjc6BaAL0%Ml+m?rGp3tl(OY6V&5AA=$7;XW<&zf3bFRAklZ(t6zF z1*v^y@ufMXe}4UoTyV*kdHt_%A?X#ZqdgtC3)nQYtmBL2b}mLRq^ZG>_I=`1AKorQvVe#VegD*xbqXCCpd&1Z^(5J?3S7fO7=O;=Vjo%AS$EZyw2SeKNwUB9=@l>k#x&7K& zN9UDZc-#Vh^8H?B4?aS8&}2xTOGG)2NUGuObXt1TvM4uc`~>i)p=BNO%j}mU7}CCm zT@1Jq{2pko7cYPD%t5tU*nI}I%S`wF^1Dnk5h_*uEr``JM55!34f^`qFgD%`Mg}uF zfU)N-jN#b)cwAW!4y%O2YJV)IthaB3Ld*vq8Cup+cDNA?X-?qV`P@B=DCCi>LpVpRZw@+5CX~t8H z=k^VNE!(!UYtQI%O7iTI9g}QXbY?Qr8TO5kjzpA~lS%ny%Nn(^s}uNP1#+ALwB>IY zj6ggAd&A|^ixgq$gpL}|#kw()bvmAaSP>6FA}KyMN<0q@gLdFjZMimGgSf_&Rs1fk z=ThKrz#;(4viQ$?@5jpPs^pGyP|yA$D6UGr(`gveiHU@KVmPd-LqqDuWK!N}S?E-j zwYhWb2ERx2>R$Kgyg3!}G*GKW7AMVWVO93c>{ggtBO=UN~xwFa=J4Il$||koHtCq}>z@ zX@xRMR77ZT$W)Y(rbcNh+=M_VH$D@x<);V`+44=bVn8)uO4xf}*bFF0JRE zC^Pg>6q%#vA4yYVJ^R8DE@=wUSvQPsN^Fn2dOmpb#&HXOH!taIL5%5J5O#hLPOg_b zdi#sUs{}*Z0z`Gjic1#hhOnP|-QcQxu#g0L`?Sy<6(3=vE!UoLyO8I!6`qt@6u;nw zKHD|ObCEc0z`{y;{VuJ?I`ho#f)1=RXRYL{+4pkSD|fLkIlI8o)g#+a5YrNK`Jth` ztbOd)hi=G1m)6sU@P3xWFihTfYg6w1E1f>(2ND>T#IA(nn-o~_ZYy_H0a?M2wg`BN z^6NuKv1<^9lVZ|4zw+PO4g4$^(!70I=wGY(K4877u%OTfF?%vQyA3>JMuesX?+k#? z4Q+X6s3;60N_0-D*LSNiPMsk#IL;0Hr6!7hY}1pRIcp^=Ub_SHpokEvj$%WdPf$Fc zPEj7{Z{v%hpbFGnz>xe>u;RonU<_HA8mNKu3z+BaOqgcgUcYj5VU zLZ2y$u+tav97_mU)iK>rb3Lzx7KbhiEuLH)(MRVk#iK^BbQzyxWO%x;Wi1YQbZWzZ zbEHpFqzoQ)eCCg4&lb*|e;fVbGttdMbKYG{@sFuN+qH@}{|82*K&zs)5$4W-IWtP{ zSHz?!BXWjTjJ8~xA5T#5+dM9q*A6bn_|GH>=Cnc67;|eXO`W!Ua!#y8Eg_C;egZSC zk_r2A!xDf6jnB|E>w(<+_s_bIu30N{?;G3$eC3qS^Vy@nn;X^ubj`dUMbddEa>`^x zJD?O(e1DPc)le&k(8Yx|R$55KYJ>$%PXiE1&A_8Z_`CK8IcxR`0SbTrx+8YK)tu>2;(ARyw~cXSmw%6X zXUDd>FNCTLqI1Xz&OiEQ9CC`(&U%a?FOHTdmfhDK+w>HEWstBWYLG2zc@zl?momBs zNa*!g8HKKyzv8WZXK{M_Z*ykR)7pQ>F?G-4vSK8Z02{V0tif6DABz1zlkM?ijEPR1 z1mgygiIn{}Ny%F@V?Ui8Mb1)Dpsf*->7riUY8I&ca_)d11`r7rYeW7`e|I$peqhc$ zG`O~-M?9nk8gUFdk&c4*&HOd9{jYIc^An_Z*5L@GaL6f+Z+wjOpr3^;A^yDUJccuk z_?>$>qHY6bQbpBut~u#)0Q9H_*`49!raut>>r6N@&f{Bx3`CCr{#dhT=PoyLLcxf;XsF*#gKIlx%0YTpJ?-spa?MGX;Y_7S#{vYJ_i*)zSF(HfNSZxc za4N$j!%bMGgrj~KE2EIy)`a3oW0?|WT19cE**Sdlfj;f3sI}q`Bb2_D)lVKLKmR1) z^N3d|@1prw|mL7q;T$bVYH?|w$M!n zr3>60T+~wWUj{B*7g}95H+hvTWsKrig3mKz6E6{wS&ApIGYcPTw0JgS47<=H9(lQo4*KTet>1s6$TT4Kq>P8O%Liy!`>Nvdk?t)c70Gk%PW3&)@fWq8Di z;T-Vb+~L8JEF?Qsy5n6RhEK;e;6Y9*_%=3>Zg-*AXOMIW&khfcLGifsyfremlBqSQF{@D)#-dj4}$s; ztGWK8Ppd9Q=H{QRI2+*F%X+?qNW)zVe!6tU>aSOG!1_O}#T85PFFiU$3jDklNY;pb z*S&dk?;xjS-0xK0W5~M1OYb!Cz;9QT-}xY7+?DCldOl0()kvmkn19w&sbekETD{@< z&{Qiuwh~?(3~3J`LMg_E1?a$oXZ-Qax|dFwzaZ^Y$3<{vku0`Jmeup=-~aJyQ<0rR z_1-_faQe~L4SQYveOl;%Rd}fpHv>HLdOQ6e`RfJq>(j@xd%O$4QY;Lw&ahk82PO1f zhMdfpn5u;AQ-p`hFI%b?WS52PFeKSwNXWV@OBoqS7ba2DZLp&ux#i|nr{0fJU{t`9F-CbX~zPKyu+#Xg|BY; za_g?41&)mVRp67BB%QbEsP>JUI%mmAw_^d~<~CK`*fMX+?(ng%Z`3HHi;JmzTU$S3o5oK8R3w>JX4eW(6YpxBvjJWxJ4h%|` zMQ8uOqR##S05T2*#js@AG&}6WPegdEHv%8;UK%Z18uCy?Ivo=dKZ4iC74bOb%wuXY z2RW3k)~T@^QNS-qQ}olkQ#`U2iM@Bb2vD_46K7dYg9O2}zc4jE#*hPqwoCmDD)-)`39o z(t4T@L!A78wPGm%JEmNs3DUhZx*pr4Sccc$qe?$b9x7rYmcQKYC5p%5&qFMXnC1{< zU1;@Y4)YAzm7=Z-t?nuES#ZBfG6~jP8|XxMn=6UctXp$!;A9}yy)@cCwUSM9uyW}5 zm`igPq7-Q*d78?t>ziSym3bNXqTO11dg(6sfGKH@3EMsnD?%l!sE>ssPnc?F z30oTBLgywdKn6El&R%52Z#6Xf_g5sTX(5?74%a@n=RgqpkUVUr^H*X-%EPtgj^*w= zv87tO<=<5}G;gA6A_`Y)b~RpC-yd4L?`xcDSUBQv&^ueJQ)r`zwZhJD^#l#R9)Gb^ zNTs>?4WpZ8q0eiPc5vRn+?~vu^O9Ek+->A!pq|{WwvQHT;$$RXkoc6gOzlw>U5p=? zkC(yKPz@BY8gnLVRdy5z94vaVb#T)+rAN$r+Z}U)_q&}0#iIWDj7?Ve<1q1y7Lp7@ zN(fLuw=}dssCXVdjaJio{ipmoh8{kjQT^U*CBB2csgvvZ76>$dmY8?rQyc~$OOXC0 zBXPIzFi+eeMdesTFL*rhu)jGEY<7H$OX%J{uY35NakEB|RpvAA)RTWd!Z}9Pa~SR^qdb z_6x0Kw62KWC}0FJw&%9)^Jkr~Xb>_IX58<(7;z5%ABW z>$X_v<5Qi>`gsab>ed>_nG)q?1aU9!EMTA9W z$#W2hP0-6I4Rz$V7;WM@3c*V_Eg4&;sWKo6UynM;RL{Q1N zeQPtvS@MI+2&wcS!)EiT0eE+FmoB!(Ubk&;@RSR|_^Vzx5h)`m$!CQEzd^p_biRI> z9>-P-1Ov4mK8uC3?ixvdL7wY&P^6Q>sH0^%9T3plDg|{F7tK5l0HJ1C6;+}~Lxaw7 zV@2%^`aocAT2!ea&$@n)Lt)fksZyJ5ZoQa)6H}z1!q3@$qTBbZEHru&vwoi|5fr{A z0!vJFE!Xkmfu@;)X^BG(EDy4)o{Xu_L#tQbUelKR!0Ypt)NE+ur{`EM7A9=tx?@L8 zElKU-z89bX1UfTB>$NF?c~2^b?ZC^_ge`Tp2ncgLkVXz(BrCT7DP{(3A{cNh~-}0J$d>Hhoo13pYfBxoixx9fs9^Wra=WJ z$X)O^Tdr!p*ZbfVsm+h&d9k+d-P=r8|GpQu1G3JPeNWv>@0*-U(R5%LHU)kvt{)kB zFFx3{SWP<`_&Ck5ZCBbzViHMH<5&Fo^WCIQt+ZKX^j$>Mw(t!7ruc2st=8|a#o$qT z*Yl>=lmX#i-@vo`awvrmad6*$=4a;llzL_cV!lM0FSrO=x!$$4N;KHW@h~?1$};8^ zTw6C@5&xFlT&B-A8b*%pewW+mizitM2lL`4)EzS;taI11;0!lFAt|K&1fuCJ(iw;5 z4ampt^P`8GNhgi|sW>uu-RkHl-89fw4yhM9RjzamJYVOvY$N#4>f> zS(limOA>**(1ZyFQLhkiUbjUa{qY0IKc8k#Jx|0idrJX&!f|Z;QTE~_C;?2#d2X{C zTw1Rr2Py7K({*fy&By`l=NyzP(TvK2!o1b?{{6crbi1r2)=|dpF9=CFzDf4uVC6xM zIL#CJ0wv}9$FILxMtoSa9Wsk8sQFvMYR}~6PX2c3BLE<=^zY;EIkN=1=5*Im73w2p zoemS@{8%xHqRMtq+5x+$t(hLWO+Qf-{e=L5(c4=kqfbMimc9n*>iwB+-PW)7g}d^7}$ftxdXt65QxS>Vk)VO@1U->4Yx`65Y0cI zzjKCU;6mwry$Q3TQs5*@ux=^*yj_(`ur8dnz&bv&m57tRFV1YsA!weYm`b2?xS=^` z<-S6L-wZlpjFx^uh0}wtqx10QIhobxSHLFwMKNH?=h9`r-X4=^IqJ~J|BLdA^RDnOUHX-n;u7)dxs?{UZU#wfVpDuh`#tKoc z<%GWcmtI}z7PS&~)*&wc-Kgc1f)i{hL!$hjEOo7~Jv0j6vTpBf545z5*c7m%4`sfJ ztnPm|7d|WIgC1X)#hE?djy{yq78wD>GLJuK9+cD@CrP^tZ)YaRxT$`pW_OS9W?s$( zmbzOaX@*sHp=vbcPE&Fv*~8zSjWrddw{JlQ`)D3u0}|-z{nyhzfeZ&{Jc!1}uDN?OR;B~W^TgxRfzhlkN$m4C_9K!_yVFfRdd;9LMkDB*yQ9klpkOjtMR2`k z=2OorFSk}lldT?~cNY0s5|H7HPX%7I$8g8OYE8b>4l|>a;X=Ik^Oxo9GQa+j#Z;Gf zk;*y!nw&yJPJ|1+pO!e3TBHdn$RV#Q8?eb~xk_lSW4k2*EI3g5KeMESq}kK+nNNt@=0P!)GtJ8i9I=zDrs-T#jiKsi z$yLK!)=^$<@!kdC@z1O7_N1C-;NKLhPyzQ>*m)Tb(YRhiD8q=jwEP0vsZ120^VO_m zBlOu-EcMVWitt-lI`+Ty4K*o>UBjtWIO;wMGp5l3I~7#i<-Rh=rBTH1g}`kHDk5 zaajF<;iGh!O4Tjku}yXZR`PKhrjD?UoBP55w}bL{tplzAnUbt{?4!Mqj;~UT2*W&E zY4)vLX(3vWJTp<1wrHZfw7BxwpBJSq@t5KbMFs6^*frO#TiMc4OV@J!C*3(Rm>bQb z7o{49vEn^d5Ng3vPLGYAs%I0v(wf$hA{q{Azbqd!>bw<=xr_TkUlWOPKP9IXhu^}; zluA~3y>Wc9f-a^udn)$q$8Hh)z6I4^bG<%Wv;>0;t6H@A%3cJevqOzUZ^h5tc`u5} zM0#-ZqRrdYQNVo{Ebq7hjbit^n@YY3_B=&8nH1xM1Ym+vbTl6~jzTRHwe0nn^i?BF zgyH6mH_1GApkZqIi7=kpVYL)I6?A@fT@^y*Y1$+Tob)h@Geuexhe{bE+q^5gGUfOF z`Wdts>9NC|pQ9sfVVB5^T>jjG=A3>DrpJ+wsN_6^r1(pWsZC)L?4+_(_z2<3*bKz| zxc6%tyNRDmZOpQ;Z@e!V)Nv!0v{8DtYfny6fY&b$Yio4Ti zxI1=>c12!(ek9`{X=iRVOHkpS)0y;L1c(}}_|p<@ayz=tLp!)_u7QIi-zH1C=xb15 zb=Y#bkRq3R#E5s^h-MUIw#L%CR-MVL)C3!Lm&-O0d%(^@&&63irsQsbZe!%!$ei8e zXiCW2P&`>+mJB1OB1>(~{)-qr4XuS*G>rwi>?URM+rS>g)&k1e#jH{lMwSlQb$o}6 zG>xSY74Ly(Cycj&quDf;EFl;@{HXw?7@l1^qVOL!iYqOeJ0xQuuHJ2EGf%EPkVAxj zgq%W29Lpwgk&afU89)u){w1o^W?WE6D3Jz9<{np-xV=7>yQRWno$hJCQcq?{7!F+| z5>rM{l#!F&KD(fRQhENolg960*lzd!QHm*GZ?+iND5Pnr@TgiC!`#)}acO$Yo0f_K z90C0(3_47tFTP(%!>r!b&+ezD3qP?p_Iyh_t8{rj-2T0M{Vn?Rjxi*evB)O#ItEdW zA=*$Q5^>=3ma9w((QI~FTrJ93uCf7PxI^GxV}e@7IqPDpVftL$<_mhax&@?;+Noc+YZU}bTh)vi16@3FL!rYPjVuTqiI_mame&1 zSv+Qhe`JG=BbR&kJ$;9^@G+{*xs+vmcceVbOt22nx-2=;lKa+j!K_-s;%7W~pGYE{ z71@`acAZJ1@Mn7*^h4U1s@OO1hu9(Rk0g7c<+td>;(E(82RTKTOw+DTK3NQ?E2Wde zcO48=91nkxrMi`$A#GxLPGxetLOA6i`S7q-k^*JzH>$}hgm8tEA!c8;kIl#nRLOTNYn(gNEqYqdJ$my#IEjFsn2}B5z;p{0E+oPjr z6PCIG+Wc1U4Ti>+)q?`&afQ`w#_>Xrr;-?UQ;twZxZOlk9d!xAop)-4V}N?-Crzm} z(=DV)HoKwioBQ$V(qn5Q@9R=`!B>EESU)nsbpymlWD7OWImLJw3a>+U2-atDsuC_? zGWxs)sDFTQ2q-2MH&%E%qrnc4Mo=I9XZP61Vb|WsJ_;ZzQJ)x5lJzXiH?^{Ph~K#z zA*{d|dO$1+=X4+s{3er@K21IQN%I=d!#A2L>LqnlyhCvuGdq#<%brxkXZ(L z2XQ+e3#IgqNb6?%{-`-Cp3pADDo$snYai2EISm_!o7ugGoxAc?@_NGf{hxCxdkVSV zgk{#)q13@2aNP^PIRqC~43_ixTDQ^l4Ci%iO1xwW(-Q5k9b#wM5!s0;i`N^^|Rk`{xc9cPsYgO%%*VeM(c@&@2?XFVucbn`Mzz5R`I10^f zu4F7%oGQROoaHYf{H6zz!n`{Fk>WN_o01h5&J)FY32+l4DuS#s0Ye_DDpUrvh=!mD& zD3r^n{-!}{7zN40Bo+4!enuBK1FnX-RmW3zXnEPXh&lHZ5fw1HZ11gVXWeLvjbHcLUugy2Xz1#0lD`kB4)Pd;@tqXlwu2t**R~NM*p3ANo5FYff z1p!CoU`)C0JRo_0tCRHt_8rYT@KD$4Pr0RE9WrKjhVy9*r;IV6w&P-ow5Lil;J;GK zf3^{0bszTaUEOxdA@N0Ri~^T&d!--e@_Qg+TK*?C!MWo95ljT!45oMS?-ykMai+8l zQ6|-lpM}4I6zzM};f~eE_lo1%ZPv{u^5YsUALbp$`?N*KOSzp*B+f?J;?sP_5O*oZ z|Gwl;5 zi1e97t<_zhe=8bY(6i{| zG%xhS3mYva8}LXAjc-ffkj4gM?a=P@1d-QVoTx%ocim_#L+95H?av}y3eewuAVCxB zH$KZs=lPY;o>GIi-5rm8e=Vd>mKXI3QiDyr9FNbKG=^=ZReeh?kWcrtV()*yK~X50 zaUL1(Of-vZ-aEl)nyiz{{JG}Ai3m!7DA{Vb9a5_)28esyM2NQJbM)Um{6!aQE*~xNcGl;>+N+D6^5Eyw z-dTo9FW#9&c?lv@hz<(PZLdnyOCtS7!IfCSXbe@*)N9#OGTPGDBpur7i<5YY*LL%A zaQ~U_%MtHL3H~eQEU6kCL)(LgwI`SIC3!G2C9?{_>y!tpOw=mbJ3{;=$L$m|_3q>cOplW#RJ?7`WPK zDlrG1saiC5RBRE)yZxrrNh8;w2F05IIvZkhAZnZK5gWXpCPrCG zpBU6fXRxbA_(U>~x1kQn{CsAiH&N~5z_pABW;TvgL({0#z>+a?Y z*Uz%lU5;cB%5D|#R87MlZ_JcIIJ&;kUT|du768Ag?A)v8=EUQ@58u#zK_&X-CbfNq zDRs6V=F~=lJVQ|jFn@Y?f*AE)2WekNs^7S_zw|&-;)PooL*R_2K``T|LAfpVhnlcFC=Pvt#1m4$ zR{z3)Ca0MB=1qm5PbPR86Un=Ipzw6}-a2w(n(tG5CNNjTSK{9Xk)1X&(8=#8y!$QE zvEtAl(H6{vxLvxtV@=wXCsX{q;5vVHaId;$s)xebqW1MSuS_Gh>g)@}IVW3l<)bCU zeqz`@WzkRQ^%&l2kX{r}T4aq0s3h?borSZZa7nirBCD#68A)4 z{Yuw-AdX93{Optu{Z90llr60nBo$$7?dJ!Z&n8a884XNbvQxRUgn;b{rPC3&6hk?X zu=4k5YJcP`?$c;pG$f?8s{62GN}T)q-a^81bICxe1SgTT``>SRE)t|T4Um?3uxT2d zHj_VpcR?7H@8=C>h^l7TbQ|3koE>o5&w%NhH8H8LiLzFGG4$ z_pRN+$Wia7kLi#lh4d64++=mHIPlzSyQd7L?oh^lY{r_q5hcHRdz5j#Ff)?U;O_D`%*3Bg}`$ERQ!+i5nwdUydS*5skI&g zbo@hI+7xMSDsaeKqI`DRj`fTR*txmMww_v<`(g;9#tMdUg#|g+5I@Tf&f_#$2ctm- zRO(0T#t~0h*P;DsF_-|Nh2Zf=_}4a>ZpVe+Kv4h$TWbx$CTuC@YKQ7Q#&6!U5|9V# z1PIjFtIC7)wC#{H_s2Qb`{J9r2+;}{U;4NNP}g-UxfB&IEi2$&e@GC3TV$9XFbu(h zK}kJF^G_=TLU^xDQ!Cp0vQ6^GHuFj!NZxpVf`qk$NIW^v3&T;c?}l3GSq#NoeF6Xh zt17`WbzR%k-aWD`uo*_{&h+?XbVf8}C6p>~ah7bygigbTha2dX3vuZl=oN@bl|!cS zv|Hs}l?90+6t=yPjw2ZU_HT8zl=;f}pP%;s{ii~Y35j7|R4u6;u#X%6UGl<@cX9c2tCiu+|#z zGD54#o#;*nq`O)>rhhglp3r8_FFc&r?6qw)M39nAW+V5SRrwEnJjohl2QE#ksX4@O z+~OQbZ2(56$3iS*a-;hw21bCw)iRfjh!&&JIQ@N{1p6K|0jaLaOy*rnw8m#~u+-zg zhOXCT#^_E2GfNqChU|Gpm7ZY6>BgdGLq3rDc`3uQoJ`_3JDs0yicdKPIw~3XD^a2A zF3IQyAp$NXNxVhv=dOvyK3yH^@12 zn(fG-p~}v|9WqM;Y$Wcl1BCN7)be6=O!gB4RgG6$+dZ@go4~55mIS^dikH94GzoOy z1)hKvE?QbF(8RSRg`Y4by3!l;G7c93#p+T+Lt{&pIko~PCf}x(Vgvt;lzL2dnay|E z%6X=?00mYxSg#XyI}7IF%uOdb4Aj!&fVzWwSlcotCjD61SN-V z$^9fe5MlL}1ILp3KrE1fdfVTx^yF`UNw!0pV6VSC0#D0xcPiMPjA8hl5#>-b;BLI2 z$l~>7pN;czZKh$Dhi?jDYI{5+Gj&>wyYl) zDf#Uedj0mb_1S!gGRgbx-E|#MfgSDJ!%WQF_-oya=J07qhUvY=rb~OXX$nSIkgGWG zSYsR^krn~M$g3}nJ=wKFH2dv4m6eK*1}W7CAO}KXjt0mIOk$h09YO!s=^hv+^3|qV z^Fo73==IL-Gp0Yk5r)s2Z=TLySLD-_8EKPM3H)vr2kj85x|O@IP(^z>gVIkOB<`9m z2UCMB3lEC5UL&|i1t$b1Z;F>yj@V+aY~33%VYW-i zW5b5xSDtDzTV!E3WF%!KBDC4wto5=qH|a3J=Wpi@D-99n1XpVZ7!^947F!fO8HgJ% zdK|({9yGsYF|+A~FL~mrRQXk*{Z|}JYQ;IwGD;WOV6QWG8QhZ^u&8qzZvJF+2S=cn zhzJkt>Yig%Dzi7|5Zd?R@CNT*PQtVFCL^|38YM%9w4sGqX`jQEtO#8 zja7n3+b(Aka7X-y*`>}4D&8VqIxxy6BbH}|8!2DRdQrLG%zlBn%>*)9L%uroy-&mh z28Qr&*FDKem~xK4rB6$9$e(T(u2PnliSP1{_c~duK?oU0j%R@gL+DX{6al6yl)mrE@!-h>JI{t#Xtqra$q2gyaeLBq}17 zL)-SEhtRpm7(VhMzc`~i)U2M28i_wLndp%}DQ+p{=xjD!Zbp)Jv1QGAVgqixPmK15 zhaUF%c?)((4AC*Dwz9cJR^P08A@SQE_8R8Se!Yuoh4UrNbi8`{$^Qb5&76g{781#_ z>#r((na}h3V_TBxbUAzK-YWhRfitk$HeWx)14Dem70R~Jlv+H(;mCldeyj`&I6X>H zcP@{1u^5f@tG~fbnnorR*}&F%+9gq^<)rJpP@rco$d=>w==FANX>dI5og5mp{=>Ls z4@WAJFL{Bvw(FUy%2A(yZaPU}WMI$ylls>tgjT2aD-$M{%E1s3w8HW6M=T%ee$YtH zK0n+hkZW7+i8}~*s#hlJWT}h4>P8}$l1pXmQ{{Rq7YXU087Yien2dvfVSML?|A(aZ()ZOCY0gXs^MU_yM$V)L zd#|$Z2G?O$h2DPhpb#3S&t&A?Gs>FF-vwI25+_)-^Ei!f+EP_qZV)WjgfzQEv2?X? zk?Pb)#&y}oT_cB4IxZCMkm0RA969&Kex;0r^sbf|^Fe&AEZPz767DB3;>(C^8q*7I z3kz1<*wg#6L(DQduSe)k?~#`^q(m9bw!mSA4kCu4WwSU{XM=l! zY{7-ND@}!b(qzc%oS`}{O{Lh~#>;152&iS}h2d{eVC1CPGXzFvp72+UeEEMrg%A<#s{&m%eq4j}j9!&#!r znKo~GH{utKTo3whS4P|Skm97cT&jj#kQ$q@khjTi$O{StpXI^-vRynH1dD}Xh}{WD zbrxaa+JJMTZ9Ldo8T%JE%CPl!;W&A%)h_P~)Bg4M|J`ihf10tdHK?e27$>jL+5G>N zu~e*Xu`UL`z(?!h?BNEmJMZ94ulYvxGM~`a0$cwn5#o5HilbP({1ysD+P)al-+$o< z&k2<2+5_-3jV59fEG;T8qjzlx4U*4ZzjuS@MzsU~lpB~T^^dOyt5Mz#rng0yKzGBu z&QAr`I7JqCPK$f9nuFTZwT~ZyiNE*5cw7kSqx3yQn7{{gTS7gIaGHppqw6}3CdYY? zo*W^V9KnEVz!flj7z@oF;JNGD)6BSGfDl59m50_c9T=j94YnmQ-KOF*a&H<~ufcoI zaUAq+Z{z)h2NCLC5bn4yvZf$`4M5H@K3J#<7M~V>jNZByZ`{^-3t8@OPa5FT^pdeO zC91C;oNm0Kd13hO8`{BET&>L~qp`b@-f~4pu}W=IO7rB8GpL~} zgI8|zkNb!<(FU(MJFLxGXS{3A0rv^0ox{C5&hh%OYRh-Ktk3m%!Pl#iCk4!fE`*}*G_itp*tQtE%UeetPIF|D$HxP0_TTE4} zPVv_;y~x;o9#Ej4ETi3l@$brOA%Onig{b-<>~V5wj@nW8CTvT-jd#QEV8CV|DXRwm zx>g}T>&H8dh)!VQY=UEG2l9)v1M>RLE8QT&acIU6Wv=3`)Qob9>ZSE`U&XwJc3)Yuw^T9;Kfn;`JZGQnH zB76Jt1!=#DCf%GT>bxJinTQ6QwB4nzv_irVmGCrZoSDnEpVc&Ewp1bN-)uZ?f zTeT)wr;V&pRv3w!Y4j@RDOWh>$)cB;zhYwTT#sw#kIygd9U(jT&L9DZ`zwEMVo~6Y zUik9ILcRQReaY?tG1fR3g=A;5XSF~5DnlxQD)|KG4hHMjFN+7i12D6 zUEIM>Z`k56rAT+sI5ecmMWz#A2y=ww=G_bE0u~VN@dbr}V<1$ghdfr3V=`)GH`fgV%d2O)Y{kRXi3nwhuAQ6A-S< z;R0>h3`bGiYy;x52kOjX7PyHPec<;v+tXei5(><}+N)FPN5kAmexgXzb2mudOUq3- zBSOrFw}(zk6DlJtrQK75BKNktvQ!7JZC!)KmJR!49^bF0{fz&FH7W@mp&vMTS6s!% zUTe}!+3!r~SFg-nyB_VubSlB?v*q<Z8qWUkC5Dmw8m z|F!2-Lk1Ert*Hxl@yzsi+1}m{&bL1{7yFXFV2>yACoPDoM5WT#MxJJ^TtEf|CoFN_ z^$(ha2(qh#|3k^Pr%ZI`41$ulllM;5eG|gDI-V-iz08EiDZvDMU?RPl(Dj`0bbdf=d+ZS37q?kbw9s!01cK@9iBPszF3+%F#dg-)&^9*JN`!6V zoXKVwr~T6WoL({E&hk>7I8CW(@s)^-rdlEG()I+k=Gt=9_boinNy0Xt9St|@+hp}^ zhUiGQ)GURcn1+jm!~RypY^WQ;&i&xYtdh z#yWZU0FOPz*35T*Khse$c79KeQloae&-C@JOR7E0S*cflkN$voPy4Egoaab%`O9v7 zfRb>W^(}e-AW15HAUVZ;@+wTmt=GW3qLQY!Z*}G?uB^gYQ}|WBVf}se6x3A4lZL&~ zAfer;r!}B#@{vgCD3I;tLFh)E=X`ZWl%Sz z73zbuTg=eQ33}#ad;CQezt-~fK9fRXZo*QZe*(R0q6z1jF87LIav+&b+%Gk;e(}(3;<6c7GOOzU^W4DJHdTHAVhCw#}7>zxe%Z z@2!TMwA_u;I61kR|32oMYu4Xg-s&5GzBTS}Geen|u*4T4GvzDF}gTVOZs6j%W}PT`*Fv zALqq;cHv%;X`8kYlUr&ubfdMnA{+5R9X-~~Iz1_BpMvTq}zp>9I&p^r{Sy$mj7+spmE$D?326mR7)u>DSWE{ws0 zT4B5&N#RJ+L@q<&&IR7>sk-2~81s9NGy3D$V_{8i^ruZN`ei;?tLF!ot(=;b4#nGM zJ@5F(pUKGD8%ngPW_C-NRny^*%&xxwVQe-go`;&zKPn0n`z!zp8*@4-k& zqTpEywov-!fJ7oi+1vv&Shv4|-BMfl=#EaW$}(EA3avyjusA_LEF7SoC zI5xsvL3vb2)4lkmVn4RkOEmn4)a`wyJO|o#Hy~}ADxe8(IPE7l%G)<4SIzzrprIQ>durLu2&#*6m2|iZd7dRcvN`+tTZQ@aM0B6AQliHeef1MCPZZOPMNtM#9_f z#-D@iNfz?Pc>X#pGO&JB@Q8X|00{2Ibh$G8vqS@09$3<<&cN6<6H`={SqA=Ti+=b; zeg!3#L~G`bNUxoWrZ`%W16~^^VGARq>UHdgOg>fe&ri`XgcrFm-*u$h3R#=9x4$-$ z1fW+}bE$nO0ES)s;`bNaKE@=IiZf^wJPr;GN)_s%!5BOr$sH8t;=@tnjVqJB*JovH!uJ|cL098{SW_!Jm?K0a^abxkL@#Ejb_c zItlb*sDAucE4B}fqzyHD91@V^NV@_1-;SMWO6dSLb}9jMqfmtD(@(;PK?Mw-)2PQ= zey^|u@S4M0U^x3c41HUyY5O3gG>SwUW$TYIVST^sirHSnC2zq0D#Z(^k#*RW*Bx%4Z`w5tk_pr+Y!mIM)OOln2t_d%Z;iM7E znLyui6poiKN{%vi9~T8~!MYDa$2$w0*|LrJkz`JOC~d_*Z3qeA!pXLCn9!n;^{kM{Q^k}em>AC~|MGOE(GlBOa53nA>XkpKVy diff --git a/webroot/rsrc/image/sprite-tokens.png b/webroot/rsrc/image/sprite-tokens.png index fc02173e40f0e18b8d5e11c7b893d14752cf1df1..acc7f1106e31d47f3ff6e0c67d98968fe9522e4b 100644 GIT binary patch literal 10736 zcmW++1ymbN7sa7C#U095v;-;c?(P(KC?4FU1&S0Y?(Xgm!HNWTcMtB)pZ@1$cQ$Y5 zOx}I>&U=~N30F~)#y}-Og@J*=kd={8gSJNh4iqHlIa-1A7TSDplF@a6fx+zmcfckx zVUohYP{GJbh-!FdoM!s?YRt9XcR#b=%Uk||Vf&OL84>YIjQQL5DYJwos&9FXrgPsU z@*M}{cpD2TY?N(|mY&8+h-zNj$Kxrrl z1K3sg2DlBlJGcqhhPm-Jm`AFQAnrIUh#c>d|LU#rv*XR)K~nqL*%D^5^jro7$68RH^&fCkO$E9ZpDb-+L;UbA zd2xpSyobEAy%E1z>q6nUyAR&no|ZEYo`xpZS`%Vw6jfpa8@dS{`g8LR!1IB>lZPU+ zw&qLDE31ALO<=S;?pK-!L*)hh5iyd8vdMYVX`Fh0&a>dKgZ%}Y^2}TQ9D|##7^az1dn69jX^u8!&#MIIjd7LeK08GlM6QPY?ko)E&{6t8Ya0&;!B;b%H8o zG_P-=Dy_0hbcAPtMT;vt(t{#l_Oe(R62dWTZG75i?|xnt0ZGgtT((GYl*K8O70jR> zHHfj7e*FEte}^QE76n0J`%L;!F`xWTe zW@OoiiKH4*T|3A%T{bZ_HgBK0>Y&{P9j+3ktyWL&%ML>ONZP8fPiw4g*}^@(6`=E% zbM4mZgoT``pd{KpbRH*YQ)D`u2_;uC8HTBgqR14>qX~s6e510PBuB+mbSA``Kyuy0 zF=o6dhJu<|LjDTdg6;{|g6GM&s`6g^Z%*=U-W&9Kw3A&6QjlsEmM15Ro!P5sUU=FG z00yzHz~q>NSY%l+5oK{ouEvG&pIb*=|G2V8G zo6C|ic%nz)vj~bt7@ENX^8My?BG9U-`R?-r8G4@-ic=dNS({p_QIf)ksGqwRkB$DV zFR;3FsSUCug6j!rvG8YWu6ishE6~gWpM@L>yFj%OrN2~Sw-hd7(30DQTi2_N7x-p0 zVEVvAh6qb9=S+);O_^sCVpmB{%(p`@{PXupz8b;&6nSSg1F{}@v-IcGgzuG{-}kj2 zk+2cpNEZ=OKt_R;GCfA54kZBui*Q6k>u-E&7uUTQx3)kD#=tmsHbrPe!t&qw-FOfp z;EGC(3w$`D6li}~6sh9jk8K?%%e%Cw;vq7{e~KTzp_jKpIEoU{v~WDbQc8}5U4GF4R{DBXu~ zE7g+w2IIu6cDNFh<|OVXC;|mb-tv(M^#racg!DKdRk;AZVc_F$AxGWDK9lWhU03(- zDVCS%b8bNsR@4ap#WUJZjZ?V02~aq?wwldBshA=BG`AFLUla+nX~Qk9MyIx8m!iA$ z^mX+2vYK|dab2DR6w6VS;GVJ$ClGxhR|*kP>1(2~o|@PDR4)U~A*Cnom#RE0it*`V_A^WRCYmUI;-Emxt_XunC`!U-KK^ABmm69TeQD z3hVR4k?Z;NsDJ<>j~qBAGh+)i6#1^t5k>jDtJau=O%{Kmn{guDjfwu=U$<_{K66=( zwn=MJ5ly3nxJ@&pYseYvu0_Xz3%6Yvbc%BF`|@Zk<5*89pyJOjqiwQ}A6{%2&{$Q% zFBTUCYK+jP9#g;Acm>~m+W1Mb>1wQ?br>Q8tp!h-P$I%`Cg(aA zHn4RJDQ*n8=}!hv33xK$6mfSoYO#%Vr%yQ0L-y}@n8`2k)sqIidB2fq}2&wtKSZn>NxR%Lb8?c_CCY)FV|-6F_%W+y|{ zZD1*7OvEKMT_k9U^LwS6_uz_CkgU?j-`zb7T#CG=gdbLw17R#<%xqlsr1>d=5@dm< zpdLHCSTM~cwaiiVxMd{p=xg6tUl!;Q)CZE<4QxYO{O#HTW0!;aNZSI{8YB``_!*|k z{c!n|x|?qIzRijE78-rIbXQvShQUAKQ8r`QbzhnaRCK(sjFXCNB0CKohfdvhoOo35 z@MF1ChRs>ge@J|qveBKtpD0VcTT#=WSPj_vVxUg&Ebx73N6YD0FeIA`QC561wE;Kj z^M0nwW16A9$Ghv*SXtTX*(4`fTd=scs-{XJS#q$5OuUASG?Up#bAR3GPm>oqLwMd2XB}{N#2JR{W4l6n99a*WFnDA0cn2g^N%^Em4x>H@C+EXljbIJQjsx`XF9#u8K_^i_O^l4`KyM7O@o<5{*+7miK|{e!0D=|_P;kte<; zW^Az((5KC&Pr(6*#Ge*nUho$Q97)N*rht*rmFyIwma>i&>%Exij7!hbM<=BnZWz@nSl!0^O$5UKs|2zj$qk>5`5dOj|e~<8cMmAG^=elrd-|84kl-Ej? z&tB!nOA$VJ!4Z@?Zd%rThb4HG@3PJto6r72>>giue^ST<7}^0!$275ML7o8(iz zfj*b*d0X#QS|aBGTwHJ;607j@y;z%Kd)3S*ZRsq4dnYuKLM%RK-PC56{so;v{-;F- z8^`sv@FHuYHloIk3gAg zMeSFIeOxq>NECk#wZs>qmUz2GJo6ZBvU%~YIOJ!1fj|nGZBnnvN}h_aI!AeG0Gu)9 zbYeKH+G4X+z-oV;#WU(SLohT#f`}&wfZ?Y13h4DKQc0Hk{-NmtCF`-J5^1H)F=VR_ zwC7)4oV*!=t&{XyJsDP++!MAmhQd;JXYxvRu3D4^v^2(T;KuJXJWY3>1&5yQ^7boL zH|shUw|4{90gHRK8_9srztKvkUe7Ic35}7=`Vv?(Aib=C11A88%uRGI>-_+uksJ=~ zz<+rcI|mJ|5;GC}3ydp_O#vb~eG6pVPFK2zxA2mP^*c0JcTj>iQU&i2*0d&Y^8Yc5 zX%JBLWfLtiVv_a`l2UQB&4^P-bcV@0>;-<#N1j;i_YVcUcRt?hlS?4ef;asPj~6(> z6tZ;4r5Fh8h>~_%p_D8ZX9YOV#B8#0;Tt z2rSqs#JFMNG{UacF5gM+tOleOy(Ab zCT`rPR<{J3-F?x(U3P3FjgUrL+CS0Aeyz=o8}1s5+|%RYZH@m|Q=_pZJAFj(%wA#M zF;TSe-gSju^BM6bzk0EfU;faXcMde9QUAsd%myKYpz1&J@-CG5#F{Owg!G&L)zxtQMgVma z$mhM0KHYu~fpOHo+d~UH#-;S#uA2WncK}ECwEAf)Jfwp;dnxnXd-Zw|eq*_=GylxB zKv)H%gZ2Gs#;0Tbk!SUcI?a{U)QPT*mNLqosl$gpa3j-1rlmlVqqe~lDGGooDcW3j z{XvTFcU8N*k^!8KB}w+g!qU_f8@Y|~DrSW%Z>G_Qr%u7+!xgQ2*ww zvXZp?9lnL>DVX~w`t3M;RxPwb+_2k-F^Xt<{b@>xzY-~fSZ=rV$4mc*?*Zpl`f0y5>S-oSgVIc1DxWhQCF zA1{^01r9bmA&!nWwM03;F0P#<-`K*UU2Z1(T7(|zab`D^P)Jog>GjB9LVu?;TcJ5A z+%1Y9C5a?oh<@V_xoB0S&75ujQLG0iLilbEO6b-^@%x+3sNGq1=`M2-q#f&8DnSlM zDoUTfJ%WNXVB#4j7Afs?UrNvvoVs_Ihn#_rpO|sI7YPjRuls^0DY_7e8PG~f9FA@P zBap~82I%zvyIcQgyDR&4cU≫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 literal 6058 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 Date: Sun, 3 Jul 2016 17:40:09 -0700 Subject: [PATCH 06/34] Make i18n string extraction faster and more flexible Summary: Ref T5267. Two general changes: - Make string extraction use a cache, so that it doesn't take several minutes every time you change something. Minor updates now only take a few seconds (like `arc liberate` and similar). - Instead of dumping a sort-of-template file out, write out to a cache (`src/.cache/i18n_strings.json`). I'm planning to add more steps to read this cache and do interesting things with it (emit translatewiki strings, generate or update standalone translation files, etc). Test Plan: - Ran `bin/i18n extract`. - Ran it again, saw it go a lot faster. - Changed stuff, ran it, saw it only look at new stuff. - Examined caches. Reviewers: chad Reviewed By: chad Maniphest Tasks: T5267 Differential Revision: https://secure.phabricator.com/D16227 --- .gitignore | 1 + ...tionalizationManagementExtractWorkflow.php | 274 +++++++++++++++--- 2 files changed, 229 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index df6c16cde6..d9f44b6bab 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ # Diviner /docs/ /.divinercache/ +/src/.cache/ # libphutil /src/.phutil_module_cache diff --git a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php index 7714ad81ab..1da2e16e27 100644 --- a/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php +++ b/src/infrastructure/internationalization/management/PhabricatorInternationalizationManagementExtractWorkflow.php @@ -3,9 +3,13 @@ final class PhabricatorInternationalizationManagementExtractWorkflow extends PhabricatorInternationalizationManagementWorkflow { + const CACHE_VERSION = 1; + protected function didConstruct() { $this ->setName('extract') + ->setExamples( + '**extract** [__options__] __library__') ->setSynopsis(pht('Extract translatable strings.')) ->setArguments( array( @@ -13,44 +17,138 @@ final class PhabricatorInternationalizationManagementExtractWorkflow 'name' => 'paths', 'wildcard' => true, ), + array( + 'name' => 'clean', + 'help' => pht('Drop caches before extracting strings. Slow!'), + ), )); } public function execute(PhutilArgumentParser $args) { $console = PhutilConsole::getConsole(); - $paths = $args->getArg('paths'); - $futures = array(); + $paths = $args->getArg('paths'); + if (!$paths) { + $paths = array(getcwd()); + } + + $targets = array(); foreach ($paths as $path) { $root = Filesystem::resolvePath($path); - $path_files = id(new FileFinder($root)) - ->withType('f') - ->withSuffix('php') - ->find(); - foreach ($path_files as $file) { - $full_path = $root.DIRECTORY_SEPARATOR.$file; - $data = Filesystem::readFile($full_path); - $futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data); + if (!Filesystem::pathExists($root) || !is_dir($root)) { + throw new PhutilArgumentUsageException( + pht( + 'Path "%s" does not exist, or is not a directory.', + $path)); + } + + $libraries = id(new FileFinder($path)) + ->withPath('*/__phutil_library_init__.php') + ->find(); + if (!$libraries) { + throw new PhutilArgumentUsageException( + pht( + 'Path "%s" contains no libphutil libraries.', + $path)); + } + + foreach ($libraries as $library) { + $targets[] = Filesystem::resolvePath(dirname($library)).'/'; } } - $console->writeErr( - "%s\n", - pht('Found %s file(s)...', phutil_count($futures))); + $targets = array_unique($targets); - $results = array(); + foreach ($targets as $library) { + echo tsprintf( + "** %s ** %s\n", + pht('EXTRACT'), + pht( + 'Extracting "%s"...', + Filesystem::readablePath($library))); + + $this->extractLibrary($library); + } + + return 0; + } + + private function extractLibrary($root) { + $files = $this->loadLibraryFiles($root); + $cache = $this->readCache($root); + + $modified = $this->getModifiedFiles($files, $cache); + $cache['files'] = $files; + + if ($modified) { + echo tsprintf( + "** %s ** %s\n", + pht('MODIFIED'), + pht( + 'Found %s modified file(s) (of %s total).', + phutil_count($modified), + phutil_count($files))); + + $old_strings = idx($cache, 'strings'); + $old_strings = array_select_keys($old_strings, $files); + $new_strings = $this->extractFiles($root, $modified); + $all_strings = $new_strings + $old_strings; + $cache['strings'] = $all_strings; + + $this->writeStrings($root, $all_strings); + } else { + echo tsprintf( + "** %s ** %s\n", + pht('NOT MODIFIED'), + pht('Strings for this library are already up to date.')); + } + + $cache = id(new PhutilJSON())->encodeFormatted($cache); + $this->writeCache($root, 'i18n_files.json', $cache); + } + + private function getModifiedFiles(array $files, array $cache) { + $known = idx($cache, 'files', array()); + $known = array_fuse($known); + + $modified = array(); + foreach ($files as $file => $hash) { + + if (isset($known[$hash])) { + continue; + } + $modified[$file] = $hash; + } + + return $modified; + } + + private function extractFiles($root_path, array $files) { + $hashes = array(); + + $futures = array(); + foreach ($files as $file => $hash) { + $full_path = $root_path.DIRECTORY_SEPARATOR.$file; + $data = Filesystem::readFile($full_path); + $futures[$full_path] = PhutilXHPASTBinary::getParserFuture($data); + + $hashes[$full_path] = $hash; + } $bar = id(new PhutilConsoleProgressBar()) ->setTotal(count($futures)); $messages = array(); + $results = array(); $futures = id(new FutureIterator($futures)) ->limit(8); foreach ($futures as $full_path => $future) { $bar->update(1); + $hash = $hashes[$full_path]; + try { $tree = XHPASTTree::newFromDataAndResolvedExecFuture( Filesystem::readFile($full_path), @@ -67,24 +165,27 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $calls = $root->selectDescendantsOfType('n_FUNCTION_CALL'); foreach ($calls as $call) { $name = $call->getChildByIndex(0)->getConcreteString(); - if ($name == 'pht') { - $params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST'); - $string_node = $params->getChildByIndex(0); - $string_line = $string_node->getLineNumber(); - try { - $string_value = $string_node->evalStatic(); + if ($name != 'pht') { + continue; + } - $results[$string_value][] = array( - 'file' => Filesystem::readablePath($full_path), - 'line' => $string_line, - ); - } catch (Exception $ex) { - $messages[] = pht( - 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', - $call->getLineNumber(), - $full_path, - $ex->getMessage()); - } + $params = $call->getChildByIndex(1, 'n_CALL_PARAMETER_LIST'); + $string_node = $params->getChildByIndex(0); + $string_line = $string_node->getLineNumber(); + try { + $string_value = $string_node->evalStatic(); + + $results[$hash][] = array( + 'string' => $string_value, + 'file' => Filesystem::readablePath($full_path, $root_path), + 'line' => $string_line, + ); + } catch (Exception $ex) { + $messages[] = pht( + 'WARNING: Failed to evaluate pht() call on line %d in "%s": %s', + $call->getLineNumber(), + $full_path, + $ex->getMessage()); } } @@ -93,28 +194,109 @@ final class PhabricatorInternationalizationManagementExtractWorkflow $bar->done(); foreach ($messages as $message) { - $console->writeErr("%s\n", $message); + echo tsprintf( + "** %s ** %s\n", + pht('WARNING'), + $message); } - ksort($results); + return $results; + } - $out = array(); - $out[] = ' $locations) { - foreach ($locations as $location) { - $out[] = ' // '.$location['file'].':'.$location['line']; + private function writeStrings($root, array $strings) { + $map = array(); + foreach ($strings as $hash => $string_list) { + foreach ($string_list as $string_info) { + $map[$string_info['string']]['uses'][] = array( + 'file' => $string_info['file'], + 'line' => $string_info['line'], + ); } - $out[] = " '".addcslashes($string, "\0..\37\\'\177..\377")."' => null,"; - $out[] = null; } - $out[] = ');'; - $out[] = null; - echo implode("\n", $out); + ksort($map); - return 0; + $json = id(new PhutilJSON())->encodeFormatted($map); + $this->writeCache($root, 'i18n_strings.json', $json); + } + + private function loadLibraryFiles($root) { + $files = id(new FileFinder($root)) + ->withType('f') + ->withSuffix('php') + ->excludePath('*/.*') + ->setGenerateChecksums(true) + ->find(); + + $map = array(); + foreach ($files as $file => $hash) { + $file = Filesystem::readablePath($file, $root); + $file = ltrim($file, '/'); + + if (dirname($file) == '.') { + continue; + } + + if (dirname($file) == 'extensions') { + continue; + } + + $map[$file] = md5($hash.$file); + } + + return $map; + } + + private function readCache($root) { + $path = $this->getCachePath($root, 'i18n_files.json'); + + $default = array( + 'version' => self::CACHE_VERSION, + 'files' => array(), + 'strings' => array(), + ); + + if ($this->getArgv()->getArg('clean')) { + return $default; + } + + if (!Filesystem::pathExists($path)) { + return $default; + } + + try { + $data = Filesystem::readFile($path); + } catch (Exception $ex) { + return $default; + } + + try { + $cache = phutil_json_decode($data); + } catch (PhutilJSONParserException $e) { + return $default; + } + + $version = idx($cache, 'version'); + if ($version !== self::CACHE_VERSION) { + return $default; + } + + return $cache; + } + + private function writeCache($root, $file, $data) { + $path = $this->getCachePath($root, $file); + + $cache_dir = dirname($path); + if (!Filesystem::pathExists($cache_dir)) { + Filesystem::createDirectory($cache_dir, 0755, true); + } + + Filesystem::writeFile($path, $data); + } + + private function getCachePath($root, $to_file) { + return $root.'/.cache/'.$to_file; } } From 2a1393c008c39a77c14391faa6cb9eefe7a2b62e Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 4 Jul 2016 10:20:56 -0700 Subject: [PATCH 07/34] Fix impropery history graph trace in Mercurial Summary: Fixes T11267. This data was coming back weird (in reverse order relative to the graph itself). Previously it worked OK anyway, but the new logic is a little more sensitive to the input. Test Plan: Viewed a Mercurial repository with linear history, saw linear history. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11267 Differential Revision: https://secure.phabricator.com/D16229 --- .../diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php index 60985e364d..fa12659915 100644 --- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php +++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php @@ -173,7 +173,7 @@ final class DiffusionHistoryQueryConduitAPIMethod } $hash_list = array_reverse($hash_list); - $this->parents = $parent_map; + $this->parents = array_reverse($parent_map, true); return DiffusionQuery::loadHistoryForCommitIdentifiers( $hash_list, From c7e7f113fd97389661a74ae0f27c443ce8622ba6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 4 Jul 2016 10:38:37 -0700 Subject: [PATCH 08/34] Open "Help" menu links in a new window Summary: Fixes T11243. Seems reasonable to open this stuff in a new window so you don't put any application state in Herald, etc., at risk -- looking in this menu for help with a currently-executing workflow is reasonable and normal. Test Plan: Clicked a help menu link, saw it open in a new page. Reviewers: avivey, chad Reviewed By: chad Maniphest Tasks: T11243 Differential Revision: https://secure.phabricator.com/D16230 --- src/applications/base/PhabricatorApplication.php | 6 ++++-- src/view/phui/PHUIListItemView.php | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php index e010d4491f..82b094c311 100644 --- a/src/applications/base/PhabricatorApplication.php +++ b/src/applications/base/PhabricatorApplication.php @@ -183,7 +183,8 @@ abstract class PhabricatorApplication $item = id(new PHUIListItemView()) ->setName($article['name']) ->setIcon('fa-book') - ->setHref($article['href']); + ->setHref($article['href']) + ->setOpenInNewWindow(true); $items[] = $item; } @@ -203,7 +204,8 @@ abstract class PhabricatorApplication $item = id(new PHUIListItemView()) ->setName($spec['name']) ->setIcon('fa-envelope-o') - ->setHref($href); + ->setHref($href) + ->setOpenInNewWindow(true); $items[] = $item; } } diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 4af02e572e..3a4909a1ee 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -29,8 +29,18 @@ final class PHUIListItemView extends AphrontTagView { private $indented; private $hideInApplicationMenu; private $icons = array(); + private $openInNewWindow = false; - public function setHideInApplicationMenu($hide) { + public function setOpenInNewWindow($open_in_new_window) { + $this->openInNewWindow = $open_in_new_window; + return $this; + } + + public function getOpenInNewWindow() { + return $this->openInNewWindow; + } + + public function setHideInApplicationMenu($hide) { $this->hideInApplicationMenu = $hide; return $this; } @@ -294,6 +304,7 @@ final class PHUIListItemView extends AphrontTagView { 'class' => implode(' ', $classes), 'meta' => $meta, 'sigil' => $sigil, + 'target' => $this->getOpenInNewWindow() ? '_blank' : null, ), array( $aural, From b53831101f39b15eb9d92fd4b037cf00c8d6b4f7 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 4 Jul 2016 17:55:11 -0700 Subject: [PATCH 09/34] Raise explicit setup issues about PHP 7 Summary: Ref T9640. Fixes T9888. Decline to support PHP 7 until the async signal handling issue in T11270 is resolved. Test Plan: Faked local version, got helpful error message. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9640, T9888 Differential Revision: https://secure.phabricator.com/D16231 --- .../check/PhabricatorPHPConfigSetupCheck.php | 17 +++++++++++++++++ src/docs/user/installation_guide.diviner | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php index 03bbb3c52d..6a08ec68ae 100644 --- a/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php +++ b/src/applications/config/check/PhabricatorPHPConfigSetupCheck.php @@ -11,6 +11,23 @@ final class PhabricatorPHPConfigSetupCheck extends PhabricatorSetupCheck { } protected function executeChecks() { + if (version_compare(phpversion(), 7, '>=')) { + $message = pht( + 'This version of Phabricator does not support PHP 7. You '. + 'are running PHP %s.', + phpversion()); + + $this->newIssue('php.version7') + ->setIsFatal(true) + ->setName(pht('PHP 7 Not Supported')) + ->setMessage($message) + ->addLink( + 'https://phurl.io/u/php7', + pht('Phabricator PHP 7 Compatibility Information')); + + return; + } + $safe_mode = ini_get('safe_mode'); if ($safe_mode) { $message = pht( diff --git a/src/docs/user/installation_guide.diviner b/src/docs/user/installation_guide.diviner index 8feb8ae1d0..749ca9a63d 100644 --- a/src/docs/user/installation_guide.diviner +++ b/src/docs/user/installation_guide.diviner @@ -70,7 +70,8 @@ Beyond an operating system, you will need **a webserver**. You will also need: - **MySQL**: You need MySQL. We strongly recommend MySQL 5.5 or newer. - - **PHP**: You need PHP 5.2 or newer. + - **PHP**: You need PHP 5.2 or newer, but note that PHP 7 is + **not supported**. You'll probably also need a **domain name**. In particular, you should read this note: From 62131de8cd1614c4ca0cc54853f48d1c624237c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jul 2016 06:50:40 -0700 Subject: [PATCH 10/34] Don't wrap task/revision titles in graph tables Summary: Fixes T11274. When task titles are long, we currently wrap stuff and the trace graph renders real weird. Instead, prevent taks/revision titles from wrapping/overflowing. (This works in a slightly weird way, and `text-overflow: ellipsis;` has no apparent effect on any of the containers.) Test Plan: {F1712394} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11274 Differential Revision: https://secure.phabricator.com/D16233 --- src/infrastructure/graph/DifferentialRevisionGraph.php | 2 ++ src/infrastructure/graph/ManiphestTaskGraph.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/infrastructure/graph/DifferentialRevisionGraph.php b/src/infrastructure/graph/DifferentialRevisionGraph.php index 3b5d638ec7..892540f610 100644 --- a/src/infrastructure/graph/DifferentialRevisionGraph.php +++ b/src/infrastructure/graph/DifferentialRevisionGraph.php @@ -54,6 +54,8 @@ final class DifferentialRevisionGraph $link = $viewer->renderHandle($phid); } + $link = AphrontTableView::renderSingleDisplayLine($link); + return array( $trace, $status, diff --git a/src/infrastructure/graph/ManiphestTaskGraph.php b/src/infrastructure/graph/ManiphestTaskGraph.php index b793493cd3..1568c8c656 100644 --- a/src/infrastructure/graph/ManiphestTaskGraph.php +++ b/src/infrastructure/graph/ManiphestTaskGraph.php @@ -67,6 +67,8 @@ final class ManiphestTaskGraph $link = $viewer->renderHandle($phid); } + $link = AphrontTableView::renderSingleDisplayLine($link); + return array( $trace, $status, From 5ffdb732735c9b1daeb2e838ce5e7beffbe09bb5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jul 2016 08:24:08 -0700 Subject: [PATCH 11/34] Don't try to prune unreachable commits from repositories with no outdated refs Summary: Fixes T11269. The basic issue is that `git log` in an empty repository exits with an error message. Prior to recent Git (2.6?), this message reads: > fatal: bad default revision 'HEAD' This message was somewhat recently changed by . After that, it reads: > fatal: your current branch 'master' does not have any commits yet This change isn't //technically// a //complete// fix because you could still hit this issue like this: - Create an empty repository. - Push some stuff to `master`. - Delete `master`. However, this is very rare and even in this case the repository will fix itself once you push something again. We can try to fix that if any users ever actually hit it. Test Plan: - Created a new empty Git repository. - Ran `bin/repository update Rxx`. - Before patch: "git log" error because of the empty repository. - After patch: clean update. - Also ran `repository update` on a non-empty repository. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11269 Differential Revision: https://secure.phabricator.com/D16234 --- .../engine/PhabricatorRepositoryDiscoveryEngine.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index fd8c57e9b4..ca9f7c5dc9 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -759,6 +759,13 @@ final class PhabricatorRepositoryDiscoveryEngine 'repositoryPHID = %s', $repository->getPHID()); + // If we don't have any refs to update, bail out before building a graph + // stream. In particular, this improves behavior in empty repositories, + // where `git log` exits with an error. + if (!$old_refs) { + return; + } + // We can share a single graph stream across all the checks we need to do. $stream = new PhabricatorGitGraphStream($repository); From 921d56efb0f0fbbcb975bf1845b094dc32dcd760 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jul 2016 16:38:05 -0700 Subject: [PATCH 12/34] Make repository URI creation work regardless of "repository" transaction order Summary: Fixes T11276. This feels slightly iffy (we `attachRepository()` here, and also when applying the TYPE_REPOSITORY transaction) but simpler than trying to reorder things. Test Plan: Created a repository URI with transactions in `["uri", "repository"]` order. Reviewers: chad, avivey Reviewed By: avivey Maniphest Tasks: T11276 Differential Revision: https://secure.phabricator.com/D16237 --- src/applications/diffusion/editor/DiffusionURIEditor.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index c22b888ac5..2ff0854a89 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -77,6 +77,11 @@ final class DiffusionURIEditor $old_uri = $object->getEffectiveURI(); } else { $old_uri = null; + + // When creating a URI, we may not have processed the repository + // transaction yet. Attach the repository here to make sure we + // have it for the calls below. + $object->attachRepository($this->repository); } $object->setURI($xaction->getNewValue()); From 989b585bbe5846e863bc4b56bdd6fe0d3c9ff0ab Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 5 Jul 2016 16:43:57 -0700 Subject: [PATCH 13/34] Fix ApplicationSearch URIs for Settings Summary: Fixes T11275. This search query doesn't actually have any options so these links are a little pointless, but generate valid links instead of 404s. Test Plan: Clicked "Advanced Search" and "Edit Queries" from `/settings/`. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11275 Differential Revision: https://secure.phabricator.com/D16238 --- .../settings/query/PhabricatorUserPreferencesSearchEngine.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php b/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php index 5e9d16f391..4b1f135c6a 100644 --- a/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php +++ b/src/applications/settings/query/PhabricatorUserPreferencesSearchEngine.php @@ -27,7 +27,7 @@ final class PhabricatorUserPreferencesSearchEngine } protected function getURI($path) { - return '/settings/list/'.$path; + return '/settings/'.$path; } protected function getBuiltinQueryNames() { From 05699388804fc6ae1df5a17559917d218c4ec26d Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Wed, 6 Jul 2016 01:54:51 +0000 Subject: [PATCH 14/34] expose renderHandle in PhabricatorModularTransactionType Test Plan: Tested with a transactionType from an extension. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Maniphest Tasks: T9789 Differential Revision: https://secure.phabricator.com/D16236 --- src/applications/phid/view/PHUIHandleView.php | 10 ++++++++++ .../storage/PhabricatorModularTransaction.php | 5 +++++ .../storage/PhabricatorModularTransactionType.php | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/applications/phid/view/PHUIHandleView.php b/src/applications/phid/view/PHUIHandleView.php index f4bce39662..6d968bd7ee 100644 --- a/src/applications/phid/view/PHUIHandleView.php +++ b/src/applications/phid/view/PHUIHandleView.php @@ -14,6 +14,7 @@ final class PHUIHandleView private $handleList; private $handlePHID; private $asTag; + private $asText; private $useShortName; private $showHovercard; @@ -32,6 +33,11 @@ final class PHUIHandleView return $this; } + public function setAsText($as_text) { + $this->asText = $as_text; + return $this; + } + public function setUseShortName($short) { $this->useShortName = $short; return $this; @@ -55,6 +61,10 @@ final class PHUIHandleView return $tag; } + if ($this->asText) { + return $handle->getLinkName(); + } + if ($this->useShortName) { $name = $handle->getName(); } else { diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 5dc6229a77..35defeddaa 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -116,6 +116,11 @@ abstract class PhabricatorModularTransaction return parent::getColor(); } + public function attachViewer(PhabricatorUser $viewer) { + $this->getTransactionImplementation()->setViewer($viewer); + return parent::attachViewer($viewer); + } + final public function hasChangeDetails() { if ($this->getTransactionImplementation()->hasChangeDetailView()) { return true; diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index b37bf0d61d..96b3547300 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -129,6 +129,18 @@ abstract class PhabricatorModularTransactionType return $this->getStorage()->renderHandleLink($object_phid); } + final protected function renderHandle($phid) { + $viewer = $this->getViewer(); + $display = $viewer->renderHandle($phid); + + $rendering_target = $this->getStorage()->getRenderingTarget(); + if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + $display->setAsText(true); + } + + return $display; + } + final protected function newError($title, $message, $xaction = null) { return new PhabricatorApplicationTransactionValidationError( $this->getTransactionTypeConstant(), From 38c290a241b1210ea2ff30a273330fdbe0abcf9b Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jul 2016 03:59:51 -0700 Subject: [PATCH 15/34] Fix a Paste activate/archive status constant in rendering Summary: Fixes T11280. I extracted this at the last minute and got the constant flipped. Test Plan: Archived, then activated a paste. Observed correct timeline stories/icons/etc. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11280 Differential Revision: https://secure.phabricator.com/D16240 --- .../paste/xaction/PhabricatorPasteStatusTransaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php b/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php index a313e45eab..b6a2d2b50d 100644 --- a/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteStatusTransaction.php @@ -14,7 +14,7 @@ final class PhabricatorPasteStatusTransaction } private function isActivate() { - return ($this->getNewValue() == PhabricatorPaste::STATUS_ARCHIVED); + return ($this->getNewValue() == PhabricatorPaste::STATUS_ACTIVE); } public function getIcon() { From 15f9e0f6ea2fc55fbcb6f22229c24a3f34e7cf53 Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 6 Jul 2016 12:24:36 -0700 Subject: [PATCH 16/34] Use CommentEditEngine in PhamePost Summary: Ref T9360. Moves PhamePost to CommentEditEngine. [x] HTTP Parameters dropdown on New Post goes to 404 [x] Implement EditEngine Comments Test Plan: Make Post, Make Comment, Laugh. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D16222 --- resources/celerity/map.php | 4 +- src/__phutil_library_map__.php | 4 +- .../PhabricatorPhameApplication.php | 15 +++-- .../post/PhamePostCommentController.php | 63 ------------------- .../post/PhamePostViewController.php | 25 ++++---- .../phame/editor/PhamePostEditEngine.php | 4 ++ .../phame/mail/PhamePostMailReceiver.php | 2 +- .../phid/PhabricatorPhamePostPHIDType.php | 4 +- .../phame/remarkup/PhamePostRemarkupRule.php | 19 ++++++ src/applications/phame/storage/PhamePost.php | 4 ++ webroot/rsrc/css/application/phame/phame.css | 4 ++ 11 files changed, 60 insertions(+), 88 deletions(-) delete mode 100644 src/applications/phame/controller/post/PhamePostCommentController.php create mode 100644 src/applications/phame/remarkup/PhamePostRemarkupRule.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index cd3ef696ae..944dc09de6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -81,7 +81,7 @@ return array( 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/paste/paste.css' => '1898e534', 'rsrc/css/application/people/people-profile.css' => '2473d929', - 'rsrc/css/application/phame/phame.css' => 'bf6a743f', + 'rsrc/css/application/phame/phame.css' => '8efb0729', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380', @@ -811,7 +811,7 @@ return array( 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-zindex-css' => '5b6fcf3f', - 'phame-css' => 'bf6a743f', + 'phame-css' => '8efb0729', 'pholio-css' => 'ca89d380', 'pholio-edit-css' => '07676f51', 'pholio-inline-comments-css' => '8e545e49', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e8c4ab5e87..791c4f8e5e 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3834,7 +3834,6 @@ phutil_register_library_map(array( 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php', - 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', @@ -3848,6 +3847,7 @@ phutil_register_library_map(array( 'PhamePostMoveController' => 'applications/phame/controller/post/PhamePostMoveController.php', 'PhamePostPublishController' => 'applications/phame/controller/post/PhamePostPublishController.php', 'PhamePostQuery' => 'applications/phame/query/PhamePostQuery.php', + 'PhamePostRemarkupRule' => 'applications/phame/remarkup/PhamePostRemarkupRule.php', 'PhamePostReplyHandler' => 'applications/phame/mail/PhamePostReplyHandler.php', 'PhamePostSearchConduitAPIMethod' => 'applications/phame/conduit/PhamePostSearchConduitAPIMethod.php', 'PhamePostSearchEngine' => 'applications/phame/query/PhamePostSearchEngine.php', @@ -8760,7 +8760,6 @@ phutil_register_library_map(array( 'PhabricatorFulltextInterface', ), 'PhamePostArchiveController' => 'PhamePostController', - 'PhamePostCommentController' => 'PhamePostController', 'PhamePostController' => 'PhameController', 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditController' => 'PhamePostController', @@ -8774,6 +8773,7 @@ phutil_register_library_map(array( 'PhamePostMoveController' => 'PhamePostController', 'PhamePostPublishController' => 'PhamePostController', 'PhamePostQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhamePostRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhamePostReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhamePostSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'PhamePostSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index d385b65215..16fd83f5b0 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -37,7 +37,8 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { public function getRoutes() { return array( - '/phame/' => array( + '/J(?P[1-9]\d*)' => 'PhamePostViewController', + '/phame/' => array( '' => 'PhameHomeController', // NOTE: The live routes include an initial "/", so leave it off @@ -46,17 +47,16 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { 'post/' => array( '(?:query/(?P[^/]+)/)?' => 'PhamePostListController', 'blogger/(?P[\w\.-_]+)/' => 'PhamePostListController', - 'edit/(?:(?P[^/]+)/)?' => 'PhamePostEditController', + $this->getEditRoutePattern('edit/') + => 'PhamePostEditController', 'history/(?P\d+)/' => 'PhamePostHistoryController', 'view/(?P\d+)/(?:(?P[^/]+)/)?' => 'PhamePostViewController', '(?Ppublish|unpublish)/(?P\d+)/' => 'PhamePostPublishController', 'preview/(?P\d+)/' => 'PhamePostPreviewController', 'preview/' => 'PhabricatorMarkupPreviewController', - 'framed/(?P\d+)/' => 'PhamePostFramedController', 'move/(?P\d+)/' => 'PhamePostMoveController', 'archive/(?P\d+)/' => 'PhamePostArchiveController', - 'comment/(?P[1-9]\d*)/' => 'PhamePostCommentController', ), 'blog/' => array( '(?:query/(?P[^/]+)/)?' => 'PhameBlogListController', @@ -107,6 +107,13 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { ); } + public function getRemarkupRules() { + return array( + new PhamePostRemarkupRule(), + ); + } + + protected function getCustomCapabilities() { return array( PhameBlogCreateCapability::CAPABILITY => array( diff --git a/src/applications/phame/controller/post/PhamePostCommentController.php b/src/applications/phame/controller/post/PhamePostCommentController.php deleted file mode 100644 index 54c041ef94..0000000000 --- a/src/applications/phame/controller/post/PhamePostCommentController.php +++ /dev/null @@ -1,63 +0,0 @@ -getViewer(); - $id = $request->getURIData('id'); - - if (!$request->isFormPost()) { - return new Aphront400Response(); - } - - $post = id(new PhamePostQuery()) - ->setViewer($viewer) - ->withIDs(array($id)) - ->executeOne(); - if (!$post) { - return new Aphront404Response(); - } - - $is_preview = $request->isPreviewRequest(); - $draft = PhabricatorDraft::buildFromRequest($request); - - $view_uri = $this->getApplicationURI('post/view/'.$post->getID().'/'); - - $xactions = array(); - $xactions[] = id(new PhamePostTransaction()) - ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) - ->attachComment( - id(new PhamePostTransactionComment()) - ->setContent($request->getStr('comment'))); - - $editor = id(new PhamePostEditor()) - ->setActor($viewer) - ->setContinueOnNoEffect($request->isContinueRequest()) - ->setContentSourceFromRequest($request) - ->setIsPreview($is_preview); - - try { - $xactions = $editor->applyTransactions($post, $xactions); - } catch (PhabricatorApplicationTransactionNoEffectException $ex) { - return id(new PhabricatorApplicationTransactionNoEffectResponse()) - ->setCancelURI($view_uri) - ->setException($ex); - } - - if ($draft) { - $draft->replaceOrDelete(); - } - - if ($request->isAjax() && $is_preview) { - return id(new PhabricatorApplicationTransactionResponse()) - ->setViewer($viewer) - ->setTransactions($xactions) - ->setIsPreview($is_preview); - } else { - return id(new AphrontRedirectResponse()) - ->setURI($view_uri); - } - } - -} diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php index f7dddebe53..a056502349 100644 --- a/src/applications/phame/controller/post/PhamePostViewController.php +++ b/src/applications/phame/controller/post/PhamePostViewController.php @@ -123,19 +123,22 @@ final class PhamePostViewController ->setImage($blogger->getProfileImageURI()) ->setImageHref($author_uri); + $monogram = $post->getMonogram(); $timeline = $this->buildTransactionTimeline( $post, id(new PhamePostTransactionQuery()) ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); - $timeline = phutil_tag_div('phui-document-view-pro-box', $timeline); + $timeline->setQuoteRef($monogram); if ($is_external) { $add_comment = null; } else { - $add_comment = $this->buildCommentForm($post); - $add_comment = phutil_tag_div('mlb mlt', $add_comment); + $add_comment = $this->buildCommentForm($post, $timeline); + $add_comment = phutil_tag_div('mlb mlt phame-comment-view', $add_comment); } + $timeline = phutil_tag_div('phui-document-view-pro-box', $timeline); + list($prev, $next) = $this->loadAdjacentPosts($post); $properties = id(new PHUIPropertyListView()) @@ -273,19 +276,13 @@ final class PhamePostViewController return $actions; } - private function buildCommentForm(PhamePost $post) { + private function buildCommentForm(PhamePost $post, $timeline) { $viewer = $this->getViewer(); - $draft = PhabricatorDraft::newFromUserAndKey( - $viewer, $post->getPHID()); - - $box = id(new PhabricatorApplicationTransactionCommentView()) - ->setUser($viewer) - ->setObjectPHID($post->getPHID()) - ->setDraft($draft) - ->setHeaderText(pht('Add Comment')) - ->setAction($this->getApplicationURI('post/comment/'.$post->getID().'/')) - ->setSubmitButtonName(pht('Add Comment')); + $box = id(new PhamePostEditEngine()) + ->setViewer($viewer) + ->buildEditEngineCommentView($post) + ->setTransactionTimeline($timeline); return phutil_tag_div('phui-document-view-pro-box', $box); } diff --git a/src/applications/phame/editor/PhamePostEditEngine.php b/src/applications/phame/editor/PhamePostEditEngine.php index 1c58722e2e..382ef3f8ff 100644 --- a/src/applications/phame/editor/PhamePostEditEngine.php +++ b/src/applications/phame/editor/PhamePostEditEngine.php @@ -68,6 +68,10 @@ final class PhamePostEditEngine return $object->getViewURI(); } + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('post/edit/'); + } + protected function buildCustomEditFields($object) { $blog_phid = $object->getBlog()->getPHID(); diff --git a/src/applications/phame/mail/PhamePostMailReceiver.php b/src/applications/phame/mail/PhamePostMailReceiver.php index 975d64a668..3655e73906 100644 --- a/src/applications/phame/mail/PhamePostMailReceiver.php +++ b/src/applications/phame/mail/PhamePostMailReceiver.php @@ -9,7 +9,7 @@ final class PhamePostMailReceiver } protected function getObjectPattern() { - return 'POST[1-9]\d*'; + return 'J[1-9]\d*'; } protected function loadObject($pattern, PhabricatorUser $viewer) { diff --git a/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php b/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php index 4250c91949..7c86d89d41 100644 --- a/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php +++ b/src/applications/phame/phid/PhabricatorPhamePostPHIDType.php @@ -32,8 +32,8 @@ final class PhabricatorPhamePostPHIDType extends PhabricatorPHIDType { foreach ($handles as $phid => $handle) { $post = $objects[$phid]; $handle->setName($post->getTitle()); - $handle->setFullName($post->getTitle()); - $handle->setURI('/phame/post/view/'.$post->getID().'/'); + $handle->setFullName(pht('Blog Post: ').$post->getTitle()); + $handle->setURI('/J'.$post->getID()); if ($post->isArchived()) { $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED); diff --git a/src/applications/phame/remarkup/PhamePostRemarkupRule.php b/src/applications/phame/remarkup/PhamePostRemarkupRule.php new file mode 100644 index 0000000000..92a2f0d350 --- /dev/null +++ b/src/applications/phame/remarkup/PhamePostRemarkupRule.php @@ -0,0 +1,19 @@ +getEngine()->getConfig('viewer'); + + return id(new PhamePostQuery()) + ->setViewer($viewer) + ->withIDs($ids) + ->execute(); + } + +} diff --git a/src/applications/phame/storage/PhamePost.php b/src/applications/phame/storage/PhamePost.php index 4c4acf4dc1..351bcb5dc0 100644 --- a/src/applications/phame/storage/PhamePost.php +++ b/src/applications/phame/storage/PhamePost.php @@ -51,6 +51,10 @@ final class PhamePost extends PhameDAO return $this->assertAttached($this->blog); } + public function getMonogram() { + return 'J'.$this->getID(); + } + public function getLiveURI() { $blog = $this->getBlog(); $is_draft = $this->isDraft(); diff --git a/webroot/rsrc/css/application/phame/phame.css b/webroot/rsrc/css/application/phame/phame.css index eca45b7741..75f1326af7 100644 --- a/webroot/rsrc/css/application/phame/phame.css +++ b/webroot/rsrc/css/application/phame/phame.css @@ -308,3 +308,7 @@ font-family: 'Aleo', {$fontfamily}; padding-top: 8px; } + +.phame-comment-view .aphront-form-control.aphront-form-control-select { + display: none; +} From 3bed16e750f09a13c44844925177eb1234f1226b Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 6 Jul 2016 13:45:42 -0700 Subject: [PATCH 17/34] Clean up parentDomain issues in PhameBlog Summary: Ref T9360. These weren't getting set properly, also make them nullable since they're optional. Test Plan: run upgrade, make a new blog with and without a parent domain. Edit a current blog. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D16242 --- .../sql/autopatches/20160706.phame.blog.parentdomain.2.sql | 2 ++ .../sql/autopatches/20160706.phame.blog.parentsite.1.sql | 2 ++ src/applications/phame/editor/PhameBlogEditor.php | 1 + src/applications/phame/storage/PhameBlog.php | 4 ++-- 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql create mode 100644 resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql diff --git a/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql b/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql new file mode 100644 index 0000000000..0fa4f17197 --- /dev/null +++ b/resources/sql/autopatches/20160706.phame.blog.parentdomain.2.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + MODIFY parentDomain VARCHAR(128) NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql b/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql new file mode 100644 index 0000000000..6c2f2e2483 --- /dev/null +++ b/resources/sql/autopatches/20160706.phame.blog.parentsite.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phame.phame_blog + MODIFY parentSite VARCHAR(128) NULL COLLATE {$COLLATE_TEXT}; diff --git a/src/applications/phame/editor/PhameBlogEditor.php b/src/applications/phame/editor/PhameBlogEditor.php index d075a750ea..ea1c132f64 100644 --- a/src/applications/phame/editor/PhameBlogEditor.php +++ b/src/applications/phame/editor/PhameBlogEditor.php @@ -66,6 +66,7 @@ final class PhameBlogEditor case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_STATUS: case PhameBlogTransaction::TYPE_PARENTSITE: + case PhameBlogTransaction::TYPE_PARENTDOMAIN: case PhameBlogTransaction::TYPE_PROFILEIMAGE: case PhameBlogTransaction::TYPE_HEADERIMAGE: return $xaction->getNewValue(); diff --git a/src/applications/phame/storage/PhameBlog.php b/src/applications/phame/storage/PhameBlog.php index 23ab671552..dc91672465 100644 --- a/src/applications/phame/storage/PhameBlog.php +++ b/src/applications/phame/storage/PhameBlog.php @@ -48,8 +48,8 @@ final class PhameBlog extends PhameDAO 'description' => 'text', 'domain' => 'text128?', 'domainFullURI' => 'text128?', - 'parentSite' => 'text128', - 'parentDomain' => 'text128', + 'parentSite' => 'text128?', + 'parentDomain' => 'text128?', 'status' => 'text32', 'mailKey' => 'bytes20', 'profileImagePHID' => 'phid?', From ee460b4f1b4c638e828218b9b5b4c964f5da01fc Mon Sep 17 00:00:00 2001 From: Chad Little Date: Wed, 6 Jul 2016 17:17:02 -0700 Subject: [PATCH 18/34] Redirect https blogs Summary: Ref T9360, forces https if we say the blog is https. Test Plan: Fake an https, get redirected. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D16241 --- src/applications/phame/site/PhameBlogSite.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/applications/phame/site/PhameBlogSite.php b/src/applications/phame/site/PhameBlogSite.php index 6a5eebaf31..20bdfd5230 100644 --- a/src/applications/phame/site/PhameBlogSite.php +++ b/src/applications/phame/site/PhameBlogSite.php @@ -18,9 +18,10 @@ final class PhameBlogSite extends PhameSite { } public function shouldRequireHTTPS() { - // TODO: We should probably provide options here eventually, but for now - // just never require HTTPS for external-domain blogs. - return false; + $full_uri = $this->getBlog()->getDomainFullURI(); + $full_uri = new PhutilURI($full_uri); + + return ($full_uri->getProtocol() == 'https'); } public function getPriority() { From 0dd6c3653edfb827358297bf5d75e54f2ec07bfb Mon Sep 17 00:00:00 2001 From: Chad Little Date: Thu, 7 Jul 2016 01:30:54 +0000 Subject: [PATCH 19/34] Clean up Blog Post crumbs Summary: Show the J monogram when internally linked, but nothing externally (cleaner UI). Ref T9360 Test Plan: View post live and internal. Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin Maniphest Tasks: T9360 Differential Revision: https://secure.phabricator.com/D16245 --- .../PhabricatorPhameApplication.php | 4 -- .../phame/controller/PhameLiveController.php | 4 +- src/docs/user/userguide/phame.diviner | 48 +++++++++++-------- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 16fd83f5b0..f56702ed62 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -31,10 +31,6 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/J(?P[1-9]\d*)' => 'PhamePostViewController', diff --git a/src/applications/phame/controller/PhameLiveController.php b/src/applications/phame/controller/PhameLiveController.php index 1287a33f35..2d8b2ee45f 100644 --- a/src/applications/phame/controller/PhameLiveController.php +++ b/src/applications/phame/controller/PhameLiveController.php @@ -195,7 +195,9 @@ abstract class PhameLiveController extends PhameController { } if ($post) { - $crumbs->addTextCrumb($post->getTitle()); + if (!$is_external) { + $crumbs->addTextCrumb('J'.$post->getID()); + } } return $crumbs; diff --git a/src/docs/user/userguide/phame.diviner b/src/docs/user/userguide/phame.diviner index 0de225c49f..f0f1732c6b 100644 --- a/src/docs/user/userguide/phame.diviner +++ b/src/docs/user/userguide/phame.diviner @@ -1,37 +1,44 @@ @title Phame User Guide @group userguide -Journal about your thoughts and feelings. Share with others. Profit. +Phame is an internal and external blogging tool. Use it to communicate both +internally to your users or externally to others. = Overview = -IMPORTANT: Phame is a prototype application. - -Phame is a simple blogging platform. You can write drafts which only you can -see. Later, you can publish these drafts as posts which anyone who can access -the Phabricator instance can see. You can also add posts to blogs to increase -your distribution. +Phame is a simple blogging platform. You can create and own multiple blogs +for various purposes internal to your organization. We include tools like +Phame in Phabricator because we believe having a single stack for all +internal tools is the best way to see they get adopted. Overall, Phame is intended to help an individual spread their message. As such, pertinent design decisions skew towards favoring the individual -rather than the collective. +rather than the collective. Phame fully integrates with Phabricator on +key levels such as Feed, Comments, Subscriptions, Policies, and more. = Drafts = -Drafts are completely private so draft away. +By default all new posts are visible unless you set the visibility to +draft when initially composing. This restricts the post to only members +with Phame Blog edit privileges. Those members may also see and edit +the post before it goes live. = Posts = -Posts are accessible to anyone who has access to the Phabricator instance. +Posts are accessible to the individual Phame Blog's view policy. This allows +you to separate your content according to audience. So you can restrict a +Engineering Blog to just engineers, or keep contractors out of the Security +Blog. If you've configured an external blog, all those posts will be publicly +viewable on publish. = Blogs = Blogs are collections of posts. Each blog has associated metadata like -a name, description, and set of bloggers who can add posts to the blog. -Each blogger can also edit metadata about the blog and delete the blog -outright. +a name, description, and set of bloggers who can add posts to the blog as +dictated by the blog's edit policy. Each blogger can also edit metadata +about the blog, the header images, or archive it. -NOTE: removing a blogger from a given blog does not remove their posts that +NOTE: Removing a blogger from a given blog does not remove their posts that are already associated with the blog. Rather, it removes their ability to edit metadata about and add posts to the blog. @@ -48,12 +55,11 @@ Phabricator instance. For the Phabricator instance, you must For your DNS authority, simply point the pertinent domain name at your Phabricator instance. e.g. by IP address. -= Comment Widgets = +There are three fields to know about when setting up a blog for external use. -Phame supports comment widgets from Facebook and Disqus. The administrator -of the Phabricator instance must properly configure Phabricator to enable -this functionality. + - **Full Domain URI:** Set this to the full URI you intend to serve the Phame + blog from. + - **Parent Site Name/URI:** Phame serves Blogs with a very minimal UI. + To help provide some context and navigation, these field may be set to give + users a way back to the parent site the blog was originally linked from. -A given comment widget is tied 1:1 with a given post. This means the same -instance of a given comment widget will appear for a given post regardless -of whether that post is being viewed in the context of a blog. From abdb02b51d603e014a71674cfd40181edac60dd8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jul 2016 18:30:21 -0700 Subject: [PATCH 20/34] Update Phame documentation to reflect changes to the application Summary: Ref T9360. Old docs felt a little weird to me (particularly very-old text like "favoring the individual rather than the collective"). Try a simpler tone focused more on use cases and examples? Test Plan: Read documentation. Also, viewed a post list and saw monograms. Reviewers: chad Reviewed By: chad Maniphest Tasks: T9895, T9360 Differential Revision: https://secure.phabricator.com/D16246 --- .../phame/query/PhamePostSearchEngine.php | 1 + src/docs/user/userguide/phame.diviner | 144 ++++++++++++------ 2 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/applications/phame/query/PhamePostSearchEngine.php b/src/applications/phame/query/PhamePostSearchEngine.php index 8f81c9362e..d1d9a791ec 100644 --- a/src/applications/phame/query/PhamePostSearchEngine.php +++ b/src/applications/phame/query/PhamePostSearchEngine.php @@ -108,6 +108,7 @@ final class PhamePostSearchEngine $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setObject($post) + ->setObjectName($post->getMonogram()) ->setHeader($post->getTitle()) ->setStatusIcon('fa-star') ->setHref($post->getViewURI()) diff --git a/src/docs/user/userguide/phame.diviner b/src/docs/user/userguide/phame.diviner index f0f1732c6b..d42ddc7d23 100644 --- a/src/docs/user/userguide/phame.diviner +++ b/src/docs/user/userguide/phame.diviner @@ -1,65 +1,121 @@ @title Phame User Guide @group userguide -Phame is an internal and external blogging tool. Use it to communicate both -internally to your users or externally to others. +Phame is a blogging platform. -= Overview = +Overview +======== -Phame is a simple blogging platform. You can create and own multiple blogs -for various purposes internal to your organization. We include tools like -Phame in Phabricator because we believe having a single stack for all -internal tools is the best way to see they get adopted. +Phame is a simple platform for writing blogs and blog posts. Content published +through Phame is integrated with other Phabricator applications (like Feed, +Herald and Dashboards). -Overall, Phame is intended to help an individual spread their message. As -such, pertinent design decisions skew towards favoring the individual -rather than the collective. Phame fully integrates with Phabricator on -key levels such as Feed, Comments, Subscriptions, Policies, and more. +You can use Phame to write and publish posts on any topic. You might use it to +make announcements, hold discussions, or provide progress updates about a +project. -= Drafts = +In the upstream, we use several Phame blogs to discuss changes to Phabricator, +make company announcements, photograph food, and provide visionary thought +leadership. -By default all new posts are visible unless you set the visibility to -draft when initially composing. This restricts the post to only members -with Phame Blog edit privileges. Those members may also see and edit -the post before it goes live. -= Posts = +Blogs +===== -Posts are accessible to the individual Phame Blog's view policy. This allows -you to separate your content according to audience. So you can restrict a -Engineering Blog to just engineers, or keep contractors out of the Security -Blog. If you've configured an external blog, all those posts will be publicly -viewable on publish. +To get started with Phame, create a blog. Blogs can be personal or edited +by a group: the **Editable By** policy controls who is allowed to write new +posts. -= Blogs = +You can provide a title, subtitle, and description to help users understand +the role and purpose of the blog. -Blogs are collections of posts. Each blog has associated metadata like -a name, description, and set of bloggers who can add posts to the blog as -dictated by the blog's edit policy. Each blogger can also edit metadata -about the blog, the header images, or archive it. +After creating a blog, you can optionally provide a header image (a large +image shown on the main blog page, like a beautiful photograph of food) and +a picture (a small logo or profile image shown in various places in the UI to +help identify the blog). -NOTE: Removing a blogger from a given blog does not remove their posts that -are already associated with the blog. Rather, it removes their ability to edit -metadata about and add posts to the blog. +Blogs can also be hosted externally. See "External Blogs", below, for +more information. -Blogs can be useful for powering external websites, like - blog.yourcompany.com +Posts +===== -by making pertinent configuration changes with your DNS authority and -Phabricator instance. For the Phabricator instance, you must +After creating a blog, you're ready to write your first post. You can navigate +to the blog and choose {nav Write Post} to get started. - - Enable `policy.allow-public` in Phabricator configuration. - - Configure the blog to have the view policy `public`. +Posts have a **Visibility** field which controls who can see them. The options +are: -For your DNS authority, simply point the pertinent domain name at your -Phabricator instance. e.g. by IP address. + - **Published**: Anyone who can see the blog will be able to read the post. + - **Draft**: Allows you to work on posts before publishing them. Only users + who can edit the blog will be able to see the post. + - **Archived**: Allows you to remove old posts. Only users who can edit + the blog will be able to see the post, and it won't appear in the pending + drafts list. -There are three fields to know about when setting up a blog for external use. +After publishing a post, it will appear on the blog and on the Phame home page +for all users who can see it. - - **Full Domain URI:** Set this to the full URI you intend to serve the Phame - blog from. - - **Parent Site Name/URI:** Phame serves Blogs with a very minimal UI. - To help provide some context and navigation, these field may be set to give - users a way back to the parent site the blog was originally linked from. +Using Phame With Other Applications +=================================== + +Phame integrates with other Phabricator applications, so you can do a few +interesting things: + +**Dashboards**: You can create a dashboard panel which shows posts on a +particular blog, then put the panel on the homepage or a custom dashboard. + +This is an easy way to create a list of recent announcements. + +**Herald**: You can use Herald rules to make sure you get notified whenever +your favorite author publishes a new post. + +**Remarkup**: You can reference a blog post in any other application using the +`J123` monogram for the post, or embed a more detailed link with `{J123}`. + +(We ran out of letters a while ago, but thinking about **j**ournal may be +helpful in remembering this.) + + +External Blogs +============== + +WARNING: This feature is still a prototype and has some known issues. + +You can host a Phame blog on an external domain, like `blog.mycompany.com`. The +Phacility corporate blog is an example of an external Phame blog: + +> https://blog.phacility.com/ + +External blogs are public (they do not require login) and are only supported if +your Phabricator install is also public. You can make an install public by +adjusting `policy.allow-public` in Config, but make sure you understand the +effects of adjusting this setting before touching it. + +Once you've made your install public, configure the blog that you want to host +like this: + + - **View Policy**: Set the "View Policy" for the blog to "Public". Blogs must + have a public view policy to be served from an external domain. + - **Full Domain URI**: Set this to the full URI of your external domain, + like `https://blog.mycompany.com/`. When users visit this URI, Phabricator + will serve the blog to them. + +To configure the blog's navigation breadcrumbs so that it links back to the +right parent site, set these options: + + - **Parent Site Name**: Put the parent site name here (like "MyCompany"). + - **Parent Site URI**: Put the parent site URI here (like + `https://www.mycompany.com`). + +Configuring these options will add a new breadcrumb to the navigation to let +users return to the blog's parent site. It will look something like this: + + - {nav My Company > Blog Name} + +Finally, configure DNS for `blog.mycompany.com` to point at Phabricator. + +If everything is set up properly, visiting `blog.mycompany.com` should now +serve your blog. From ef13b0e52b465c985bcf8fa91fc8bb686688cf47 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 6 Jul 2016 19:10:50 -0700 Subject: [PATCH 21/34] Expose repository "importing" flag via diffusion.repository.search Summary: See Z2352#28072. Expose this flag to allow callers to take actions after an import finishes, which is generally reasonable. Test Plan: Ran query from console, saw `isImporting` flag in results. Reviewers: chad Reviewed By: chad Differential Revision: https://secure.phabricator.com/D16247 --- .../repository/storage/PhabricatorRepository.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 4a7e3d4681..654ea42c3e 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -2400,6 +2400,12 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO ->setKey('status') ->setType('string') ->setDescription(pht('Active or inactive status.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('isImporting') + ->setType('bool') + ->setDescription( + pht( + 'True if the repository is importing initial commits.')), ); } @@ -2410,6 +2416,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'callsign' => $this->getCallsign(), 'shortName' => $this->getRepositorySlug(), 'status' => $this->getStatus(), + 'isImporting' => (bool)$this->isImporting(), ); } From 70505062673ae0f7b69e912cbde1c58a4344836e Mon Sep 17 00:00:00 2001 From: epriestley Date: Thu, 7 Jul 2016 14:49:17 -0700 Subject: [PATCH 22/34] Fix a bad getURI() call in Profile Panel handle construction Summary: Fixes T11285. We can end up loading panel handles while processing edits (e.g., disabling menu items on a project). We probably started loading these after the modular transaction changes in T9789, which load the handle for the transaction object unconditionally. The handles aren't too useful, but they currently fail to load/build because panels don't have a URI. We could give them some sort of method here, but just nuke it for now since they don't appear anywhere and this unclogs the daemon queue. Test Plan: - Disabled a menu item on a project. - Ran publish task with `bin/worker execute --id `. - Before patch: fatal on getURI() with stack trace similar to T11285. - After patch: clean execution. Reviewers: chad, avivey Reviewed By: avivey Maniphest Tasks: T11285 Differential Revision: https://secure.phabricator.com/D16249 --- .../search/phidtype/PhabricatorProfilePanelPHIDType.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php b/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php index c96c0717a9..e2285e2543 100644 --- a/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php +++ b/src/applications/search/phidtype/PhabricatorProfilePanelPHIDType.php @@ -33,7 +33,6 @@ final class PhabricatorProfilePanelPHIDType $config = $objects[$phid]; $handle->setName(pht('Profile Panel')); - $handle->setURI($config->getURI()); } } From c50811137d694adc8896fbc86c1ca495c3dce377 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 8 Jul 2016 00:16:51 +0000 Subject: [PATCH 23/34] Only show future triggers in Upcoming Triggers panel Test Plan: Have one-off triggers, look in daemon console, don't see expired ones. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D16250 --- .../daemon/controller/PhabricatorDaemonConsoleController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index 7f7b323956..a70cde04c4 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -190,6 +190,7 @@ final class PhabricatorDaemonConsoleController $triggers = id(new PhabricatorWorkerTriggerQuery()) ->setViewer($viewer) ->setOrder(PhabricatorWorkerTriggerQuery::ORDER_EXECUTION) + ->withNextEventBetween(0, null) ->needEvents(true) ->setLimit(10) ->execute(); From b656c87e3756a6c8124135aec405d867efa523f7 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 8 Jul 2016 00:17:16 +0000 Subject: [PATCH 24/34] horribly fix plain-text email for modular transactions Summary: This is the quickest and dirtiest fix I could come up with. `PhabricatorApplicationTransaction::getTitleForMail()` is using `clone $this`, which doesn't actually effect `implementation`. Ref T9789. Test Plan: update paste comment, get plaintext mail. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T9789 Differential Revision: https://secure.phabricator.com/D16251 --- .../storage/PhabricatorModularTransaction.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/applications/transactions/storage/PhabricatorModularTransaction.php b/src/applications/transactions/storage/PhabricatorModularTransaction.php index 35defeddaa..8ea6248563 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransaction.php +++ b/src/applications/transactions/storage/PhabricatorModularTransaction.php @@ -98,6 +98,15 @@ abstract class PhabricatorModularTransaction return parent::getTitle(); } + public function getTitleForMail() { + $old_target = $this->getRenderingTarget(); + $new_target = self::TARGET_TEXT; + $this->setRenderingTarget($new_target); + $title = $this->getTitle(); + $this->setRenderingTarget($old_target); + return $title; + } + final public function getTitleForFeed() { $title = $this->getTransactionImplementation()->getTitleForFeed(); if ($title !== null) { From c5efb453befdf32459076b7747221e6060ce5a12 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 04:17:26 -0700 Subject: [PATCH 25/34] Show more repository information in Owners path editing dropdown Summary: Fixes T11293. Test Plan: {F1716175} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11293 Differential Revision: https://secure.phabricator.com/D16253 --- .../controller/PhabricatorOwnersPathsController.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index d1d6de760d..7f911d29d9 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -82,7 +82,13 @@ final class PhabricatorOwnersPathsController } } - $repos = mpull($repos, 'getDisplayName', 'getPHID'); + + $repo_map = array(); + foreach ($repos as $key => $repo) { + $monogram = $repo->getMonogram(); + $name = $repo->getName(); + $repo_map[$repo->getPHID()] = "{$monogram} {$name}"; + } asort($repos); $template = new AphrontTypeaheadTemplateView(); @@ -94,7 +100,7 @@ final class PhabricatorOwnersPathsController 'root' => 'path-editor', 'table' => 'paths', 'add_button' => 'addpath', - 'repositories' => $repos, + 'repositories' => $repo_map, 'input_template' => $template, 'pathRefs' => $path_refs, From 1c088822b4d242ed17a73234ed647681af527f1f Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 04:31:41 -0700 Subject: [PATCH 26/34] Fix a fatal when viewing a daemon task with an invalid ID Summary: Fixes T11295. Prior to this change, the "404 page" for daemon tasks fatals. This page is special cased a little bit and not a normal 404 page, because it's possible for you to click a valid link and the task to get GC'd by the time you load the page, or similar. It tries to be a little more user-friendly than a bare 404. Test Plan: - Visited `/daemon/task/1428348920328/` (any invalid ID). - Now got a nice "no such task" page. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11295 Differential Revision: https://secure.phabricator.com/D16254 --- .../PhabricatorWorkerTaskDetailController.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index ad15d41b9d..85c2f2f87e 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -15,9 +15,13 @@ final class PhabricatorWorkerTaskDetailController $task = reset($tasks); } + $header = new PHUIHeaderView(); + if (!$task) { $title = pht('Task Does Not Exist'); + $header->setHeader(pht('Task %d Missing', $id)); + $error_view = new PHUIInfoView(); $error_view->setTitle(pht('No Such Task')); $error_view->appendChild(phutil_tag( @@ -30,11 +34,11 @@ final class PhabricatorWorkerTaskDetailController } else { $title = pht('Task %d', $task->getID()); - $header = id(new PHUIHeaderView()) - ->setHeader(pht('Task %d: %s', + $header->setHeader( + pht( + 'Task %d: %s', $task->getID(), - $task->getTaskClass())) - ->setHeaderIcon('fa-sort'); + $task->getTaskClass())); $properties = $this->buildPropertyListView($task); @@ -59,6 +63,8 @@ final class PhabricatorWorkerTaskDetailController ); } + $header->setHeaderIcon('fa-sort'); + $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($title); $crumbs->setBorder(true); From dabafda0426c5fd1aa636e5d35b28f3be5c82b23 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 07:25:52 -0700 Subject: [PATCH 27/34] Make Phriction previews of the root document work correctly Summary: Fixes T11146. Allow no slug in the URI. Test Plan: Previewed root document in Phriction. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11146 Differential Revision: https://secure.phabricator.com/D16257 --- .../phriction/application/PhabricatorPhrictionApplication.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php index c996365c67..d0eb46a069 100644 --- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php +++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php @@ -59,7 +59,7 @@ final class PhabricatorPhrictionApplication extends PhabricatorApplication { 'new/' => 'PhrictionNewController', 'move/(?P[1-9]\d*)/' => 'PhrictionMoveController', - 'preview/(?P.+/)' => 'PhrictionMarkupPreviewController', + 'preview/(?P.*/)' => 'PhrictionMarkupPreviewController', 'diff/(?P[1-9]\d*)/' => 'PhrictionDiffController', ), ); From 5c8dabdf8034f6fd51c461559ab54c908d77fe4c Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 05:48:26 -0700 Subject: [PATCH 28/34] Add a strong hint about importing or observing repositories to repository creation Summary: Fixes T11278. Also mention `svnsync`, since we have some evidence that it works. Test Plan: {F1716250} Reviewers: chad Reviewed By: chad Maniphest Tasks: T11278 Differential Revision: https://secure.phabricator.com/D16255 --- .../DiffusionRepositoryEditController.php | 23 ++++++- .../user/userguide/diffusion_existing.diviner | 68 +++++++++++++++++++ .../user/userguide/diffusion_uris.diviner | 10 +-- 3 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/docs/user/userguide/diffusion_existing.diviner diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php index b8892a94ea..d3801fa206 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php @@ -56,9 +56,30 @@ final class DiffusionRepositoryEditController $layout->addColumn($action); } + $hints = id(new AphrontMultiColumnView()) + ->setFluidLayout(true); + + $observe_href = PhabricatorEnv::getDoclink( + 'Diffusion User Guide: Existing Repositories'); + + $hints->addColumn( + id(new PHUIActionPanelView()) + ->setIcon('fa-book') + ->setHeader(pht('Import or Observe an Existing Repository')) + ->setHref($observe_href) + ->setSubheader( + pht( + 'Review the documentation describing how to import or observe an '. + 'existing repository.'))); + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter($layout); + ->setFooter( + array( + $layout, + phutil_tag('br'), + $hints, + )); return $this->newPage() ->setTitle($title) diff --git a/src/docs/user/userguide/diffusion_existing.diviner b/src/docs/user/userguide/diffusion_existing.diviner new file mode 100644 index 0000000000..56226a84a1 --- /dev/null +++ b/src/docs/user/userguide/diffusion_existing.diviner @@ -0,0 +1,68 @@ +@title Diffusion User Guide: Existing Repositories +@group userguide + +Quick guide for importing or observing existing repositories. + + +Overview +======== + +If you have an existing repository, you can observe or import it into +Diffusion. + +Observing a repository creates a read-only copy in Phabricator that is kept +up to date by continuously importing new changes. + +Importing a repository creates a read-write copy. + +This document is a quick guide to getting started. For an overview of +Diffusion, see @{article:Diffusion User Guide}. For a more detailed guide +about managing repositories and URIs in Diffusion, see +@{article:Diffusion User Guide: URIs}. + + +Observing Repositories +====================== + +To observe an existing repository: + + - Create a repository in Diffusion, but do not activate it yet. + - Add the URI for the existing repository you wish to observe in the + **URIs** section, in **Observe** mode. + - Activate the repository in Diffusion. + +This creates a read-only copy of the repository in Phabricator. Phabricator +will keep its copy in sync with the remote by periodically polling the remote +for changes. + +For more details, see @{article:Diffusion User Guide: URIs}. + + +Importing Repositories +====================== + +There are two primary ways to import an existing repository: + +**Observe First**: In Git or Mercurial, you can observe the repository first. +Once the import completes, disable the **Observe** URI to automatically convert +it into a hosted repository. + +**Push to Empty Repository**: Create an activate an empty repository, then push +all of your changes to empty the repository. + +In Git and Mercurial, you can do this with `git push` or `hg push`. + +In Subversion, you can do this with `svnsync`. + +For more details, see @{article:Diffusion User Guide: URIs}. + + +Next Steps +========== + +Continue by: + + - reading an overview of Diffusion in + @{article:Diffusion User Guide}; or + - learning more about managing remote repository URIs in + @{article:Diffusion User Guide: URIs}. diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 08be97b6b8..d1f4b541bc 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -135,10 +135,6 @@ Import a Repository If you have an existing repository that you want to move so it is hosted on Phabricator, there are three ways to do it: -**Push Everything**: //(Git, Mercurial)// Create a new empty hosted repository -according to the instructions above. Once the empty repository initializes, -push your entire existing repository to it. - **Observe First**: //(Git, Mercurial)// Observe the existing repository first, according to the instructions above. Once Phabricator's copy of the repository is fully synchronized, change the **I/O Type** for the **Observe** URI to @@ -149,6 +145,12 @@ writable, and you can begin pushing to it. If you've adjusted URI configuration away from the defaults, you may need to set at least one URI to **Read/Write** mode so you can push to it. +**Push Everything**: //(Git, Mercurial, Subversion)// Create a new empty hosted +repository according to the instructions above. Once the empty repository +initializes, push your entire existing repository to it. + +In Subversion, you can do this with the `svnsync` tool. + **Copy on Disk**: //(Git, Mercurial, Subversion)// Create a new empty hosted repository according to the instructions above, but do not activate it yet. From bd6d300282ec5d3e39fa2098997a62c9078778bb Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 07:38:37 -0700 Subject: [PATCH 29/34] Strip timestamps from popup notification bubbles Summary: Fixes T11097. Currently, popup notifications show a useless timestamp with the current time, after D16041 made some things more consistent. Strip these from the popup bubbles. Test Plan: - Saw a popup bubble, no timestamp. - Viewed main notification list, saw timestamps. Reviewers: chad Reviewed By: chad Maniphest Tasks: T11097 Differential Revision: https://secure.phabricator.com/D16258 --- .../PhabricatorNotificationBuilder.php | 13 +++++++ ...icatorNotificationIndividualController.php | 3 +- src/view/phui/PHUIFeedStoryView.php | 37 +++++++++++++------ 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/applications/notification/builder/PhabricatorNotificationBuilder.php b/src/applications/notification/builder/PhabricatorNotificationBuilder.php index a8af4ff00e..12e4b57bfb 100644 --- a/src/applications/notification/builder/PhabricatorNotificationBuilder.php +++ b/src/applications/notification/builder/PhabricatorNotificationBuilder.php @@ -5,6 +5,7 @@ final class PhabricatorNotificationBuilder extends Phobject { private $stories; private $parsedStories; private $user = null; + private $showTimestamps = true; public function __construct(array $stories) { assert_instances_of($stories, 'PhabricatorFeedStory'); @@ -16,6 +17,15 @@ final class PhabricatorNotificationBuilder extends Phobject { return $this; } + public function setShowTimestamps($show_timestamps) { + $this->showTimestamps = $show_timestamps; + return $this; + } + + public function getShowTimestamps() { + return $this->showTimestamps; + } + private function parseStories() { if ($this->parsedStories) { @@ -121,6 +131,9 @@ final class PhabricatorNotificationBuilder extends Phobject { // TODO: Render a nice debuggable notice instead? continue; } + + $view->setShowTimestamp($this->getShowTimestamps()); + $null_view->appendChild($view->renderNotification($this->user)); } diff --git a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php index bfaebaf606..41dade2747 100644 --- a/src/applications/notification/controller/PhabricatorNotificationIndividualController.php +++ b/src/applications/notification/controller/PhabricatorNotificationIndividualController.php @@ -31,7 +31,8 @@ final class PhabricatorNotificationIndividualController } $builder = id(new PhabricatorNotificationBuilder(array($story))) - ->setUser($viewer); + ->setUser($viewer) + ->setShowTimestamps(false); $content = $builder->buildView()->render(); $dict = $builder->buildDict(); diff --git a/src/view/phui/PHUIFeedStoryView.php b/src/view/phui/PHUIFeedStoryView.php index 8267fa0197..fbba4a3f46 100644 --- a/src/view/phui/PHUIFeedStoryView.php +++ b/src/view/phui/PHUIFeedStoryView.php @@ -17,6 +17,7 @@ final class PHUIFeedStoryView extends AphrontView { private $chronologicalKey; private $tags; private $authorIcon; + private $showTimestamp = true; public function setTags($tags) { $this->tags = $tags; @@ -97,6 +98,15 @@ final class PHUIFeedStoryView extends AphrontView { return $this; } + public function setShowTimestamp($show_timestamp) { + $this->showTimestamp = $show_timestamp; + return $this; + } + + public function getShowTimestamp() { + return $this->showTimestamp; + } + public function addProject($project) { $this->projects[] = $project; return $this; @@ -136,20 +146,25 @@ final class PHUIFeedStoryView extends AphrontView { if (!$this->viewed) { $classes[] = 'phabricator-notification-unread'; } - if ($this->epoch) { - if ($user) { - $foot = phabricator_datetime($this->epoch, $user); - $foot = phutil_tag( - 'span', - array( - 'class' => 'phabricator-notification-date', - ), - $foot); + + if ($this->getShowTimestamp()) { + if ($this->epoch) { + if ($user) { + $foot = phabricator_datetime($this->epoch, $user); + $foot = phutil_tag( + 'span', + array( + 'class' => 'phabricator-notification-date', + ), + $foot); + } else { + $foot = null; + } } else { - $foot = null; + $foot = pht('No time specified.'); } } else { - $foot = pht('No time specified.'); + $foot = null; } return javelin_tag( From a8a9fddb07380966e69af1723142bc9707c0e895 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 08:17:51 -0700 Subject: [PATCH 30/34] Trigger autocomplete when "@" is typed on German keyboards Summary: Ref T10252. On the German keyboard layout, you must type "Alt" + "L" to generate an "@" character. We currently ignore this event, assuming it's a keyboard command. However, I think we can safely continue so that autocomplete works on German layouts. Test Plan: - Switched keyboard layout to German. - Typed Alt + L to generate an "@". - Typed some username text. - Got autocompleter. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10252 Differential Revision: https://secure.phabricator.com/D16259 --- resources/celerity/map.php | 16 ++++++++-------- webroot/rsrc/js/phuix/PHUIXAutocomplete.js | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 944dc09de6..3e748b2f34 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -523,7 +523,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => '9196fb06', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da', 'rsrc/js/phuix/PHUIXFormControl.js' => 'e15869a8', 'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b', @@ -871,7 +871,7 @@ return array( 'phui-workpanel-view-css' => '92197373', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '8cf6d262', - 'phuix-autocomplete' => '9196fb06', + 'phuix-autocomplete' => '6d86ce8b', 'phuix-dropdown-menu' => '82e270da', 'phuix-form-control-view' => 'e15869a8', 'phuix-icon-view' => 'bff6884b', @@ -1441,6 +1441,12 @@ return array( 'javelin-typeahead', 'javelin-uri', ), + '6d86ce8b' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '70baed2f' => array( 'javelin-install', 'javelin-dom', @@ -1608,12 +1614,6 @@ return array( 'javelin-dom', 'javelin-request', ), - '9196fb06' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 92197373 => array( 'phui-workcard-view-css', ), diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index 801fa36ffb..94f37dc413 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -197,7 +197,11 @@ JX.install('PHUIXAutocomplete', { _onkeypress: function(e) { var r = e.getRawEvent(); - if (r.metaKey || r.altKey || r.ctrlKey) { + // NOTE: We allow events to continue with "altKey", because you need + // to press Alt to type characters like "@" on a German keyboard layout. + // The cost of misfiring autocompleters is very small since we do not + // eat the keystroke. See T10252. + if (r.metaKey || r.ctrlKey) { return; } From 444e353700fae1de6d845349ba293f83a04dc4e4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Fri, 8 Jul 2016 08:30:30 -0700 Subject: [PATCH 31/34] Fix "\" keyboard shortcut on German keyboard layouts Summary: Ref T10252. This is similar to D16259, but makes KeyboardShortcutManager more relaxed about `altKey` when typing obscure characters. Test Plan: Pressed Option + Shift + 7 on a German keyboard layout, saw Conphernece sidebar toggle. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10252 Differential Revision: https://secure.phabricator.com/D16260 --- resources/celerity/map.php | 20 ++++++++-------- .../rsrc/js/core/KeyboardShortcutManager.js | 24 ++++++++++++++++++- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 3e748b2f34..7b84306aef 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '2fbe65a2', - 'core.pkg.js' => 'f2139810', + 'core.pkg.js' => '49f8bdc0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3e81ae60', 'differential.pkg.js' => '634399e9', @@ -463,7 +463,7 @@ return array( 'rsrc/js/core/FileUpload.js' => '680ea2c8', 'rsrc/js/core/Hovercard.js' => '1bd28176', 'rsrc/js/core/KeyboardShortcut.js' => '1ae869f2', - 'rsrc/js/core/KeyboardShortcutManager.js' => 'c1700f6f', + 'rsrc/js/core/KeyboardShortcutManager.js' => '4a021c10', 'rsrc/js/core/MultirowRowManager.js' => 'b5d57730', 'rsrc/js/core/Notification.js' => 'ccf1cbf8', 'rsrc/js/core/Prefab.js' => 'cfd23f37', @@ -780,7 +780,7 @@ return array( 'phabricator-filetree-view-css' => 'fccf9f82', 'phabricator-flag-css' => '5337623f', 'phabricator-keyboard-shortcut' => '1ae869f2', - 'phabricator-keyboard-shortcut-manager' => 'c1700f6f', + 'phabricator-keyboard-shortcut-manager' => '4a021c10', 'phabricator-main-menu-view' => 'b623169f', 'phabricator-nav-view-css' => 'ac79a758', 'phabricator-notification' => 'ccf1cbf8', @@ -1222,6 +1222,13 @@ return array( 'javelin-dom', 'javelin-stratcom', ), + '4a021c10' => array( + 'javelin-install', + 'javelin-util', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-vector', + ), '4b700e9e' => array( 'javelin-behavior', 'javelin-dom', @@ -1882,13 +1889,6 @@ return array( 'javelin-install', 'javelin-dom', ), - 'c1700f6f' => array( - 'javelin-install', - 'javelin-util', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-vector', - ), 'c587b80f' => array( 'javelin-install', ), diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js index 3acef196a7..c9a1fbe64e 100644 --- a/webroot/rsrc/js/core/KeyboardShortcutManager.js +++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js @@ -32,6 +32,18 @@ JX.install('KeyboardShortcutManager', { down: 1 }, + /** + * Some keys require Alt to be pressed in order to type them on certain + * keyboard layouts. + */ + _altkeys: { + // "Alt+L" on German layouts. + '@': 1, + + // "Alt+Shift+7" on German layouts. + '\\': 1 + }, + getInstance : function() { if (!JX.KeyboardShortcutManager._instance) { JX.KeyboardShortcutManager._instance = new JX.KeyboardShortcutManager(); @@ -119,14 +131,24 @@ JX.install('KeyboardShortcutManager', { } }, _onkeyhit : function(e) { + var self = JX.KeyboardShortcutManager; + var raw = e.getRawEvent(); - if (raw.altKey || raw.ctrlKey || raw.metaKey) { + if (raw.ctrlKey || raw.metaKey) { // Never activate keyboard shortcuts if modifier keys are also // depressed. return; } + // For most keystrokes, don't activate keyboard shortcuts if the Alt + // key is depressed. However, we continue if the character requires the + // use of Alt to type it on some keyboard layouts. + var key = this._getKey(e); + if (raw.altKey && !(key in self._altkeys)) { + return; + } + var target = e.getTarget(); var ignore = ['input', 'select', 'textarea', 'object', 'embed']; if (JX.DOM.isType(target, ignore)) { From 1a303e7d2a60d669f71247a9d9e37bf17985cd7c Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 8 Jul 2016 13:57:33 -0700 Subject: [PATCH 32/34] Make "/" focus the search input again Summary: See D1902, T989, T11263, D15984, T4103 , D15976, https://secure.phabricator.com/w/changelog/2016.22/, T2527, T11231, T8286, T11264 for discussion! When we get another copy of T989, I will rename it to "Build a complicated keybinding settings page like a cool video game" and leave it open forever. Test Plan: Pressed "/" in Firefox, had my pristine browsing experience inexplicably hijacked by this horrible application. Reviewers: avivey, chad Reviewed By: avivey Subscribers: Korvin Differential Revision: https://secure.phabricator.com/D15984 --- resources/celerity/map.php | 20 +++++++++---------- .../page/menu/PhabricatorMainMenuView.php | 10 +++++++++- .../js/core/behavior-keyboard-shortcuts.js | 14 +++++++++++-- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 7b84306aef..a483d311de 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '2fbe65a2', - 'core.pkg.js' => '49f8bdc0', + 'core.pkg.js' => '1bcca0f3', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3e81ae60', 'differential.pkg.js' => '634399e9', @@ -491,7 +491,7 @@ return array( 'rsrc/js/core/behavior-history-install.js' => '7ee2b591', 'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64', 'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0', - 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '7835f8c9', + 'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0', 'rsrc/js/core/behavior-lightbox-attachments.js' => 'f8ba29d7', 'rsrc/js/core/behavior-line-linker.js' => '1499a8cb', 'rsrc/js/core/behavior-more.js' => 'a80d0378', @@ -654,7 +654,7 @@ return array( 'javelin-behavior-phabricator-gesture' => '3ab51e2c', 'javelin-behavior-phabricator-gesture-example' => '558829c2', 'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0', - 'javelin-behavior-phabricator-keyboard-shortcuts' => '7835f8c9', + 'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0', 'javelin-behavior-phabricator-line-linker' => '1499a8cb', 'javelin-behavior-phabricator-nav' => '56a1ca03', 'javelin-behavior-phabricator-notification-example' => '8ce821c5', @@ -921,6 +921,13 @@ return array( 'javelin-workflow', 'phabricator-draggable-list', ), + '01fca1f0' => array( + 'javelin-behavior', + 'javelin-workflow', + 'javelin-json', + 'javelin-dom', + 'phabricator-keyboard-shortcut', + ), '031cee25' => array( 'javelin-behavior', 'javelin-request', @@ -1499,13 +1506,6 @@ return array( 'multirow-row-manager', 'javelin-json', ), - '7835f8c9' => array( - 'javelin-behavior', - 'javelin-workflow', - 'javelin-json', - 'javelin-dom', - 'phabricator-keyboard-shortcut', - ), '7927a7d3' => array( 'javelin-behavior', 'javelin-quicksand', diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php index 69ff522d5b..25dba1871a 100644 --- a/src/view/page/menu/PhabricatorMainMenuView.php +++ b/src/view/page/menu/PhabricatorMainMenuView.php @@ -186,9 +186,17 @@ final class PhabricatorMainMenuView extends AphrontView { } $result = $search; + $keyboard_config['searchID'] = $search->getID(); } - Javelin::initBehavior('phabricator-keyboard-shortcuts', $keyboard_config); + $keyboard_config['pht'] = array( + '/' => pht('Give keyboard focus to the search box.'), + '?' => pht('Show keyboard shortcut help for the current page.'), + ); + + Javelin::initBehavior( + 'phabricator-keyboard-shortcuts', + $keyboard_config); if ($result) { $result = id(new PHUIListItemView()) diff --git a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js index a2f7ca172b..e1c6fa97d8 100644 --- a/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js +++ b/webroot/rsrc/js/core/behavior-keyboard-shortcuts.js @@ -11,10 +11,10 @@ * Define global keyboard shortcuts. */ JX.behavior('phabricator-keyboard-shortcuts', function(config) { + var pht = JX.phtize(config.pht); var workflow = null; - var desc = 'Show keyboard shortcut help for the current page.'; - new JX.KeyboardShortcut('?', desc) + new JX.KeyboardShortcut('?', pht('?')) .setHandler(function(manager) { if (workflow) { // Already showing the dialog. @@ -30,4 +30,14 @@ JX.behavior('phabricator-keyboard-shortcuts', function(config) { }) .register(); + if (config.searchID) { + new JX.KeyboardShortcut('/', pht('/')) + .setHandler(function() { + var search = JX.$(config.searchID); + search.focus(); + search.select(); + }) + .register(); + } + }); From c56a4fce6615a9d97261f2c162164fff2195280c Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 8 Jul 2016 22:34:25 +0000 Subject: [PATCH 33/34] Only load refs that are actual commits Summary: Fix T11301. Git is git. Test Plan: tagged a file! run discover. no crash. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin Maniphest Tasks: T11301 Differential Revision: https://secure.phabricator.com/D16261 --- .../PhabricatorRepositoryDiscoveryEngine.php | 9 +++++---- ...habricatorWorkingCopyDiscoveryTestCase.php | 1 + .../engine/__tests__/data/GT.git.tgz | Bin 6830 -> 7568 bytes 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php index ca9f7c5dc9..a4f697da0c 100644 --- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php @@ -174,12 +174,13 @@ final class PhabricatorRepositoryDiscoveryEngine continue; } - // In Git, it's possible to tag a tag. We just skip these, we'll discover - // them when we process the target tag. See T11180. + // In Git, it's possible to tag anything. We just skip tags that don't + // point to a commit. See T11301. $fields = $ref->getRawFields(); + $ref_type = idx($fields, 'objecttype'); $tag_type = idx($fields, '*objecttype'); - if ($tag_type == 'tag') { - $this->log(pht('Skipping, this is a tag of a tag.')); + if ($ref_type != 'commit' && $tag_type != 'commit') { + $this->log(pht('Skipping, this is not a commit.')); continue; } diff --git a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php index f912a551c5..a15cb3d79f 100644 --- a/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php +++ b/src/applications/repository/engine/__tests__/PhabricatorWorkingCopyDiscoveryTestCase.php @@ -30,6 +30,7 @@ final class PhabricatorWorkingCopyDiscoveryTestCase $this->assertEqual( array( '763d4ab372445551c95fb5cccd1a7a223f5b2ac8', + '41fa35914aa19c1aa6e57004d9745c05929c3563', ), mpull($refs, 'getIdentifier')); } diff --git a/src/applications/repository/engine/__tests__/data/GT.git.tgz b/src/applications/repository/engine/__tests__/data/GT.git.tgz index b53e9a2d1c2eceeb3e3c3fbdf6c9223debdb02dc..afd7e04a9680a39f0093a7677349ac8ac305653a 100644 GIT binary patch literal 7568 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 literal 6830 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 From f790dd5235b9ba80d369df3ec3eeadb103606f1d Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Fri, 8 Jul 2016 16:01:42 -0700 Subject: [PATCH 34/34] add renderValue() in transactions Summary: fix T11290. Test Plan: Paste language type, view in web and in emails (It uses quotes in HTML emails, which I think is something else). Reviewers: epriestley, chad, #blessed_reviewers Reviewed By: chad, #blessed_reviewers Subscribers: Korvin Maniphest Tasks: T11290 Differential Revision: https://secure.phabricator.com/D16252 --- resources/celerity/map.php | 6 +++--- .../PhabricatorPasteLanguageTransaction.php | 12 ++++++++---- .../storage/PhabricatorModularTransactionType.php | 14 ++++++++++++++ webroot/rsrc/css/phui/phui-timeline-view.css | 5 +++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index a483d311de..5763349c0e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '2fbe65a2', + 'core.pkg.css' => '4e7e9bde', 'core.pkg.js' => '1bcca0f3', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '3e81ae60', @@ -155,7 +155,7 @@ return array( 'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', - 'rsrc/css/phui/phui-timeline-view.css' => 'c3782437', + 'rsrc/css/phui/phui-timeline-view.css' => 'bc523970', 'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', @@ -863,7 +863,7 @@ return array( 'phui-status-list-view-css' => 'd5263e49', 'phui-tag-view-css' => '6bbd83e2', 'phui-theme-css' => '027ba77e', - 'phui-timeline-view-css' => 'c3782437', + 'phui-timeline-view-css' => 'bc523970', 'phui-two-column-view-css' => '9fb86c85', 'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-view-css' => 'e6d89647', diff --git a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php index 2dccc57385..75cf8559d1 100644 --- a/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php +++ b/src/applications/paste/xaction/PhabricatorPasteLanguageTransaction.php @@ -15,15 +15,19 @@ final class PhabricatorPasteLanguageTransaction public function getTitle() { return pht( - "%s updated the paste's language.", - $this->renderAuthor()); + "%s updated the paste's language from %s to %s.", + $this->renderAuthor(), + $this->renderValue($this->getOldValue()), + $this->renderValue($this->getNewValue())); } public function getTitleForFeed() { return pht( - '%s updated the language for %s.', + '%s updated the language for %s from %s to %s.', $this->renderAuthor(), - $this->renderObject()); + $this->renderObject(), + $this->renderValue($this->getOldValue()), + $this->renderValue($this->getNewValue())); } } diff --git a/src/applications/transactions/storage/PhabricatorModularTransactionType.php b/src/applications/transactions/storage/PhabricatorModularTransactionType.php index 96b3547300..a76f734c14 100644 --- a/src/applications/transactions/storage/PhabricatorModularTransactionType.php +++ b/src/applications/transactions/storage/PhabricatorModularTransactionType.php @@ -141,6 +141,20 @@ abstract class PhabricatorModularTransactionType return $display; } + final protected function renderValue($value) { + $rendering_target = $this->getStorage()->getRenderingTarget(); + if ($rendering_target == PhabricatorApplicationTransaction::TARGET_TEXT) { + return sprintf('"%s"', $value); + } + + return phutil_tag( + 'span', + array( + 'class' => 'phui-timeline-value', + ), + $value); + } + final protected function newError($title, $message, $xaction = null) { return new PhabricatorApplicationTransactionValidationError( $this->getTransactionTypeConstant(), diff --git a/webroot/rsrc/css/phui/phui-timeline-view.css b/webroot/rsrc/css/phui/phui-timeline-view.css index e6f43f70ad..9460fbac2f 100644 --- a/webroot/rsrc/css/phui/phui-timeline-view.css +++ b/webroot/rsrc/css/phui/phui-timeline-view.css @@ -256,6 +256,11 @@ color: {$lightgreytext}; } +.phui-timeline-title .phui-timeline-value { + font-style: italic; + color: black; +} + .device-desktop .phui-timeline-extra { float: right; }