1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-22 14:52:41 +01:00

Introduce lightbox view for images

Summary:
images attached to maniphest tasks and mentioned in remarkup anywhere now invoke a lightbox control that lets the user page through all the images.

lightbox includes a download button, next / prev buttons, and if we're not at the tippy toppy of hte page an "X" or close button.

we also respond to left, right, and esc for navigating.

next time we should get non-images working in here...!

Test Plan:
played with maniphest - looks good
made comments with images. looks good.
made sure multiple image comments worked.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin, chad

Maniphest Tasks: T1896

Differential Revision: https://secure.phabricator.com/D3705
This commit is contained in:
Bob Trahan 2012-10-22 19:06:56 -07:00
parent ddde99f80c
commit 3ffc764141
24 changed files with 858 additions and 214 deletions

View file

@ -161,6 +161,13 @@ celerity_register_resource_map(array(
'disk' => '/rsrc/image/icon/fatcow/calendar_edit.png',
'type' => 'png',
),
'/rsrc/image/icon/fatcow/document_black.png' =>
array(
'hash' => '44d65a7f05a9c921719deedc160d68f7',
'uri' => '/res/44d65a7f/rsrc/image/icon/fatcow/document_black.png',
'disk' => '/rsrc/image/icon/fatcow/document_black.png',
'type' => 'png',
),
'/rsrc/image/icon/fatcow/flag_blue.png' =>
array(
'hash' => '75a080492f900fbe489e4b27e403962b',
@ -378,6 +385,48 @@ celerity_register_resource_map(array(
'disk' => '/rsrc/image/icon/fatcow/thumbnails/zip60x45.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/close-2.png' =>
array(
'hash' => '72ff3ddcc1ed5d19a715ed6242114b53',
'uri' => '/res/72ff3ddc/rsrc/image/icon/lightbox/close-2.png',
'disk' => '/rsrc/image/icon/lightbox/close-2.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/close-hover-2.png' =>
array(
'hash' => '6ad4bd4a7820547a1d9041752546ba16',
'uri' => '/res/6ad4bd4a/rsrc/image/icon/lightbox/close-hover-2.png',
'disk' => '/rsrc/image/icon/lightbox/close-hover-2.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/left-arrow-2.png' =>
array(
'hash' => 'd84cbb0d42739f87b8f25b2f1d2f1153',
'uri' => '/res/d84cbb0d/rsrc/image/icon/lightbox/left-arrow-2.png',
'disk' => '/rsrc/image/icon/lightbox/left-arrow-2.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/left-arrow-hover-2.png' =>
array(
'hash' => 'cdf05f98fff3f390cd8df0c89894a3e1',
'uri' => '/res/cdf05f98/rsrc/image/icon/lightbox/left-arrow-hover-2.png',
'disk' => '/rsrc/image/icon/lightbox/left-arrow-hover-2.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/right-arrow-2.png' =>
array(
'hash' => '52021038cb6995c71f62a804bc2d420d',
'uri' => '/res/52021038/rsrc/image/icon/lightbox/right-arrow-2.png',
'disk' => '/rsrc/image/icon/lightbox/right-arrow-2.png',
'type' => 'png',
),
'/rsrc/image/icon/lightbox/right-arrow-hover-2.png' =>
array(
'hash' => '65d5756b7b9cfcdeb2eb197a9aa6bbd2',
'uri' => '/res/65d5756b/rsrc/image/icon/lightbox/right-arrow-hover-2.png',
'disk' => '/rsrc/image/icon/lightbox/right-arrow-hover-2.png',
'type' => 'png',
),
'/rsrc/image/icon/subscribe.png' =>
array(
'hash' => '5f47a4b17de245af39a4e7a097e40623',
@ -1334,6 +1383,21 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/application/core/behavior-konami.js',
),
'javelin-behavior-lightbox-attachments' =>
array(
'uri' => '/res/bf398927/rsrc/js/application/core/behavior-lightbox-attachments.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
3 => 'javelin-mask',
4 => 'javelin-vector',
5 => 'javelin-util',
),
'disk' => '/rsrc/js/application/core/behavior-lightbox-attachments.js',
),
'javelin-behavior-line-chart' =>
array(
'uri' => '/res/1aa5ac88/rsrc/js/application/maniphest/behavior-line-chart.js',
@ -2172,6 +2236,15 @@ celerity_register_resource_map(array(
),
'disk' => '/rsrc/js/javelin/lib/Workflow.js',
),
'lightbox-attachment-css' =>
array(
'uri' => '/res/398fdb62/rsrc/css/aphront/lightbox-attachment.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/lightbox-attachment.css',
),
'maniphest-batch-editor' =>
array(
'uri' => '/res/fb15d744/rsrc/css/application/maniphest/batch-editor.css',
@ -2351,7 +2424,7 @@ celerity_register_resource_map(array(
),
'phabricator-drag-and-drop-file-upload' =>
array(
'uri' => '/res/136bea81/rsrc/js/application/core/DragAndDropFileUpload.js',
'uri' => '/res/496110e1/rsrc/js/application/core/DragAndDropFileUpload.js',
'type' => 'js',
'requires' =>
array(
@ -2473,7 +2546,7 @@ celerity_register_resource_map(array(
),
'phabricator-main-menu-view' =>
array(
'uri' => '/res/9fd2cece/rsrc/css/application/base/main-menu-view.css',
'uri' => '/res/844aa317/rsrc/css/application/base/main-menu-view.css',
'type' => 'css',
'requires' =>
array(
@ -2562,7 +2635,7 @@ celerity_register_resource_map(array(
),
'phabricator-paste-file-upload' =>
array(
'uri' => '/res/10b67881/rsrc/js/application/core/PasteFileUpload.js',
'uri' => '/res/b0b8afd8/rsrc/js/application/core/PasteFileUpload.js',
'type' => 'js',
'requires' =>
array(
@ -3030,7 +3103,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/8eb36c35/core.pkg.css',
'type' => 'css',
),
'9de93af8' =>
'f3d6fc91' =>
array(
'name' => 'core.pkg.js',
'symbols' =>
@ -3053,7 +3126,7 @@ celerity_register_resource_map(array(
15 => 'javelin-behavior-phabricator-tooltips',
16 => 'phabricator-prefab',
),
'uri' => '/res/pkg/9de93af8/core.pkg.js',
'uri' => '/res/pkg/f3d6fc91/core.pkg.js',
'type' => 'js',
),
'2ba14b3d' =>
@ -3079,7 +3152,7 @@ celerity_register_resource_map(array(
'uri' => '/res/pkg/2ba14b3d/differential.pkg.css',
'type' => 'css',
),
49566242 =>
'8136e4a6' =>
array(
'name' => 'differential.pkg.js',
'symbols' =>
@ -3102,7 +3175,7 @@ celerity_register_resource_map(array(
15 => 'differential-inline-comment-editor',
16 => 'javelin-behavior-differential-dropdown-menus',
),
'uri' => '/res/pkg/49566242/differential.pkg.js',
'uri' => '/res/pkg/8136e4a6/differential.pkg.js',
'type' => 'js',
),
'c8ce2d88' =>
@ -3210,7 +3283,7 @@ celerity_register_resource_map(array(
'aphront-typeahead-control-css' => '8eb36c35',
'differential-changeset-view-css' => '2ba14b3d',
'differential-core-view-css' => '2ba14b3d',
'differential-inline-comment-editor' => '49566242',
'differential-inline-comment-editor' => '8136e4a6',
'differential-local-commits-view-css' => '2ba14b3d',
'differential-results-table-css' => '2ba14b3d',
'differential-revision-add-comment-css' => '2ba14b3d',
@ -3224,20 +3297,20 @@ celerity_register_resource_map(array(
'inline-comment-summary-css' => '2ba14b3d',
'javelin-behavior' => 'c50bbf3a',
'javelin-behavior-aphront-basic-tokenizer' => 'dd024ca1',
'javelin-behavior-aphront-drag-and-drop' => '49566242',
'javelin-behavior-aphront-drag-and-drop-textarea' => '49566242',
'javelin-behavior-aphront-form-disable-on-submit' => '9de93af8',
'javelin-behavior-aphront-drag-and-drop' => '8136e4a6',
'javelin-behavior-aphront-drag-and-drop-textarea' => '8136e4a6',
'javelin-behavior-aphront-form-disable-on-submit' => 'f3d6fc91',
'javelin-behavior-audit-preview' => '5e68be89',
'javelin-behavior-differential-accept-with-errors' => '49566242',
'javelin-behavior-differential-add-reviewers-and-ccs' => '49566242',
'javelin-behavior-differential-comment-jump' => '49566242',
'javelin-behavior-differential-diff-radios' => '49566242',
'javelin-behavior-differential-dropdown-menus' => '49566242',
'javelin-behavior-differential-edit-inline-comments' => '49566242',
'javelin-behavior-differential-feedback-preview' => '49566242',
'javelin-behavior-differential-keyboard-navigation' => '49566242',
'javelin-behavior-differential-populate' => '49566242',
'javelin-behavior-differential-show-more' => '49566242',
'javelin-behavior-differential-accept-with-errors' => '8136e4a6',
'javelin-behavior-differential-add-reviewers-and-ccs' => '8136e4a6',
'javelin-behavior-differential-comment-jump' => '8136e4a6',
'javelin-behavior-differential-diff-radios' => '8136e4a6',
'javelin-behavior-differential-dropdown-menus' => '8136e4a6',
'javelin-behavior-differential-edit-inline-comments' => '8136e4a6',
'javelin-behavior-differential-feedback-preview' => '8136e4a6',
'javelin-behavior-differential-keyboard-navigation' => '8136e4a6',
'javelin-behavior-differential-populate' => '8136e4a6',
'javelin-behavior-differential-show-more' => '8136e4a6',
'javelin-behavior-diffusion-commit-graph' => '5e68be89',
'javelin-behavior-diffusion-pull-lastmodified' => '5e68be89',
'javelin-behavior-maniphest-batch-selector' => '7707de41',
@ -3245,20 +3318,20 @@ celerity_register_resource_map(array(
'javelin-behavior-maniphest-transaction-controls' => '7707de41',
'javelin-behavior-maniphest-transaction-expand' => '7707de41',
'javelin-behavior-maniphest-transaction-preview' => '7707de41',
'javelin-behavior-phabricator-autofocus' => '9de93af8',
'javelin-behavior-phabricator-keyboard-shortcuts' => '9de93af8',
'javelin-behavior-phabricator-object-selector' => '49566242',
'javelin-behavior-phabricator-oncopy' => '9de93af8',
'javelin-behavior-phabricator-tooltips' => '9de93af8',
'javelin-behavior-phabricator-watch-anchor' => '9de93af8',
'javelin-behavior-refresh-csrf' => '9de93af8',
'javelin-behavior-repository-crossreference' => '49566242',
'javelin-behavior-workflow' => '9de93af8',
'javelin-behavior-phabricator-autofocus' => 'f3d6fc91',
'javelin-behavior-phabricator-keyboard-shortcuts' => 'f3d6fc91',
'javelin-behavior-phabricator-object-selector' => '8136e4a6',
'javelin-behavior-phabricator-oncopy' => 'f3d6fc91',
'javelin-behavior-phabricator-tooltips' => 'f3d6fc91',
'javelin-behavior-phabricator-watch-anchor' => 'f3d6fc91',
'javelin-behavior-refresh-csrf' => 'f3d6fc91',
'javelin-behavior-repository-crossreference' => '8136e4a6',
'javelin-behavior-workflow' => 'f3d6fc91',
'javelin-dom' => 'c50bbf3a',
'javelin-event' => 'c50bbf3a',
'javelin-install' => 'c50bbf3a',
'javelin-json' => 'c50bbf3a',
'javelin-mask' => '9de93af8',
'javelin-mask' => 'f3d6fc91',
'javelin-request' => 'c50bbf3a',
'javelin-stratcom' => 'c50bbf3a',
'javelin-tokenizer' => 'dd024ca1',
@ -3270,7 +3343,7 @@ celerity_register_resource_map(array(
'javelin-uri' => 'c50bbf3a',
'javelin-util' => 'c50bbf3a',
'javelin-vector' => 'c50bbf3a',
'javelin-workflow' => '9de93af8',
'javelin-workflow' => 'f3d6fc91',
'maniphest-task-summary-css' => '7839ae2d',
'maniphest-transaction-detail-css' => '7839ae2d',
'phabricator-app-buttons-css' => '8eb36c35',
@ -3278,21 +3351,21 @@ celerity_register_resource_map(array(
'phabricator-core-buttons-css' => '8eb36c35',
'phabricator-core-css' => '8eb36c35',
'phabricator-directory-css' => '8eb36c35',
'phabricator-drag-and-drop-file-upload' => '49566242',
'phabricator-dropdown-menu' => '9de93af8',
'phabricator-drag-and-drop-file-upload' => '8136e4a6',
'phabricator-dropdown-menu' => 'f3d6fc91',
'phabricator-flag-css' => '8eb36c35',
'phabricator-jump-nav' => '8eb36c35',
'phabricator-keyboard-shortcut' => '9de93af8',
'phabricator-keyboard-shortcut-manager' => '9de93af8',
'phabricator-menu-item' => '9de93af8',
'phabricator-keyboard-shortcut' => 'f3d6fc91',
'phabricator-keyboard-shortcut-manager' => 'f3d6fc91',
'phabricator-menu-item' => 'f3d6fc91',
'phabricator-object-selector-css' => '2ba14b3d',
'phabricator-paste-file-upload' => '9de93af8',
'phabricator-prefab' => '9de93af8',
'phabricator-paste-file-upload' => 'f3d6fc91',
'phabricator-prefab' => 'f3d6fc91',
'phabricator-project-tag-css' => '7839ae2d',
'phabricator-remarkup-css' => '8eb36c35',
'phabricator-shaped-request' => '49566242',
'phabricator-shaped-request' => '8136e4a6',
'phabricator-standard-page-view' => '8eb36c35',
'phabricator-tooltip' => '9de93af8',
'phabricator-tooltip' => 'f3d6fc91',
'phabricator-transaction-view-css' => '8eb36c35',
'syntax-highlighting-css' => '8eb36c35',
),

View file

@ -29,7 +29,6 @@ phutil_register_library_map(array(
'AphrontDialogView' => 'view/AphrontDialogView.php',
'AphrontErrorView' => 'view/form/AphrontErrorView.php',
'AphrontException' => 'aphront/exception/AphrontException.php',
'AphrontFilePreviewView' => 'view/layout/AphrontFilePreviewView.php',
'AphrontFileResponse' => 'aphront/response/AphrontFileResponse.php',
'AphrontFormCheckboxControl' => 'view/form/control/AphrontFormCheckboxControl.php',
'AphrontFormControl' => 'view/form/control/AphrontFormControl.php',
@ -736,6 +735,8 @@ phutil_register_library_map(array(
'PhabricatorFileDropUploadController' => 'applications/files/controller/PhabricatorFileDropUploadController.php',
'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php',
'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php',
'PhabricatorFileLinkListView' => 'view/layout/PhabricatorFileLinkListView.php',
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
'PhabricatorFileProxyController' => 'applications/files/controller/PhabricatorFileProxyController.php',
'PhabricatorFileProxyImage' => 'applications/files/storage/PhabricatorFileProxyImage.php',
@ -1256,6 +1257,7 @@ phutil_register_library_map(array(
'phabricator_parse_bytes' => 'view/viewutils.php',
'phabricator_relative_date' => 'view/viewutils.php',
'phabricator_render_form' => 'infrastructure/javelin/markup.php',
'phabricator_render_form_magic' => 'infrastructure/javelin/markup.php',
'phabricator_time' => 'view/viewutils.php',
'phid_get_type' => 'applications/phid/utils.php',
'phid_group_by_type' => 'applications/phid/utils.php',
@ -1280,7 +1282,6 @@ phutil_register_library_map(array(
'AphrontDialogView' => 'AphrontView',
'AphrontErrorView' => 'AphrontView',
'AphrontException' => 'Exception',
'AphrontFilePreviewView' => 'AphrontView',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormCheckboxControl' => 'AphrontFormControl',
'AphrontFormControl' => 'AphrontView',
@ -1857,6 +1858,7 @@ phutil_register_library_map(array(
'PhabricatorEdgeGraph' => 'AbstractDirectedGraph',
'PhabricatorEdgeQuery' => 'PhabricatorQuery',
'PhabricatorEdgeTestCase' => 'PhabricatorTestCase',
'PhabricatorEditor' => 'Phobject',
'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
'PhabricatorEmailVerificationController' => 'PhabricatorPeopleController',
@ -1914,6 +1916,8 @@ phutil_register_library_map(array(
'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
'PhabricatorFileImageMacro' => 'PhabricatorFileDAO',
'PhabricatorFileInfoController' => 'PhabricatorFileController',
'PhabricatorFileLinkListView' => 'AphrontView',
'PhabricatorFileLinkView' => 'AphrontView',
'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileProxyController' => 'PhabricatorFileController',
'PhabricatorFileProxyImage' => 'PhabricatorFileDAO',

View file

@ -55,7 +55,7 @@ final class PhabricatorApplicationFiles extends PhabricatorApplication {
'data/(?P<key>[^/]+)/(?P<phid>[^/]+)/.*'
=> 'PhabricatorFileDataController',
'proxy/' => 'PhabricatorFileProxyController',
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/'
'xform/(?P<transform>[^/]+)/(?P<phid>[^/]+)/(?P<key>[^/]+)/'
=> 'PhabricatorFileTransformController',
),
);

View file

@ -57,9 +57,10 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
$response->setContent($data);
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
$is_view = $file->isViewableInBrowser();
$is_viewable = $file->isViewableInBrowser();
$force_download = $request->getExists('download');
if ($is_view) {
if ($is_viewable && !$force_download) {
$response->setMimeType($file->getViewableMimeType());
} else {
if (!$request->isHTTPPost()) {

View file

@ -24,11 +24,25 @@ final class PhabricatorFileTransformController
public function willProcessRequest(array $data) {
$this->transform = $data['transform'];
$this->phid = $data['phid'];
$this->phid = $data['phid'];
$this->key = $data['key'];
}
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid);
if (!$file) {
return new Aphront404Response();
}
if (!$file->validateSecretKey($this->key)) {
return new Aphront403Response();
}
$xform = id(new PhabricatorTransformedFile())
->loadOneWhere(
'originalPHID = %s AND transform = %s',
@ -39,11 +53,6 @@ final class PhabricatorFileTransformController
return $this->buildTransformedFileResponse($xform);
}
$file = id(new PhabricatorFile())->loadOneWhere('phid = %s', $this->phid);
if (!$file) {
return new Aphront404Response();
}
$type = $file->getMimeType();
if (!$file->isViewableInBrowser() || !$file->isTransformableImage()) {

View file

@ -307,20 +307,34 @@ final class PhabricatorFile extends PhabricatorFileDAO {
}
}
public function getDownloadURI() {
$uri = id(new PhutilURI($this->getViewURI()))
->setQueryParam('download', true);
return (string) $uri;
}
public function getThumb60x45URI() {
return '/file/xform/thumb-60x45/'.$this->getPHID().'/';
$path = '/file/xform/thumb-60x45/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb160x120URI() {
return '/file/xform/thumb-160x120/'.$this->getPHID().'/';
$path = '/file/xform/thumb-160x120/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getPreview220URI() {
return '/file/xform/preview-220/'.$this->getPHID().'/';
$path = '/file/xform/preview-220/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function getThumb220x165URI() {
return '/file/xform/thumb-220x165/'.$this->getPHID().'/';
$path = '/file/xform/thumb-220x165/'.$this->getPHID().'/'
.$this->getSecretKey().'/';
return PhabricatorEnv::getCDNURI($path);
}
public function isViewableInBrowser() {

View file

@ -197,13 +197,10 @@ final class ManiphestTaskDetailController extends ManiphestController {
'phid IN (%Ls)',
$file_phids);
$views = array();
foreach ($files as $file) {
$view = new AphrontFilePreviewView();
$view->setFile($file);
$views[] = $view->render();
}
$dict['Files'] = implode('', $views);
$view = new PhabricatorFileLinkListView();
$view->setFiles($files);
$dict['Files'] = $view->render();
}
$context_bar = null;

View file

@ -55,23 +55,26 @@ function javelin_render_tag(
function phabricator_render_form(PhabricatorUser $user, $attributes, $content) {
if (strcasecmp(idx($attributes, 'method'), 'POST') == 0 &&
!preg_match('#^(https?:|//)#', idx($attributes, 'action'))) {
$content =
phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => AphrontRequest::getCSRFTokenName(),
'value' => $user->getCSRFToken(),
)).
phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => '__form__',
'value' => true,
)).
$content;
$content = phabricator_render_form_magic($user).$content;
}
return javelin_render_tag('form', $attributes, $content);
}
function phabricator_render_form_magic(PhabricatorUser $user) {
return
phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => AphrontRequest::getCSRFTokenName(),
'value' => $user->getCSRFToken(),
)).
phutil_render_tag(
'input',
array(
'type' => 'hidden',
'name' => '__form__',
'value' => true,
));
}

View file

@ -57,6 +57,7 @@ final class PhabricatorMarkupEngine {
private $objects = array();
private $viewer;
private $version = 0;
/* -( Markup Pipeline )---------------------------------------------------- */
@ -185,7 +186,7 @@ final class PhabricatorMarkupEngine {
private function getMarkupFieldKey(
PhabricatorMarkupInterface $object,
$field) {
return $object->getMarkupFieldKey($field);
return $object->getMarkupFieldKey($field).'@'.$this->version;
}

View file

@ -22,6 +22,8 @@
final class PhabricatorRemarkupRuleEmbedFile
extends PhutilRemarkupRule {
const KEY_RULE_EMBED_FILE = 'rule.embed.file';
public function apply($text) {
return preg_replace_callback(
"@{F(\d+)([^}]+?)?}@",
@ -40,6 +42,13 @@ final class PhabricatorRemarkupRuleEmbedFile
if (!$file) {
return $matches[0];
}
$phid = $file->getPHID();
$engine = $this->getEngine();
$token = $engine->storeText('');
$metadata_key = self::KEY_RULE_EMBED_FILE;
$metadata = $engine->getTextMetadata($metadata_key, array());
$bundle = array('token' => $token);
$options = array(
'size' => 'thumb',
@ -52,87 +61,121 @@ final class PhabricatorRemarkupRuleEmbedFile
$matches[2] = trim($matches[2], ', ');
$options = PhutilSimpleOptions::parse($matches[2]) + $options;
}
$file_name = coalesce($options['name'], $file->getName());
if (!$file->isViewableImage() || $options['layout'] == 'link') {
// If a file isn't in image, just render a link to it.
$link = phutil_render_tag(
'a',
array(
'href' => $file->getBestURI(),
'target' => '_blank',
'class' => 'phabricator-remarkup-embed-layout-link',
),
phutil_escape_html($file_name));
return $this->getEngine()->storeText($link);
}
$options['name'] = $file_name;
$attrs = array();
switch ($options['size']) {
case 'full':
$attrs['src'] = $file->getBestURI();
$link = null;
$options['image_class'] = null;
break;
case 'thumb':
default:
$attrs['src'] = $file->getPreview220URI();
$link = $file->getBestURI();
$options['image_class'] = 'phabricator-remarkup-embed-image';
break;
}
$bundle['attrs'] = $attrs;
$bundle['options'] = $options;
$embed = phutil_render_tag('img', $attrs);
$bundle['meta'] = array(
'phid' => $file->getPHID(),
'viewable' => $file->isViewableImage(),
'uri' => $file->getBestURI(),
'dUri' => $file->getDownloadURI(),
'name' => $options['name'],
);
$metadata[$phid][] = $bundle;
$engine->setTextMetadata($metadata_key, $metadata);
if ($link) {
$embed = phutil_render_tag(
'a',
array(
'href' => $link,
'class' => 'phabricator-remarkup-embed-image',
'target' => '_blank',
),
$embed);
return $token;
}
public function didMarkupText() {
$engine = $this->getEngine();
$metadata_key = self::KEY_RULE_EMBED_FILE;
$metadata = $engine->getTextMetadata($metadata_key, array());
if (!$metadata) {
return;
}
$layout_class = null;
switch ($options['layout']) {
case 'right':
case 'center':
case 'inline':
case 'left':
$layout_class = 'phabricator-remarkup-embed-layout-'.$options['layout'];
break;
default:
$layout_class = 'phabricator-remarkup-embed-layout-left';
break;
}
foreach ($metadata as $phid => $bundles) {
foreach ($bundles as $data) {
if ($options['float']) {
switch ($options['layout']) {
case 'center':
case 'inline':
break;
case 'right':
$layout_class .= ' phabricator-remarkup-embed-float-right';
break;
case 'left':
default:
$layout_class .= ' phabricator-remarkup-embed-float-left';
break;
$options = $data['options'];
$meta = $data['meta'];
if (!$meta['viewable'] || $options['layout'] == 'link') {
$link = id(new PhabricatorFileLinkView())
->setFilePHID($meta['phid'])
->setFileName($meta['name'])
->setFileDownloadURI($meta['dUri'])
->setFileViewURI($meta['uri'])
->setFileViewable($meta['viewable']);
$embed = $link->render();
$engine->overwriteStoredText($data['token'], $embed);
continue;
}
require_celerity_resource('lightbox-attachment-css');
$img = phutil_render_tag('img', $data['attrs']);
$embed = javelin_render_tag(
'a',
array(
'href' => '#',
'class' => $options['image_class'],
'sigil' => 'lightboxable',
'mustcapture' => true,
'meta' => $meta,
),
$img);
$layout_class = null;
switch ($options['layout']) {
case 'right':
case 'center':
case 'inline':
case 'left':
$layout_class = 'phabricator-remarkup-embed-layout-'.
$options['layout'];
break;
default:
$layout_class = 'phabricator-remarkup-embed-layout-left';
break;
}
if ($options['float']) {
switch ($options['layout']) {
case 'center':
case 'inline':
break;
case 'right':
$layout_class .= ' phabricator-remarkup-embed-float-right';
break;
case 'left':
default:
$layout_class .= ' phabricator-remarkup-embed-float-left';
break;
}
}
if ($layout_class) {
$embed = phutil_render_tag(
'div',
array(
'class' => $layout_class,
),
$embed);
}
$engine->overwriteStoredText($data['token'], $embed);
}
}
if ($layout_class) {
$embed = phutil_render_tag(
'div',
array(
'class' => $layout_class,
),
$embed);
}
return $this->getEngine()->storeText($embed);
$engine->setTextMetadata($metadata_key, array());
}
}

View file

@ -1,71 +0,0 @@
<?php
/*
* Copyright 2011 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class AphrontFilePreviewView extends AphrontView {
private $file;
public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
public function render() {
require_celerity_resource('aphront-attached-file-view-css');
$file = $this->file;
$img = phutil_render_tag(
'img',
array(
'src' => $file->getThumb160x120URI(),
'width' => 160,
'height' => 120,
'title' => $file->getName(),
));
$link = phutil_render_tag(
'a',
array(
'href' => $file->getBestURI(),
'target' => '_blank',
),
$img);
$display_name = $file->getName();
if (strlen($display_name) > 22) {
$display_name =
substr($display_name, 0, 11).
"\xE2\x80\xA6".
substr($display_name, -9);
}
return
'<div class="aphront-file-preview-view">
<div class="aphront-file-preview-thumb">'.
$link.
'</div>'.
phutil_render_tag(
'span',
array(
'title' => $file->getName(),
),
phutil_escape_html($display_name)).
'</div>';
}
}

View file

@ -0,0 +1,53 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorFileLinkListView extends AphrontView {
private $files;
public function setFiles(array $files) {
assert_instances_of($files, 'PhabricatorFile');
$this->files = $files;
return $this;
}
private function getFiles() {
return $this->files;
}
public function render() {
$files = $this->getFiles();
if (!$files) {
return '';
}
require_celerity_resource('phabricator-remarkup-css');
$file_links = array();
foreach ($this->getFiles() as $file) {
$view = id(new PhabricatorFileLinkView())
->setFilePHID($file->getPHID())
->setFileName($file->getName())
->setFileDownloadURI($file->getDownloadURI())
->setFileViewURI($file->getBestURI())
->setFileViewable($file->isViewableImage());
$file_links[] = $view->render();
}
return implode('<br />', $file_links);
}
}

View file

@ -0,0 +1,98 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorFileLinkView extends AphrontView {
private $fileName;
private $fileDownloadURI;
private $fileViewURI;
private $fileViewable;
private $filePHID;
public function setFilePHID($file_phid) {
$this->filePHID = $file_phid;
return $this;
}
private function getFilePHID() {
return $this->filePHID;
}
public function setFileViewable($file_viewable) {
$this->fileViewable = $file_viewable;
return $this;
}
private function getFileViewable() {
return $this->fileViewable;
}
public function setFileViewURI($file_view_uri) {
$this->fileViewURI = $file_view_uri;
return $this;
}
private function getFileViewURI() {
return $this->fileViewURI;
}
public function setFileDownloadURI($file_download_uri) {
$this->fileDownloadURI = $file_download_uri;
return $this;
}
private function getFileDownloadURI() {
return $this->fileDownloadURI;
}
public function setFileName($file_name) {
$this->fileName = $file_name;
return $this;
}
private function getFileName() {
return $this->fileName;
}
public function render() {
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('lightbox-attachment-css');
$sigil = null;
$meta = null;
$mustcapture = false;
if ($this->getFileViewable()) {
$mustcapture = true;
$sigil = 'lightboxable';
$meta = array(
'phid' => $this->getFilePHID(),
'viewable' => $this->getFileViewable(),
'uri' => $this->getFileViewURI(),
'dUri' => $this->getFileDownloadURI(),
'name' => $this->getFileName(),
);
}
return javelin_render_tag(
'a',
array(
'href' => $this->getFileViewURI(),
'class' => 'phabricator-remarkup-embed-layout-link',
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
),
phutil_escape_html($this->getFileName())
);
}
}

View file

@ -121,6 +121,16 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
}
Javelin::initBehavior('workflow', array());
$download_form = phabricator_render_form_magic($user);
$default_img_uri =
PhabricatorEnv::getCDNURI('/rsrc/image/icon/fatcow/document_black.png');
Javelin::initBehavior(
'lightbox-attachments',
array(
'defaultImageUri' => $default_img_uri,
'downloadForm' => $download_form,
));
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('konami', array());
Javelin::initBehavior(

View file

@ -0,0 +1,112 @@
/**
* @provides lightbox-attachment-css
*/
.lightbox-attached {
height: 100%;
overflow-y: hidden;
}
.lightbox-attached .jx-mask {
opacity: 0.76;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=76)";
filter: alpha(opacity=76);
/* jx-mask behavior sets this dynamically so !important to win */
width: 100% !important;
}
.lightbox-attachment {
position: fixed;
top: 44px;
left: 0;
z-index: 6;
width: 100%;
}
.lightbox-attachment img {
margin: 12px auto 0px auto;
}
.lightbox-attachment .loading {
position: absolute;
top: -9999px;
}
.lightbox-attachment .attachment-name {
width: 100%;
color: #F2F2F2;
line-height: 30px;
text-align: center;
}
.lightbox-attachment .lightbox-status {
background: #010101;
color: #F2F2F2;
line-height: 30px;
position: fixed;
bottom: 0px;
width: 100%;
}
.lightbox-attachment .lightbox-status .lightbox-status-txt {
padding: 0px 0px 0px 20px;
}
.lightbox-attachment .lightbox-status .lightbox-download {
padding: 0px 20px 0px 0px;
float: right;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form {
display: inline;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form button {
border: 0;
background: #010101;
}
.lightbox-attachment .lightbox-status .lightbox-download
.lightbox-download-form button:hover {
background: #333;
}
.lightbox-attachment .lightbox-close {
top: 22px;
right: 20px;
position: fixed;
display: block;
height: 26px;
width: 26px;
background: url('/rsrc/image/icon/lightbox/close-2.png');
}
.lightbox-attachment .lightbox-close:hover {
background: url('/rsrc/image/icon/lightbox/close-hover-2.png');
}
.lightbox-attachment .lightbox-left {
top: 46%;
left: 20px;
position: fixed;
display: block;
height: 38px;
width: 21px;
background: url('/rsrc/image/icon/lightbox/left-arrow-2.png');
}
.lightbox-attachment .lightbox-left:hover {
background: url('/rsrc/image/icon/lightbox/left-arrow-hover-2.png');
}
.lightbox-attachment .lightbox-right {
top: 46%;
right: 20px;
position: fixed;
display: block;
height: 38px;
width: 21px;
background: url('/rsrc/image/icon/lightbox/right-arrow-2.png');
}
.lightbox-attachment .lightbox-right:hover {
background: url('/rsrc/image/icon/lightbox/right-arrow-hover-2.png');
}

View file

@ -16,6 +16,7 @@
position: relative;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.25);
height: 44px;
z-index: 6;
}
.phabricator-main-menu a:hover {
@ -25,7 +26,6 @@
.device-desktop .phabricator-main-menu {
text-align: right;
width: 100%;
z-index: 6;
}
/* - Main Menu Group -----------------------------------------------------------

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,297 @@
/**
* @provides javelin-behavior-lightbox-attachments
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
* javelin-mask
* javelin-vector
* javelin-util
*/
JX.behavior('lightbox-attachments', function (config) {
var lightbox = null;
var prev = null;
var next = null;
var x_margin = 40;
var y_margin = 100;
var onscroll = window.onscroll;
var downloadForm = JX.$H(config.downloadForm);
function loadLightBox(e) {
if (JX.Stratcom.pass()) {
return;
}
e.prevent();
var links = JX.DOM.scry(document, 'a', 'lightboxable');
var phids = {};
var data;
for (var i = 0; i < links.length; i++) {
data = JX.Stratcom.getData(links[i]);
phids[data.phid] = links[i];
}
// Now that we have the big picture phid situation sorted out, figure
// out how the actual node the user clicks fits into that big picture
// and build some pretty UI to show the attachment.
var target = e.getNode('lightboxable');
var target_data = JX.Stratcom.getData(target);
var total = JX.keys(phids).length;
var current = 1;
var past_target = false;
for (var phid in phids) {
if (past_target) {
next = phids[phid];
break;
} else if (phid == target_data.phid) {
past_target = true;
} else {
prev = phids[phid];
current++;
}
}
var img_uri = '';
var extra_status = '';
var name_element = '';
// for now, this conditional is always true
// revisit if / when we decide to add non-images to lightbox view
if (target_data.viewable) {
img_uri = target_data.uri;
} else {
img_uri = config.defaultImageUri;
extra_status = ' Image may not be representative of actual attachment.';
name_element = JX.$N('div',
{ className : 'attachment-name' },
target_data.name
);
}
var img = JX.$N('img',
{
className : 'loading',
alt : target_data.name,
title : target_data.name
}
);
// Evil hack - onload events don't work through Stratcom to prevent
// the inevitable systematic abuse if it was possible. This is a
// weird case so just hack it...!
img.onload = lightBoxOnload;
lightbox = JX.$N('div',
{
className : 'lightbox-attachment'
},
img
);
JX.DOM.appendContent(lightbox, name_element);
var s = JX.Vector.getScroll();
var closeIcon = '';
// Don't show the close icon if the beautiful header is
// still mostly present. Optimizes for common case of
// clicking on an attachment in object detail view without
// scrolling.
if (s.y >= 22) {
closeIcon = JX.$N('a',
{
className : 'lightbox-close',
href : '#'
}
);
JX.DOM.listen(closeIcon, 'click', null, closeLightBox);
}
JX.DOM.appendContent(lightbox, closeIcon);
var leftIcon = '';
if (next) {
leftIcon = JX.$N('a',
{
className : 'lightbox-right',
href : '#'
}
);
JX.DOM.listen(leftIcon,
'click',
null,
JX.bind(null, loadAnotherLightBox, next)
);
}
JX.DOM.appendContent(lightbox, leftIcon);
var rightIcon = '';
if (prev) {
rightIcon = JX.$N('a',
{
className : 'lightbox-left',
href : '#'
}
);
JX.DOM.listen(rightIcon,
'click',
null,
JX.bind(null, loadAnotherLightBox, prev)
);
}
JX.DOM.appendContent(lightbox, rightIcon);
var statusSpan = JX.$N('span',
{
className: 'lightbox-status-txt'
},
'Image '+current+' of '+total+'.'+extra_status
);
var form = JX.$N('form',
{
action : target_data.dUri,
method : 'POST',
className : 'lightbox-download-form'
},
downloadForm
);
JX.DOM.appendContent(form, JX.$N('button', {}, 'Download'));
JX.DOM.listen(form,
'click',
null,
function (e) {
e.prevent(); closeLightBox(e); form.submit();
}
);
var downloadSpan = JX.$N('span',
{
className : 'lightbox-download'
},
form
);
var statusHTML = JX.$N('div',
{
className : 'lightbox-status'
},
[statusSpan, downloadSpan]
);
JX.DOM.appendContent(lightbox, statusHTML);
JX.DOM.alterClass(document.body, 'lightbox-attached', true);
JX.Mask.show();
document.body.appendChild(lightbox);
img.src = img_uri;
}
// TODO - make this work with KeyboardShortcut, which means
// making an uninstall / de-register for KeyboardShortcut
function lightBoxHandleKeyDown(e) {
if (!lightbox) {
return;
}
var raw = e.getRawEvent();
if (raw.altKey || raw.ctrlKey || raw.metaKey) {
return;
}
if (JX.Stratcom.pass()) {
return;
}
var handler = JX.bag;
switch (e.getSpecialKey()) {
case 'esc':
handler = closeLightBox;
break;
case 'right':
if (next) {
handler = JX.bind(null, loadAnotherLightBox, next);
}
break;
case 'left':
if (prev) {
handler = JX.bind(null, loadAnotherLightBox, prev);
}
break;
}
return handler(e);
}
function closeLightBox(e) {
if (!lightbox) {
return;
}
e.prevent();
JX.DOM.remove(lightbox);
JX.Mask.hide();
JX.DOM.alterClass(document.body, 'lightbox-attached', false);
lightbox = null;
prev = null;
next = null;
window.onscroll = onscroll;
}
function loadAnotherLightBox(el, e) {
if (!el) {
return;
}
e.prevent();
closeLightBox(e);
el.click();
}
function lightBoxOnload(e) {
if (!lightbox) {
return;
}
var img = JX.DOM.find(lightbox, 'img');
var d = JX.Vector.getDim(img);
var s = JX.Vector.getScroll();
JX.Stratcom.addData(img, { x : d.x, y : d.y } );
window.onscroll = function() {
window.scrollTo(s.x, s.y);
};
return resizeLightBox(e);
}
function resizeLightBox(e) {
if (!lightbox) {
return;
}
var img = JX.DOM.find(lightbox, 'img');
var v = JX.Vector.getViewport();
var s = JX.Vector.getScroll();
var d = JX.Stratcom.getData(img);
var w = d.x;
var h = d.y;
var scale = 0;
if (w > (v.x - x_margin)) {
scale = (v.x - x_margin) / w;
w = w * scale;
h = h * scale;
}
if (h > (v.y - y_margin)) {
scale = (v.y - y_margin) / h;
w = w * scale;
h = h * scale;
}
JX.DOM.alterClass(img, 'loading', false);
JX.$V(w, h).setDim(img);
JX.Vector.getViewport().setDim(lightbox);
}
JX.Stratcom.listen(
'click',
['lightboxable', 'tag:a'],
loadLightBox
);
JX.Stratcom.listen(
'resize',
null,
resizeLightBox
);
JX.Stratcom.listen(
'keydown',
null,
lightBoxHandleKeyDown
);
});