mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-18 19:40:55 +01:00
Added initial class for displaying invisible chars
Summary: Fixes T11586. First pass at a class for displaying invisible characters. Still need to: - Write a couple unit tests - Add some styling to the .invisible-special spans - Actually start using the class when displaying form errors to users Currently this makes the string `"\nab\x00c\x01d\te\nf"` look like: {F1812711} Test Plan: Unit tests all pass and run in <1ms: {F1812998} Reviewers: epriestley, #blessed_reviewers, chad Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley, yelirekim Maniphest Tasks: T11586 Differential Revision: https://secure.phabricator.com/D16541
This commit is contained in:
parent
ff64c4e02b
commit
2588b4fac0
7 changed files with 173 additions and 3 deletions
|
@ -146,6 +146,7 @@ return array(
|
||||||
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
|
'rsrc/css/phui/phui-image-mask.css' => 'a8498f9c',
|
||||||
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
|
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
|
||||||
'rsrc/css/phui/phui-info-view.css' => '28efab79',
|
'rsrc/css/phui/phui-info-view.css' => '28efab79',
|
||||||
|
'rsrc/css/phui/phui-invisible-character-view.css' => '6993d9f0',
|
||||||
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
'rsrc/css/phui/phui-list.css' => '9da2aa00',
|
||||||
'rsrc/css/phui/phui-object-box.css' => '6b487c57',
|
'rsrc/css/phui/phui-object-box.css' => '6b487c57',
|
||||||
'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0',
|
'rsrc/css/phui/phui-object-item-list-view.css' => '87278fa0',
|
||||||
|
@ -922,6 +923,7 @@ return array(
|
||||||
'phui-info-panel-css' => '27ea50a1',
|
'phui-info-panel-css' => '27ea50a1',
|
||||||
'phui-info-view-css' => '28efab79',
|
'phui-info-view-css' => '28efab79',
|
||||||
'phui-inline-comment-view-css' => '5953c28e',
|
'phui-inline-comment-view-css' => '5953c28e',
|
||||||
|
'phui-invisible-character-view-css' => '6993d9f0',
|
||||||
'phui-list-view-css' => '9da2aa00',
|
'phui-list-view-css' => '9da2aa00',
|
||||||
'phui-object-box-css' => '6b487c57',
|
'phui-object-box-css' => '6b487c57',
|
||||||
'phui-object-item-list-view-css' => '87278fa0',
|
'phui-object-item-list-view-css' => '87278fa0',
|
||||||
|
|
|
@ -1652,6 +1652,8 @@ phutil_register_library_map(array(
|
||||||
'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php',
|
'PHUIInfoPanelExample' => 'applications/uiexample/examples/PHUIInfoPanelExample.php',
|
||||||
'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php',
|
'PHUIInfoPanelView' => 'view/phui/PHUIInfoPanelView.php',
|
||||||
'PHUIInfoView' => 'view/form/PHUIInfoView.php',
|
'PHUIInfoView' => 'view/form/PHUIInfoView.php',
|
||||||
|
'PHUIInvisibleCharacterTestCase' => 'view/phui/__tests__/PHUIInvisibleCharacterTestCase.php',
|
||||||
|
'PHUIInvisibleCharacterView' => 'view/phui/PHUIInvisibleCharacterView.php',
|
||||||
'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
|
'PHUIListExample' => 'applications/uiexample/examples/PHUIListExample.php',
|
||||||
'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
|
'PHUIListItemView' => 'view/phui/PHUIListItemView.php',
|
||||||
'PHUIListView' => 'view/phui/PHUIListView.php',
|
'PHUIListView' => 'view/phui/PHUIListView.php',
|
||||||
|
@ -6320,6 +6322,8 @@ phutil_register_library_map(array(
|
||||||
'PHUIInfoPanelExample' => 'PhabricatorUIExample',
|
'PHUIInfoPanelExample' => 'PhabricatorUIExample',
|
||||||
'PHUIInfoPanelView' => 'AphrontView',
|
'PHUIInfoPanelView' => 'AphrontView',
|
||||||
'PHUIInfoView' => 'AphrontView',
|
'PHUIInfoView' => 'AphrontView',
|
||||||
|
'PHUIInvisibleCharacterTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PHUIInvisibleCharacterView' => 'AphrontView',
|
||||||
'PHUIListExample' => 'PhabricatorUIExample',
|
'PHUIListExample' => 'PhabricatorUIExample',
|
||||||
'PHUIListItemView' => 'AphrontTagView',
|
'PHUIListItemView' => 'AphrontTagView',
|
||||||
'PHUIListView' => 'AphrontTagView',
|
'PHUIListView' => 'AphrontTagView',
|
||||||
|
|
|
@ -89,13 +89,14 @@ final class PhabricatorAuthRegisterController
|
||||||
// user expectation and it's not clear the cases it enables are valuable.
|
// user expectation and it's not clear the cases it enables are valuable.
|
||||||
// See discussion in T3472.
|
// See discussion in T3472.
|
||||||
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
|
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
|
||||||
|
$debug_email = new PHUIInvisibleCharacterView($default_email);
|
||||||
return $this->renderError(
|
return $this->renderError(
|
||||||
array(
|
array(
|
||||||
pht(
|
pht(
|
||||||
'The account you are attempting to register with has an invalid '.
|
'The account you are attempting to register with has an invalid '.
|
||||||
'email address (%s). This Phabricator install only allows '.
|
'email address (%s). This Phabricator install only allows '.
|
||||||
'registration with specific email addresses:',
|
'registration with specific email addresses:',
|
||||||
$default_email),
|
$debug_email),
|
||||||
phutil_tag('br'),
|
phutil_tag('br'),
|
||||||
phutil_tag('br'),
|
phutil_tag('br'),
|
||||||
PhabricatorUserEmail::describeAllowedAddresses(),
|
PhabricatorUserEmail::describeAllowedAddresses(),
|
||||||
|
|
|
@ -123,18 +123,23 @@ final class PhabricatorPhurlURLEditor
|
||||||
foreach ($xactions as $xaction) {
|
foreach ($xactions as $xaction) {
|
||||||
if ($xaction->getOldValue() != $xaction->getNewValue()) {
|
if ($xaction->getOldValue() != $xaction->getNewValue()) {
|
||||||
$new_alias = $xaction->getNewValue();
|
$new_alias = $xaction->getNewValue();
|
||||||
|
$debug_alias = new PHUIInvisibleCharacterView($new_alias);
|
||||||
if (!preg_match('/[a-zA-Z]/', $new_alias)) {
|
if (!preg_match('/[a-zA-Z]/', $new_alias)) {
|
||||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
$type,
|
$type,
|
||||||
pht('Invalid Alias'),
|
pht('Invalid Alias'),
|
||||||
pht('The alias must contain at least one letter.'),
|
pht('The alias you provided (%s) must contain at least one '.
|
||||||
|
'letter.',
|
||||||
|
$debug_alias),
|
||||||
$xaction);
|
$xaction);
|
||||||
}
|
}
|
||||||
if (preg_match('/[^a-z0-9]/i', $new_alias)) {
|
if (preg_match('/[^a-z0-9]/i', $new_alias)) {
|
||||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||||
$type,
|
$type,
|
||||||
pht('Invalid Alias'),
|
pht('Invalid Alias'),
|
||||||
pht('The alias may only contain letters and numbers.'),
|
pht('The alias you provided (%s) may only contain letters and '.
|
||||||
|
'numbers.',
|
||||||
|
$debug_alias),
|
||||||
$xaction);
|
$xaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
94
src/view/phui/PHUIInvisibleCharacterView.php
Normal file
94
src/view/phui/PHUIInvisibleCharacterView.php
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API for replacing whitespace characters and some control characters with
|
||||||
|
* their printable representations. This is useful for debugging and
|
||||||
|
* displaying more helpful error messages to users.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final class PHUIInvisibleCharacterView extends AphrontView {
|
||||||
|
|
||||||
|
private $inputText;
|
||||||
|
private $plainText = false;
|
||||||
|
|
||||||
|
// This is a list of the common invisible characters that are
|
||||||
|
// actually typeable. Other invisible characters will simply
|
||||||
|
// be displayed as their hex representations.
|
||||||
|
private static $invisibleChars = array(
|
||||||
|
"\x00" => 'NULL',
|
||||||
|
"\t" => 'TAB',
|
||||||
|
"\n" => 'NEWLINE',
|
||||||
|
"\x20" => 'SPACE',
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __construct($input_text) {
|
||||||
|
$this->inputText = $input_text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPlainText($plain_text) {
|
||||||
|
$this->plainText = $plain_text;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getStringParts() {
|
||||||
|
$input_text = $this->inputText;
|
||||||
|
$text_array = phutil_utf8v($input_text);
|
||||||
|
|
||||||
|
for ($ii = 0; $ii < count($text_array); $ii++) {
|
||||||
|
$char = $text_array[$ii];
|
||||||
|
$char_hex = bin2hex($char);
|
||||||
|
if (array_key_exists($char, self::$invisibleChars)) {
|
||||||
|
$text_array[$ii] = array(
|
||||||
|
'special' => true,
|
||||||
|
'value' => '<'.self::$invisibleChars[$char].'>',
|
||||||
|
);
|
||||||
|
} else if (ord($char) < 32) {
|
||||||
|
$text_array[$ii] = array(
|
||||||
|
'special' => true,
|
||||||
|
'value' => '<0x'.$char_hex.'>',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$text_array[$ii] = array(
|
||||||
|
'special' => false,
|
||||||
|
'value' => $char,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $text_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderHtmlArray() {
|
||||||
|
$html_array = array();
|
||||||
|
$parts = $this->getStringParts();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part['special']) {
|
||||||
|
$html_array[] = phutil_tag(
|
||||||
|
'span',
|
||||||
|
array('class' => 'invisible-special'),
|
||||||
|
$part['value']);
|
||||||
|
} else {
|
||||||
|
$html_array[] = $part['value'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $html_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function renderPlainText() {
|
||||||
|
$parts = $this->getStringParts();
|
||||||
|
$res = '';
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$res .= $part['value'];
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() {
|
||||||
|
require_celerity_resource('phui-invisible-character-view-css');
|
||||||
|
if ($this->plainText) {
|
||||||
|
return $this->renderPlainText();
|
||||||
|
} else {
|
||||||
|
return $this->renderHtmlArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/view/phui/__tests__/PHUIInvisibleCharacterTestCase.php
Normal file
52
src/view/phui/__tests__/PHUIInvisibleCharacterTestCase.php
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PHUIInvisibleCharacterTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
public function testEmptyString() {
|
||||||
|
$view = new PHUIInvisibleCharacterView('');
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertEqual($res, array());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyPlainText() {
|
||||||
|
$view = (new PHUIInvisibleCharacterView(''))
|
||||||
|
->setPlainText(true);
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertEqual($res, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithNamedChars() {
|
||||||
|
$test_input = "\x00\n\t ";
|
||||||
|
$view = (new PHUIInvisibleCharacterView($test_input))
|
||||||
|
->setPlainText(true);
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertEqual($res, '<NULL><NEWLINE><TAB><SPACE>');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithHexChars() {
|
||||||
|
$test_input = "abc\x01";
|
||||||
|
$view = (new PHUIInvisibleCharacterView($test_input))
|
||||||
|
->setPlainText(true);
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertEqual($res, 'abc<0x01>');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithNamedAsHex() {
|
||||||
|
$test_input = "\x00\x0a\x09\x20";
|
||||||
|
$view = (new PHUIInvisibleCharacterView($test_input))
|
||||||
|
->setPlainText(true);
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertEqual($res, '<NULL><NEWLINE><TAB><SPACE>');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHtmlDecoration() {
|
||||||
|
$test_input = "a\x00\n\t ";
|
||||||
|
$view = new PHUIInvisibleCharacterView($test_input);
|
||||||
|
$res = $view->render();
|
||||||
|
$this->assertFalse($res[0] instanceof PhutilSafeHTML);
|
||||||
|
$this->assertTrue($res[1] instanceof PhutilSafeHTML);
|
||||||
|
$this->assertTrue($res[2] instanceof PhutilSafeHTML);
|
||||||
|
$this->assertTrue($res[3] instanceof PhutilSafeHTML);
|
||||||
|
$this->assertTrue($res[4] instanceof PhutilSafeHTML);
|
||||||
|
}
|
||||||
|
}
|
12
webroot/rsrc/css/phui/phui-invisible-character-view.css
Normal file
12
webroot/rsrc/css/phui/phui-invisible-character-view.css
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* @provides phui-invisible-character-view-css
|
||||||
|
*/
|
||||||
|
|
||||||
|
.invisible-special {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #000;
|
||||||
|
background: rgba({$alphablue},0.1);
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
Loading…
Reference in a new issue