Page MenuHomestyx hydra

No OneTemporary

diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php
new file mode 100644
index 0000000000..a69ac9a5a8
--- /dev/null
+++ b/scripts/__init_script__.php
@@ -0,0 +1,32 @@
+<?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.
+ */
+
+$include_path = ini_get('include_path');
+ini_set('include_path', $include_path.':'.dirname(__FILE__).'/../../');
+@require_once 'libphutil/src/__phutil_library_init__.php';
+if (!@constant('__LIBPHUTIL__')) {
+ echo "ERROR: Unable to load libphutil. Update your PHP 'include_path' to ".
+ "include the parent directory of libphutil/.\n";
+ exit(1);
+}
+
+if (!ini_get('date.timezone')) {
+ date_default_timezone_set('America/Los_Angeles');
+}
+
+phutil_load_library(dirname(__FILE__).'/../src/');
diff --git a/scripts/celerity_mapper.php b/scripts/celerity_mapper.php
new file mode 100755
index 0000000000..a7a388c087
--- /dev/null
+++ b/scripts/celerity_mapper.php
@@ -0,0 +1,102 @@
+#!/usr/bin/env php
+<?php
+
+require_once dirname(__FILE__).'/__init_script__.php';
+
+if ($argc != 2) {
+ $self = basename($argv[0]);
+ echo "usage: {$self} <webroot>\n";
+ exit(1);
+}
+
+phutil_require_module('phutil', 'filesystem');
+phutil_require_module('phutil', 'filesystem/filefinder');
+phutil_require_module('phutil', 'future/exec');
+phutil_require_module('phutil', 'parser/docblock');
+
+$root = Filesystem::resolvePath($argv[1]);
+
+echo "Finding static resources...\n";
+$files = id(new FileFinder($root))
+ ->withType('f')
+ ->withSuffix('js')
+ ->withSuffix('css')
+ ->setGenerateChecksums(true)
+ ->find();
+
+echo "Processing ".count($files)." files";
+
+$file_map = array();
+foreach ($files as $path => $hash) {
+ echo ".";
+ $name = '/'.Filesystem::readablePath($path, $root);
+ $file_map[$name] = array(
+ 'hash' => $hash,
+ 'disk' => $path,
+ );
+}
+echo "\n";
+
+$runtime_map = array();
+
+$parser = new PhutilDocblockParser();
+foreach ($file_map as $path => $info) {
+ $data = Filesystem::readFile($info['disk']);
+ $matches = array();
+ $ok = preg_match('@/[*][*].*?[*]/@s', $data, $matches);
+ if (!$ok) {
+ throw new Exception(
+ "File {$path} does not have a header doc comment. Encode dependency ".
+ "data in a header docblock.");
+ }
+
+ list($description, $metadata) = $parser->parse($matches[0]);
+
+ $provides = preg_split('/\s+/', trim(idx($metadata, 'provides')));
+ $requires = preg_split('/\s+/', trim(idx($metadata, 'requires')));
+ $provides = array_filter($provides);
+ $requires = array_filter($requires);
+
+ if (count($provides) !== 1) {
+ throw new Exception(
+ "File {$path} must @provide exactly one Celerity target.");
+ }
+
+ $provides = reset($provides);
+
+ $type = 'js';
+ if (preg_match('/\.css$/', $path)) {
+ $type = 'css';
+ }
+
+ $path = '/res/'.substr($info['hash'], 0, 8).$path;
+
+ $runtime_map[$provides] = array(
+ 'path' => $path,
+ 'type' => $type,
+ 'requires' => $requires,
+ );
+}
+
+$runtime_map = var_export($runtime_map, true);
+$runtime_map = preg_replace('/\s+$/m', '', $runtime_map);
+$runtime_map = preg_replace('/array \(/', 'array(', $runtime_map);
+
+$resource_map = <<<EOFILE
+<?php
+
+/**
+ * This file is automatically generated. Use 'celerity_mapper.php' to rebuild
+ * it.
+ * @generated
+ */
+
+celerity_register_resource_map({$runtime_map});
+
+EOFILE;
+
+echo "Writing map...\n";
+Filesystem::writeFile(
+ $root.'/../src/__celerity_resource_map__.php',
+ $resource_map);
+echo "Done.\n";
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
new file mode 100644
index 0000000000..72ca5f3e7a
--- /dev/null
+++ b/src/__celerity_resource_map__.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * This file is automatically generated. Use 'celerity_mapper.php' to rebuild
+ * it.
+ * @generated
+ */
+
+celerity_register_resource_map(array(
+ 'phabricator-core-css' =>
+ array(
+ 'path' => '/res/ffa0140c/rsrc/css/base.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ ),
+ 'phabricator-syntax-css' =>
+ array(
+ 'path' => '/res/bf911307/rsrc/css/syntax.css',
+ 'type' => 'css',
+ 'requires' =>
+ array(
+ ),
+ ),
+ 'javelin-init-dev' =>
+ array(
+ 'path' => '/res/c57a9e89/rsrc/js/javelin/init.dev.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ ),
+ ),
+ 'javelin-init-prod' =>
+ array(
+ 'path' => '/res/f0172c54/rsrc/js/javelin/init.min.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ ),
+ ),
+ 'javelin-lib-dev' =>
+ array(
+ 'path' => '/res/3e747182/rsrc/js/javelin/javelin.dev.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ ),
+ ),
+ 'javelin-lib-prod' =>
+ array(
+ 'path' => '/res/9438670e/rsrc/js/javelin/javelin.min.js',
+ 'type' => 'js',
+ 'requires' =>
+ array(
+ ),
+ ),
+));
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index 13f5a717a0..e663af492f 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,226 +1,233 @@
<?php
/**
* This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
* @generated
*/
phutil_register_library_map(array(
'class' =>
array(
'Aphront404Response' => 'aphront/response/404',
'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration',
'AphrontController' => 'aphront/controller',
'AphrontDatabaseConnection' => 'storage/connection/base',
'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration',
'AphrontDefaultApplicationController' => 'aphront/default/controller',
'AphrontDialogResponse' => 'aphront/response/dialog',
'AphrontDialogView' => 'view/dialog',
'AphrontErrorView' => 'view/form/error',
'AphrontFileResponse' => 'aphront/response/file',
'AphrontFormControl' => 'view/form/control/base',
'AphrontFormFileControl' => 'view/form/control/file',
'AphrontFormMarkupControl' => 'view/form/control/markup',
'AphrontFormSelectControl' => 'view/form/control/select',
'AphrontFormStaticControl' => 'view/form/control/static',
'AphrontFormSubmitControl' => 'view/form/control/submit',
'AphrontFormTextAreaControl' => 'view/form/control/textarea',
'AphrontFormTextControl' => 'view/form/control/text',
'AphrontFormView' => 'view/form/base',
'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
'AphrontNullView' => 'view/null',
'AphrontPageView' => 'view/page/base',
'AphrontPanelView' => 'view/layout/panel',
'AphrontQueryConnectionException' => 'storage/exception/connection',
'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost',
'AphrontQueryCountException' => 'storage/exception/count',
'AphrontQueryException' => 'storage/exception/base',
'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing',
'AphrontQueryParameterException' => 'storage/exception/parameter',
'AphrontQueryRecoverableException' => 'storage/exception/recoverable',
'AphrontRedirectResponse' => 'aphront/response/redirect',
'AphrontRequest' => 'aphront/request',
'AphrontResponse' => 'aphront/response/base',
'AphrontSideNavView' => 'view/layout/sidenav',
'AphrontTableView' => 'view/control/table',
'AphrontURIMapper' => 'aphront/mapper',
'AphrontView' => 'view/base',
'AphrontWebpageResponse' => 'aphront/response/webpage',
+ 'CelerityAPI' => 'infratructure/celerity/api',
+ 'CelerityResourceController' => 'infratructure/celerity/controller',
+ 'CelerityResourceMap' => 'infratructure/celerity/map',
+ 'CelerityStaticResourceResponse' => 'infratructure/celerity/response',
'ConduitAPIMethod' => 'applications/conduit/method/base',
'ConduitAPIRequest' => 'applications/conduit/protocol/request',
'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff',
'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty',
'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload',
'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find',
'ConduitException' => 'applications/conduit/protocol/exception',
'DifferentialAction' => 'applications/differential/constants/action',
'DifferentialChangeType' => 'applications/differential/constants/changetype',
'DifferentialChangeset' => 'applications/differential/storage/changeset',
'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview',
'DifferentialChangesetParser' => 'applications/differential/parser/changeset',
'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview',
'DifferentialController' => 'applications/differential/controller/base',
'DifferentialDAO' => 'applications/differential/storage/base',
'DifferentialDiff' => 'applications/differential/storage/diff',
'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty',
'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents',
'DifferentialDiffViewController' => 'applications/differential/controller/diffview',
'DifferentialHunk' => 'applications/differential/storage/hunk',
'DifferentialLintStatus' => 'applications/differential/constants/lintstatus',
'DifferentialRevision' => 'applications/differential/storage/revision',
'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem',
'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus',
'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus',
'LiskDAO' => 'storage/lisk/dao',
'PhabricatorConduitAPIController' => 'applications/conduit/controller/api',
'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog',
'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console',
'PhabricatorConduitController' => 'applications/conduit/controller/base',
'PhabricatorConduitDAO' => 'applications/conduit/storage/base',
'PhabricatorConduitLogController' => 'applications/conduit/controller/log',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog',
'PhabricatorController' => 'applications/base/controller/base',
'PhabricatorDirectoryCategory' => 'applications/directory/storage/category',
'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete',
'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit',
'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist',
'PhabricatorDirectoryController' => 'applications/directory/controller/base',
'PhabricatorDirectoryDAO' => 'applications/directory/storage/base',
'PhabricatorDirectoryItem' => 'applications/directory/storage/item',
'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete',
'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit',
'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist',
'PhabricatorDirectoryMainController' => 'applications/directory/controller/main',
'PhabricatorFile' => 'applications/files/storage/file',
'PhabricatorFileController' => 'applications/files/controller/base',
'PhabricatorFileDAO' => 'applications/files/storage/base',
'PhabricatorFileListController' => 'applications/files/controller/list',
'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob',
'PhabricatorFileURI' => 'applications/files/uri',
'PhabricatorFileUploadController' => 'applications/files/controller/upload',
'PhabricatorFileViewController' => 'applications/files/controller/view',
'PhabricatorLiskDAO' => 'applications/base/storage/lisk',
'PhabricatorPHID' => 'applications/phid/storage/phid',
'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate',
'PhabricatorPHIDController' => 'applications/phid/controller/base',
'PhabricatorPHIDDAO' => 'applications/phid/storage/base',
'PhabricatorPHIDListController' => 'applications/phid/controller/list',
'PhabricatorPHIDType' => 'applications/phid/storage/type',
'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit',
'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist',
'PhabricatorPeopleController' => 'applications/people/controller/base',
'PhabricatorPeopleEditController' => 'applications/people/controller/edit',
'PhabricatorPeopleListController' => 'applications/people/controller/list',
'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
'PhabricatorStandardPageView' => 'view/page/standard',
'PhabricatorUser' => 'applications/people/storage/user',
'PhabricatorUserDAO' => 'applications/people/storage/base',
),
'function' =>
array(
'_qsprintf_check_scalar_type' => 'storage/qsprintf',
'_qsprintf_check_type' => 'storage/qsprintf',
+ 'celerity_register_resource_map' => 'infratructure/celerity/map',
'qsprintf' => 'storage/qsprintf',
'queryfx' => 'storage/queryfx',
'queryfx_all' => 'storage/queryfx',
'queryfx_one' => 'storage/queryfx',
+ 'require_celerity_resource' => 'infratructure/celerity/api',
'vqsprintf' => 'storage/qsprintf',
'vqueryfx' => 'storage/queryfx',
'xsprintf_query' => 'storage/qsprintf',
),
'requires_class' =>
array(
'Aphront404Response' => 'AphrontResponse',
'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
'AphrontDefaultApplicationController' => 'AphrontController',
'AphrontDialogResponse' => 'AphrontResponse',
'AphrontDialogView' => 'AphrontView',
'AphrontErrorView' => 'AphrontView',
'AphrontFileResponse' => 'AphrontResponse',
'AphrontFormControl' => 'AphrontView',
'AphrontFormFileControl' => 'AphrontFormControl',
'AphrontFormMarkupControl' => 'AphrontFormControl',
'AphrontFormSelectControl' => 'AphrontFormControl',
'AphrontFormStaticControl' => 'AphrontFormControl',
'AphrontFormSubmitControl' => 'AphrontFormControl',
'AphrontFormTextAreaControl' => 'AphrontFormControl',
'AphrontFormTextControl' => 'AphrontFormControl',
'AphrontFormView' => 'AphrontView',
'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
'AphrontNullView' => 'AphrontView',
'AphrontPageView' => 'AphrontView',
'AphrontPanelView' => 'AphrontView',
'AphrontQueryConnectionException' => 'AphrontQueryException',
'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException',
'AphrontQueryCountException' => 'AphrontQueryException',
'AphrontQueryObjectMissingException' => 'AphrontQueryException',
'AphrontQueryParameterException' => 'AphrontQueryException',
'AphrontQueryRecoverableException' => 'AphrontQueryException',
'AphrontRedirectResponse' => 'AphrontResponse',
'AphrontSideNavView' => 'AphrontView',
'AphrontTableView' => 'AphrontView',
'AphrontWebpageResponse' => 'AphrontResponse',
+ 'CelerityResourceController' => 'AphrontController',
'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',
'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
'ConduitAPI_user_find_Method' => 'ConduitAPIMethod',
'DifferentialChangeset' => 'DifferentialDAO',
'DifferentialChangesetDetailView' => 'AphrontView',
'DifferentialChangesetViewController' => 'DifferentialController',
'DifferentialController' => 'PhabricatorController',
'DifferentialDAO' => 'PhabricatorLiskDAO',
'DifferentialDiff' => 'DifferentialDAO',
'DifferentialDiffProperty' => 'DifferentialDAO',
'DifferentialDiffTableOfContentsView' => 'AphrontView',
'DifferentialDiffViewController' => 'DifferentialController',
'DifferentialHunk' => 'DifferentialDAO',
'DifferentialRevision' => 'DifferentialDAO',
'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
'PhabricatorConduitController' => 'PhabricatorController',
'PhabricatorConduitDAO' => 'PhabricatorLiskDAO',
'PhabricatorConduitLogController' => 'PhabricatorConduitController',
'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
'PhabricatorController' => 'AphrontController',
'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryController' => 'PhabricatorController',
'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO',
'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO',
'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController',
'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController',
'PhabricatorFile' => 'PhabricatorFileDAO',
'PhabricatorFileController' => 'PhabricatorController',
'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
'PhabricatorFileUploadController' => 'PhabricatorFileController',
'PhabricatorFileViewController' => 'PhabricatorFileController',
'PhabricatorLiskDAO' => 'LiskDAO',
'PhabricatorPHID' => 'PhabricatorPHIDDAO',
'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController',
'PhabricatorPHIDController' => 'PhabricatorController',
'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO',
'PhabricatorPHIDListController' => 'PhabricatorPHIDController',
'PhabricatorPHIDType' => 'PhabricatorPHIDDAO',
'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController',
'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController',
'PhabricatorPeopleController' => 'PhabricatorController',
'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
'PhabricatorStandardPageView' => 'AphrontPageView',
'PhabricatorUser' => 'PhabricatorUserDAO',
'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
),
'requires_interface' =>
array(
),
));
diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
index 8aa024d12f..d6f7168334 100644
--- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
+++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php
@@ -1,133 +1,138 @@
<?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.
*/
/**
* @group aphront
*/
class AphrontDefaultApplicationConfiguration
extends AphrontApplicationConfiguration {
public function getApplicationName() {
return 'aphront-default';
}
public function getURIMap() {
return array(
'/repository/' => array(
'$' => 'RepositoryListController',
'new/$' => 'RepositoryEditController',
'edit/(?<id>\d+)/$' => 'RepositoryEditController',
'delete/(?<id>\d+)/$' => 'RepositoryDeleteController',
),
'/' => array(
'$' => 'PhabricatorDirectoryMainController',
),
'/directory/' => array(
'item/$'
=> 'PhabricatorDirectoryItemListController',
'item/edit/(?:(?<id>\d+)/)?$'
=> 'PhabricatorDirectoryItemEditController',
'item/delete/(?<id>\d+)/'
=> 'PhabricatorDirectoryItemDeleteController',
'category/$'
=> 'PhabricatorDirectoryCategoryListController',
'category/edit/(?:(?<id>\d+)/)?$'
=> 'PhabricatorDirectoryCategoryEditController',
'category/delete/(?<id>\d+)/'
=> 'PhabricatorDirectoryCategoryDeleteController',
),
'/file/' => array(
'$' => 'PhabricatorFileListController',
'upload/$' => 'PhabricatorFileUploadController',
'(?<view>info)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController',
'(?<view>view)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController',
'(?<view>download)/(?<phid>[^/]+)/' => 'PhabricatorFileViewController',
),
'/phid/' => array(
'$' => 'PhabricatorPHIDListController',
'type/$' => 'PhabricatorPHIDTypeListController',
'type/edit/(?:(?<id>\d+)/)?$' => 'PhabricatorPHIDTypeEditController',
'new/$' => 'PhabricatorPHIDAllocateController',
),
'/people/' => array(
'$' => 'PhabricatorPeopleListController',
'edit/(?:(?<username>\w+)/)?$' => 'PhabricatorPeopleEditController',
),
'/p/(?<username>\w+)/$' => 'PhabricatorPeopleProfileController',
'/conduit/' => array(
'$' => 'PhabricatorConduitConsoleController',
'method/(?<method>[^/]+)$' => 'PhabricatorConduitConsoleController',
'log/$' => 'PhabricatorConduitLogController',
),
'/api/(?<method>[^/]+)$' => 'PhabricatorConduitAPIController',
'/differential/' => array(
'diff/(?<id>\d+)/$' => 'DifferentialDiffViewController',
'changeset/(?<id>\d+)/$' => 'DifferentialChangesetViewController',
),
+ '/res/' => array(
+ '(?<hash>[a-f0-9]{8})/(?<path>[^.]+\.(?:css|js))$'
+ => 'CelerityResourceController',
+ ),
+
'.*' => 'AphrontDefaultApplicationController',
);
}
public function buildRequest() {
$request = new AphrontRequest($this->getHost(), $this->getPath());
$request->setRequestData($_GET + $_POST);
return $request;
}
public function handleException(Exception $ex) {
$class = phutil_escape_html(get_class($ex));
$message = phutil_escape_html($ex->getMessage());
$content =
'<div class="aphront-unhandled-exception">'.
'<h1>Unhandled Exception "'.$class.'": '.$message.'</h1>'.
'<code>'.phutil_escape_html((string)$ex).'</code>'.
'</div>';
$view = new PhabricatorStandardPageView();
$view->appendChild($content);
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
public function willSendResponse(AphrontResponse $response) {
$request = $this->getRequest();
if ($response instanceof AphrontDialogResponse) {
if (!$request->isAjax()) {
$view = new PhabricatorStandardPageView();
$view->appendChild(
'<div style="padding: 2em 0;">'.
$response->buildResponseString().
'</div>');
$response = new AphrontWebpageResponse();
$response->setContent($view->render());
return $response;
}
}
return $response;
}
}
diff --git a/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
index 6c5e4c2188..314c3f91b6 100644
--- a/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
+++ b/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
@@ -1,121 +1,121 @@
<?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 DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function render() {
$rows = array();
$changesets = $this->changesets;
foreach ($changesets as $changeset) {
$file = $changeset->getFilename();
$display_file = $changeset->getDisplayFilename();
$type = $changeset->getChangeType();
$ftype = $changeset->getFileType();
if (DifferentialChangeType::isOldLocationChangeType($type)) {
$link = phutil_escape_html($display_file);
$away = $changeset->getAwayPaths();
if (count($away) > 1) {
$meta = array();
if ($type == DifferentialChangeType::TYPE_MULTICOPY) {
$meta[] = 'Deleted after being copied to multiple locations:';
} else {
$meta[] = 'Copied to multiple locations:';
}
foreach ($away as $path) {
$meta[] = $path;
}
$meta = implode('<br />', $meta);
} else {
if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) {
$meta = 'Moved to '.reset($away);
} else {
$meta = 'Copied to '.reset($away);
}
}
} else {
$link = phutil_render_tag(
'a',
array(
'href' => '#', // TODO: filename normalizer
),
phutil_escape_html($display_file));
if ($type == DifferentialChangeType::TYPE_MOVE_HERE) {
$meta = 'Moved from '.phutil_escape_html($changeset->getOldFile());
} else if ($type == DifferentialChangeType::TYPE_COPY_HERE) {
$meta = 'Copied from '.phutil_escape_html($changeset->getOldFile());
} else {
$meta = null;
}
}
$line_count = $changeset->getAffectedLineCount();
if ($line_count == 0) {
$lines = null;
} else if ($line_count == 1) {
$lines = ' (1 line)';
} else {
$lines = ' ('.$line_count.' lines)';
}
$char = DifferentialChangeType::getSummaryCharacterForChangeType($type);
$chartitle = DifferentialChangeType::getFullNameForChangeType($type);
$desc = DifferentialChangeType::getShortNameForFileType($ftype);
if ($desc) {
$desc = '('.$desc.')';
}
$pchar =
($changeset->getOldProperties() === $changeset->getNewProperties())
? null
: '<span title="Properties Changed">M</span>';
$rows[] =
'<tr>'.
- '<td class="differential-toc-char" title={$chartitle}>'.$char.'</td>'.
+ '<td class="differential-toc-char" title='.$chartitle.'>'.$char.'</td>'.
'<td class="differential-toc-prop">'.$pchar.'</td>'.
'<td class="differential-toc-ftype">'.$desc.'</td>'.
'<td class="differential-toc-file">'.$link.$lines.'</td>'.
'</tr>';
if ($meta) {
$rows[] =
'<tr>'.
'<td colspan="3" />'.
'<td class="differential-toc-meta">'.$meta.'</td>'.
'</tr>';
}
}
return
'<div class="differential-toc">'.
'<h1>Table of Contents</h1>'.
'<table>'.
implode("\n", $rows).
'</table>'.
'</div>';
}
}
diff --git a/src/infratructure/celerity/api/CelerityAPI.php b/src/infratructure/celerity/api/CelerityAPI.php
new file mode 100644
index 0000000000..77ba73f10c
--- /dev/null
+++ b/src/infratructure/celerity/api/CelerityAPI.php
@@ -0,0 +1,35 @@
+<?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 CelerityAPI {
+
+ private static $response;
+
+ public static function getStaticResourceResponse() {
+ if (empty(self::$response)) {
+ self::$response = new CelerityStaticResourceResponse();
+ }
+ return self::$response;
+ }
+
+}
+
+function require_celerity_resource($symbol) {
+ $response = CelerityAPI::getStaticResourceResponse();
+ $response->requireResource($symbol);
+}
diff --git a/src/infratructure/celerity/api/__init__.php b/src/infratructure/celerity/api/__init__.php
new file mode 100644
index 0000000000..c05435461a
--- /dev/null
+++ b/src/infratructure/celerity/api/__init__.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phabricator', 'infratructure/celerity/response');
+
+
+phutil_require_source('CelerityAPI.php');
diff --git a/src/infratructure/celerity/controller/CelerityResourceController.php b/src/infratructure/celerity/controller/CelerityResourceController.php
new file mode 100644
index 0000000000..184e7f2acc
--- /dev/null
+++ b/src/infratructure/celerity/controller/CelerityResourceController.php
@@ -0,0 +1,64 @@
+<?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.
+ */
+
+class CelerityResourceController extends AphrontController {
+
+ private $path;
+ private $hash;
+
+ public function willProcessRequest(array $data) {
+ $this->path = $data['path'];
+ $this->hash = $data['hash'];
+ }
+
+ public function processRequest() {
+ $path = $this->path;
+
+ // Sanity checking to keep this from exposing anything sensitive.
+ $path = preg_replace('@(//|\\.\\.)@', '', $path);
+ $matches = null;
+ if (!preg_match('/\.(css|js)$/', $path, $matches)) {
+ throw new Exception("Only CSS and JS resources may be served.");
+ }
+
+ $type = $matches[1];
+
+
+ $root = dirname(phutil_get_library_root('phabricator'));
+
+ try {
+ $data = Filesystem::readFile($root.'/webroot/'.$path);
+ } catch (Exception $ex) {
+ return new Aphront404Response();
+ }
+
+ $response = new AphrontFileResponse();
+ $response->setContent($data);
+ switch ($type) {
+ case 'css':
+ $response->setMimeType("text/css; charset=utf-8");
+ break;
+ case 'js':
+ $response->setMimeType("text/javascript; charset=utf-8");
+ break;
+ }
+
+ return $response;
+ }
+
+}
diff --git a/src/infratructure/celerity/controller/__init__.php b/src/infratructure/celerity/controller/__init__.php
new file mode 100644
index 0000000000..c31e65c785
--- /dev/null
+++ b/src/infratructure/celerity/controller/__init__.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phabricator', 'aphront/controller');
+phutil_require_module('phabricator', 'aphront/response/file');
+
+phutil_require_module('phutil', 'filesystem');
+phutil_require_module('phutil', 'moduleutils');
+
+
+phutil_require_source('CelerityResourceController.php');
diff --git a/src/infratructure/celerity/map/CelerityResourceMap.php b/src/infratructure/celerity/map/CelerityResourceMap.php
new file mode 100644
index 0000000000..ee3ea1ca17
--- /dev/null
+++ b/src/infratructure/celerity/map/CelerityResourceMap.php
@@ -0,0 +1,75 @@
+<?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 CelerityResourceMap {
+
+ private static $instance;
+ private $resourceMap;
+
+ public static function getInstance() {
+ if (empty(self::$instance)) {
+ self::$instance = new CelerityResourceMap();
+ $root = phutil_get_library_root('phabricator');
+ $ok = @include_once $root.'/__celerity_resource_map__.php';
+ if (!$ok) {
+ throw new Exception("Failed to load Celerity resource map!");
+ }
+ }
+ return self::$instance;
+ }
+
+ public function setResourceMap($resource_map) {
+ $this->resourceMap = $resource_map;
+ return $this;
+ }
+
+ public function resolveResources(array $symbols) {
+ $map = array();
+ foreach ($symbols as $symbol) {
+ if (!empty($map[$symbol])) {
+ continue;
+ }
+ $this->resolveResource($map, $symbol);
+ }
+
+ return $map;
+ }
+
+ private function resolveResource(array &$map, $symbol) {
+ if (empty($this->resourceMap[$symbol])) {
+ throw new Exception(
+ "Attempting to resolve unknown resource, '{$symbol}'.");
+ }
+
+ $info = $this->resourceMap[$symbol];
+ foreach ($info['requires'] as $requires) {
+ if (!empty($map[$requires])) {
+ continue;
+ }
+ $this->resolveResource($map, $requires);
+ }
+
+ $map[$symbol] = $info;
+ }
+
+}
+
+function celerity_register_resource_map(array $map) {
+ $instance = CelerityResourceMap::getInstance();
+ $instance->setResourceMap($map);
+}
diff --git a/src/infratructure/celerity/map/__init__.php b/src/infratructure/celerity/map/__init__.php
new file mode 100644
index 0000000000..e2362e7274
--- /dev/null
+++ b/src/infratructure/celerity/map/__init__.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phutil', 'moduleutils');
+
+
+phutil_require_source('CelerityResourceMap.php');
diff --git a/src/infratructure/celerity/response/CelerityStaticResourceResponse.php b/src/infratructure/celerity/response/CelerityStaticResourceResponse.php
new file mode 100644
index 0000000000..59d9c8a00e
--- /dev/null
+++ b/src/infratructure/celerity/response/CelerityStaticResourceResponse.php
@@ -0,0 +1,63 @@
+<?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 CelerityStaticResourceResponse {
+
+ private $symbols = array();
+ private $needsResolve = true;
+ private $resolved;
+
+ public function requireResource($symbol) {
+ $this->symbols[$symbol] = true;
+ $this->needsResolve = true;
+ return $this;
+ }
+
+ private function resolveResources() {
+ if ($this->needsResolve) {
+ $map = CelerityResourceMap::getInstance();
+ $this->resolved = $map->resolveResources(array_keys($this->symbols));
+ $this->needsResolve = false;
+ }
+ return $this;
+ }
+
+ public function renderResourcesOfType($type) {
+ $this->resolveResources();
+ $output = array();
+ foreach ($this->resolved as $resource) {
+ if ($resource['type'] == $type) {
+ $output[] = $this->renderResource($resource);
+ }
+ }
+ return implode("\n", $output);
+ }
+
+ private function renderResource(array $resource) {
+ switch ($resource['type']) {
+ case 'css':
+ $path = phutil_escape_html($resource['path']);
+ return '<link rel="stylesheet" type="text/css" href="'.$path.'" />';
+ case 'js':
+ $path = phutil_escape_html($resource['path']);
+ return '<script type="text/javascript" src="'.$path.'" />';
+ }
+ throw new Exception("Unable to render resource.");
+ }
+
+}
diff --git a/src/infratructure/celerity/response/__init__.php b/src/infratructure/celerity/response/__init__.php
new file mode 100644
index 0000000000..f6c34a4047
--- /dev/null
+++ b/src/infratructure/celerity/response/__init__.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phabricator', 'infratructure/celerity/map');
+
+phutil_require_module('phutil', 'markup');
+
+
+phutil_require_source('CelerityStaticResourceResponse.php');
diff --git a/src/view/page/base/AphrontPageView.php b/src/view/page/base/AphrontPageView.php
index f0883ecd46..e470fe802b 100755
--- a/src/view/page/base/AphrontPageView.php
+++ b/src/view/page/base/AphrontPageView.php
@@ -1,67 +1,73 @@
<?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.
*/
class AphrontPageView extends AphrontView {
private $title;
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
protected function getHead() {
return '';
}
protected function getBody() {
return $this->renderChildren();
}
protected function getTail() {
return '';
}
+
+ protected function willRenderPage() {
+ return;
+ }
public function render() {
+
+ $this->willRenderPage();
$title = $this->getTitle();
$head = $this->getHead();
$body = $this->getBody();
$tail = $this->getTail();
return <<<EOHTML
<!DOCTYPE html>
<html>
<head>
<title>{$title}</title>
{$head}
</head>
<body>
{$body}
</body>
{$tail}
</html>
EOHTML;
}
}
diff --git a/src/view/page/standard/PhabricatorStandardPageView.php b/src/view/page/standard/PhabricatorStandardPageView.php
index a350955b95..d44ed78aac 100755
--- a/src/view/page/standard/PhabricatorStandardPageView.php
+++ b/src/view/page/standard/PhabricatorStandardPageView.php
@@ -1,121 +1,128 @@
<?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.
*/
class PhabricatorStandardPageView extends AphrontPageView {
private $baseURI;
private $applicationName;
private $tabs = array();
private $selectedTab;
private $glyph;
+ private $bodyContent;
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setTabs(array $tabs, $selected_tab) {
$this->tabs = $tabs;
$this->selectedTab = $selected_tab;
return $this;
}
public function getTitle() {
return $this->getGlyph().' '.parent::getTitle();
}
+
+ protected function willRenderPage() {
+ require_celerity_resource('phabricator-core-css');
+
+ $this->bodyContent = $this->renderChildren();
+ }
+
+
protected function getHead() {
+ $response = CelerityAPI::getStaticResourceResponse();
return
- '<link rel="stylesheet" type="text/css" href="/rsrc/css/base.css" />'.
- '<link rel="stylesheet" type="text/css" href="/rsrc/css/syntax.css" />'.
+ $response->renderResourcesOfType('css').
'<script type="text/javascript">window.__DEV__=1;</script>'.
'<script type="text/javascript" src="/rsrc/js/javelin/init.dev.js">'.
'</script>';
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function getBody() {
$tabs = array();
foreach ($this->tabs as $name => $tab) {
$tabs[] = phutil_render_tag(
'a',
array(
'href' => idx($tab, 'href'),
'class' => ($name == $this->selectedTab)
? 'aphront-selected-tab'
: null,
),
phutil_escape_html(idx($tab, 'name')));
}
$tabs = implode('', $tabs);
if ($tabs) {
$tabs = '<span class="aphront-head-tabs">'.$tabs.'</span>';
}
return
'<div class="aphront-standard-page">'.
'<div class="aphront-standard-header">'.
'<a href="/">Phabricator</a> '.
phutil_render_tag(
'a',
array(
'href' => $this->getBaseURI(),
'class' => 'aphront-head-appname',
),
phutil_escape_html($this->getApplicationName())).
$tabs.
'</div>'.
- $this->renderChildren().
+ $this->bodyContent.
'<div style="clear: both;"></div>'.
'</div>';
}
protected function getTail() {
- return
- '<script type="text/javascript" src="/rsrc/js/javelin/javelin.dev.js">'.
- '</script>'.
+ $response = CelerityAPI::getStaticResourceResponse();
+ return
+ $response->renderResourcesOfType('js').
'<script type="text/javascript">'.
'JX.Stratcom.mergeData(0, {});'.
'</script>';
-
- ;
}
}
diff --git a/src/view/page/standard/__init__.php b/src/view/page/standard/__init__.php
index d931d61dd5..2d2bef01bd 100644
--- a/src/view/page/standard/__init__.php
+++ b/src/view/page/standard/__init__.php
@@ -1,15 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('phabricator', 'infratructure/celerity/api');
phutil_require_module('phabricator', 'view/page/base');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorStandardPageView.php');
diff --git a/webroot/rsrc/css/base.css b/webroot/rsrc/css/base.css
index dedfd4699e..c5033f27d6 100644
--- a/webroot/rsrc/css/base.css
+++ b/webroot/rsrc/css/base.css
@@ -1,760 +1,764 @@
+/**
+ * @provides phabricator-core-css
+ */
+
html {
overflow-y: scroll;
}
body, div, dl, dt, dd, ul, ol, li,
h1, h2, h3, h4, h5, h6,
pre, form, fieldset,
p, blockquote, th, td, button {
margin: 0;
padding: 0;
outline: 0;
border: 0;
}
html {
padding-bottom: 16em;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
fieldset, img {
border: 0;
}
address, caption, cite, code, dfn, th, var {
font-style: normal;
font-weight: normal;
}
ol, ul {
list-style: none;
}
caption, th {
text-align: left;
}
td, th {
vertical-align: top;
}
h1, h2, h3, h4, h5, h6 {
font-size: 100%;
font-weight: bold;
}
body {
font: 13px/1.231 'lucida grande', tahoma, verdana, arial, sans-serif;
background: #ACACAC;
direction: ltr;
text-align: left;
unicode-bidi: embed;
*font-size: small;
}
select, input, button, textarea, button {
font: 99% 'lucida grande', tahoma, verdana, arial, clean, sans-serif;
}
table {
font-size: inherit;
font: 100%;
}
h1 {
font-size: 16px;
}
h2 {
font-size: 14px;
}
a {
-moz-outline-style: none;
text-decoration: none;
cursor: pointer;
}
a:visited {
color: #3b5998;
}
a:link {
color: #3b5998;
}
a:hover {
text-decoration: underline;
}
img {
display: block;
}
/******************************************************************************/
/* Buttons */
/******************************************************************************/
button,
a.button,
a.button:visited,
input.inputsubmit {
background: #5e77aa url('/rsrc/image/sprite.png') 0 0 repeat-x;
border: 1px solid #29447e;
border-bottom-color: #1a356e;
color: #fff;
cursor: pointer;
font-weight: bold;
text-align: center;
white-space: nowrap;
display: inline-block;
font-size: 13px;
overflow: visible;
padding: 2px 8px 3px 8px;
line-height: 18px;
vertical-align: baseline;
width: auto;
box-shadow: 0px 1px 0px rgba(0,0,0,.12);
-moz-box-shadow: 0px 1px 0px rgba(0,0,0,.12);
-webkit-box-shadow: 0px 1px 0px rgba(0,0,0,.12);
}
button {
*padding: 2px 4px 1px 8px;
_padding-right: 6px;
}
a.button,
a.button:visited {
*padding: 3px 8px 4px;
}
/* Buttons with images (full size only) */
button.icon,
a.icon,
a.icon:visited {
padding-left: 0;
position: relative;
text-indent: 29px;
}
/* Fix for IE7 within table cells ? */
td button {
*width: 100%;
*padding-right: 8px;
}
button:active,
a.button:active {
background-color: #4f6aa3;
background-position: 0 -100px;
border-bottom-color: #29447e;
}
button.green,
a.green,
a.green:visited {
background-color: #6da952;
background-position: 0 -50px;
border: 1px solid #3b6e22;
border-bottom-color: #2c5a15;
}
button.green:active,
a.green:active {
background-color: #5e9d43;
background-position: 0 -150px;
border-bottom-color: #3b6e22;
}
button.grey,
input.inputaux,
a.grey,
a.grey:visited,
a.button.disabled,
button.disabled {
background-color: #e4e5e5;
background-position: 0 -250px;
border: 1px solid #999;
border-bottom-color: #888;
color: #333;
box-shadow: 0px 1px 0px rgba(0,0,0,.07);
-moz-box-shadow: 0px 1px 0px rgba(0,0,0,.07);
-webkit-box-shadow: 0px 1px 0px rgba(0,0,0,.07);
}
a.disabled,
button.disabled {
filter:alpha(opacity=50);
-moz-opacity:0.5;
-khtml-opacity: 0.5;
opacity: 0.5;
}
button.grey:active,
a.grey:active,
button.grey_active {
background-color: #dddddd;
background-position: 0 -200px;
border-bottom-color: #999;
}
button:active::-moz-focus-inner,
button:focus::-moz-focus-inner {
border-color: #405071;
}
button.green:active::-moz-focus-inner,
button.green:focus::-moz-focus-inner {
border-color: #4c713b;
}
button.grey:active::-moz-focus-inner,
button.grey:focus::-moz-focus-inner {
border-color: #666;
}
a.button:hover {
text-decoration: none;
}
button.small,
a.small,
a.small:visited {
padding: 2px 7px;
height: auto;
font-size: 11px;
line-height: 16px;
}
/******************************************************************************/
/* Aphront */
/******************************************************************************/
.aphront-standard-page {
background: #ffffff;
border-bottom: 1px solid #888888;
font-size: 14px;
-webkit-box-shadow: 0 0 6px #000;
-mox-box-shadow: 0 0 6px #000;
box-shadow: 0 0 6px #000;
}
.aphront-standard-header {
background: #003366;
color: white;
padding: 1em 1em 0.5em 1em;
overflow: hidden;
position: relative;
}
.aphront-standard-header a {
color: white;
}
.aphront-standard-header .aphront-head-tabs {
padding: 0 1em;
font-size: 13px;
font-weight: bold;
}
.aphront-standard-header .aphront-head-tabs a {
border-bottom: 3px solid transparent;
padding: 0.5em 0.75em;
position: relative;
bottom: 2px;
}
.aphront-standard-header .aphront-head-tabs a.aphront-selected-tab {
border-bottom-color: #cccccc;
}
.aphront-standard-header .aphront-head-appname {
padding: 0 1em;
text-transform: uppercase;
}
.aphront-directory-list {
margin: 1em 3% 8em;
}
.aphront-directory-category h1 {
border-bottom: 1px solid #cccccc;
margin-bottom: .5em;
padding-bottom: .1em;
}
.aphront-directory-list h2 {
font-size: 14px;
font-weight: bold;
padding: 0;
margin: 0;
}
.aphront-directory-list p {
color: #444444;
font-size: 12px;
padding: .05em .5em .5em;
}
.aphront-directory-category {
padding: 10px;
width: 300px;
float: left;
}
.aphront-directory-group {
padding: 0 .5em 3em;
}
.aphront-panel-view {
background: #f3f3f3;
border: 1px solid #c0c0c0;
border-width: 1px 0 0;
padding: 1em 2em;
margin: 1em 2em;
}
.aphront-panel-view h1 {
font-size: 14px;
font-weight: bold;
padding: 2px 0 8px;
}
.aphront-panel-view a.create-button {
float: right;
}
.aphront-panel-width-form {
width: 720px;
margin-right: auto;
margin-left: auto;
}
.aphront-panel-width-wide {
width: 1080px;
margin-right: auto;
margin-left: auto;
}
.aphront-table-view {
width: 100%;
border-collapse: collapse;
background: #fdfdfd;
border: 1px solid #003366;
}
.aphront-table-view tr.alt {
background: #efefef;
}
.aphront-table-view th {
font-size: 12px;
font-weight: bold;
padding: 4px 8px;
background: #003366;
color: white;
white-space: nowrap;
}
.aphront-table-view td.header {
padding: 4px 8px;
background: #3b5998;
color: white;
white-space: nowrap;
text-align: right;
}
.aphront-table-view td {
vertical-align: top;
padding: 4px 8px;
font-size: 11px;
white-space: nowrap;
}
.aphront-table-view td.action {
padding-top: 1px;
padding-bottom: 1px;
}
.aphront-table-view td.larger {
font-size: 14px;
}
.aphront-table-view td.pri {
font-weight: bold;
}
.aphront-table-view td.wide {
white-space: normal;
width: 100%;
}
.aphront-table-view td.right {
text-align: right;
}
.aphront-table-view td.mono {
font-family: "Monaco", monospace;
font-size: 10px;
}
.aphront-table-view tr.no-data td {
padding: 1em;
text-align: center;
color: #888888;
font-style: italic;
}
/******************************************************************************/
/* forms */
/******************************************************************************/
.aphront-form-view {
background: #e7e7e7;
border: 1px solid #c4c4c4;
padding: 1em;
}
.aphront-form-view label {
padding-top: 4px;
width: 14%;
float: left;
text-align: right;
font-weight: bold;
font-size: 13px;
color: #666666;
}
.aphront-form-input {
margin-left: 15%;
margin-right: 25%;
width: 60%;
}
.aphront-form-error {
width: 23%;
float: right;
color: #aa0000;
font-weight: bold;
padding-top: 4px;
}
.aphront-form-input input,
.aphront-form-input textarea {
font-size: 12px;
width: 100%;
}
.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: 1em 0 0em 2%;
}
.aphront-form-view .aphront-form-caption {
font-size: 11px;
color: #444444;
text-align: right;
clear: both;
margin-right: 25%;
margin-left: 15%;
}
.aphront-error-view {
width: 720px;
margin: 1em auto;
border: 1px solid #aa0000;
padding: 1em;
background: #f9b9bc;
}
.aphront-form-instructions {
margin: 2em 3%;
}
.aphront-form-control-static .aphront-form-input {
padding-top: 4px;
font-size: 13px;
}
/******************************************************************************/
/* dialog */
/******************************************************************************/
.aphront-dialog-view {
width: 480px;
padding: 8px;
background: #666;
margin: auto;
}
.aphront-dialog-head {
background: #003366;
border: none;
font-size: 15px;
padding: 5px 12px 6px;
color: #ffffff;
}
.aphront-dialog-body {
background: #ffffff;
padding: 16px 12px;
border: none;
overflow: hidden;
}
.aphront-dialog-tail {
border: none;
background: #ededed;
padding: 0.5em;
text-align: right;
}
.aphront-dialog-tail button,
.aphront-dialog-tail a.button {
float: right;
margin-left: .5em;
}
/******************************************************************************/
/* side nav */
/******************************************************************************/
table.aphront-side-nav-view {
width: 100%;
}
td.aphront-side-nav-content {
width: 100%;
}
th.aphront-side-nav-navigation {
border-right: 1px solid #bbbbbb;
}
th.aphront-side-nav-navigation a {
display: block;
margin: 0 0 2px;
min-width: 150px;
padding: 3px 8px 3px 24px;
font-weight: bold;
white-space: nowrap;
text-decoration: none;
}
th.aphront-side-nav-navigation a:hover {
text-decoration: none;
background: #f3f3f9;
}
th.aphront-side-nav-navigation hr {
height: 1px;
background: #eeeeee;
border: 0px;
margin: 12px 0px;
}
th.aphront-side-nav-navigation a.aphront-side-nav-selected,
th.aphront-side-nav-navigation a.aphront-side-nav-selected:hover {
background: #d8dfea;
}
/******************************************************************************/
/* differential toc */
/******************************************************************************/
.differential-toc {
margin: 25px 0;
max-width: 1118px;
border: 1px solid #666622;
background: #efefdf;
padding: 15px 20px;
font-size: 13px;
}
.differential-toc-meta {
color: #666666;
padding-left: 1em;
}
.differential-toc-char,
.differential-toc-prop {
width: 1.25em;
text-align: center;
font-weight: bold;
}
.differential-toc-ftype {
padding: 0 .5em;
text-align: center;
color: #666666;
}
.differential-toc-file {
color: #444444;
}
.differential-toc h1 {
border-bottom: 1px solid #aaaa99;
padding-bottom: 8px;
margin-bottom: 8px;
}
/******************************************************************************/
/* changeset view */
/******************************************************************************/
.differential-diff {
background: #ffffff;
font-family: "Menlo", "Consolas", "Monaco", monospace;
font-size: 10px;
width: 100%;
}
.differential-diff td {
/* using monospace fonts makes ex/em most useful:
*
* Unfortunately, firefox 3.6 renders diffs columns for added and removed
* files "way-too-wide" when given em as the dimension measurement, so we
* use an eyeballed ex equivalent and reset it to the ch character width
* measurement for browsers that support that css3 measurement.
*/
width: 88ex;
width: 81ch;
/*
Disable ligatures in Firefox. Firefox 3 has fancypants ligature support, but
it gets applied to monospaced fonts, which sucks because it means that the
"fi" ligature only takes up one character, e.g. It's probably the font's
fault that it even specifies ligatures (seriously, what the hell?) but
that's hard to fix and this is "easy" to "fix": custom letter spacing
disables ligatures, as long as it's at least 0.008333-repeating pixels of
custom letter spacing. I have no idea where this number comes from, but note
that .83333.. = 5/6. -epriestley
*/
letter-spacing: 0.0083334px;
vertical-align: top;
white-space: pre;
padding: 0 8px 1px;
line-height: 16px;
overflow: hidden;
}
.differential-diff th {
text-align: right;
padding: 2px 6px;
width: 44px;
vertical-align: top;
background: #eeeeee;
color: #888888;
cursor: pointer;
border-style: solid;
border-width: 0px 1px;
border-color: #eeeeee #999999 #eeeeee #dddddd;
font-weight: bold;
font-family: "Verdana";
font-size: 11px;
overflow: hidden;
}
.differential-diff td.old {
background: #ffd0d0;
color: #161111;
}
.differential-diff td.new {
background: #d0ffd0;
color: #111611;
}
.differential-diff td.old-full,
.differential-diff td.old span.bright {
background: #ffaaaa;
color: #221111;
}
.differential-diff td.new-full,
.differential-diff td.new span.bright {
background: #aaffaa;
color: #112211;
}
.differential-diff td.show-more,
.differential-diff td.differential-shield {
background: #ffffee;
padding: 1em;
text-align: center;
font-family: "Verdana";
font-size: 11px;
border: 1px solid #ccccaa;
white-space: normal;
}
.differential-diff td.show-more {
color: #999966;
}
.differential-diff td.differential-shield {
text-align: center;
max-width: 1160px;
}
.differential-diff td.differential-shield a {
font-weight: bold;
}
.differential-primary-pane {
margin: 0 40px;
max-width: 1162px;
}
/************* meta notice ************************************************ */
.differential-meta-notice {
border: 1px solid #ffdd99;
background: #ffeeaa;
font-family: "Verdana";
font-size: 11px;
padding: 1em;
margin: 0 0 6px 0;
}
diff --git a/webroot/rsrc/css/syntax.css b/webroot/rsrc/css/syntax.css
index 706a864d0f..dfa773dfd7 100644
--- a/webroot/rsrc/css/syntax.css
+++ b/webroot/rsrc/css/syntax.css
@@ -1,119 +1,123 @@
+/**
+ * @provides phabricator-syntax-css
+ */
+
.remarkup-code .uu { /* Forbidden Unicode */
color: #aa0066;
}
.remarkup-code .over-the-line {
color: #aa0066;
margin-right: 1px;
}
.remarkup-code td span {
padding: 1px 0 3px;
}
.remarkup-code .hll {
background-color: #ffffcc;
}
.remarkup-code .c, /* Comment */
.remarkup-code .cm, /* Comment.Multiline */
.remarkup-code .c1, /* Comment.Single */
.remarkup-code .cs { /* Comment.Special */
color: #666666;
}
.remarkup-code .sd, /* Literal.String.Doc */
.remarkup-code .sh { /* Literal.String.Heredoc */
color: #000000;
}
.remarkup-code .s, /* Literal.String */
.remarkup-code .sb, /* Literal.String.Backtick */
.remarkup-code .sc, /* Literal.String.Char */
.remarkup-code .s2, /* Literal.String.Double */
.remarkup-code .s1, /* Literal.String.Single */
.remarkup-code .sx { /* Literal.String.Other */
color: #766510;
}
.remarkup-code .sr { /* Literal.String.Regex */
color: #BB6688;
}
.remarkup-code .nv, /* Name.Variable */
.remarkup-code .vi, /* Name.Variable.Instance */
.remarkup-code .vg { /* Name.Variable.Global */
color: #001294;
}
.remarkup-code .na { /* Name.Attribute */
color: #354BB3;
}
.remarkup-code .kc, /* Keyword.Constant */
.remarkup-code .no { /* Name.Constant */
color: #000A65;
}
.remarkup-code .k, /* Keyword */
.remarkup-code .kd, /* Keyword.Declaration */
.remarkup-code .kn, /* Keyword.Namespace */
.remarkup-code .kt { /* Keyword.Type */
color: #AA4000;
}
.remarkup-code .cp, /* Comment.Preproc */
.remarkup-code .kp, /* Keyword.Pseudo */
.remarkup-code .kr, /* Keyword.Reserved */
.remarkup-code .nb, /* Name.Builtin */
.remarkup-code .bp { /* Name.Builtin.Pseudo */
color: #304A96;
}
.remarkup-code .nc, /* Name.Class */
.remarkup-code .nt, /* Name.Tag */
.remarkup-code .vc { /* Name.Variable.Class */
color: #00702A;
}
.remarkup-code .nf, /* Name.Function */
.remarkup-code .nx { /* Name.Other */
color: #004012;
}
.remarkup-code .o, /* Operator */
.remarkup-code .ss { /* Literal.String.Symbol */
color: #AA2211;
}
.remarkup-code .m, /* Literal.Number */
.remarkup-code .mf, /* Literal.Number.Float */
.remarkup-code .mh, /* Literal.Number.Hex */
.remarkup-code .mi, /* Literal.Number.Integer */
.remarkup-code .mo, /* Literal.Number.Oct */
.remarkup-code .il { /* Literal.Number.Integer.Long */
color: #601200;
}
.remarkup-code .gd { color: #A00000 } /* Generic.Deleted */
.remarkup-code .ge { } /* Generic.Emph */
.remarkup-code .gr { color: #FF0000 } /* Generic.Error */
.remarkup-code .gh { color: #000080; } /* Generic.Heading */
.remarkup-code .gi { color: #00A000 } /* Generic.Inserted */
.remarkup-code .go { color: #808080 } /* Generic.Output */
.remarkup-code .gp { color: #000080 } /* Generic.Prompt */
.remarkup-code .gs { } /* Generic.Strong */
.remarkup-code .gu { color: #800080 } /* Generic.Subheading */
.remarkup-code .gt { color: #0040D0 } /* Generic.Traceback */
.remarkup-code .nd { color: #AA22FF } /* Name.Decorator */
.remarkup-code .ni { color: #999999 } /* Name.Entity */
.remarkup-code .ne { color: #D2413A } /* Name.Exception */
.remarkup-code .nl { color: #A0A000 } /* Name.Label */
.remarkup-code .nn { color: #0000FF } /* Name.Namespace */
.remarkup-code .ow { color: #AA22FF } /* Operator.Word */
.remarkup-code .w { color: #bbbbbb } /* Text.Whitespace */
.remarkup-code .se { color: #BB6622 } /* Literal.String.Escape */
.remarkup-code .si { color: #BB6688 } /* Literal.String.Interpol */
diff --git a/webroot/rsrc/js/javelin/init.dev.js b/webroot/rsrc/js/javelin/init.dev.js
index 38c386e7fb..eadfd68a8e 100644
--- a/webroot/rsrc/js/javelin/init.dev.js
+++ b/webroot/rsrc/js/javelin/init.dev.js
@@ -1,179 +1,180 @@
+/** @provides javelin-init-dev */
/**
* Javelin core; installs Javelin and Stratcom event delegation.
*
* @provides javelin-magical-init
* @nopackage
*
* @javelin-installs JX.__rawEventQueue
* @javelin-installs JX.__simulate
* @javelin-installs JX.enableDispatch
* @javelin-installs JX.onload
*
* @javelin
*/
(function() {
if (window.JX) {
return;
}
window.JX = {};
window['__DEV__'] = window['__DEV__'] || 0;
var loaded = false;
var onload = [];
var master_event_queue = [];
var root = document.documentElement;
var has_add_event_listener = !!root.addEventListener;
JX.__rawEventQueue = function(what) {
master_event_queue.push(what);
// Evade static analysis - JX.Stratcom
var Stratcom = JX['Stratcom'];
if (Stratcom && Stratcom.ready) {
// Empty the queue now so that exceptions don't cause us to repeatedly
// try to handle events.
var local_queue = master_event_queue;
master_event_queue = [];
for (var ii = 0; ii < local_queue.length; ++ii) {
var evt = local_queue[ii];
// Sometimes IE gives us events which throw when ".type" is accessed;
// just ignore them since we can't meaningfully dispatch them. TODO:
// figure out where these are coming from.
try { var test = evt.type; } catch (x) { continue; }
if (!loaded && evt.type == 'domready') {
document.body && (document.body.id = null);
loaded = true;
for (var ii = 0; ii < onload.length; ii++) {
onload[ii]();
}
}
Stratcom.dispatch(evt);
}
} else {
var target = what.srcElement || what.target;
if (target &&
(what.type in {click: 1, submit: 1}) &&
(/ FI_CAPTURE /).test(' ' + target.className + ' ')) {
what.returnValue = false;
what.preventDefault && what.preventDefault();
document.body.id = 'event_capture';
// For versions of IE that use attachEvent, the event object is somehow
// stored globally by reference, and all the references we push to the
// master_event_queue will always refer to the most recent event. We
// work around this by popping the useless global event off the queue,
// and pushing a clone of the event that was just fired using the IE's
// proprietary createEventObject function.
// see: http://msdn.microsoft.com/en-us/library/ms536390(v=vs.85).aspx
if (!add_event_listener && document.createEventObject) {
master_event_queue.pop();
master_event_queue.push(document.createEventObject(what));
}
return false;
}
}
}
JX.enableDispatch = function(target, type) {
if (target.addEventListener) {
target.addEventListener(type, JX.__rawEventQueue, true);
} else if (target.attachEvent) {
target.attachEvent('on' + type, JX.__rawEventQueue);
}
};
var document_events = [
'click',
'change',
'keypress',
'mousedown',
'mouseover',
'mouseout',
'mouseup',
'keydown',
'drop',
'dragenter',
'dragleave',
'dragover'
];
// Simulate focus and blur in old versions of IE using focusin and focusout
// TODO: Document the gigantic IE mess here with focus/blur.
// TODO: beforeactivate/beforedeactivate?
// http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
if (!has_add_event_listener) {
document_events.push('focusin', 'focusout');
}
// Opera is multilol: it propagates focus / blur odd, and submit differently
if (window.opera) {
document_events.push('focus', 'blur');
} else {
document_events.push('submit');
}
for (var ii = 0; ii < document_events.length; ++ii) {
JX.enableDispatch(root, document_events[ii]);
}
// In particular, we're interested in capturing window focus/blur here so
// long polls can abort when the window is not focused.
var window_events = [
('onpagehide' in window) ? 'pagehide' : 'unload',
'resize',
'focus',
'blur'
];
for (var ii = 0; ii < window_events.length; ++ii) {
JX.enableDispatch(window, window_events[ii]);
}
JX.__simulate = function(node, event) {
if (!has_add_event_listener) {
var e = {target: node, type: event};
JX.__rawEventQueue(e);
if (e.returnValue === false) {
return false;
}
}
};
if (has_add_event_listener) {
document.addEventListener('DOMContentLoaded', function() {
JX.__rawEventQueue({type: 'domready'});
}, true);
} else {
var ready =
"if (this.readyState == 'complete') {" +
"JX.__rawEventQueue({type: 'domready'});" +
"}";
document.write(
'<script' +
' defer="defer"' +
' src="javascript:void(0)"' +
' onreadystatechange="' + ready + '"' +
'><\/sc' + 'ript\>');
}
JX.onload = function(func) {
if (loaded) {
func();
} else {
onload.push(func);
}
}
})();
diff --git a/webroot/rsrc/js/javelin/init.min.js b/webroot/rsrc/js/javelin/init.min.js
index af32f431c8..264f75659c 100644
--- a/webroot/rsrc/js/javelin/init.min.js
+++ b/webroot/rsrc/js/javelin/init.min.js
@@ -1 +1,2 @@
+/** @provides javelin-init-prod */
(function(){if(window.JX)return;window.JX={};window.__DEV__=window.__DEV__||0;var d=false;var f=[];var e=[];var h=document.documentElement;var b=!!h.addEventListener;JX.__rawEventQueue=function(o){e.push(o);var j=JX.Stratcom;if(j&&j.ready){var m=e;e=[];for(var l=0;l<m.length;++l){var k=m[l];try{var test=k.type;}catch(p){continue;}if(!d&&k.type=='domready'){document.body&&(document.body.id=null);d=true;for(var l=0;l<f.length;l++)f[l]();}j.dispatch(k);}}else{var n=o.srcElement||o.target;if(n&&(o.type in {click:1,submit:1})&&(/ FI_CAPTURE /).test(' '+n.className+' ')){o.returnValue=false;o.preventDefault&&o.preventDefault();document.body.id='event_capture';if(!add_event_listener&&document.createEventObject){e.pop();e.push(document.createEventObject(o));}return false;}}};JX.enableDispatch=function(j,k){if(j.addEventListener){j.addEventListener(k,JX.__rawEventQueue,true);}else if(j.attachEvent)j.attachEvent('on'+k,JX.__rawEventQueue);};var a=['click','change','keypress','mousedown','mouseover','mouseout','mouseup','keydown','drop','dragenter','dragleave','dragover'];if(!b)a.push('focusin','focusout');if(window.opera){a.push('focus','blur');}else a.push('submit');for(var c=0;c<a.length;++c)JX.enableDispatch(h,a[c]);var i=[('onpagehide' in window)?'pagehide':'unload','resize','focus','blur'];for(var c=0;c<i.length;++c)JX.enableDispatch(window,i[c]);JX.__simulate=function(k,event){if(!b){var j={target:k,type:event};JX.__rawEventQueue(j);if(j.returnValue===false)return false;}};if(b){document.addEventListener('DOMContentLoaded',function(){JX.__rawEventQueue({type:'domready'});},true);}else{var g="if (this.readyState == 'complete') {"+"JX.__rawEventQueue({type: 'domready'});"+"}";document.write('<script'+' defer="defer"'+' src="javascript:void(0)"'+' onreadystatechange="'+g+'"'+'><\/sc'+'ript\>');}JX.onload=function(j){if(d){j();}else f.push(j);};})();
\ No newline at end of file
diff --git a/webroot/rsrc/js/javelin/javelin.dev.js b/webroot/rsrc/js/javelin/javelin.dev.js
index ad7e5cfdd1..7f877a124f 100644
--- a/webroot/rsrc/js/javelin/javelin.dev.js
+++ b/webroot/rsrc/js/javelin/javelin.dev.js
@@ -1,2934 +1,2936 @@
+/** @provides javelin-lib-dev */
+
/**
* Javelin utility functions.
*
* @provides javelin-util
*
* @javelin-installs JX.$A
* @javelin-installs JX.$AX
* @javelin-installs JX.copy
* @javelin-installs JX.bind
* @javelin-installs JX.bag
* @javelin-installs JX.keys
* @javelin-installs JX.defer
* @javelin-installs JX.go
* @javelin-installs JX.log
*
* @javelin
*/
/**
* Convert an array-like object (usually ##arguments##) into a real Array. An
* "array-like object" is something with a ##length## property and numerical
* keys. The most common use for this is to let you call Array functions on the
* magical ##arguments## object.
*
* JX.$A(arguments).slice(1);
*
* @param obj Array, or array-like object.
* @return Array Actual array.
*/
JX.$A = function(mysterious_arraylike_object) {
// NOTE: This avoids the Array.slice() trick because some bizarre COM object
// I dug up somewhere was freaking out when I tried to do it and it made me
// very upset, so do not replace this with Array.slice() cleverness.
var r = [];
for (var ii = 0; ii < mysterious_arraylike_object.length; ii++) {
r.push(mysterious_arraylike_object[ii]);
}
return r;
};
/**
* Cast a value into an array, by wrapping scalars into singletons. If the
* argument is an array, it is returned unmodified. If it is a scalar, an array
* with a single element is returned. For example:
*
* JX.$AX([3]); // Returns [3].
* JX.$AX(3); // Returns [3].
*
* Note that this function uses an "instanceof Array" check so you may need to
* convert array-like objects (such as ##arguments## and Array instances from
* iframes) into real arrays with @{JX.$A()}.
*
* @param wild Scalar or Array.
* @return Array If the argument was a scalar, an Array with the argument as
* its only element. Otherwise, the original Array.
*
*/
JX.$AX = function(maybe_scalar) {
return (maybe_scalar instanceof Array) ? maybe_scalar : [maybe_scalar];
};
/**
* Copy properties from one object to another. Note: does not copy the
* ##toString## property or anything else which isn't enumerable or is somehow
* magic or just doesn't work. But it's usually what you want. If properties
* already exist, they are overwritten.
*
* var cat = {
* ears: 'clean',
* paws: 'clean',
* nose: 'DIRTY OH NOES'
* };
* var more = {
* nose: 'clean',
* tail: 'clean'
* };
*
* JX.copy(cat, more);
*
* // cat is now:
* // {
* // ears: 'clean',
* // paws: 'clean',
* // nose: 'clean',
* // tail: 'clean'
* // }
*
* @param obj Destination object, which properties should be copied to.
* @param obj Source object, which properties should be copied from.
* @return obj Destination object.
*/
JX.copy = function(copy_dst, copy_src) {
for (var k in copy_src) {
copy_dst[k] = copy_src[k];
}
return copy_dst;
};
/**
* Create a function which invokes another function with a bound context and
* arguments (i.e., partial function application) when called; king of all
* functions.
*
* Bind performs context binding (letting you select what the value of ##this##
* will be when a function is invoked) and partial function application (letting
* you create some function which calls another one with bound arguments).
*
* = Context Binding =
*
* Normally, when you call ##obj.method()##, the magic ##this## object will be
* the ##obj## you invoked the method from. This can be undesirable when you
* need to pass a callback to another function. For instance:
*
* COUNTEREXAMPLE
* var dog = new JX.Dog();
* dog.barkNow(); // Makes the dog bark.
*
* JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work!
*
* This doesn't work because ##this## is ##window## when the function is
* later invoked; @{JX.Stratcom.listen()} does not know about the context
* object ##dog##. The solution is to pass a function with a bound context
* object:
*
* var dog = new JX.Dog();
* var bound_function = JX.bind(dog, dog.barkNow);
*
* JX.Stratcom.listen('click', 'bark', bound_function);
*
* ##bound_function## is a function with ##dog## bound as ##this##; ##this##
* will always be ##dog## when the function is called, no matter what
* property chain it is invoked from.
*
* You can also pass ##null## as the context argument to implicitly bind
* ##window##.
*
* = Partial Function Application =
*
* @{JX.bind()} also performs partial function application, which allows you
* to bind one or more arguments to a function. For instance, if we have a
* simple function which adds two numbers:
*
* function add(a, b) { return a + b; }
* add(3, 4); // 7
*
* Suppose we want a new function, like this:
*
* function add3(b) { return 3 + b; }
* add3(4); // 7
*
* Instead of doing this, we can define ##add3()## in terms of ##add()## by
* binding the value ##3## to the ##a## argument:
*
* var add3_bound = JX.bind(null, add, 3);
* add3_bound(4); // 7
*
* Zero or more arguments may be bound in this way. This is particularly useful
* when using closures in a loop:
*
* COUNTEREXAMPLE
* for (var ii = 0; ii < button_list.length; ii++) {
* button_list[ii].onclick = function() {
* JX.log('You clicked button number '+ii+'!'); // Fails!
* };
* }
*
* This doesn't work; all the buttons report the highest number when clicked.
* This is because the local ##ii## is captured by the closure. Instead, bind
* the current value of ##ii##:
*
* var func = function(button_num) {
* JX.log('You clicked button number '+button_num+'!');
* }
* for (var ii = 0; ii < button_list.length; ii++) {
* button_list[ii].onclick = JX.bind(null, func, ii);
* }
*
* @param obj|null Context object to bind as ##this##.
* @param function Function to bind context and arguments to.
* @param ... Zero or more arguments to bind.
* @return function New function which invokes the original function with
* bound context and arguments when called.
*/
JX.bind = function(context, func, more) {
if (__DEV__) {
if (typeof func != 'function') {
throw new Error(
'JX.bind(context, <yuck>, ...): '+
'Attempting to bind something that is not a function.');
}
}
var bound = JX.$A(arguments).slice(2);
return function() {
return func.apply(context || window, bound.concat(JX.$A(arguments)));
}
};
/**
* "Bag of holding"; function that does nothing. Primarily, it's used as a
* placeholder when you want something to be callable but don't want it to
* actually have an effect.
*
* @return void
*/
JX.bag = function() {
// \o\ \o/ /o/ woo dance party
};
/**
* Convert an object's keys into a list. For example:
*
* JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars']
*
* @param obj Object to retrieve keys from.
* @return list List of keys.
*/
JX.keys = function(obj) {
var r = [];
for (var k in obj) {
r.push(k);
}
return r;
};
/**
* Defer a function for later execution, similar to ##setTimeout()##. Returns
* an object with a ##stop()## method, which cancels the deferred call.
*
* var ref = JX.defer(yell, 3000); // Yell in 3 seconds.
* // ...
* ref.stop(); // Cancel the yell.
*
* @param function Function to invoke after the timeout.
* @param int? Timeout, in milliseconds. If this value is omitted, the
* function will be invoked once control returns to the browser
* event loop, as with ##setTimeout(func, 0)##.
* @return obj An object with a ##stop()## method, which cancels function
* execution.
*/
JX.defer = function(func, timeout) {
var t = setTimeout(func, timeout || 0);
return {stop : function() { clearTimeout(t); }}
};
/**
* Redirect the browser to another page by changing the window location.
*
* @param string Optional URI to redirect the browser to. If no URI is
* provided, the current page will be reloaded.
* @return void
*/
JX.go = function(uri) {
// Foil static analysis, etc. Strictly speaking, JX.go() doesn't really need
// to be in javelin-utils so we could do this properly at some point.
JX['Stratcom'] && JX['Stratcom'].invoke('go', null, {uri: uri});
(uri && (window.location = uri)) || window.location.reload(true);
};
if (__DEV__) {
if (!window.console || !window.console.log) {
if (window.opera && window.opera.postError) {
window.console = {log: function(m) { window.opera.postError(m); }};
} else {
window.console = {log: function(m) { }};
}
}
/**
* Print a message to the browser debugging console (like Firebug). This
* method exists only in ##__DEV__##.
*
* @param string Message to print to the browser debugging console.
* @return void
*/
JX.log = function(message) {
window.console.log(message);
}
window.alert = (function(native_alert) {
var recent_alerts = [];
var in_alert = false;
return function(msg) {
if (in_alert) {
JX.log(
'alert(...): '+
'discarded reentrant alert.');
return;
}
in_alert = true;
recent_alerts.push(new Date().getTime());
if (recent_alerts.length > 3) {
recent_alerts.splice(0, recent_alerts.length - 3);
}
if (recent_alerts.length >= 3 &&
(recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) {
if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) {
window.alert = JX.bag;
}
} else {
// Note that we can't .apply() the IE6 version of this "function".
native_alert(msg);
}
in_alert = false;
}
})(window.alert);
}
/**
* @requires javelin-util
* @provides javelin-install
* @javelin-installs JX.install
* @javelin
*/
/**
* Install a class into the Javelin ("JX") namespace. The first argument is the
* name of the class you want to install, and the second is a map of these
* attributes (all of which are optional):
*
* - ##construct## //(function)// Class constructor. If you don't provide one,
* one will be created for you (but it will be very boring).
* - ##extend## //(string)// The name of another JX-namespaced class to extend
* via prototypal inheritance.
* - ##members## //(map)// A map of instance methods and properties.
* - ##statics## //(map)// A map of static methods and properties.
* - ##initialize## //(function)// A function which will be run once, after
* this class has been installed.
* - ##properties## //(map)// A map of properties that should have instance
* getters and setters automatically generated for them. The key is the
* property name and the value is its default value. For instance, if you
* provide the property "size", the installed class will have the methods
* "getSize()" and "setSize()". It will **NOT** have a property ".size"
* and no guarantees are made about where install is actually chosing to
* store the data. The motivation here is to let you cheaply define a
* stable interface and refine it later as necessary.
* - ##events## //(list)// List of event types this class is capable of
* emitting.
*
* For example:
*
* JX.install('Dog', {
* construct : function(name) {
* this.setName(name);
* },
* members : {
* bark : function() {
* // ...
* }
* },
* properites : {
* name : null,
* }
* });
*
* This creates a new ##Dog## class in the ##JX## namespace:
*
* var d = new JX.Dog();
* d.bark();
*
* Javelin classes are normal Javascript functions and generally behave in
* the expected way. Some properties and methods are automatically added to
* all classes:
*
* - ##instance.__id__## Globally unique identifier attached to each instance.
* - ##instance.__super__## Reference to the parent class constructor, if one
* exists. Allows use of ##this.__super__.apply(this, ...)## to call the
* superclass's constructor.
* - ##instance.__parent__## Reference to the parent class prototype, if one
* exists. Allows use of ##this.__parent__.someMethod.apply(this, ...)##
* to call the superclass's methods.
* - ##prototype.__class__## Reference to the class constructor.
* - ##constructor.__path__## List of path tokens used emit events. It is
* probably never useful to access this directly.
* - ##constructor.__readable__## //DEV ONLY!// Readable class name. You could
* plausibly use this when constructing error messages.
* - ##constructor.__events__## //DEV ONLY!// List of events supported by
* this class.
* - ##constructor.listen()## Listen to all instances of this class. See
* @{JX.Base}.
* - ##instance.listen()## Listen to one instance of this class. See
* @{JX.Base}.
* - ##instance.invoke()## Invoke an event from an instance. See @{JX.Base}.
*
*
* @param string Name of the class to install. It will appear in the JX
* "namespace" (e.g., JX.Pancake).
* @param map Map of properties, see method documentation.
* @return void
*
* @author epriestley
*/
JX.install = function(new_name, new_junk) {
if (typeof JX.install._nextObjectID == 'undefined') {
JX.install._nextObjectID = 0;
}
// If we've already installed this, something is up.
if (new_name in JX) {
if (__DEV__) {
throw new Error(
'JX.install("' + new_name + '", ...): ' +
'trying to reinstall something that has already been installed.');
}
return;
}
// Since we may end up loading things out of order (e.g., Dog extends Animal
// but we load Dog first) we need to keep a list of things that we've been
// asked to install but haven't yet been able to install around.
if (!JX.install._queue) {
JX.install._queue = [];
}
JX.install._queue.push([new_name, new_junk]);
do {
var junk;
var name = null;
for (var ii = 0; ii < JX.install._queue.length; ++ii) {
junk = JX.install._queue[ii][1];
if (junk.extend && !JX[junk.extend]) {
// We need to extend something that we haven't been able to install
// yet, so just keep this in queue.
continue;
}
// Install time! First, get this out of the queue.
name = JX.install._queue[ii][0];
JX.install._queue.splice(ii, 1);
--ii;
if (__DEV__) {
var valid = {
construct : 1,
statics : 1,
members : 1,
extend : 1,
initialize: 1,
properties : 1,
events : 1,
canCallAsFunction : 1
};
for (var k in junk) {
if (!(k in valid)) {
throw new Error(
'JX.install("' + name + '", {"' + k + '": ...}): ' +
'trying to install unknown property `' + k + '`.');
}
}
if (junk.constructor !== {}.constructor) {
throw new Error(
'JX.install("' + name + '", {"constructor": ...}): ' +
'property `constructor` should be called `construct`.');
}
}
// First, build the constructor. If construct is just a function, this
// won't change its behavior (unless you have provided a really awesome
// function, in which case it will correctly punish you for your attempt
// at creativity).
JX[name] = (function(name, junk) {
var result = function() {
this.__id__ = '__obj__' + (++JX.install._nextObjectID);
this.__super__ = JX[junk.extend] || JX.bag;
this.__parent__ = JX[name].prototype;
if (JX[name].__prototyping__) {
return;
}
return (junk.construct || JX.bag).apply(this, arguments);
// TODO: Allow mixins to initialize here?
// TODO: Also, build mixins?
};
if (__DEV__) {
if (!junk.canCallAsFunction) {
var inner = result;
result = function() {
if (this === window || this === JX) {
throw new Error("<" + JX[name].__readable__ + ">: " +
"Tried to construct an instance " +
"without the 'new' operator. Either use " +
"'new' or set 'canCallAsFunction' where you " +
"install the class.");
}
return inner.apply(this, arguments);
};
}
}
return result;
})(name, junk);
// Copy in all the static methods and properties.
JX.copy(JX[name], junk.statics);
if (__DEV__) {
JX[name].__readable__ = 'JX.' + name;
}
JX[name].__prototyping__ = 0;
var proto;
if (junk.extend) {
JX[junk.extend].__prototyping__++;
proto = JX[name].prototype = new JX[junk.extend]();
JX[junk.extend].__prototyping__--;
} else {
proto = JX[name].prototype = {};
}
proto.__class__ = JX[name];
// Build getters and setters from the `prop' map.
for (var k in (junk.properties || {})) {
var base = k.charAt(0).toUpperCase()+k.substr(1);
var prop = '__auto__' + k;
proto[prop] = junk.properties[k];
proto['set' + base] = (function(prop) {
return function(v) {
this[prop] = v;
return this;
}
})(prop);
proto['get' + base] = (function(prop) {
return function() {
return this[prop];
}
})(prop);
}
if (__DEV__) {
// Check for aliasing in default values of members. If we don't do this,
// you can run into a problem like this:
//
// JX.install('List', { members : { stuff : [] }});
//
// var i_love = new JX.List();
// var i_hate = new JX.List();
//
// i_love.stuff.push('Psyduck'); // I love psyduck!
// JX.log(i_hate.stuff); // Show stuff I hate.
//
// This logs ["Psyduck"] because the push operation modifies
// JX.List.prototype.stuff, which is what both i_love.stuff and
// i_hate.stuff resolve to. To avoid this, set the default value to
// null (or any other scalar) and do "this.stuff = [];" in the
// constructor.
for (var member_name in junk.members) {
if (junk.extend && member_name[0] == '_') {
throw new Error(
'JX.install("' + name + '", ...): ' +
'installed member "' + member_name + '" must not be named with ' +
'a leading underscore because it is in a subclass. Variables ' +
'are analyzed and crushed one file at a time, and crushed ' +
'member variables in subclasses alias crushed member variables ' +
'in superclasses. Remove the underscore, refactor the class so ' +
'it does not extend anything, or fix the minifier to be ' +
'capable of safely crushing subclasses.');
}
var member_value = junk.members[member_name];
if (typeof member_value == 'object' && member_value !== null) {
throw new Error(
'JX.install("' + name + '", ...): ' +
'installed member "' + member_name + '" is not a scalar or ' +
'function. Prototypal inheritance in Javascript aliases object ' +
'references across instances so all instances are initialized ' +
'to point at the exact same object. This is almost certainly ' +
'not what you intended. Make this member static to share it ' +
'across instances, or initialize it in the constructor to ' +
'prevent reference aliasing and give each instance its own ' +
'copy of the value.');
}
}
}
// This execution order intentionally allows you to override methods
// generated from the "properties" initializer.
JX.copy(proto, junk.members);
// Build this ridiculous event model thing. Basically, this defines
// two instance methods, invoke() and listen(), and one static method,
// listen(). If you listen to an instance you get events for that
// instance; if you listen to a class you get events for all instances
// of that class (including instances of classes which extend it).
//
// This is rigged up through Stratcom. Each class has a path component
// like "class:Dog", and each object has a path component like
// "obj:23". When you invoke on an object, it emits an event with
// a path that includes its class, all parent classes, and its object
// ID.
//
// Calling listen() on an instance listens for just the object ID.
// Calling listen() on a class listens for that class's name. This
// has the effect of working properly, but installing them is pretty
// messy.
if (junk.events && junk.events.length) {
var parent = JX[junk.extend] || {};
// If we're in dev, we build up a list of valid events (for this
// class or some parent class) and then check them whenever we try
// to listen or invoke.
if (__DEV__) {
var valid_events = parent.__events__ || {};
for (var ii = 0; ii < junk.events.length; ++ii) {
valid_events[junk.events[ii]] = true;
}
JX[name].__events__ = valid_events;
}
// Build the class name chain.
JX[name].__name__ = 'class:' + name;
var ancestry = parent.__path__ || [];
JX[name].__path__ = ancestry.concat([JX[name].__name__]);
proto.invoke = function(type) {
if (__DEV__) {
if (!(type in this.__class__.__events__)) {
throw new Error(
this.__class__.__readable__ + '.invoke("' + type + '", ...): ' +
'invalid event type. Valid event types are: ' +
JX.keys(this.__class__.__events__).join(', ') + '.');
}
}
// Here and below, this nonstandard access notation is used to mask
// these callsites from the static analyzer. JX.Stratcom is always
// available by the time we hit these execution points.
return JX['Stratcom'].invoke(
'obj:' + type,
this.__class__.__path__.concat([this.__id__]),
{args : JX.$A(arguments).slice(1)});
};
proto.listen = function(type, callback) {
if (__DEV__) {
if (!(type in this.__class__.__events__)) {
throw new Error(
this.__class__.__readable__ + '.listen("' + type + '", ...): ' +
'invalid event type. Valid event types are: ' +
JX.keys(this.__class__.__events__).join(', ') + '.');
}
}
return JX['Stratcom'].listen(
'obj:' + type,
this.__id__,
JX.bind(this, function(e) {
return callback.apply(this, e.getData().args);
}));
};
JX[name].listen = function(type, callback) {
if (__DEV__) {
if (!(type in this.__events__)) {
throw new Error(
this.__readable__ + '.listen("' + type + '", ...): ' +
'invalid event type. Valid event types are: ' +
JX.keys(this.__events__).join(', ') + '.');
}
}
return JX['Stratcom'].listen(
'obj:' + type,
this.__name__,
JX.bind(this, function(e) {
return callback.apply(this, e.getData().args);
}));
};
} else if (__DEV__) {
var error_message =
'class does not define any events. Pass an "events" property to ' +
'JX.install() to define events.';
JX[name].listen = JX[name].listen || function() {
throw new Error(
this.__readable__ + '.listen(...): ' +
error_message);
};
JX[name].invoke = JX[name].invoke || function() {
throw new Error(
this.__readable__ + '.invoke(...): ' +
error_message);
};
proto.listen = proto.listen || function() {
throw new Error(
this.__class__.__readable__ + '.listen(...): ' +
error_message);
};
proto.invoke = proto.invoke || function() {
throw new Error(
this.__class__.__readable__ + '.invoke(...): ' +
error_message);
};
}
// Finally, run the init function if it was provided.
(junk.initialize || JX.bag)();
}
// In effect, this exits the loop as soon as we didn't make any progress
// installing things, which means we've installed everything we have the
// dependencies for.
} while (name);
}
/**
* @requires javelin-install
* @provides javelin-event
* @javelin
*/
/**
* A generic event, routed by @{JX.Stratcom}. All events within Javelin are
* represented by a {@JX.Event}, regardless of whether they originate from
* a native DOM event (like a mouse click) or are custom application events.
*
* Events have a propagation model similar to native Javascript events, in that
* they can be stopped with stop() (which stops them from continuing to
* propagate to other handlers) or prevented with prevent() (which prevents them
* from taking their default action, like following a link). You can do both at
* once with kill().
*
* @author epriestley
* @task stop Stopping Event Behaviors
* @task info Getting Event Information
*/
JX.install('Event', {
members : {
/**
* Stop an event from continuing to propagate. No other handler will
* receive this event, but its default behavior will still occur. See
* ""Using Events"" for more information on the distinction between
* 'stopping' and 'preventing' an event. See also prevent() (which prevents
* an event but does not stop it) and kill() (which stops and prevents an
* event).
*
* @return this
* @task stop
*/
stop : function() {
var r = this.getRawEvent();
if (r) {
r.cancelBubble = true;
r.stopPropagation && r.stopPropagation();
}
this.setStopped(true);
return this;
},
/**
* Prevent an event's default action. This depends on the event type, but
* the common default actions are following links, submitting forms,
* and typing text. Event prevention is generally used when you have a link
* or form which work properly without Javascript but have a specialized
* Javascript behavior. When you intercept the event and make the behavior
* occur, you prevent it to keep the browser from following the link.
*
* Preventing an event does not stop it from propagating, so other handlers
* will still receive it. See ""Using Events"" for more information on the
* distinction between 'stopping' and 'preventing' an event. See also
* stop() (which stops an event but does not prevent it) and kill()
* (which stops and prevents an event).
*
* @return this
* @task stop
*/
prevent : function() {
var r = this.getRawEvent();
if (r) {
r.returnValue = false;
r.preventDefault && r.preventDefault();
}
this.setPrevented(true);
return this;
},
/**
* Stop and prevent an event, which stops it from propagating and prevents
* its defualt behavior. This is a convenience function, see stop() and
* prevent() for information on what it means to stop or prevent an event.
*
* @return this
* @task stop
*/
kill : function() {
this.prevent();
this.stop();
return this;
},
/**
* Get the special key (like tab or return), if any, associated with this
* event. Browsers report special keys differently; this method allows you
* to identify a keypress in a browser-agnostic way. Note that this detects
* only some special keys: delete, tab, return escape, left, up, right,
* down.
*
* For example, if you want to react to the escape key being pressed, you
* could install a listener like this:
*
* JX.Stratcom.listen('keydown', 'example', function(e) {
* if (e.getSpecialKey() == 'esc') {
* JX.log("You pressed 'Escape'! Well done! Bravo!");
* }
* });
*
*
* @return string|null ##null## if there is no associated special key,
* or one of the strings 'delete', 'tab', 'return',
* 'esc', 'left', 'up', 'right', or 'down'.
* @task info
*/
getSpecialKey : function() {
var r = this.getRawEvent();
if (!r || r.shiftKey) {
return null;
}
var c = r.keyCode;
do {
c = JX.Event._keymap[c] || null;
} while (c && JX.Event._keymap[c])
return c;
},
/**
* Get the node corresponding to the specified key in this event's node map.
* This is a simple helper method that makes the API for accessing nodes
* less ugly.
*
* JX.Stratcom.listen('click', 'tag:a', function(e) {
* var a = e.getNode('nearest:a');
* // do something with the link that was clicked
* });
*
* @param string sigil or stratcom node key
* @return node|null Node mapped to the specified key, or null if it the
* key does not exist. The available keys include:
* - 'tag:'+tag - first node of each type
* - 'id:'+id - all nodes with an id
* - sigil - first node of each sigil
* @task info
*/
getNode: function(key) {
return this.getNodes()[key] || null;
}
},
statics : {
_keymap : {
8 : 'delete',
9 : 'tab',
13 : 'return',
27 : 'esc',
37 : 'left',
38 : 'up',
39 : 'right',
40 : 'down',
63232 : 38,
63233 : 40,
62234 : 37,
62235 : 39
}
},
properties : {
/**
* Native Javascript event which generated this @{JX.Event}. Not every
* event is generated by a native event, so there may be ##null## in
* this field.
*
* @type Event|null
* @task info
*/
rawEvent : null,
/**
* String describing the event type, like 'click' or 'mousedown'. This
* may also be an application or object event.
*
* @type string
* @task info
*/
type : null,
/**
* If available, the DOM node where this event occurred. For example, if
* this event is a click on a button, the target will be the button which
* was clicked. Application events will not have a target, so this property
* will return the value ##null##.
*
* @type DOMNode|null
* @task info
*/
target : null,
/**
* Metadata attached to nodes associated with this event.
*
* For native events, the DOM is walked from the event target to the root
* element. Each sigil which is encountered while walking up the tree is
* added to the map as a key. If the node has associated metainformation,
* it is set as the value; otherwise, the value is null.
*
* @type dict<string, *>
* @task info
*/
data : null,
/**
* Sigil path this event was activated from. TODO: explain this
*
* @type list<string>
* @task info
*/
path : [],
/**
* True if propagation of the event has been stopped. See stop().
*
* @type bool
* @task stop
*/
stopped : false,
/**
* True if default behavior of the event has been prevented. See prevent().
*
* @type bool
* @task stop
*/
prevented : false,
/**
* @task info
*/
nodes : {}
},
/**
* @{JX.Event} installs a toString() method in ##__DEV__## which allows you to
* log or print events and get a reasonable representation of them:
*
* Event<'click', ['path', 'stuff'], [object HTMLDivElement]>
*/
initialize : function() {
if (__DEV__) {
JX.Event.prototype.toString = function() {
var path = '['+this.getPath().join(', ')+']';
return 'Event<'+this.getType()+', '+path+', '+this.getTarget()+'>';
}
}
}
});
/**
* @requires javelin-install javelin-event javelin-util javelin-magical-init
* @provides javelin-stratcom
* @javelin
*/
/**
* Javelin strategic command, the master event delegation core. This class is
* a sort of hybrid between Arbiter and traditional event delegation, and
* serves to route event information to handlers in a general way.
*
* Each Javelin :JX.Event has a 'type', which may be a normal Javascript type
* (for instance, a click or a keypress) or an application-defined type. It
* also has a "path", based on the path in the DOM from the root node to the
* event target. Note that, while the type is required, the path may be empty
* (it often will be for application-defined events which do not originate
* from the DOM).
*
* The path is determined by walking down the tree to the event target and
* looking for nodes that have been tagged with metadata. These names are used
* to build the event path, and unnamed nodes are ignored. Each named node may
* also have data attached to it.
*
* Listeners specify one or more event types they are interested in handling,
* and, optionally, one or more paths. A listener will only receive events
* which occurred on paths it is listening to. See listen() for more details.
*
* @author epriestley
*
* @task invoke Invoking Events
* @task listen Listening to Events
* @task handle Responding to Events
* @task sigil Managing Sigils
* @task internal Internals
*/
JX.install('Stratcom', {
statics : {
ready : false,
_targets : {},
_handlers : [],
_need : {},
_matchName : /\bFN_([^ ]+)/,
_matchData : /\bFD_([^ ]+)_([^ ]+)/,
_auto : '*',
_data : {},
_execContext : [],
_typeMap : {focusin: 'focus', focusout: 'blur'},
/**
* Node metadata is stored in a series of blocks to prevent collisions
* between indexes that are generated on the server side (and potentially
* concurrently). Block 0 is for metadata on the initial page load, block 1
* is for metadata added at runtime with JX.Stratcom.siglize(), and blocks
* 2 and up are for metadata generated from other sources (e.g. JX.Request).
* Use allocateMetadataBlock() to reserve a block, and mergeData() to fill
* a block with data.
*
* When a JX.Request is sent, a block is allocated for it and any metadata
* it returns is filled into that block.
*/
_dataBlock : 2,
/**
* Within each datablock, data is identified by a unique index. The data
* pointer on a node looks like this:
*
* FD_1_2
*
* ...where 1 is the block, and 2 is the index within that block. Normally,
* blocks are filled on the server side, so index allocation takes place
* there. However, when data is provided with JX.Stratcom.sigilize(), we
* need to allocate indexes on the client.
*/
_dataIndex : 0,
/**
* Dispatch a simple event that does not have a corresponding native event
* object. It is unusual to call this directly. Generally, you will instead
* dispatch events from an object using the invoke() method present on all
* objects. See @{JX.Base.invoke()} for documentation.
*
* @param string Event type.
* @param list? Optionally, a path to attach to the event. This is
* rarely meaingful for simple events.
* @param object? Optionally, arbitrary data to send with the event.
* @return @{JX.Event} The event object which was dispatched to listeners.
* The main use of this is to test whether any
* listeners prevented the event.
* @task invoke
*/
invoke : function(type, path, data) {
var proxy = new JX.Event()
.setType(type)
.setData(data || {})
.setPath(path || []);
return this._dispatchProxy(proxy);
},
/**
* Listen for events on given paths. Specify one or more event types, and
* zero or more paths to filter on. If you don't specify a path, you will
* receive all events of the given type:
*
* // Listen to all clicks.
* JX.Stratcom.listen('click', null, handler);
*
* This will notify you of all clicks anywhere in the document (unless
* they are intercepted and killed by a higher priority handler before they
* get to you).
*
* Often, you may be interested in only clicks on certain elements. You
* can specify the paths you're interested in to filter out events which
* you do not want to be notified of.
*
* // Listen to all clicks inside elements annotated "news-feed".
* JX.Stratcom.listen('click', 'news-feed', handler);
*
* By adding more elements to the path, you can create a finer-tuned
* filter:
*
* // Listen to only "like" clicks inside "news-feed".
* JX.Stratcom.listen('click', ['news-feed', 'like'], handler);
*
*
* TODO: Further explain these shenanigans.
*
* @param string|list<string> Event type (or list of event names) to
* listen for. For example, ##'click'## or
* ##['keydown', 'keyup']##.
*
* @param wild Sigil paths to listen for this event on. See discussion
* in method documentation.
*
* @param function Callback to invoke when this event is triggered. It
* should have the signature ##f(:JX.Event e)##.
*
* @return object A reference to the installed listener. You can later
* remove the listener by calling this object's remove()
* method.
* @author epriestley
* @task listen
*/
listen : function(types, paths, func) {
if (__DEV__) {
if (arguments.length == 4) {
throw new Error(
'JX.Stratcom.listen(...): '+
'requires exactly 3 arguments. Did you mean JX.DOM.listen?');
}
if (arguments.length != 3) {
throw new Error(
'JX.Stratcom.listen(...): '+
'requires exactly 3 arguments.');
}
if (typeof func != 'function') {
throw new Error(
'JX.Stratcom.listen(...): '+
'callback is not a function.');
}
}
var ids = [];
types = JX.$AX(types);
if (!paths) {
paths = this._auto;
}
if (!(paths instanceof Array)) {
paths = [[paths]];
} else if (!(paths[0] instanceof Array)) {
paths = [paths];
}
// To listen to multiple event types on multiple paths, we just install
// the same listener a whole bunch of times: if we install for two
// event types on three paths, we'll end up with six references to the
// listener.
//
// TODO: we'll call your listener twice if you install on two paths where
// one path is a subset of another. The solution is "don't do that", but
// it would be nice to verify that the caller isn't doing so, in __DEV__.
for (var ii = 0; ii < types.length; ++ii) {
var type = types[ii];
if (('onpagehide' in window) && type == 'unload') {
// If we use "unload", we break the bfcache ("Back-Forward Cache") in
// Safari and Firefox. The BFCache makes using the back/forward
// buttons really fast since the pages can come out of magical
// fairyland instead of over the network, so use "pagehide" as a proxy
// for "unload" in these browsers.
type = 'pagehide';
}
if (!(type in this._targets)) {
this._targets[type] = {};
}
var type_target = this._targets[type];
for (var jj = 0; jj < paths.length; ++jj) {
var path = paths[jj];
var id = this._handlers.length;
this._handlers.push(func);
this._need[id] = path.length;
ids.push(id);
for (var kk = 0; kk < path.length; ++kk) {
if (__DEV__) {
if (path[kk] == 'tag:#document') {
throw new Error(
'JX.Stratcom.listen(..., "tag:#document", ...): ' +
'listen for document events as "tag:window", not ' +
'"tag:#document", in order to get consistent behavior ' +
'across browsers.');
}
}
if (!type_target[path[kk]]) {
type_target[path[kk]] = [];
}
type_target[path[kk]].push(id);
}
}
}
return {
remove : function() {
for (var ii = 0; ii < ids.length; ii++) {
delete JX.Stratcom._handlers[ids[ii]];
}
}
};
},
/**
* Dispatch a native Javascript event through the Stratcom control flow.
* Generally, this is automatically called for you by the master dipatcher
* installed by ##init.js##. When you want to dispatch an application event,
* you should instead call invoke().
*
* @param Event Native event for dispatch.
* @return :JX.Event Dispatched :JX.Event.
* @task internal
*/
dispatch : function(event) {
// TODO: simplify this :P
var target;
try {
target = event.srcElement || event.target;
if (target === window || (!target || target.nodeName == '#document')) {
target = {nodeName: 'window'};
}
} catch (x) {
target = null;
}
var path = [];
var nodes = {};
var push = function(key, node) {
// we explicitly only store the first occurrence of each key
if (!(key in nodes)) {
nodes[key] = node;
path.push(key);
}
};
var cursor = target;
while (cursor) {
push('tag:' + cursor.nodeName.toLowerCase(), cursor);
var id = cursor.id;
if (id) {
push('id:' + id, cursor);
}
var source = cursor.className || '';
// className is an SVGAnimatedString for SVG elements, use baseVal
var token = ((source.baseVal || source).match(this._matchName) || [])[1];
if (token) {
push(token, cursor);
}
cursor = cursor.parentNode;
}
var etype = event.type;
if (etype in this._typeMap) {
etype = this._typeMap[etype];
}
var data = {};
for (var key in nodes) {
data[key] = this.getData(nodes[key]);
}
var proxy = new JX.Event()
.setRawEvent(event)
.setType(etype)
.setTarget(target)
.setData(data)
.setNodes(nodes)
.setPath(path.reverse());
// JX.log('~> '+proxy.toString());
return this._dispatchProxy(proxy);
},
/**
* Dispatch a previously constructed proxy :JX.Event.
*
* @param :JX.Event Event to dispatch.
* @return :JX.Event Returns the event argument.
* @task internal
*/
_dispatchProxy : function(proxy) {
var scope = this._targets[proxy.getType()];
if (!scope) {
return proxy;
}
var path = proxy.getPath();
var len = path.length;
var hits = {};
var matches;
for (var root = -1; root < len; ++root) {
if (root == -1) {
matches = scope[this._auto];
} else {
matches = scope[path[root]];
}
if (!matches) {
continue;
}
for (var ii = 0; ii < matches.length; ++ii) {
hits[matches[ii]] = (hits[matches[ii]] || 0) + 1;
}
}
var exec = [];
for (var k in hits) {
if (hits[k] == this._need[k]) {
var handler = this._handlers[k];
if (handler) {
exec.push(handler);
}
}
}
this._execContext.push({
handlers: exec,
event: proxy,
cursor: 0
});
this.pass();
this._execContext.pop();
return proxy;
},
/**
* Pass on an event, allowing other handlers to process it. The use case
* here is generally something like:
*
* if (JX.Stratcom.pass()) {
* // something else handled the event
* return;
* }
* // handle the event
* event.prevent();
*
* This allows you to install event handlers that operate at a lower
* effective priority, and provide a default behavior which is overridable
* by listeners.
*
* @return bool True if the event was stopped or prevented by another
* handler.
* @task handle
*/
pass : function() {
var context = this._execContext[this._execContext.length - 1];
while (context.cursor < context.handlers.length) {
var cursor = context.cursor;
++context.cursor;
(context.handlers[cursor] || JX.bag)(context.event);
if (context.event.getStopped()) {
break;
}
}
return context.event.getStopped() || context.event.getPrevented();
},
/**
* Retrieve the event (if any) which is currently being dispatched.
*
* @return :JX.Event|null Event which is currently being dispatched, or
* null if there is no active dispatch.
* @task handle
*/
context : function() {
var len = this._execContext.length;
if (!len) {
return null;
}
return this._execContext[len - 1].event;
},
/**
* Merge metadata. You must call this (even if you have no metadata) to
* start the Stratcom queue.
*
* @param int The datablock to merge data into.
* @param dict Dictionary of metadata.
* @return void
* @task internal
*/
mergeData : function(block, data) {
this._data[block] = data;
if (block == 0) {
JX.Stratcom.ready = true;
JX.__rawEventQueue({type: 'start-queue'});
}
},
/**
* Attach a sigil (and, optionally, metadata) to a node. Note that you can
* not overwrite, remove or replace a sigil.
*
* @param Node Node without any sigil.
* @param string Sigil to name the node with.
* @param object? Optional metadata object to attach to the node.
* @return void
* @task sigil
*/
sigilize : function(node, sigil, data) {
if (__DEV__) {
if (node.className.match(this._matchName)) {
throw new Error(
'JX.Stratcom.sigilize(<node>, ' + sigil + ', ...): ' +
'node already has a sigil, sigils may not be overwritten.');
}
if (typeof data != 'undefined' &&
(data === null || typeof data != 'object')) {
throw new Error(
'JX.Stratcom.sigilize(..., ..., <nonobject>): ' +
'data to attach to node is not an object. You must use ' +
'objects, not primitives, for metadata.');
}
}
if (data) {
JX.Stratcom._setData(node, data);
}
node.className = 'FN_' + sigil + ' ' + node.className;
},
/**
* Determine if a node has a specific sigil.
*
* @param Node Node to test.
* @param string Sigil to check for.
* @return bool True if the node has the sigil.
*
* @task sigil
*/
hasSigil : function(node, sigil) {
if (!node.className) {
// Some nodes don't have a className, notably 'document'. We hit
// 'document' when following .parentNode chains, e.g. in
// JX.DOM.nearest(), so exit early if we don't have a className to avoid
// fataling on 'node.className.match' being undefined.
return false;
}
return (node.className.match(this._matchName) || [])[1] == sigil;
},
/**
* Retrieve a node's metadata.
*
* @param Node Node from which to retrieve data.
* @return object Data attached to the node, or an empty dictionary if
* the node has no data attached. In this case, the empty
* dictionary is set as the node's metadata -- i.e.,
* subsequent calls to getData() will retrieve the same
* object.
*
* @task sigil
*/
getData : function(node) {
if (__DEV__) {
if (!node) {
throw new Error(
'JX.Stratcom.getData(<empty>): ' +
'you must provide a node to get associated data from.');
}
}
var matches = (node.className || '').match(this._matchData);
if (matches) {
var block = this._data[matches[1]];
var index = matches[2];
if (block && (index in block)) {
return block[index];
}
}
return JX.Stratcom._setData(node, {});
},
/**
* @task internal
*/
allocateMetadataBlock : function() {
return this._dataBlock++;
},
/**
* Attach metadata to a node. This data can later be retrieved through
* @{JX.Stratcom.getData()}, or @{JX.Event.getData()}.
*
* @param Node Node which data should be attached to.
* @param object Data to attach.
* @return object Attached data.
*
* @task internal
*/
_setData : function(node, data) {
if (!this._data[1]) { // data block 1 is reserved for javascript
this._data[1] = {};
}
this._data[1][this._dataIndex] = data;
node.className = 'FD_1_' + (this._dataIndex++) + ' ' + node.className;
return data;
}
}
});
/**
* @provides javelin-behavior
*
* @javelin-installs JX.behavior
* @javelin-installs JX.initBehaviors
*
* @javelin
*/
JX.behavior = function(name, control_function) {
if (__DEV__) {
if (name in JX.behavior._behaviors) {
throw new Error(
'JX.behavior("'+name+'", ...): '+
'behavior is already registered.');
}
if (!control_function) {
throw new Error(
'JX.behavior("'+name+'", <nothing>): '+
'initialization function is required.');
}
if (typeof control_function != 'function') {
throw new Error(
'JX.behavior("'+name+'", <garbage>): '+
'initialization function is not a function.');
}
}
JX.behavior._behaviors[name] = control_function;
};
JX.initBehaviors = function(map) {
for (var name in map) {
if (__DEV__) {
if (!(name in JX.behavior._behaviors)) {
throw new Error(
'JX.initBehavior("'+name+'", ...): '+
'behavior is not registered.');
}
}
var configs = map[name];
if (!configs.length) {
if (name in JX.behavior._initialized) {
continue;
} else {
configs = [null];
}
}
for (var ii = 0; ii < configs.length; ii++) {
JX.behavior._behaviors[name](configs[ii]);
}
JX.behavior._initialized[name] = true;
}
};
!function(JX) {
JX.behavior._behaviors = {};
JX.behavior._initialized = {};
}(JX);
/**
* @requires javelin-install
* javelin-stratcom
* javelin-util
* javelin-behavior
* @provides javelin-request
* @javelin
*/
/**
* Make basic AJAX XMLHTTPRequests.
*/
JX.install('Request', {
construct : function(uri, handler) {
this.setURI(uri);
if (handler) {
this.listen('done', handler);
}
},
events : ['send', 'done', 'error', 'finally'],
members : {
_xhrkey : null,
_transport : null,
_finished : false,
_block : null,
send : function() {
var xport = null;
try {
try {
xport = new XMLHttpRequest();
} catch (x) {
xport = new ActiveXObject("Msxml2.XMLHTTP");
}
} catch (x) {
xport = new ActiveXObject("Microsoft.XMLHTTP");
}
this._transport = xport;
this._xhrkey = JX.Request._xhr.length;
JX.Request._xhr.push(this);
xport.onreadystatechange = JX.bind(this, this._onreadystatechange);
var data = this.getData() || {};
data.__ajax__ = true;
this._block = JX.Stratcom.allocateMetadataBlock();
data.__metablock__ = this._block;
var q = (this.getDataSerializer() ||
JX.Request.defaultDataSerializer)(data);
var uri = this.getURI();
var method = this.getMethod().toUpperCase();
if (method == 'GET') {
uri += ((uri.indexOf('?') === -1) ? '?' : '&') + q;
}
this.invoke('send', this);
if (this.getTimeout()) {
this._timer = JX.defer(
JX.bind(
this,
this._fail,
JX.Request.ERROR_TIMEOUT),
this.getTimeout());
}
xport.open(method, uri, true);
if (__DEV__) {
if (this.getFile()) {
if (method != 'POST') {
throw new Error(
'JX.Request.send(): ' +
'attempting to send a file over GET. You must use POST.');
}
if (this.getData()) {
throw new Error(
'JX.Request.send(): ' +
'attempting to send data and a file. You can not send both ' +
'at once.');
}
}
}
if (method == 'POST') {
if (this.getFile()) {
xport.send(this.getFile());
} else {
xport.setRequestHeader(
'Content-Type',
'application/x-www-form-urlencoded');
xport.send(q);
}
} else {
xport.send(null);
}
},
abort : function() {
this._cleanup();
},
_onreadystatechange : function() {
var xport = this._transport;
try {
if (this._finished) {
return;
}
if (xport.readyState != 4) {
return;
}
if (xport.status < 200 || xport.status >= 300) {
this._fail();
return;
}
if (__DEV__) {
if (!xport.responseText.length) {
throw new Error(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an empty response.');
}
if (xport.responseText.indexOf('for (;;);') != 0) {
throw new Error(
'JX.Request("'+this.getURI()+'", ...): '+
'server returned an invalid response.');
}
}
var text = xport.responseText.substring('for (;;);'.length);
var response = eval('('+text+')');
} catch (exception) {
if (__DEV__) {
JX.log(
'JX.Request("'+this.getURI()+'", ...): '+
'caught exception processing response: '+exception);
}
this._fail();
return;
}
try {
if (response.error) {
this._fail(response.error);
} else {
JX.Stratcom.mergeData(
this._block,
response.javelin_metadata || {});
this._done(response);
JX.initBehaviors(response.javelin_behaviors || {});
}
} catch (exception) {
// In Firefox+Firebug, at least, something eats these. :/
JX.defer(function() {
throw exception;
});
}
},
_fail : function(error) {
this._cleanup();
this.invoke('error', error, this);
this.invoke('finally');
},
_done : function(response) {
this._cleanup();
if (response.onload) {
for (var ii = 0; ii < response.onload.length; ii++) {
(new Function(response.onload[ii]))();
}
}
this.invoke('done', this.getRaw() ? response : response.payload, this);
this.invoke('finally');
},
_cleanup : function() {
this._finished = true;
delete JX.Request._xhr[this._xhrkey];
this._timer && this._timer.stop();
this._transport.abort();
}
},
statics : {
_xhr : [],
shutdown : function() {
for (var ii = 0; ii < JX.Request._xhr.length; ii++) {
try {
JX.Request._xhr[ii] && JX.Request._xhr[ii].abort();
} catch (x) {
// Ignore.
}
}
JX.Request._xhr = [];
},
ERROR_TIMEOUT : -9000,
defaultDataSerializer : function(data) {
var uri = [];
for (var k in data) {
uri.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k]));
}
return uri.join('&');
}
},
properties : {
URI : null,
data : null,
dataSerializer : null,
/**
* Configure which HTTP method to use for the request. Permissible values
* are "POST" (default) or "GET".
*
* @param string HTTP method, one of "POST" or "GET".
*/
method : 'POST',
file : null,
raw : false,
/**
* Configure a timeout, in milliseconds. If the request has not resolved
* (either with success or with an error) within the provided timeframe,
* it will automatically fail with error JX.Request.ERROR_TIMEOUT.
*
* @param int Timeout, in milliseconds (e.g. 3000 = 3 seconds).
*/
timeout : null
},
initialize : function() {
JX.Stratcom.listen('unload', 'tag:window', JX.Request.shutdown);
}
});
/**
* @requires javelin-install javelin-event
* @provides javelin-vector
* @javelin
*/
/**
* Query and update positions and dimensions of nodes (and other things)
* within a document. 'V' stands for 'Vector'. Each vector has two elements,
* 'x' and 'y', which usually represent width/height (a "dimension vector") or
* left/top (a "position vector").
*
* Vectors are used to manage the sizes and positions of elements, events,
* the document, and the viewport (the visible section of the document, i.e.
* how much of the page the user can actually see in their browser window).
* Unlike most Javelin classes, @{JX.$V} exposes two bare properties, 'x' and
* 'y'. You can read and manipulate these directly:
*
* // Give the user information about elements when they click on them.
* JX.Stratcom.listen(
* 'click',
* null,
* function(e) {
* var p = JX.$V(e);
* var d = JX.$V.getDim(e.getTarget());
*
* alert('You clicked at <'+p.x+','+p.y'>; the element you clicked '+
* 'is '+d.x+' pixels wide and '+d.y+' pixels high.');
* });
*
* You can also update positions and dimensions using vectors:
*
* // When the user clicks on something, make it 10px wider and 10px taller.
* JX.Stratcom.listen(
* 'click',
* null,
* function(e) {
* var t = e.getTarget();
* JX.$V(t).add(10, 10).setDim(t);
* });
*
* Additionally, vectors can be used to query document and viewport information:
*
* var v = JX.$V.getViewport(); // Viewport (window) width and height.
* var d = JX.$V.getDocument(); // Document width and height.
* var visible_area = parseInt(100 * (v.x * v.y) / (d.x * d.y), 10);
* alert('You can currently see '+visible_area'+ percent of the document.');
*
* @author epriestley
*
* @task query Querying Positions and Dimensions
* @task update Changing Positions and Dimensions
* @task manip Manipulating Vectors
*
*/
JX.install('$V', {
/**
* Construct a vector, either from explicit coordinates or from a node
* or event. You can pass two Numbers to construct an explicit vector:
*
* var v = JX.$V(35, 42);
*
* Otherwise, you can pass a @{JX.Event} or a Node to implicitly construct a
* vector:
*
* var u = JX.$V(some_event);
* var v = JX.$V(some_node);
*
* These are just like calling getPos() on the @{JX.Event} or Node.
*
* For convenience, @{JX.$V()} constructs a new vector even without the 'new'
* keyword. That is, these are equivalent:
*
* var q = new JX.$V(x, y);
* var r = JX.$V(x, y);
*
* Methods like getScroll(), getViewport() and getDocument() also create
* new vectors.
*
* Once you have a vector, you can manipulate it with add():
*
* var u = JX.$V(35, 42);
* var v = u.add(5, -12); // v = <40, 30>
*
* @param wild 'x' component of the vector, or a @{JX.Event}, or a Node.
* @param Number? If providing an 'x' component, the 'y' component of the
* vector.
* @return @{JX.$V} Specified vector.
* @task query
*/
construct : function(x, y) {
if (this == JX || this == window) {
return new JX.$V(x, y);
}
if (typeof y == 'undefined') {
return JX.$V.getPos(x);
}
this.x = parseFloat(x);
this.y = parseFloat(y);
},
canCallAsFunction : true,
members : {
x : null,
y : null,
/**
* Move a node around by setting the position of a Node to the vector's
* coordinates. For instance, if you want to move an element to the top left
* corner of the document, you could do this (assuming it has 'position:
* absolute'):
*
* JX.$V(0, 0).setPos(node);
*
* @param Node Node to move.
* @return this
* @task update
*/
setPos : function(node) {
node.style.left = (this.x === null) ? '' : (parseInt(this.x, 10) + 'px');
node.style.top = (this.y === null) ? '' : (parseInt(this.y, 10) + 'px');
return this;
},
/**
* Change the size of a node by setting its dimensions to the vector's
* coordinates. For instance, if you want to change an element to be 100px
* by 100px:
*
* JX.$V(100, 100).setDim(node);
*
* Or if you want to expand a node's dimensions by 50px:
*
* JX.$V(node).add(50, 50).setDim(node);
*
* @param Node Node to resize.
* @return this
* @task update
*/
setDim : function(node) {
node.style.width =
(this.x === null)
? ''
: (parseInt(this.x, 10) + 'px');
node.style.height =
(this.y === null)
? ''
: (parseInt(this.y, 10) + 'px');
return this;
},
/**
* Change a vector's x and y coordinates by adding numbers to them, or
* adding the coordinates of another vector. For example:
*
* var u = JX.$V(3, 4).add(100, 200); // u = <103, 204>
*
* You can also add another vector:
*
* var q = JX.$V(777, 999);
* var r = JX.$V(1000, 2000);
* var s = q.add(r); // s = <1777, 2999>
*
* Note that this method returns a new vector. It does not modify the
* 'this' vector.
*
* @param wild Value to add to the vector's x component, or another
* vector.
* @param Number? Value to add to the vector's y component.
* @return @{JX.$V} New vector, with summed components.
* @task manip
*/
add : function(x, y) {
if (x instanceof JX.$V) {
return this.add(x.x, x.y);
}
return JX.$V(this.x + parseFloat(x), this.y + parseFloat(y));
}
},
statics : {
_viewport: null,
/**
* Determine where in a document an element is (or where an event, like
* a click, occurred) by building a new vector containing the position of a
* Node or @{JX.Event}. The 'x' component of the vector will correspond to
* the pixel offset of the argument relative to the left edge of the
* document, and the 'y' component will correspond to the pixel offset of
* the argument relative to the top edge of the document. Note that all
* vectors are generated in document coordinates, so the scroll position
* does not affect them.
*
* See also getDim(), used to determine an element's dimensions.
*
* @param Node|@{JX.Event} Node or event to determine the position of.
* @return @{JX.$V} New vector with the argument's position.
* @task query
*/
getPos : function(node) {
JX.Event && (node instanceof JX.Event) && (node = node.getRawEvent());
if (('pageX' in node) || ('clientX' in node)) {
var c = JX.$V._viewport;
return JX.$V(
node.pageX || (node.clientX + c.scrollLeft),
node.pageY || (node.clientY + c.scrollTop));
}
var x = node.offsetLeft;
var y = node.offsetTop;
while (node.offsetParent && (node.offsetParent != document.body)) {
node = node.offsetParent;
x += node.offsetLeft;
y += node.offsetTop;
}
return JX.$V(x, y);
},
/**
* Determine the width and height of a node by building a new vector with
* dimension information. The 'x' component of the vector will correspond
* to the element's width in pixels, and the 'y' component will correspond
* to its height in pixels.
*
* See also getPos(), used to determine an element's position.
*
* @param Node Node to determine the display size of.
* @return @{JX.$V} New vector with the node's dimensions.
* @task query
*/
getDim : function(node) {
return JX.$V(node.offsetWidth, node.offsetHeight);
},
/**
* Determine the current scroll position by building a new vector where
* the 'x' component corresponds to how many pixels the user has scrolled
* from the left edge of the document, and the 'y' component corresponds to
* how many pixels the user has scrolled from the top edge of the document.
*
* See also getViewport(), used to determine the size of the viewport.
*
* @return @{JX.$V} New vector with the document scroll position.
* @task query
*/
getScroll : function() {
// We can't use $V._viewport here because there's diversity between
// browsers with respect to where position/dimension and scroll position
// information is stored.
var b = document.body;
var e = document.documentElement;
return JX.$V(b.scrollLeft || e.scrollLeft, b.scrollTop || e.scrollTop);
},
/**
* Determine the size of the viewport (basically, the browser window) by
* building a new vector where the 'x' component corresponds to the width
* of the viewport in pixels and the 'y' component corresponds to the height
* of the viewport in pixels.
*
* See also getScroll(), used to determine the position of the viewport, and
* getDocument(), used to determine the size of the entire document.
*
* @return @{JX.$V} New vector with the viewport dimensions.
* @task query
*/
getViewport : function() {
var c = JX.$V._viewport;
var w = window;
return JX.$V(
w.innerWidth || c.clientWidth || 0,
w.innerHeight || c.clientHeight || 0
);
},
/**
* Determine the size of the document, including any area outside the
* current viewport which the user would need to scroll in order to see, by
* building a new vector where the 'x' component corresponds to the document
* width in pixels and the 'y' component corresponds to the document height
* in pixels.
*
* @return @{JX.$V} New vector with the document dimensions.
* @task query
*/
getDocument : function() {
var c = JX.$V._viewport;
return JX.$V(c.scrollWidth || 0, c.scrollHeight || 0);
}
},
/**
* On initialization, the browser-dependent viewport root is determined and
* stored.
*
* In ##__DEV__##, @{JX.$V} installs a toString() method so vectors print in a
* debuggable way:
*
* <23, 92>
*
* @return void
*/
initialize : function() {
var c = ((c = document) && (c = c.documentElement)) ||
((c = document) && (c = c.body))
JX.$V._viewport = c;
if (__DEV__) {
JX.$V.prototype.toString = function() {
return '<'+this.x+', '+this.y+'>';
}
}
}
});
/**
* @requires javelin-install javelin-util javelin-vector javelin-stratcom
* @provides javelin-dom
*
* @javelin-installs JX.$
* @javelin-installs JX.$N
*
* @javelin
*/
/**
* Select an element by its "id" attribute, like ##document.getElementById()##.
* For example:
*
* var node = JX.$('some_id');
*
* This will select the node with the specified "id" attribute:
*
* LANG=HTML
* <div id="some_id">...</div>
*
* If the specified node does not exist, @{JX.$()} will throw ##JX.$.NotFound##.
* For other ways to select nodes from the document, see @{JX.DOM.scry()} and
* @{JX.DOM.find()}.
*
* @param string "id" attribute to select from the document.
* @return Node Node with the specified "id" attribute.
*/
JX.$ = function(id) {
if (__DEV__) {
if (!id) {
throw new Error('Empty ID passed to JX.$()!');
}
}
var node = document.getElementById(id);
if (!node || (node.id != id)) {
if (__DEV__) {
if (node && (node.id != id)) {
throw new Error(
'JX.$("'+id+'"): '+
'document.getElementById() returned an element without the '+
'correct ID. This usually means that the element you are trying '+
'to select is being masked by a form with the same value in its '+
'"name" attribute.');
}
}
throw JX.$.NotFound;
}
return node;
};
JX.$.NotFound = {};
if (__DEV__) {
// If we're in dev, upgrade this object into an Error so that it will
// print something useful if it escapes the stack after being thrown.
JX.$.NotFound = new Error(
'JX.$() or JX.DOM.find() call matched no nodes.');
}
/**
* Upcast a string into an HTML object so it is treated as markup instead of
* plain text. See @{JX.$N} for discussion of Javelin's security model. Every
* time you call this function you potentially open up a security hole. Avoid
* its use wherever possible.
*
* This class intentionally supports only a subset of HTML because many browsers
* named "Internet Explorer" have awkward restrictions around what they'll
* accept for conversion to document fragments. Alter your datasource to emit
* valid HTML within this subset if you run into an unsupported edge case. All
* the edge cases are crazy and you should always be reasonably able to emit
* a cohesive tag instead of an unappendable fragment.
*
* @task build String into HTML
* @task nodes HTML into Nodes
*/
JX.install('HTML', {
/**
* Build a new HTML object from a trustworthy string.
*
* @task build
* @param string A string which you want to be treated as HTML, because you
* know it is from a trusted source and any data in it has been
* properly escaped.
* @return JX.HTML HTML object, suitable for use with @{JX.$N}.
*/
construct : function(str) {
if (this == JX || this == window) {
return new JX.HTML(str);
}
if (__DEV__) {
var tags = ['legend', 'thead', 'tbody', 'tfoot', 'column', 'colgroup',
'caption', 'tr', 'th', 'td', 'option'];
var evil_stuff = new RegExp('^\\s*<('+tags.join('|')+')\\b', 'i');
var match = null;
if (match = str.match(evil_stuff)) {
throw new Error(
'JX.HTML("<'+match[1]+'>..."): '+
'call initializes an HTML object with an invalid partial fragment '+
'and can not be converted into DOM nodes. The enclosing tag of an '+
'HTML content string must be appendable to a document fragment. '+
'For example, <table> is allowed but <tr> or <tfoot> are not.');
}
var really_evil = /<script\b/;
if (str.match(really_evil)) {
throw new Error(
'JX.HTML("...<script>..."): '+
'call initializes an HTML object with an embedded script tag! '+
'Are you crazy?! Do NOT do this!!!');
}
var wont_work = /<object\b/;
if (str.match(wont_work)) {
throw new Error(
'JX.HTML("...<object>..."): '+
'call initializes an HTML object with an embedded <object> tag. IE '+
'will not do the right thing with this.');
}
// TODO(epriestley): May need to deny <option> more broadly, see
// http://support.microsoft.com/kb/829907 and the whole mess in the
// heavy stack. But I seem to have gotten away without cloning into the
// documentFragment below, so this may be a nonissue.
}
this._content = str;
},
canCallAsFunction : true,
members : {
_content : null,
/**
* Convert the raw HTML string into a DOM node tree.
*
* @task nodes
* @return DocumentFragment A document fragment which contains the nodes
* corresponding to the HTML string you provided.
*/
getFragment : function() {
var wrapper = JX.$N('div');
wrapper.innerHTML = this._content;
var fragment = document.createDocumentFragment();
while (wrapper.firstChild) {
// TODO(epriestley): Do we need to do a bunch of cloning junk here?
// See heavy stack. I'm disconnecting the nodes instead; this seems
// to work but maybe my test case just isn't extensive enough.
fragment.appendChild(wrapper.removeChild(wrapper.firstChild));
}
return fragment;
}
}
});
/**
* Create a new DOM node with attributes and content.
*
* var link = JX.$N('a');
*
* This creates a new, empty anchor tag without any attributes. The equivalent
* markup would be:
*
* LANG=HTML
* <a />
*
* You can also specify attributes by passing a dictionary:
*
* JX.$N('a', {name: 'anchor'});
*
* This is equivalent to:
*
* LANG=HTML
* <a name="anchor" />
*
* Additionally, you can specify content:
*
* JX.$N(
* 'a',
* {href: 'http://www.javelinjs.com'},
* 'Visit the Javelin Homepage');
*
* This is equivalent to:
*
* LANG=HTML
* <a href="http://www.javelinjs.com">Visit the Javelin Homepage</a>
*
* If you only want to specify content, you can omit the attribute parameter.
* That is, these calls are equivalent:
*
* JX.$N('div', {}, 'Lorem ipsum...'); // No attributes.
* JX.$N('div', 'Lorem ipsum...') // Same as above.
*
* Both are equivalent to:
*
* LANG=HTML
* <div>Lorem ipsum...</div>
*
* Note that the content is treated as plain text, not HTML. This means it is
* safe to use untrusted strings:
*
* JX.$N('div', '<script src="evil.com" />');
*
* This is equivalent to:
*
* LANG=HTML
* <div>&lt;script src="evil.com" /&gt;</div>
*
* That is, the content will be properly escaped and will not create a
* vulnerability. If you want to set HTML content, you can use @{JX.HTML}:
*
* JX.$N('div', JX.HTML(some_html));
*
* **This is potentially unsafe**, so make sure you understand what you're
* doing. You should usually avoid passing HTML around in string form. See
* @{JX.HTML} for discussion.
*
* You can create new nodes with a Javelin sigil (and, optionally, metadata) by
* providing "sigil" and "metadata" keys in the attribute dictionary.
*
* @param string Tag name, like 'a' or 'div'.
* @param dict|string|@{JX.HTML}? Property dictionary, or content if you don't
* want to specify any properties.
* @param string|@{JX.HTML}? Content string (interpreted as plain text)
* or @{JX.HTML} object (interpreted as HTML,
* which may be dangerous).
* @return Node New node with whatever attributes and
* content were specified.
*/
JX.$N = function(tag, attr, content) {
if (typeof content == 'undefined' &&
(typeof attr != 'object' || attr instanceof JX.HTML)) {
content = attr;
attr = {};
}
if (__DEV__) {
if (tag.toLowerCase() != tag) {
throw new Error(
'$N("'+tag+'", ...): '+
'tag name must be in lower case; '+
'use "'+tag.toLowerCase()+'", not "'+tag+'".');
}
}
var node = document.createElement(tag);
if (attr.style) {
JX.copy(node.style, attr.style);
delete attr.style;
}
if (attr.sigil) {
JX.Stratcom.sigilize(node, attr.sigil, attr.meta);
delete attr.sigil;
delete attr.meta;
}
if (__DEV__) {
if (('metadata' in attr) || ('data' in attr)) {
throw new Error(
'$N(' + tag + ', ...): ' +
'use the key "meta" to specify metadata, not "data" or "metadata".');
}
if (attr.meta) {
throw new Error(
'$N(' + tag + ', ...): ' +
'if you specify "meta" metadata, you must also specify a "sigil".');
}
}
// prevent sigil from being wiped by blind copying the className
if (attr.className) {
JX.DOM.alterClass(node, attr.className, true);
delete attr.className;
}
JX.copy(node, attr);
if (content) {
JX.DOM.setContent(node, content);
}
return node;
};
/**
* Query and update the DOM. Everything here is static, this is essentially
* a collection of common utility functions.
*
* @task stratcom Attaching Event Listeners
* @task content Changing DOM Content
* @task nodes Updating Nodes
* @task test Testing DOM Properties
* @task convenience Convenience Methods
* @task query Finding Nodes in the DOM
* @task view Changing View State
*/
JX.install('DOM', {
statics : {
_autoid : 0,
_metrics : {},
/**
* @task content
*/
setContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
throw new Error(
'JX.DOM.setContent(<yuck>, ...): '+
'first argument must be a DOM node.');
}
}
while (node.firstChild) {
JX.DOM.remove(node.firstChild);
}
JX.DOM.appendContent(node, content);
},
/**
* @task content
*/
prependContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
throw new Error(
'JX.DOM.prependContent(<junk>, ...): '+
'first argument must be a DOM node.');
}
}
this._insertContent(node, content, this._mechanismPrepend);
},
/**
* @task content
*/
appendContent : function(node, content) {
if (__DEV__) {
if (!JX.DOM.isNode(node)) {
throw new Error(
'JX.DOM.appendContent(<bleh>, ...): '+
'first argument must be a DOM node.');
}
}
this._insertContent(node, content, this._mechanismAppend);
},
/**
* @task content
*/
_mechanismPrepend : function(node, content) {
node.insertBefore(content, node.firstChild);
},
/**
* @task content
*/
_mechanismAppend : function(node, content) {
node.appendChild(content);
},
/**
* @task content
*/
_insertContent : function(parent, content, mechanism) {
if (content === null || typeof content == 'undefined') {
return;
}
if (content instanceof JX.HTML) {
content = content.getFragment();
}
if (content instanceof Array) {
for (var ii = 0; ii < content.length; ii++) {
var child = (typeof content[ii] == 'string')
? document.createTextNode(content[ii])
: content[ii];
mechanism(parent, child);
}
} else if (content.nodeType) {
mechanism(parent, content);
} else {
mechanism(parent, document.createTextNode(content));
}
},
/**
* @task nodes
*/
remove : function(node) {
node.parentNode && JX.DOM.replace(node, null);
return node;
},
/**
* @task nodes
*/
replace : function(node, replacement) {
if (__DEV__) {
if (!node.parentNode) {
throw new Error(
'JX.DOM.replace(<node>, ...): '+
'node has no parent node, so it can not be replaced.');
}
}
var mechanism;
if (node.nextSibling) {
mechanism = JX.bind(node.nextSibling, function(parent, content) {
parent.insertBefore(content, this);
});
} else {
mechanism = this._mechanismAppend;
}
var parent = node.parentNode;
node.parentNode.removeChild(node);
this._insertContent(parent, replacement, mechanism);
return node;
},
/**
* Retrieve the nearest parent node matching the desired sigil.
* @param Node The child element to search from
* @return The matching parent or null if no parent could be found
* @author jgabbard
*/
nearest : function(node, sigil) {
while (node && !JX.Stratcom.hasSigil(node, sigil)) {
node = node.parentNode;
}
return node;
},
serialize : function(form) {
var elements = form.getElementsByTagName('*');
var data = {};
for (var ii = 0; ii < elements.length; ++ii) {
if (!elements[ii].name) {
continue;
}
var type = elements[ii].type;
var tag = elements[ii].tagName;
if ((type in {radio: 1, checkbox: 1} && elements[ii].checked) ||
type in {text: 1, hidden: 1, password: 1} ||
tag in {TEXTAREA: 1, SELECT: 1}) {
data[elements[ii].name] = elements[ii].value;
}
}
return data;
},
/**
* Test if an object is a valid Node.
*
* @task test
* @param wild Something which might be a Node.
* @return bool True if the parameter is a DOM node.
*/
isNode : function(node) {
return !!(node && node.nodeName && (node !== window));
},
/**
* Test if an object is a node of some specific (or one of several) types.
* For example, this tests if the node is an ##<input />##, ##<select />##,
* or ##<textarea />##.
*
* JX.DOM.isType(node, ['input', 'select', 'textarea']);
*
* @task test
* @param wild Something which might be a Node.
* @param string|list One or more tags which you want to test for.
* @return bool True if the object is a node, and it's a node of one
* of the provided types.
*/
isType : function(node, of_type) {
node = ('' + (node.nodeName || '')).toUpperCase();
of_type = JX.$AX(of_type);
for (var ii = 0; ii < of_type.length; ++ii) {
if (of_type[ii].toUpperCase() == node) {
return true;
}
}
return false;
},
/**
* Listen for events occuring beneath a specific node in the DOM. This is
* similar to @{JX.Stratcom.listen()}, but allows you to specify some node
* which serves as a scope instead of the default scope (the whole document)
* which you get if you install using @{JX.Stratcom.listen()} directly. For
* example, to listen for clicks on nodes with the sigil 'menu-item' below
* the root menu node:
*
* var the_menu = getReferenceToTheMenuNodeSomehow();
* JX.DOM.listen(the_menu, 'click', 'menu-item', function(e) { ... });
*
* @task stratcom
* @param Node The node to listen for events underneath.
* @param string|list One or more event types to listen for.
* @param list? A path to listen on.
* @param function Callback to invoke when a matching event occurs.
* @return object A reference to the installed listener. You can later
* remove the listener by calling this object's remove()
* method.
*/
listen : function(node, type, path, callback) {
return JX.Stratcom.listen(
type,
['id:'+JX.DOM.uniqID(node)].concat(JX.$AX(path || [])),
callback);
},
uniqID : function(node) {
if (!node.id) {
node.id = 'autoid_'+(++JX.DOM._autoid);
}
return node.id;
},
alterClass : function(node, className, add) {
var has = ((' '+node.className+' ').indexOf(' '+className+' ') > -1);
if (add && !has) {
node.className += ' '+className;
} else if (has && !add) {
node.className = node.className.replace(
new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), ' ');
}
},
htmlize : function(str) {
return (''+str)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
},
/**
* Show one or more elements, by removing their "display" style. This
* assumes you have hidden them with hide(), or explicitly set the style
* to "display: none;".
*
* @task convenience
* @param ... One or more nodes to remove "display" styles from.
* @return void
*/
show : function() {
if (__DEV__) {
for (var ii = 0; ii < arguments.length; ++ii) {
if (!arguments[ii]) {
throw new Error(
'JX.DOM.show(...): ' +
'one or more arguments were null or empty.');
}
}
}
for (var ii = 0; ii < arguments.length; ++ii) {
arguments[ii].style.display = '';
}
},
/**
* Hide one or more elements, by setting "display: none;" on them. This is
* a convenience method. See also show().
*
* @task convenience
* @param ... One or more nodes to set "display: none" on.
* @return void
*/
hide : function() {
if (__DEV__) {
for (var ii = 0; ii < arguments.length; ++ii) {
if (!arguments[ii]) {
throw new Error(
'JX.DOM.hide(...): ' +
'one or more arguments were null or empty.');
}
}
}
for (var ii = 0; ii < arguments.length; ++ii) {
arguments[ii].style.display = 'none';
}
},
textMetrics : function(node, pseudoclass, x) {
if (!this._metrics[pseudoclass]) {
var n = JX.$N(
'var',
{className: pseudoclass});
this._metrics[pseudoclass] = n;
}
var proxy = this._metrics[pseudoclass];
document.body.appendChild(proxy);
proxy.style.width = x ? (x+'px') : '';
JX.DOM.setContent(
proxy,
JX.HTML(JX.DOM.htmlize(node.value).replace(/\n/g, '<br />')));
var metrics = JX.$V.getDim(proxy);
document.body.removeChild(proxy);
return metrics;
},
/**
* Search the document for DOM nodes by providing a root node to look
* beneath, a tag name, and (optionally) a sigil. Nodes which match all
* specified conditions are returned.
*
* @task query
*
* @param Node Root node to search beneath.
* @param string Tag name, like 'a' or 'textarea'.
* @param string Optionally, a sigil which nodes are required to have.
*
* @return list List of matching nodes, which may be empty.
*/
scry : function(root, tagname, sigil) {
if (__DEV__) {
if (!JX.DOM.isNode(root)) {
throw new Error(
'JX.DOM.scry(<yuck>, ...): '+
'first argument must be a DOM node.');
}
}
var nodes = root.getElementsByTagName(tagname);
if (!sigil) {
return JX.$A(nodes);
}
var result = [];
for (var ii = 0; ii < nodes.length; ii++) {
if (JX.Stratcom.hasSigil(nodes[ii], sigil)) {
result.push(nodes[ii]);
}
}
return result;
},
/**
* Select a node uniquely identified by a root, tagname and sigil. This
* is similar to JX.DOM.scry() but expects exactly one result. It will
* throw JX.$.NotFound if it matches no results.
*
* @task query
*
* @param Node Root node to search beneath.
* @param string Tag name, like 'a' or 'textarea'.
* @param string Optionally, sigil which selected node must have.
*
* @return Node Node uniquely identified by the criteria.
*/
find : function(root, tagname, sigil) {
if (__DEV__) {
if (!JX.DOM.isNode(root)) {
throw new Error(
'JX.DOM.find(<glop>, "'+tagname+'", "'+sigil+'"): '+
'first argument must be a DOM node.');
}
}
var result = JX.DOM.scry(root, tagname, sigil);
if (__DEV__) {
if (result.length > 1) {
throw new Error(
'JX.DOM.find(<node>, "'+tagname+'", "'+sigil+'"): '+
'matched more than one node.');
}
}
if (!result.length) {
throw JX.$.NotFound;
}
return result[0];
},
/**
* Focus a node safely. This is just a convenience wrapper that allows you
* to avoid IE's habit of throwing when nearly any focus operation is
* invoked.
*
* @task convenience
* @param Node Node to move cursor focus to, if possible.
* @return void
*/
focus : function(node) {
try { node.focus(); } catch (lol_ie) {}
},
/**
* Scroll to the position of an element in the document.
* @task view
* @param Node Node to move document scroll position to, if possible.
* @return void
*/
scrollTo : function(node) {
window.scrollTo(0, JX.$V(node).y);
}
}
});
/**
* Simple JSON serializer.
*
* @requires javelin-install javelin-util
* @provides javelin-json
* @javelin
*/
JX.install('JSON', {
statics : {
serialize : function(obj) {
if (__DEV__) {
try {
return JX.JSON._val(obj);
} catch (x) {
JX.log(
'JX.JSON.serialize(...): '+
'caught exception while serializing object. ('+x+')');
}
} else {
return JX.JSON._val(obj);
}
},
_val : function(val) {
var out = [];
if (val === null) {
return 'null';
} else if (val.push && val.pop) {
for (var ii = 0; ii < val.length; ii++) {
if (typeof val[ii] != 'undefined') {
out.push(JX.JSON._val(val[ii]));
}
}
return '['+out.join(',')+']';
} else if (val === true) {
return 'true';
} else if (val === false) {
return 'false';
} else if (typeof val == 'string') {
return JX.JSON._esc(val);
} else if (typeof val == 'number') {
return val;
} else {
for (var k in val) {
out.push(JX.JSON._esc(k)+':'+JX.JSON._val(val[k]));
}
return '{'+out.join(',')+'}';
}
},
_esc : function(str) {
return '"'+str.replace(/\\/g, '\\\\').replace(/"/g, '\\"')+'"';
}
}
});
diff --git a/webroot/rsrc/js/javelin/javelin.min.js b/webroot/rsrc/js/javelin/javelin.min.js
index 60bb82e21f..e199548c52 100644
--- a/webroot/rsrc/js/javelin/javelin.min.js
+++ b/webroot/rsrc/js/javelin/javelin.min.js
@@ -1 +1,2 @@
+/** @provides javelin-lib-prod */
JX.$A=function(b){var c=[];for(var a=0;a<b.length;a++)c.push(b[a]);return c;};JX.$AX=function(a){return (a instanceof Array)?a:[a];};JX.copy=function(a,b){for(var c in b)a[c]=b[c];return a;};JX.bind=function(b,c,d){var a=JX.$A(arguments).slice(2);return function(){return c.apply(b||window,a.concat(JX.$A(arguments)));};};JX.bag=function(){};JX.keys=function(b){var c=[];for(var a in b)c.push(a);return c;};JX.defer=function(a,c){var b=setTimeout(a,c||0);return {stop:function(){clearTimeout(b);}};};JX.go=function(a){JX.Stratcom&&JX.Stratcom.invoke('go',null,{uri:a});(a&&(window.location=a))||window.location.reload(true);};JX.install=function(h,g){if(typeof JX.install._a=='undefined')JX.install._a=0;if(h in JX)return;if(!JX.install._b)JX.install._b=[];JX.install._b.push([h,g]);do{var d;var f=null;for(var c=0;c<JX.install._b.length;++c){d=JX.install._b[c][1];if(d.extend&&!JX[d.extend])continue;f=JX.install._b[c][0];JX.install._b.splice(c,1);--c;JX[f]=(function(m,l){var n=function(){this.__id__='__obj__'+(++JX.install._a);this.__super__=JX[l.extend]||JX.bag;this.__parent__=JX[m].prototype;if(JX[m].__prototyping__)return;return (l.construct||JX.bag).apply(this,arguments);};return n;})(f,d);JX.copy(JX[f],d.statics);JX[f].__prototyping__=0;var k;if(d.extend){JX[d.extend].__prototyping__++;k=JX[f].prototype=new JX[d.extend]();JX[d.extend].__prototyping__--;}else k=JX[f].prototype={};k.__class__=JX[f];for(var e in (d.properties||{})){var b=e.charAt(0).toUpperCase()+e.substr(1);var j='__auto__'+e;k[j]=d.properties[e];k['set'+b]=(function(l){return function(m){this[l]=m;return this;};})(j);k['get'+b]=(function(l){return function(){return this[l];};})(j);}JX.copy(k,d.members);if(d.events&&d.events.length){var i=JX[d.extend]||{};JX[f].__name__='class:'+f;var a=i.__path__||[];JX[f].__path__=a.concat([JX[f].__name__]);k.invoke=function(l){return JX.Stratcom.invoke('obj:'+l,this.__class__.__path__.concat([this.__id__]),{args:JX.$A(arguments).slice(1)});};k.listen=function(m,l){return JX.Stratcom.listen('obj:'+m,this.__id__,JX.bind(this,function(n){return l.apply(this,n.getData().args);}));};JX[f].listen=function(m,l){return JX.Stratcom.listen('obj:'+m,this.__name__,JX.bind(this,function(n){return l.apply(this,n.getData().args);}));};}(d.initialize||JX.bag)();}}while(f);};JX.install('Event',{members:{stop:function(){var a=this.getRawEvent();if(a){a.cancelBubble=true;a.stopPropagation&&a.stopPropagation();}this.setStopped(true);return this;},prevent:function(){var a=this.getRawEvent();if(a){a.returnValue=false;a.preventDefault&&a.preventDefault();}this.setPrevented(true);return this;},kill:function(){this.prevent();this.stop();return this;},getSpecialKey:function(){var b=this.getRawEvent();if(!b||b.shiftKey)return null;var a=b.keyCode;do{a=JX.Event._c[a]||null;}while(a&&JX.Event._c[a]);return a;},getNode:function(a){return this.getNodes()[a]||null;}},statics:{_c:{8:'delete',9:'tab',13:'return',27:'esc',37:'left',38:'up',39:'right',40:'down',63232:38,63233:40,62234:37,62235:39}},properties:{rawEvent:null,type:null,target:null,data:null,path:[],stopped:false,prevented:false,nodes:{}},initialize:function(){}});JX.install('Stratcom',{statics:{ready:false,_d:{},_e:[],_f:{},_g:/\bFN_([^ ]+)/,_h:/\bFD_([^ ]+)_([^ ]+)/,_i:'*',_j:{},_k:[],_l:{focusin:'focus',focusout:'blur'},_m:2,_n:0,invoke:function(d,b,a){var c=new JX.Event().setType(d).setData(a||{}).setPath(b||[]);return this._o(c);},listen:function(k,h,a){var c=[];k=JX.$AX(k);if(!h)h=this._i;if(!(h instanceof Array)){h=[[h]];}else if(!(h[0] instanceof Array))h=[h];for(var d=0;d<k.length;++d){var i=k[d];if(('onpagehide' in window)&&i=='unload')i='pagehide';if(!(i in this._d))this._d[i]={};var j=this._d[i];for(var e=0;e<h.length;++e){var g=h[e];var b=this._e.length;this._e.push(a);this._f[b]=g.length;c.push(b);for(var f=0;f<g.length;++f){if(!j[g[f]])j[g[f]]=[];j[g[f]].push(b);}}}return {remove:function(){for(var l=0;l<c.length;l++)delete JX.Stratcom._e[c[l]];}};},dispatch:function(event){var k;try{k=event.srcElement||event.target;if(k===window||(!k||k.nodeName=='#document'))k={nodeName:'window'};}catch(m){k=null;}var g=[];var f={};var i=function(n,o){if(!(n in f)){f[n]=o;g.push(n);}};var a=k;while(a){i('tag:'+a.nodeName.toLowerCase(),a);var d=a.id;if(d)i('id:'+d,a);var j=a.className||'';var l=((j.baseVal||j).match(this._g)||[])[1];if(l)i(l,a);a=a.parentNode;}var c=event.type;if(c in this._l)c=this._l[c];var b={};for(var e in f)b[e]=this.getData(f[e]);var h=new JX.Event().setRawEvent(event).setType(c).setTarget(k).setData(b).setNodes(f).setPath(g.reverse());return this._o(h);},_o:function(i){var k=this._d[i.getType()];if(!k)return i;var h=i.getPath();var f=h.length;var c={};var g;for(var j=-1;j<f;++j){if(j==-1){g=k[this._i];}else g=k[h[j]];if(!g)continue;for(var d=0;d<g.length;++d)c[g[d]]=(c[g[d]]||0)+1;}var a=[];for(var e in c)if(c[e]==this._f[e]){var b=this._e[e];if(b)a.push(b);}this._k.push({handlers:a,event:i,cursor:0});this.pass();this._k.pop();return i;},pass:function(){var a=this._k[this._k.length-1];while(a.cursor<a.handlers.length){var b=a.cursor;++a.cursor;(a.handlers[b]||JX.bag)(a.event);if(a.event.getStopped())break;}return a.event.getStopped()||a.event.getPrevented();},context:function(){var a=this._k.length;if(!a)return null;return this._k[a-1].event;},mergeData:function(a,b){this._j[a]=b;if(a==0){JX.Stratcom.ready=true;JX.__rawEventQueue({type:'start-queue'});}},sigilize:function(b,c,a){if(a)JX.Stratcom._p(b,a);b.className='FN_'+c+' '+b.className;},hasSigil:function(a,b){if(!a.className)return false;return (a.className.match(this._g)||[])[1]==b;},getData:function(d){var c=(d.className||'').match(this._h);if(c){var a=this._j[c[1]];var b=c[2];if(a&&(b in a))return a[b];}return JX.Stratcom._p(d,{});},allocateMetadataBlock:function(){return this._m++;},_p:function(b,a){if(!this._j[1])this._j[1]={};this._j[1][this._n]=a;b.className='FD_1_'+(this._n++)+' '+b.className;return a;}}});JX.behavior=function(b,a){JX.behavior._q[b]=a;};JX.initBehaviors=function(c){for(var d in c){var a=c[d];if(!a.length)if(d in JX.behavior._r){continue;}else a=[null];for(var b=0;b<a.length;b++)JX.behavior._q[d](a[b]);JX.behavior._r[d]=true;}};!function(a){a.behavior._q={};a.behavior._r={};}(JX);JX.install('Request',{construct:function(b,a){this.setURI(b);if(a)this.listen('done',a);},events:['send','done','error','finally'],members:{_s:null,_t:null,_u:false,_v:null,send:function(){var f=null;try{try{f=new XMLHttpRequest();}catch(e){f=new ActiveXObject("Msxml2.XMLHTTP");}}catch(e){f=new ActiveXObject("Microsoft.XMLHTTP");}this._t=f;this._s=JX.Request._w.length;JX.Request._w.push(this);f.onreadystatechange=JX.bind(this,this._x);var a=this.getData()||{};a.__ajax__=true;this._v=JX.Stratcom.allocateMetadataBlock();a.__metablock__=this._v;var c=(this.getDataSerializer()||JX.Request.defaultDataSerializer)(a);var d=this.getURI();var b=this.getMethod().toUpperCase();if(b=='GET')d+=((d.indexOf('?')===-1)?'?':'&')+c;this.invoke('send',this);if(this.getTimeout())this._y=JX.defer(JX.bind(this,this._z,JX.Request.ERROR_TIMEOUT),this.getTimeout());f.open(b,d,true);if(b=='POST'){if(this.getFile()){f.send(this.getFile());}else{f.setRequestHeader('Content-Type','application/x-www-form-urlencoded');f.send(c);}}else f.send(null);},abort:function(){this._za();},_x:function(){var xport=this._t;try{if(this._u)return;if(xport.readyState!=4)return;if(xport.status<200||xport.status>=300){this._z();return;}var text=xport.responseText.substring('for (;;);'.length);var response=eval('('+text+')');}catch(exception){this._z();return;}try{if(response.error){this._z(response.error);}else{JX.Stratcom.mergeData(this._v,response.javelin_metadata||{});this._zb(response);JX.initBehaviors(response.javelin_behaviors||{});}}catch(exception){JX.defer(function(){throw exception;});}},_z:function(a){this._za();this.invoke('error',a,this);this.invoke('finally');},_zb:function(b){this._za();if(b.onload)for(var a=0;a<b.onload.length;a++)(new Function(b.onload[a]))();this.invoke('done',this.getRaw()?b:b.payload,this);this.invoke('finally');},_za:function(){this._u=true;delete JX.Request._w[this._s];this._y&&this._y.stop();this._t.abort();}},statics:{_w:[],shutdown:function(){for(var a=0;a<JX.Request._w.length;a++)try{JX.Request._w[a]&&JX.Request._w[a].abort();}catch(b){}JX.Request._w=[];},ERROR_TIMEOUT:-9000,defaultDataSerializer:function(a){var c=[];for(var b in a)c.push(encodeURIComponent(b)+'='+encodeURIComponent(a[b]));return c.join('&');}},properties:{URI:null,data:null,dataSerializer:null,method:'POST',file:null,raw:false,timeout:null},initialize:function(){JX.Stratcom.listen('unload','tag:window',JX.Request.shutdown);}});JX.install('$V',{construct:function(a,b){if(this==JX||this==window)return new JX.$V(a,b);if(typeof b=='undefined')return JX.$V.getPos(a);this.x=parseFloat(a);this.y=parseFloat(b);},canCallAsFunction:true,members:{x:null,y:null,setPos:function(a){a.style.left=(this.x===null)?'':(parseInt(this.x,10)+'px');a.style.top=(this.y===null)?'':(parseInt(this.y,10)+'px');return this;},setDim:function(a){a.style.width=(this.x===null)?'':(parseInt(this.x,10)+'px');a.style.height=(this.y===null)?'':(parseInt(this.y,10)+'px');return this;},add:function(a,b){if(a instanceof JX.$V)return this.add(a.x,a.y);return JX.$V(this.x+parseFloat(a),this.y+parseFloat(b));}},statics:{_zc:null,getPos:function(b){JX.Event&&(b instanceof JX.Event)&&(b=b.getRawEvent());if(('pageX' in b)||('clientX' in b)){var a=JX.$V._zc;return JX.$V(b.pageX||(b.clientX+a.scrollLeft),b.pageY||(b.clientY+a.scrollTop));}var c=b.offsetLeft;var d=b.offsetTop;while(b.offsetParent&&(b.offsetParent!=document.body)){b=b.offsetParent;c+=b.offsetLeft;d+=b.offsetTop;}return JX.$V(c,d);},getDim:function(a){return JX.$V(a.offsetWidth,a.offsetHeight);},getScroll:function(){var a=document.body;var b=document.documentElement;return JX.$V(a.scrollLeft||b.scrollLeft,a.scrollTop||b.scrollTop);},getViewport:function(){var a=JX.$V._zc;var b=window;return JX.$V(b.innerWidth||a.clientWidth||0,b.innerHeight||a.clientHeight||0);},getDocument:function(){var a=JX.$V._zc;return JX.$V(a.scrollWidth||0,a.scrollHeight||0);}},initialize:function(){var a=((a=document)&&(a=a.documentElement))||((a=document)&&(a=a.body));JX.$V._zc=a;}});JX.$=function(a){var b=document.getElementById(a);if(!b||(b.id!=a))throw JX.$.NotFound;return b;};JX.$.NotFound={};JX.install('HTML',{construct:function(a){if(this==JX||this==window)return new JX.HTML(a);this._zd=a;},canCallAsFunction:true,members:{_zd:null,getFragment:function(){var b=JX.$N('div');b.innerHTML=this._zd;var a=document.createDocumentFragment();while(b.firstChild)a.appendChild(b.removeChild(b.firstChild));return a;}}});JX.$N=function(d,a,b){if(typeof b=='undefined'&&(typeof a!='object'||a instanceof JX.HTML)){b=a;a={};}var c=document.createElement(d);if(a.style){JX.copy(c.style,a.style);delete a.style;}if(a.sigil){JX.Stratcom.sigilize(c,a.sigil,a.meta);delete a.sigil;delete a.meta;}if(a.className){JX.DOM.alterClass(c,a.className,true);delete a.className;}JX.copy(c,a);if(b)JX.DOM.setContent(c,b);return c;};JX.install('DOM',{statics:{_ze:0,_zf:{},setContent:function(b,a){while(b.firstChild)JX.DOM.remove(b.firstChild);JX.DOM.appendContent(b,a);},prependContent:function(b,a){this._zg(b,a,this._zh);},appendContent:function(b,a){this._zg(b,a,this._zi);},_zh:function(b,a){b.insertBefore(a,b.firstChild);},_zi:function(b,a){b.appendChild(a);},_zg:function(e,b,d){if(b===null||typeof b=='undefined')return;if(b instanceof JX.HTML)b=b.getFragment();if(b instanceof Array){for(var c=0;c<b.length;c++){var a=(typeof b[c]=='string')?document.createTextNode(b[c]):b[c];d(e,a);}}else if(b.nodeType){d(e,b);}else d(e,document.createTextNode(b));},remove:function(a){a.parentNode&&JX.DOM.replace(a,null);return a;},replace:function(b,d){var a;if(b.nextSibling){a=JX.bind(b.nextSibling,function(f,e){f.insertBefore(e,this);});}else a=this._zi;var c=b.parentNode;b.parentNode.removeChild(b);this._zg(c,d,a);return b;},nearest:function(a,b){while(a&&!JX.Stratcom.hasSigil(a,b))a=a.parentNode;return a;},serialize:function(c){var b=c.getElementsByTagName('*');var a={};for(var d=0;d<b.length;++d){if(!b[d].name)continue;var f=b[d].type;var e=b[d].tagName;if((f in {radio:1,checkbox:1}&&b[d].checked)||f in {text:1,hidden:1,password:1}||e in {TEXTAREA:1,SELECT:1})a[b[d].name]=b[d].value;}return a;},isNode:function(a){return !!(a&&a.nodeName&&(a!==window));},isType:function(b,c){b=(''+(b.nodeName||'')).toUpperCase();c=JX.$AX(c);for(var a=0;a<c.length;++a)if(c[a].toUpperCase()==b)return true;return false;},listen:function(b,d,c,a){return JX.Stratcom.listen(d,['id:'+JX.DOM.uniqID(b)].concat(JX.$AX(c||[])),a);},uniqID:function(a){if(!a.id)a.id='autoid_'+(++JX.DOM._ze);return a.id;},alterClass:function(d,b,a){var c=((' '+d.className+' ').indexOf(' '+b+' ')>-1);if(a&&!c){d.className+=' '+b;}else if(c&&!a)d.className=d.className.replace(new RegExp('(^|\\s)'+b+'(?:\\s|$)','g'),' ');},htmlize:function(a){return (''+a).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;');},show:function(){for(var a=0;a<arguments.length;++a)arguments[a].style.display='';},hide:function(){for(var a=0;a<arguments.length;++a)arguments[a].style.display='none';},textMetrics:function(c,e,f){if(!this._zf[e]){var b=JX.$N('var',{className:e});this._zf[e]=b;}var d=this._zf[e];document.body.appendChild(d);d.style.width=f?(f+'px'):'';JX.DOM.setContent(d,JX.HTML(JX.DOM.htmlize(c.value).replace(/\n/g,'<br />')));var a=JX.$V.getDim(d);document.body.removeChild(d);return a;},scry:function(d,f,e){var b=d.getElementsByTagName(f);if(!e)return JX.$A(b);var c=[];for(var a=0;a<b.length;a++)if(JX.Stratcom.hasSigil(b[a],e))c.push(b[a]);return c;},find:function(b,d,c){var a=JX.DOM.scry(b,d,c);if(!a.length)throw JX.$.NotFound;return a[0];},focus:function(b){try{b.focus();}catch(a){}},scrollTo:function(a){window.scrollTo(0,JX.$V(a).y);}}});JX.install('JSON',{statics:{serialize:function(a){return JX.JSON._zj(a);},_zj:function(d){var c=[];if(d===null){return 'null';}else if(d.push&&d.pop){for(var a=0;a<d.length;a++)if(typeof d[a]!='undefined')c.push(JX.JSON._zj(d[a]));return '['+c.join(',')+']';}else if(d===true){return 'true';}else if(d===false){return 'false';}else if(typeof d=='string'){return JX.JSON._zk(d);}else if(typeof d=='number'){return d;}else{for(var b in d)c.push(JX.JSON._zk(b)+':'+JX.JSON._zj(d[b]));return '{'+c.join(',')+'}';}},_zk:function(a){return '"'+a.replace(/\\/g,'\\\\').replace(/"/g,'\\"')+'"';}}});
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 15, 8:25 AM (17 h, 50 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
337719
Default Alt Text
(177 KB)

Event Timeline