Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/auth/controller/PhabricatorLoginController.php b/src/applications/auth/controller/PhabricatorLoginController.php
index f4bb0ab57a..19b589934d 100644
--- a/src/applications/auth/controller/PhabricatorLoginController.php
+++ b/src/applications/auth/controller/PhabricatorLoginController.php
@@ -1,311 +1,314 @@
<?php
final class PhabricatorLoginController
extends PhabricatorAuthController {
public function shouldRequireLogin() {
return false;
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if ($user->isLoggedIn()) {
// Kick the user out if they're already logged in.
return id(new AphrontRedirectResponse())->setURI('/');
}
if ($request->isAjax()) {
// We end up here if the user clicks a workflow link that they need to
// login to use. We give them a dialog saying "You need to login..".
if ($request->isDialogFormPost()) {
return id(new AphrontRedirectResponse())->setURI(
$request->getRequestURI());
}
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle(pht('Login Required'));
$dialog->appendChild(phutil_tag('p', array(), pht(
'You must login to continue.')));
$dialog->addSubmitButton(pht('Login'));
$dialog->addCancelButton('/', pht('Cancel'));
return id(new AphrontDialogResponse())->setDialog($dialog);
}
if ($request->isConduit()) {
// A common source of errors in Conduit client configuration is getting
// the request path wrong. The client will end up here, so make some
// effort to give them a comprehensible error message.
$request_path = $this->getRequest()->getPath();
$conduit_path = '/api/<method>';
$example_path = '/api/conduit.ping';
$message =
"ERROR: You are making a Conduit API request to '{$request_path}', ".
"but the correct HTTP request path to use in order to access a ".
"Conduit method is '{$conduit_path}' (for example, ".
"'{$example_path}'). Check your configuration.";
return id(new AphrontPlainTextResponse())->setContent($message);
}
$error_view = null;
if ($request->getCookie('phusr') && $request->getCookie('phsid')) {
// The session cookie is invalid, so clear it.
$request->clearCookie('phusr');
$request->clearCookie('phsid');
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Invalid Session'));
$error_view->setErrors(array(
pht("Your login session is invalid. Try logging in again. If that ".
"doesn't work, clear your browser cookies.")
));
}
- $next_uri_path = $this->getRequest()->getPath();
- if ($next_uri_path == '/login/') {
- $next_uri = '/';
- } else {
- $next_uri = $this->getRequest()->getRequestURI();
+
+ $next_uri = $request->getStr('next');
+ if (!$next_uri) {
+ $next_uri_path = $this->getRequest()->getPath();
+ if ($next_uri_path == '/login/') {
+ $next_uri = '/';
+ } else {
+ $next_uri = $this->getRequest()->getRequestURI();
+ }
}
if (!$request->isFormPost()) {
$request->setCookie('next_uri', $next_uri);
}
$password_auth = PhabricatorEnv::getEnvConfig('auth.password-auth-enabled');
$username_or_email = $request->getCookie('phusr');
$forms = array();
$errors = array();
if ($password_auth) {
$require_captcha = false;
$e_captcha = true;
if ($request->isFormPost()) {
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
$failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP(
PhabricatorUserLog::ACTION_LOGIN_FAILURE,
60 * 15);
if (count($failed_attempts) > 5) {
$require_captcha = true;
if (!AphrontFormRecaptchaControl::processCaptcha($request)) {
if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {
$e_captcha = pht('Invalid');
$errors[] = pht('CAPTCHA was not entered correctly.');
} else {
$e_captcha = pht('Required');
$errors[] = pht('Too many login failures recently. You must '.
'submit a CAPTCHA with your login request.');
}
}
}
}
$username_or_email = $request->getStr('username_or_email');
$user = id(new PhabricatorUser())->loadOneWhere(
'username = %s',
$username_or_email);
if (!$user) {
$user = PhabricatorUser::loadOneWithEmailAddress($username_or_email);
}
if (!$errors) {
// Perform username/password tests only if we didn't get rate limited
// by the CAPTCHA.
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
if (!$user || !$user->comparePassword($envelope)) {
$errors[] = pht('Bad username/password.');
}
}
if (!$errors) {
$session_key = $user->establishSession('web');
$request->setCookie('phusr', $user->getUsername());
$request->setCookie('phsid', $session_key);
- $uri = new PhutilURI('/login/validate/');
- $uri->setQueryParams(
- array(
- 'phusr' => $user->getUsername(),
+ $uri = id(new PhutilURI('/login/validate/'))
+ ->setQueryParams(
+ array('phusr' => $user->getUsername()
));
return id(new AphrontRedirectResponse())
->setURI((string)$uri);
} else {
$log = PhabricatorUserLog::newLog(
null,
$user,
PhabricatorUserLog::ACTION_LOGIN_FAILURE);
$log->save();
$request->clearCookie('phusr');
$request->clearCookie('phsid');
}
}
if ($errors) {
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('Login Failed'));
$error_view->setErrors($errors);
}
$form = new AphrontFormView();
$form
->setUser($request->getUser())
->setAction('/login/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Username/Email'))
->setName('username_or_email')
->setValue($username_or_email))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Password'))
->setName('password')
->setCaption(hsprintf(
'<a href="/login/email/">%s</a>',
pht('Forgot your password? / Email Login'))));
if ($require_captcha) {
$form->appendChild(
id(new AphrontFormRecaptchaControl())
->setError($e_captcha));
}
$form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Login')));
// $panel->setCreateButton('Register New Account', '/login/register/');
$forms['Phabricator Login'] = $form;
}
$ldap_provider = new PhabricatorLDAPProvider();
if ($ldap_provider->isProviderEnabled()) {
$ldap_form = new AphrontFormView();
$ldap_form
->setUser($request->getUser())
->setAction('/ldap/login/')
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('LDAP username'))
->setName('username')
->setValue($username_or_email))
->appendChild(
id(new AphrontFormPasswordControl())
->setLabel(pht('Password'))
->setName('password'));
$ldap_form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Login')));
$forms['LDAP Login'] = $ldap_form;
}
$providers = PhabricatorOAuthProvider::getAllProviders();
foreach ($providers as $provider) {
$enabled = $provider->isProviderEnabled();
if (!$enabled) {
continue;
}
$auth_uri = $provider->getAuthURI();
$redirect_uri = $provider->getRedirectURI();
$client_id = $provider->getClientID();
$provider_name = $provider->getProviderName();
$minimum_scope = $provider->getMinimumScope();
$extra_auth = $provider->getExtraAuthParameters();
// TODO: In theory we should use 'state' to prevent CSRF, but the total
// effect of the CSRF attack is that an attacker can cause a user to login
// to Phabricator if they're already logged into some OAuth provider. This
// does not seem like the most severe threat in the world, and generating
// CSRF for logged-out users is vaugely tricky.
if ($provider->isProviderRegistrationEnabled()) {
$title = pht("Login or Register with %s", $provider_name);
$body = pht('Login or register for Phabricator using your %s account.',
$provider_name);
$button = pht("Login or Register with %s", $provider_name);
} else {
$title = pht("Login with %s", $provider_name);
$body = hsprintf(
'%s<br /><br /><strong>%s</strong>',
pht(
'Login to your existing Phabricator account using your %s account.',
$provider_name),
pht(
'You can not use %s to register a new account.',
$provider_name));
$button = pht("Log in with %s", $provider_name);
}
$auth_form = new AphrontFormView();
$auth_form
->setAction($auth_uri)
->addHiddenInput('client_id', $client_id)
->addHiddenInput('redirect_uri', $redirect_uri)
->addHiddenInput('scope', $minimum_scope);
foreach ($extra_auth as $key => $value) {
$auth_form->addHiddenInput($key, $value);
}
$auth_form
->setUser($request->getUser())
->setMethod('GET')
->appendChild(hsprintf(
'<p class="aphront-form-instructions">%s</p>',
$body))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue("{$button} \xC2\xBB"));
$forms[$title] = $auth_form;
}
$panel = new AphrontPanelView();
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
$panel->setNoBackground();
foreach ($forms as $name => $form) {
$panel->appendChild(phutil_tag('h1', array(), $name));
$panel->appendChild($form);
$panel->appendChild(phutil_tag('br'));
}
$login_message = PhabricatorEnv::getEnvConfig('auth.login-message');
return $this->buildApplicationPage(
array(
$error_view,
phutil_safe_html($login_message),
$panel,
),
array(
'title' => pht('Login'),
'device' => true
));
}
}
diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php
index f5f312f3e6..b9e6c569a1 100644
--- a/src/applications/pholio/controller/PholioMockViewController.php
+++ b/src/applications/pholio/controller/PholioMockViewController.php
@@ -1,212 +1,219 @@
<?php
/**
* @group pholio
*/
final class PholioMockViewController extends PholioController {
private $id;
private $imageID;
+ public function shouldAllowPublic() {
+ return true;
+ }
+
public function willProcessRequest(array $data) {
$this->id = $data['id'];
$this->imageID = idx($data, 'imageID');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$mock = id(new PholioMockQuery())
->setViewer($user)
->withIDs(array($this->id))
->needImages(true)
->needCoverFiles(true)
->executeOne();
if (!$mock) {
return new Aphront404Response();
}
$xactions = id(new PholioTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($mock->getPHID()))
->execute();
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$mock->getPHID());
$phids = array();
$phids[] = $mock->getAuthorPHID();
foreach ($subscribers as $subscriber) {
$phids[] = $subscriber;
}
$this->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$title = $mock->getName();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionView($mock);
$properties = $this->buildPropertyView($mock, $engine, $subscribers);
require_celerity_resource('pholio-css');
require_celerity_resource('pholio-inline-comments-css');
- $output = new PholioMockImagesView();
- $output->setMock($mock);
- $output->setImageID($this->imageID);
+ $output = id(new PholioMockImagesView())
+ ->setRequestURI($request->getRequestURI())
+ ->setUser($user)
+ ->setMock($mock)
+ ->setImageID($this->imageID);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($this->getRequest()->getUser())
->setTransactions($xactions)
->setMarkupEngine($engine);
$add_comment = $this->buildAddCommentView($mock);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('M'.$mock->getID())
->setHref('/M'.$mock->getID()));
$content = array(
$crumbs,
$header,
$actions,
$properties,
$output->render(),
$xaction_view,
$add_comment,
);
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$user,
$mock->getPHID());
return $this->buildApplicationPage(
$content,
array(
'title' => 'M'.$mock->getID().' '.$title,
'device' => true,
'pageObjects' => array($mock->getPHID()),
));
}
private function buildActionView(PholioMock $mock) {
$user = $this->getRequest()->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
->setObject($mock);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$mock,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Mock'))
->setHref($this->getApplicationURI('/edit/'.$mock->getID()))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
PholioMock $mock,
PhabricatorMarkupEngine $engine,
array $subscribers) {
$user = $this->getRequest()->getUser();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($mock);
$properties->addProperty(
pht('Author'),
$this->getHandle($mock->getAuthorPHID())->renderLink());
$properties->addProperty(
pht('Created'),
phabricator_datetime($mock->getDateCreated(), $user));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$mock);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
if ($subscribers) {
$sub_view = array();
foreach ($subscribers as $subscriber) {
$sub_view[] = $this->getHandle($subscriber)->renderLink();
}
$sub_view = phutil_implode_html(', ', $sub_view);
} else {
$sub_view = phutil_tag('em', array(), pht('None'));
}
$properties->addProperty(
pht('Subscribers'),
$sub_view);
$properties->invokeWillRenderEvent();
$properties->addImageContent(
$engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION));
return $properties;
}
private function buildAddCommentView(PholioMock $mock) {
$user = $this->getRequest()->getUser();
$draft = PhabricatorDraft::newFromUserAndKey($user, $mock->getPHID());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$title = $is_serious
? pht('Add Comment')
: pht('History Beckons');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$button_name = $is_serious
? pht('Add Comment')
: pht('Answer The Call');
$form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setDraft($draft)
->setSubmitButtonName($button_name)
- ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/'));
+ ->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/'))
+ ->setRequestURI($this->getRequest()->getRequestURI());
return array(
$header,
$form,
);
}
}
diff --git a/src/applications/pholio/view/PholioMockImagesView.php b/src/applications/pholio/view/PholioMockImagesView.php
index 72116f14ea..a8f3a6c209 100644
--- a/src/applications/pholio/view/PholioMockImagesView.php
+++ b/src/applications/pholio/view/PholioMockImagesView.php
@@ -1,150 +1,163 @@
<?php
final class PholioMockImagesView extends AphrontView {
private $mock;
private $imageID;
+ private $requestURI;
+
+ public function setRequestURI(PhutilURI $request_uri) {
+ $this->requestURI = $request_uri;
+ return $this;
+ }
+ public function getRequestURI() {
+ return $this->requestURI;
+ }
public function setImageID($image_id) {
$this->imageID = $image_id;
return $this;
}
public function getImageID() {
return $this->imageID;
}
public function setMock(PholioMock $mock) {
$this->mock = $mock;
return $this;
}
public function render() {
if (!$this->mock) {
throw new Exception("Call setMock() before render()!");
}
$mock = $this->mock;
require_celerity_resource('javelin-behavior-pholio-mock-view');
$images = array();
$panel_id = celerity_generate_unique_node_id();
$viewport_id = celerity_generate_unique_node_id();
$ids = mpull($mock->getImages(), 'getID');
if ($this->imageID && isset($ids[$this->imageID])) {
$selected_id = $this->imageID;
} else {
$selected_id = head_key($ids);
}
foreach ($mock->getImages() as $image) {
$file = $image->getFile();
$metadata = $file->getMetadata();
$x = idx($metadata, PhabricatorFile::METADATA_IMAGE_WIDTH);
$y = idx($metadata, PhabricatorFile::METADATA_IMAGE_HEIGHT);
$images[] = array(
'id' => $image->getID(),
'fullURI' => $image->getFile()->getBestURI(),
'pageURI' => '/M'.$mock->getID().'/'.$image->getID().'/',
'width' => $x,
'height' => $y,
'title' => $file->getName(),
'desc' => 'Lorem ipsum dolor sit amet: there is no way to set any '.
'descriptive text yet; were there, it would appear here.',
);
}
+ $login_uri = id(new PhutilURI('/login/'))
+ ->setQueryParam('next', (string) $this->getRequestURI());
$config = array(
'mockID' => $mock->getID(),
'panelID' => $panel_id,
'viewportID' => $viewport_id,
'images' => $images,
'selectedID' => $selected_id,
+ 'loggedIn' => $this->getUser()->isLoggedIn(),
+ 'logInLink' => (string) $login_uri
);
Javelin::initBehavior('pholio-mock-view', $config);
$mockview = '';
$mock_wrapper = javelin_tag(
'div',
array(
'id' => $viewport_id,
'sigil' => 'mock-viewport',
'class' => 'pholio-mock-image-viewport'
),
'');
$mock_wrapper = javelin_tag(
'div',
array(
'id' => $panel_id,
'sigil' => 'mock-panel',
'class' => 'pholio-mock-image-panel',
),
$mock_wrapper);
$inline_comments_holder = javelin_tag(
'div',
array(
'id' => 'mock-inline-comments',
'sigil' => 'mock-inline-comments',
'class' => 'pholio-mock-inline-comments'
),
'');
$carousel_holder = '';
if (count($mock->getImages()) > 0) {
$thumbnails = array();
foreach ($mock->getImages() as $image) {
$thumbfile = $image->getFile();
$dimensions = PhabricatorImageTransformer::getPreviewDimensions(
$thumbfile,
140);
$tag = phutil_tag(
'img',
array(
'width' => $dimensions['sdx'],
'height' => $dimensions['sdy'],
'src' => $thumbfile->getPreview140URI(),
'class' => 'pholio-mock-carousel-thumbnail',
'style' => 'top: '.floor((140 - $dimensions['sdy'] ) / 2).'px',
));
$thumbnails[] = javelin_tag(
'a',
array(
'sigil' => 'mock-thumbnail',
'class' => 'pholio-mock-carousel-thumb-item',
'href' => '/M'.$mock->getID().'/'.$image->getID().'/',
'meta' => array(
'imageID' => $image->getID(),
),
),
$tag);
}
$carousel_holder = phutil_tag(
'div',
array(
'id' => 'pholio-mock-carousel',
'class' => 'pholio-mock-carousel',
),
$thumbnails);
}
$mockview[] = phutil_tag(
'div',
array(
'class' => 'pholio-mock-image-container',
'id' => 'pholio-mock-image-container'
),
array($mock_wrapper, $carousel_holder, $inline_comments_holder));
return $this->renderSingleView($mockview);
}
}
diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
index a943ec6179..ec26997456 100644
--- a/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
+++ b/src/applications/transactions/view/PhabricatorApplicationTransactionCommentView.php
@@ -1,174 +1,203 @@
<?php
/**
* @concrete-extensible
*/
class PhabricatorApplicationTransactionCommentView extends AphrontView {
private $submitButtonName;
private $action;
private $previewPanelID;
private $previewTimelineID;
private $previewToggleID;
private $formID;
private $statusID;
private $commentID;
private $draft;
+ private $requestURI;
+
+ public function setRequestURI(PhutilURI $request_uri) {
+ $this->requestURI = $request_uri;
+ return $this;
+ }
+ public function getRequestURI() {
+ return $this->requestURI;
+ }
public function setDraft(PhabricatorDraft $draft) {
$this->draft = $draft;
return $this;
}
public function getDraft() {
return $this->draft;
}
public function setSubmitButtonName($submit_button_name) {
$this->submitButtonName = $submit_button_name;
return $this;
}
public function getSubmitButtonName() {
return $this->submitButtonName;
}
public function setAction($action) {
$this->action = $action;
return $this;
}
public function getAction() {
return $this->action;
}
public function render() {
+ $user = $this->getUser();
+ if (!$user->isLoggedIn()) {
+ $uri = id(new PhutilURI('/login/'))
+ ->setQueryParam('next', (string) $this->getRequestURI());
+ return self::renderSingleView(
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'login-to-comment'
+ ),
+ javelin_tag(
+ 'a',
+ array(
+ 'class' => 'button',
+ 'sigil' => 'workflow',
+ 'href' => $uri
+ ),
+ pht('Login to Comment'))));
+ }
+
$data = array();
$comment = $this->renderCommentPanel();
$preview = $this->renderPreviewPanel();
Javelin::initBehavior(
'phabricator-transaction-comment-form',
array(
'formID' => $this->getFormID(),
'timelineID' => $this->getPreviewTimelineID(),
'panelID' => $this->getPreviewPanelID(),
'statusID' => $this->getStatusID(),
'commentID' => $this->getCommentID(),
'loadingString' => pht('Loading Preview...'),
'savingString' => pht('Saving Draft...'),
'draftString' => pht('Saved Draft'),
'actionURI' => $this->getAction(),
'draftKey' => $this->getDraft()->getDraftKey(),
));
return self::renderSingleView(
array(
$comment,
$preview,
));
}
private function renderCommentPanel() {
$status = phutil_tag(
'div',
array(
'id' => $this->getStatusID(),
),
'');
$draft_comment = '';
if ($this->getDraft()) {
$draft_comment = $this->getDraft()->getDraft();
}
return id(new AphrontFormView())
->setUser($this->getUser())
->setFlexible(true)
->addSigil('transaction-append')
->setWorkflow(true)
->setAction($this->getAction())
->setID($this->getFormID())
->appendChild(
id(new PhabricatorRemarkupControl())
->setID($this->getCommentID())
->setName('comment')
->setLabel(pht('Comment'))
->setUser($this->getUser())
->setValue($draft_comment))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($this->getSubmitButtonName()))
->appendChild(
id(new AphrontFormMarkupControl())
->setValue($status));
}
private function renderPreviewPanel() {
$preview = id(new PhabricatorTimelineView())
->setID($this->getPreviewTimelineID());
$header = phutil_tag(
'div',
array(
'class' => 'phabricator-timeline-preview-header',
),
pht('Preview'));
return phutil_tag(
'div',
array(
'id' => $this->getPreviewPanelID(),
'style' => 'display: none',
),
self::renderSingleView(
array(
$header,
$preview,
)));
}
private function getPreviewPanelID() {
if (!$this->previewPanelID) {
$this->previewPanelID = celerity_generate_unique_node_id();
}
return $this->previewPanelID;
}
private function getPreviewTimelineID() {
if (!$this->previewTimelineID) {
$this->previewTimelineID = celerity_generate_unique_node_id();
}
return $this->previewTimelineID;
}
private function getFormID() {
if (!$this->formID) {
$this->formID = celerity_generate_unique_node_id();
}
return $this->formID;
}
private function getStatusID() {
if (!$this->statusID) {
$this->statusID = celerity_generate_unique_node_id();
}
return $this->statusID;
}
private function getCommentID() {
if (!$this->commentID) {
$this->commentID = celerity_generate_unique_node_id();
}
return $this->commentID;
}
}
diff --git a/webroot/rsrc/css/aphront/form-view.css b/webroot/rsrc/css/aphront/form-view.css
index abca6a6b2d..40eb7b0226 100644
--- a/webroot/rsrc/css/aphront/form-view.css
+++ b/webroot/rsrc/css/aphront/form-view.css
@@ -1,365 +1,370 @@
/**
* @provides aphront-form-view-css
*/
/**
* These styles are overrides for .aphront-form-view
*/
.aphront-form-view-shaded {
border: 1px solid #d4dae0;
background: #f4f5f8;
}
.aphront-form-view-padded {
padding: 1em;
}
.aphront-form-view label.aphront-form-label {
padding-top: 4px;
width: 19%;
float: left;
text-align: right;
font-weight: bold;
font-size: 13px;
color: #666666;
}
.device-phone .aphront-form-view label.aphront-form-label {
display: block;
float: none;
text-align: left;
width: 100%;
}
.aphront-form-input {
margin-left: 20%;
margin-right: 25%;
width: 55%;
}
.device-phone .aphront-form-input {
margin-left: 0%;
margin-right: 0%;
width: 100%;
}
.aphront-form-control-text .aphront-form-input input,
.aphront-form-control-password .aphront-form-input input {
font-size: 13px;
padding: 4px 4px;
color: #333;
vertical-align: middle;
background-color: #ffffff;
border: 1px solid #96A6C5;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.aphront-form-error {
width: 23%;
float: right;
color: #aa0000;
font-weight: bold;
padding-top: 4px;
}
.device-phone .aphront-form-error {
float: none;
width: 100%;
}
.device-phone .aphront-form-drag-and-drop-upload {
display: none;
}
.aphront-form-required {
font-weight: normal;
color: #888888;
font-size: 11px;
}
.aphront-form-input input,
.aphront-form-input textarea {
font-size: 13px;
display: block;
width: 100%;
box-sizing: border-box;
}
.aphront-form-input textarea {
height: 12em;
}
.aphront-form-control {
padding: 4px;
}
.aphront-form-control-submit button,
.aphront-form-control-submit a.button {
float: right;
margin: 0.5em 0 0em 2%;
}
.aphront-form-control-textarea textarea.aphront-textarea-very-short {
height: 3em;
border: 1px solid #96A6C5;
border-radius: 3px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.aphront-form-control-textarea textarea.aphront-textarea-very-tall {
height: 24em;
}
.aphront-form-control-select .aphront-form-input {
padding-top: 2px;
}
.aphront-form-view .aphront-form-caption {
font-size: 12px;
color: #888;
padding: 2px;
text-align: right;
margin-right: 25%;
margin-left: 20%;
}
.device-phone .aphront-form-view .aphront-form-caption {
margin-right: 0%;
}
/* override for when inside an aphront-panel-view */
.aphront-panel-view .aphront-form-view h1 {
padding: 0em 0em .8em 0em;
}
.aphront-form-instructions {
margin: 0.75em 3% 1.25em;
}
.aphront-form-important {
margin: .5em 0;
background: #ffffdd;
padding: .5em 1em;
}
.aphront-form-important code {
display: block;
padding: .25em;
margin: .5em 2em;
}
.aphront-form-control-static .aphront-form-input,
.aphront-form-control-markup .aphront-form-input {
padding-top: 4px;
font-size: 13px;
}
.aphront-form-control-togglebuttons .aphront-form-input {
padding-top: 5px;
}
table.aphront-form-control-radio-layout,
table.aphront-form-control-checkbox-layout {
margin-top: 3px;
font-size: 13px;
}
table.aphront-form-control-radio-layout th,
table.aphront-form-control-checkbox-layout th {
padding-top: 2px;
padding-left: 0.35em;
padding-bottom: 4px;
}
.aphront-form-control-radio-layout td input,
.aphront-form-control-checkbox-layout td input {
margin-top: 4px;
width: auto;
}
.aphront-form-radio-caption {
font-size: 11px;
color: #444444;
max-width: 400px;
}
.aphront-form-control-image span {
margin: 0px 4px 0px 2px;
}
.aphront-form-control-image .default-image {
display: inline;
width: 12px;
}
.aphront-form-input hr {
border: none;
background: #bbbbbb;
height: 1px;
position: relative;
}
.aphront-form-inset {
margin: 0 0 1em;
padding: .75em;
background: #f3f3f3;
border: 1px solid #afafaf;
}
.aphront-form-drag-and-drop-file-list {
width: 400px;
}
.drag-and-drop-instructions {
color: #333333;
font-size: 11px;
padding: 6px 8px;
}
.drag-and-drop-file-target {
border: 1px dashed #bfbfbf;
padding-top: 10px;
padding-bottom: 10px;
}
.aphront-textarea-drag-and-drop {
background: #99ff99;
border-color: #669966;
}
.aphront-form-crop .crop-box {
cursor: move;
overflow: hidden;
}
.aphront-form-crop .crop-box .crop-image {
position: relative;
top: 0px;
left: 0px;
}
.calendar-button {
display: inline;
background: url(/rsrc/image/icon/fatcow/calendar_edit.png)
no-repeat center center;
padding: 8px 12px;
margin: 2px 8px 2px 2px;
position: relative;
border: 1px solid transparent;
}
.aphront-form-date-container {
position: relative;
display: inline;
}
.aphront-form-date-container select {
margin: 2px;
display: inline;
}
.aphront-form-date-container input.aphront-form-date-time-input {
width: 7em;
display: inline;
}
.fancy-datepicker {
position: absolute;
width: 240px;
}
.fancy-datepicker-core {
padding: 1px;
font-size: 11px;
font-family: Verdana;
text-align: center;
}
.fancy-datepicker-core .month-table,
.fancy-datepicker-core .day-table {
margin: 0 auto;
border-collapse: separate;
border-spacing: 1px;
width: 100%;
}
.fancy-datepicker-core .month-table {
margin-bottom: 6px;
}
.fancy-datepicker-core .month-table td.lrbutton {
width: 20%;
}
.fancy-datepicker-core .month-table td {
padding: 4px;
font-weight: bold;
color: #444444;
}
.fancy-datepicker-core .month-table td.lrbutton {
background: #e6e6e6;
border: 1px solid;
border-color: #a6a6a6 #969696 #868686 #a6a6a6;
}
.fancy-datepicker-core .day-table td {
overflow: hidden;
background: #f6f6f6;
vertical-align: center;
text-align: center;
border: 1px solid #d6d6d6;
padding: 4px 0;
}
.fancy-datepicker-core .day-table td.day-placeholder {
border-color: transparent;
background: transparent;
}
.fancy-datepicker-core .day-table td.weekend {
color: #666666;
border-color: #e6e6e6;
}
.fancy-datepicker-core .day-table td.day-name {
background: transparent;
border: 1px transparent;
vertical-align: bottom;
color: #888888;
}
.fancy-datepicker-core .day-table td.today {
background: #eeee99;
border-color: #aaaa66;
}
.fancy-datepicker-core .day-table td.datepicker-selected {
background: #0099ff;
border-color: #0066cc;
}
.fancy-datepicker-core td {
cursor: pointer;
}
.fancy-datepicker-core td.novalue {
cursor: inherit;
}
.picker-open .calendar-button,
.fancy-datepicker-core {
background-color: white;
border: 1px solid #777777;
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.25);
}
.picker-open .calendar-button {
border-left: 1px solid white;
}
+
+.login-to-comment {
+ padding: 12px;
+ float: right;
+}
diff --git a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
index 095722e014..4d5105fe93 100644
--- a/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
+++ b/webroot/rsrc/js/application/pholio/behavior-pholio-mock-view.js
@@ -1,714 +1,719 @@
/**
* @provides javelin-behavior-pholio-mock-view
* @requires javelin-behavior
* javelin-util
* javelin-stratcom
* javelin-dom
* javelin-vector
* javelin-magical-init
* javelin-request
* javelin-history
* javelin-workflow
* javelin-mask
* javelin-behavior-device
* phabricator-keyboard-shortcut
*/
JX.behavior('pholio-mock-view', function(config) {
var is_dragging = false;
var drag_begin;
var drag_end;
var panel = JX.$(config.panelID);
var viewport = JX.$(config.viewportID);
var selection_border;
var selection_fill;
var active_image;
var inline_comments = {};
/* -( Stage )-------------------------------------------------------------- */
var stage = (function() {
var loading = false;
var stageElement = JX.$(config.panelID);
var viewElement = JX.$(config.viewportID);
var gutterElement = JX.$('mock-inline-comments');
var reticles = [];
var cards = [];
var inline_phid_map = {};
function begin_load() {
if (loading) {
return;
}
loading = true;
clear_stage();
draw_loading();
}
function end_load() {
if (!loading) {
return;
}
loading = false;
draw_loading();
}
function draw_loading() {
JX.DOM.alterClass(stageElement, 'pholio-image-loading', loading);
}
function add_inline_node(node, phid) {
inline_phid_map[phid] = (inline_phid_map[phid] || []);
inline_phid_map[phid].push(node);
}
function add_reticle(reticle, phid) {
mark_ref(reticle, phid);
reticles.push(reticle);
add_inline_node(reticle, phid);
viewElement.appendChild(reticle);
}
function clear_stage() {
for (var ii = 0; ii < reticles.length; ii++) {
JX.DOM.remove(reticles[ii]);
}
for (var ii = 0; ii < cards.length; ii++) {
JX.DOM.remove(cards[ii]);
}
reticles = [];
cards = [];
inline_phid_map = {};
}
function highlight_inline(phid, show) {
var nodes = inline_phid_map[phid] || [];
var cls = 'pholio-mock-inline-comment-highlight';
for (var ii = 0; ii < nodes.length; ii++) {
JX.DOM.alterClass(nodes[ii], cls, show);
}
}
function remove_inline(phid) {
var nodes = inline_phid_map[phid] || [];
for (var ii = 0; ii < nodes.length; ii++) {
JX.DOM.remove(nodes[ii]);
}
delete inline_phid_map[phid];
}
function mark_ref(node, phid) {
JX.Stratcom.addSigil(node, 'pholio-inline-ref');
JX.Stratcom.addData(node, {phid: phid});
}
function add_card(card, phid) {
mark_ref(card, phid);
cards.push(card);
add_inline_node(card, phid);
gutterElement.appendChild(card);
}
return {
beginLoad: begin_load,
endLoad: end_load,
addReticle: add_reticle,
clearStage: clear_stage,
highlightInline: highlight_inline,
removeInline: remove_inline,
addCard: add_card
};
})();
function get_image_index(id) {
for (var ii = 0; ii < config.images.length; ii++) {
if (config.images[ii].id == id) {
return ii;
}
}
return null;
}
function get_image(id) {
var idx = get_image_index(id);
if (idx === null) {
return idx;
}
return config.images[idx];
}
function onload_image(id) {
if (active_image.id != id) {
// The user has clicked another image before this one loaded, so just
// bail.
return;
}
active_image.tag = this;
redraw_image();
}
function switch_image(delta) {
if (!active_image) {
return;
}
var idx = get_image_index(active_image.id)
idx = (idx + delta + config.images.length) % config.images.length;
select_image(config.images[idx].id);
}
function redraw_image() {
// Force the stage to scale as a function of the viewport size. Broadly,
// we make the stage 95% of the height of the viewport, then scale images
// to fit within it.
var new_y = (JX.Vector.getViewport().y * 0.90) - 150;
new_y = Math.max(320, new_y);
panel.style.height = new_y + 'px';
if (!active_image || !active_image.tag) {
return;
}
var tag = active_image.tag;
// If the image is too wide or tall for the viewport, scale it down so it
// fits.
var w = JX.Vector.getDim(panel);
w.x -= 40;
w.y -= 40;
var scale = 1;
if (w.x < tag.naturalWidth) {
scale = Math.min(scale, w.x / tag.naturalWidth);
}
if (w.y < tag.naturalHeight) {
scale = Math.min(scale, w.y / tag.naturalHeight);
}
if (scale < 1) {
tag.width = Math.floor(scale * tag.naturalWidth);
tag.height = Math.floor(scale * tag.naturalHeight);
} else {
tag.width = tag.naturalWidth;
tag.height = tag.naturalHeight;
}
viewport.style.top = Math.floor((new_y - tag.height) / 2) + 'px';
stage.endLoad();
JX.DOM.setContent(viewport, tag);
redraw_inlines(active_image.id);
}
function select_image(image_id) {
active_image = get_image(image_id);
active_image.tag = null;
stage.beginLoad();
var img = JX.$N('img', {className: 'pholio-mock-image'});
img.onload = JX.bind(img, onload_image, active_image.id);
img.src = active_image.fullURI;
var thumbs = JX.DOM.scry(
JX.$('pholio-mock-carousel'),
'a',
'mock-thumbnail');
for(var k in thumbs) {
var thumb_meta = JX.Stratcom.getData(thumbs[k]);
JX.DOM.alterClass(
thumbs[k],
'pholio-mock-carousel-thumb-current',
(active_image.id == thumb_meta.imageID));
}
load_inline_comments();
if (image_id != config.selectedID) {
JX.History.replace(active_image.pageURI);
}
}
JX.Stratcom.listen(
['mousedown', 'click'],
'mock-thumbnail',
function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
e.kill();
select_image(e.getNodeData('mock-thumbnail').imageID);
});
select_image(config.selectedID);
JX.Stratcom.listen('mousedown', 'mock-viewport', function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
if (JX.Device.getDevice() != 'desktop') {
return;
}
if (drag_begin) {
return;
}
e.kill();
is_dragging = true;
drag_begin = get_image_xy(JX.$V(e));
drag_end = drag_begin;
redraw_selection();
});
JX.enableDispatch(document.body, 'mousemove');
JX.Stratcom.listen('mousemove', null, function(e) {
if (!is_dragging) {
return;
}
drag_end = get_image_xy(JX.$V(e));
redraw_selection();
});
JX.Stratcom.listen(
['mouseover', 'mouseout'],
'pholio-inline-ref',
function(e) {
var phid = e.getNodeData('pholio-inline-ref').phid;
var show = (e.getType() == 'mouseover');
stage.highlightInline(phid, show);
});
JX.Stratcom.listen(
'mouseup',
null,
function(e) {
if (!is_dragging) {
return;
}
is_dragging = false;
+ if (!config.loggedIn) {
+ new JX.Workflow(config.logInLink).start();
+ return;
+ }
+
drag_end = get_image_xy(JX.$V(e));
var data = {mockID: config.mockID};
var handler = function(r) {
var dialog = JX.$H(r).getFragment().firstChild;
JX.DOM.appendContent(viewport, dialog);
JX.$V(
Math.min(drag_begin.x, drag_end.x),
Math.max(drag_begin.y, drag_end.y) + 4
).setPos(dialog);
JX.DOM.focus(JX.DOM.find(dialog, 'textarea'));
}
new JX.Workflow('/pholio/inline/save/', data)
.setHandler(handler)
.start();
});
function redraw_inlines(id) {
if (!active_image) {
return;
}
if (active_image.id != id) {
return;
}
stage.clearStage();
var comment_holder = JX.$('mock-inline-comments');
JX.DOM.setContent(comment_holder, render_image_info(active_image));
var inlines = inline_comments[active_image.id];
if (!inlines || !inlines.length) {
return;
}
for (var ii = 0; ii < inlines.length; ii++) {
var inline = inlines[ii];
var card = JX.$H(inline.contentHTML).getFragment().firstChild;
stage.addCard(card, inline.phid);
if (!active_image.tag) {
// The image itself hasn't loaded yet, so we can't draw the inline
// reticles.
continue;
}
var inline_selection = render_reticle_fill();
stage.addReticle(inline_selection, inline.phid);
position_inline_rectangle(inline, inline_selection);
if (!inline.transactionphid) {
var inline_draft = render_reticle_border();
stage.addReticle(inline_draft, inline.phid);
position_inline_rectangle(inline, inline_draft);
}
}
}
function position_inline_rectangle(inline, rect) {
var scale = active_image.tag.width / active_image.tag.naturalWidth;
JX.$V(scale * inline.x, scale * inline.y).setPos(rect);
JX.$V(scale * inline.width, scale * inline.height).setDim(rect);
}
function get_image_xy(p) {
var img = active_image.tag;
var imgp = JX.$V(img);
var scale = 1 / get_image_scale();
var x = scale * Math.max(0, Math.min(p.x - imgp.x, img.width));
var y = scale * Math.max(0, Math.min(p.y - imgp.y, img.height));
return {
x: x,
y: y
};
}
function get_image_scale() {
var img = active_image.tag;
return img.width / img.naturalWidth;
}
function redraw_selection() {
selection_border = selection_border || render_reticle_border();
selection_fill = selection_fill || render_reticle_fill();
var p = JX.$V(
Math.min(drag_begin.x, drag_end.x),
Math.min(drag_begin.y, drag_end.y));
var d = JX.$V(
Math.max(drag_begin.x, drag_end.x) - p.x,
Math.max(drag_begin.y, drag_end.y) - p.y);
var scale = get_image_scale();
p.x *= scale;
p.y *= scale;
d.x *= scale;
d.y *= scale;
var nodes = [selection_fill, selection_border];
for (var ii = 0; ii < nodes.length; ii++) {
var node = nodes[ii];
viewport.appendChild(node);
p.setPos(node);
d.setDim(node);
}
}
function clear_selection() {
selection_fill && JX.DOM.remove(selection_fill);
selection_border && JX.DOM.remove(selection_border);
}
function load_inline_comments() {
var id = active_image.id;
var inline_comments_uri = "/pholio/inline/" + id + "/";
new JX.Request(inline_comments_uri, function(r) {
inline_comments[id] = r;
redraw_inlines(id);
}).send();
}
JX.Stratcom.listen(
'click',
'inline-delete',
function(e) {
var data = e.getNodeData('inline-delete');
e.kill();
interrupt_typing();
stage.removeInline(data.phid);
var deleteURI = '/pholio/inline/delete/' + data.id + '/';
var del = new JX.Request(deleteURI, function(r) {
});
del.send();
});
JX.Stratcom.listen(
'click',
'inline-edit',
function(e) {
var data = e.getNodeData('inline-edit');
e.kill();
interrupt_typing();
var editURI = "/pholio/inline/edit/" + data.id + '/';
var edit_dialog = new JX.Request(editURI, function(r) {
var dialog = JX.$N(
'div',
{
className: 'pholio-edit-inline-popup'
},
JX.$H(r));
JX.DOM.setContent(JX.$(data.phid + '_comment'), dialog);
});
edit_dialog.send();
});
JX.Stratcom.listen(
'click',
'inline-edit-cancel',
function(e) {
var data = e.getNodeData('inline-edit-cancel');
e.kill();
load_inline_comment(data.id);
});
JX.Stratcom.listen(
'click',
'inline-edit-submit',
function(e) {
var data = e.getNodeData('inline-edit-submit');
var editURI = "/pholio/inline/edit/" + data.id + '/';
e.kill();
var edit = new JX.Request(editURI, function(r) {
load_inline_comment(data.id);
});
edit.addData({
op: 'update',
content: JX.DOM.find(JX.$(data.phid + '_comment'), 'textarea').value
});
edit.send();
});
JX.Stratcom.listen(
'click',
'inline-save-cancel',
function(e) {
e.kill();
interrupt_typing();
}
);
JX.Stratcom.listen(
'click',
'inline-save-submit',
function(e) {
e.kill();
var form = JX.$('pholio-new-inline-comment-dialog');
var text = JX.DOM.find(form, 'textarea').value;
if (!text.length) {
interrupt_typing();
return;
}
var data = {
mockID: config.mockID,
imageID: active_image.id,
startX: Math.min(drag_begin.x, drag_end.x),
startY: Math.min(drag_begin.y, drag_end.y),
endX: Math.max(drag_begin.x, drag_end.x),
endY: Math.max(drag_begin.y, drag_end.y)
};
var handler = function(r) {
if (!inline_comments[active_image.id]) {
inline_comments[active_image.id] = [];
}
inline_comments[active_image.id].push(r);
interrupt_typing();
redraw_inlines(active_image.id);
};
JX.Workflow.newFromForm(form, data)
.setHandler(handler)
.start();
}
);
function load_inline_comment(id) {
var viewInlineURI = '/pholio/inline/view/' + id + '/';
var inline_comment = new JX.Request(viewInlineURI, function(r) {
JX.DOM.replace(JX.$(r.phid + '_comment'), JX.$H(r.contentHTML));
});
inline_comment.send();
}
function interrupt_typing() {
clear_selection();
try {
JX.DOM.remove(JX.$('pholio-new-inline-comment-dialog'));
} catch (x) {
// TODO: For now, ignore this.
}
drag_begin = null;
}
load_inline_comments();
JX.Stratcom.listen('resize', null, redraw_image);
redraw_image();
/* -( Keyboard Shortcuts )------------------------------------------------- */
new JX.KeyboardShortcut('j', 'Show next image.')
.setHandler(function() {
switch_image(1);
})
.register();
new JX.KeyboardShortcut('k', 'Show previous image.')
.setHandler(function() {
switch_image(-1);
})
.register();
/* -( Render )------------------------------------------------------------- */
function render_image_info(image) {
var info = [];
var title = JX.$N(
'div',
{className: 'pholio-image-title'},
image.title);
info.push(title);
var desc = JX.$N(
'div',
{className: 'pholio-image-description'},
image.desc);
info.push(desc);
var visible = null;
if (image.tag) {
var area = Math.round(100 * (image.tag.width / image.width));
area = ['(' + area + '%' + ')'];
visible = [' ', JX.$N('span', {className: 'pholio-visible-size'}, area)];
}
info.push([image.width, '\u00d7', image.height, 'px', visible]);
var full_link = JX.$N(
'a',
{href: image.fullURI, target: '_blank'},
'View Full Image');
info.push(full_link);
for (var ii = 0; ii < info.length; ii++) {
info[ii] = JX.$N('div', {className: 'pholio-image-info-item'}, info[ii]);
}
info = JX.$N('div', {className: 'pholio-image-info'}, info);
return info;
}
function render_reticle_border() {
return JX.$N(
'div',
{className: 'pholio-mock-select-border'});
}
function render_reticle_fill() {
return JX.$N(
'div',
{className: 'pholio-mock-select-fill'});
}
/* -( Device Lightbox )---------------------------------------------------- */
// On devices, we show images full-size when the user taps them instead of
// attempting to implement inlines.
var lightbox = null;
JX.Stratcom.listen('click', 'mock-viewport', function(e) {
if (!e.isNormalMouseEvent()) {
return;
}
if (JX.Device.getDevice() == 'desktop') {
return;
}
lightbox_attach();
e.kill();
});
JX.Stratcom.listen('click', 'pholio-device-lightbox', lightbox_detach);
JX.Stratcom.listen('resize', null, lightbox_resize);
function lightbox_attach() {
JX.DOM.alterClass(document.body, 'lightbox-attached', true);
JX.Mask.show('jx-dark-mask');
lightbox = lightbox_render();
var image = JX.$N('img');
image.onload = lightbox_loaded;
setTimeout(function() {
image.src = active_image.fullURI;
}, 1000);
JX.DOM.setContent(lightbox, image);
JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', true);
lightbox_resize();
document.body.appendChild(lightbox);
}
function lightbox_detach() {
JX.DOM.remove(lightbox);
JX.Mask.hide();
JX.DOM.alterClass(document.body, 'lightbox-attached', false);
lightbox = null;
}
function lightbox_resize(e) {
if (!lightbox) {
return;
}
JX.Vector.getScroll().setPos(lightbox);
JX.Vector.getViewport().setDim(lightbox);
}
function lightbox_loaded() {
JX.DOM.alterClass(lightbox, 'pholio-device-lightbox-loading', false);
}
function lightbox_render() {
var el = JX.$N('div', {className: 'pholio-device-lightbox'});
JX.Stratcom.addSigil(el, 'pholio-device-lightbox');
return el;
}
});

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 15, 8:31 PM (7 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
338418
Default Alt Text
(52 KB)

Event Timeline