Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'staging' into production

  • Loading branch information...
commit eb2d041dcd2c0a8c6486b3aa07965cf67659921c 2 parents 6dc1adc + e7a623f
@zalun zalun authored
Showing with 17,444 additions and 1,219 deletions.
  1. +1 −0  .gitignore
  2. +3 −0  .gitmodules
  3. +1 −1  README
  4. +10 −3 apps/amo/authentication.py
  5. +1 −1  apps/api/templates/api.html
  6. +30 −8 apps/api/views.py
  7. +1 −1  apps/base/templates/500.html
  8. +6 −3 apps/base/templates/base.html
  9. +30 −0 apps/base/templates/monitor.html
  10. +15 −0 apps/base/templates/settings.html
  11. +10 −2 apps/base/templatetags/safe_csrf.py
  12. +6 −0 apps/base/urls.py
  13. +54 −3 apps/base/views.py
  14. +2 −1  apps/cronjobs/management/commands/cron.py
  15. +6 −8 apps/jetpack/cron.py
  16. +5 −0 apps/jetpack/errors.py
  17. +11 −12 apps/jetpack/management/__init__.py
  18. +5 −0 apps/jetpack/management/commands/loaddata.py
  19. +24 −13 apps/jetpack/media/css/UI.Layout.css
  20. BIN  apps/jetpack/media/img/spinner.gif
  21. +8 −9 apps/jetpack/media/js/FlightDeck.Bespin.js
  22. +3 −1 apps/jetpack/media/js/FlightDeck.Browser.js
  23. +3 −3 apps/jetpack/media/js/FlightDeck.Editor.js
  24. +310 −178 apps/jetpack/media/js/Package.js
  25. +1 −1  apps/jetpack/media/js/Sidebar.js
  26. +321 −144 apps/jetpack/models.py
  27. +145 −0 apps/jetpack/package_helpers.py
  28. +12 −0 apps/jetpack/tasks.py
  29. +3 −0  apps/jetpack/templates/_attachment_code_textarea.html
  30. +2 −1  apps/jetpack/templates/_edit_package_info.html
  31. +4 −3 apps/jetpack/templates/_editor_app_menu_items.html
  32. +0 −1  apps/jetpack/templates/_module_code_textarea.html
  33. +1 −20 apps/jetpack/templates/_package_edit_view_source_bar.html
  34. +1 −5 apps/jetpack/templates/_package_info.html
  35. +19 −0 apps/jetpack/templates/_upload_package.html
  36. +5 −3 apps/jetpack/templates/addon_edit.html
  37. +8 −1 apps/jetpack/templates/edit.html
  38. +4 −1 apps/jetpack/templates/js/_edit_package_initiate.js
  39. +3 −1 apps/jetpack/templates/js/_view_package_initiate.js
  40. +1 −1  apps/jetpack/templates/json/_edit_urls.json
  41. +1 −1  apps/jetpack/templates/json/addon_created.json
  42. +3 −2 apps/jetpack/templates/json/attachment_added.json
  43. +2 −3 apps/jetpack/templates/json/attachment_removed.json
  44. +1 −1  apps/jetpack/templates/json/library_created.json
  45. +11 −0 apps/jetpack/templates/retry_download.html
  46. +1 −1  apps/jetpack/templates/view.html
  47. +0 −9 apps/jetpack/tests/addon_tests.py
  48. +21 −17 apps/jetpack/tests/attachment_test.py
  49. +2 −1  apps/jetpack/tests/corelib_tests.py
  50. +0 −1  apps/jetpack/tests/library_tests.py
  51. +1 −3 apps/jetpack/tests/manifest_tests.py
  52. +100 −12 apps/jetpack/tests/package_tests.py
  53. +33 −15 apps/jetpack/tests/revision_tests.py
  54. BIN  apps/jetpack/tests/sample_addon.xpi
  55. BIN  apps/jetpack/tests/sample_addon.zip
  56. BIN  apps/jetpack/tests/sample_addon_with_libs.xpi
  57. BIN  apps/jetpack/tests/sample_library.zip
  58. +266 −0 apps/jetpack/tests/test_views.py
  59. +48 −54 apps/jetpack/urls.py
  60. +175 −263 apps/jetpack/views.py
  61. +0 −79 apps/jetpack/xpi_utils.py
  62. +7 −3 apps/person/forms.py
  63. +43 −35 apps/person/templates/dashboard.html
  64. +1 −0  apps/person/templates/profile.html
  65. +0 −6 apps/person/templates/registration/login.html
  66. +2 −1  apps/person/templates/user_dashboard.html
  67. 0  apps/xpi/__init__.py
  68. +52 −48 apps/{jetpack/tests/xpibuild_tests.py → xpi/tests.py}
  69. +20 −0 apps/xpi/urls.py
  70. +132 −0 apps/xpi/views.py
  71. +56 −0 apps/xpi/xpi_utils.py
  72. +2 −0  docs/source/installation.rst
  73. +1 −0  lib/addon-sdk-1.0b1
  74. +1 −0  lib/jetpack-sdk
  75. +1 −3 log_settings.py
  76. +1 −1  manage.py
  77. +189 −189 media/bespin/BespinMain.js
  78. BIN  media/img/favicon.ico
  79. +18 −0 media/js/Dashboard.js
  80. +21 −22 media/js/FlightDeck.Roar.js
  81. +10 −0 media/js/FlightDeck.js
  82. +5,800 −0 media/js/lib/mootools-core-1.3.js
  83. +9,225 −0 media/js/lib/mootools-more-1.3.0.1.js
  84. +4 −4 media/js/lib/sendFile.js
  85. +4 −4 media/roar/Roar.js
  86. +1 −0  requirements/development.txt
  87. +5 −0 requirements/production.txt
  88. +6 −1 scripts/build.sh
  89. +51 −0 scripts/crontab/make-crons.py
  90. +6 −0 scripts/crontab/prod
  91. +13 −9 settings.py
  92. +11 −1 urls.py
  93. +21 −1 utils/os_utils.py
View
1  .gitignore
@@ -12,6 +12,7 @@ flightdeck/media/tutorial
flightdeck/media/codemirror
flightdeck/media/api
flightdeck/jetpack/media/media
+lib/temp-*
upload
sdk_versions
settings_local.py
View
3  .gitmodules
@@ -4,3 +4,6 @@
[submodule "lib/addon-sdk-0.9"]
path = lib/addon-sdk-0.9
url = git://github.com/mozilla/addon-sdk.git
+[submodule "lib/addon-sdk-1.0b1"]
+ path = lib/addon-sdk-1.0b1
+ url = git://github.com/mozilla/addon-sdk
View
2  README
@@ -2,4 +2,4 @@ FlightDeck is an SDK for creating JetPack
(c) Mozilla Foundation
Documentation is distributed with the source in /docs/. Also available online
-at http://fddoc.zalewa.info/
+at http://mozilla.github.com/FlightDeck/.
View
13 apps/amo/authentication.py
@@ -1,14 +1,17 @@
import MySQLdb
import hashlib
+import commonware
from django.contrib.auth.models import User
from django.utils.encoding import smart_str
from django.conf import settings
-from person.models import Profile, Limit
+from person.models import Profile
DEFAULT_AMO_PASSWORD = 'saved in AMO'
+log = commonware.log.getLogger('f.authentication')
+
class AMOAuthentication:
@@ -84,15 +87,19 @@ def auth_db_authenticate(self, username, password):
columns = ('id', 'email', 'username', 'display_name', 'password',
'homepage')
+ #try:
auth_conn = MySQLdb.connect(
host=settings.AUTH_DATABASE['HOST'],
user=settings.AUTH_DATABASE['USER'],
passwd=settings.AUTH_DATABASE['PASSWORD'],
db=settings.AUTH_DATABASE['NAME']
)
+ #except Exception, err:
+ # log.critical("Authentication database connection failure: %s"
+ # % str(err))
auth_cursor = auth_conn.cursor()
- SQL = ('SELECT %s FROM %s WHERE email="%s"'
- ) % (','.join(columns), settings.AUTH_DATABASE['TABLE'], username)
+ SQL = ('SELECT %s FROM %s WHERE email="%s"') % (
+ ','.join(columns), settings.AUTH_DATABASE['TABLE'], username)
auth_cursor.execute(SQL)
data = auth_cursor.fetchone()
user_data = {}
View
2  apps/api/templates/api.html
@@ -17,7 +17,7 @@ <h2 class="UI_Heading"><a title="Tutorial" href="{% url tutorial %}">Tutorial</a
<h2 class="UI_Heading"><a title="Addon Kit" href="{% url api_package "addon-kit" %}">Addon Kit ({{ sdk_version }})</a></h2>
{% endif %}
{% if package_name == addon_kit.package.name %}{# display jetpack-core #}
- <h2 class="UI_Heading"><a title="Core Lib" href="{% url api_package "jetpack-core" %}">Core Library ({{ sdk_version }})</a></h2>
+ <h2 class="UI_Heading"><a title="Core Lib" href="{% url api_package "api-utils" %}">API Utils ({{ sdk_version }})</a></h2>
{% endif %}
{% endif %}
<h2 class="UI_Heading"><a title="{{ package_name}}" href="{% url api_package package_name %}">{{ package.name }}
View
38 apps/api/views.py
@@ -1,17 +1,22 @@
import os
+import commonware.log
from cuddlefish import apiparser
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.conf import settings
+from django.http import Http404
from jetpack.models import SDK
+log = commonware.log.getLogger('f.api')
+
sdks = SDK.objects.all()
if sdks.count() > 0:
MAIN_SDK = sdks[0]
- SDKPACKAGESDIR = os.path.join(settings.SDK_SOURCE_DIR, MAIN_SDK.dir, 'packages')
+ SDKPACKAGESDIR = os.path.join(
+ settings.SDK_SOURCE_DIR, MAIN_SDK.dir, 'packages')
SDKVERSION = MAIN_SDK.version
ADDON_KIT = MAIN_SDK.kit_lib
CORELIB_NAME = MAIN_SDK.core_lib.package.name
@@ -43,6 +48,7 @@ def _get_package_fullname(package_name):
return special[package_name]
return package_name
+
def homepage(r, package_name=None):
if not package_name:
package_name = DEFAULTLIB_NAME
@@ -63,6 +69,18 @@ def homepage(r, package_name=None):
}, context_instance=RequestContext(r))
+def _get_hunks(text):
+ # changing the tuples to dictionaries
+ try:
+ hunks = list(apiparser.parse_hunks(text))
+ except Exception, err:
+ log.error(str(err))
+ hunks = [[None,'<p>Sorry. Error in reading the doc. '
+ 'Please check <a href="https://jetpack.mozillalabs.com/'
+ 'sdk/1.0b1/docs/#package/addon-kit">official docs</a></p>']]
+ return hunks
+
+
def package(r, package_name=None):
"""
containing a listing of all modules docs
@@ -83,9 +101,8 @@ def package(r, package_name=None):
if not os.path.isdir(path):
text = open(
os.path.join(SDKPACKAGESDIR, package_name, 'docs', d)).read()
+ hunks = _get_hunks(text)
(doc_name, extension) = os.path.splitext(d)
- # changing the tuples to dictionaries
- hunks = list(apiparser.parse_hunks(text))
data = {}
for h in hunks:
data[h[0]] = h[1]
@@ -113,11 +130,16 @@ def module(r, package_name, module_name):
sdk_version = SDKVERSION
doc_file = '.'.join((module_name, 'md'))
- text = open(
- os.path.join(SDKPACKAGESDIR,
- package_name, 'docs', doc_file)).read()
- # changing the tuples to dictionaries
- hunks = list(apiparser.parse_hunks(text))
+ doc_path = os.path.join(SDKPACKAGESDIR,
+ package_name, 'docs', doc_file)
+ try:
+ text = open(doc_path).read()
+ except Exception, err:
+ log.error(str(err))
+ raise Http404
+
+ hunks = _get_hunks(text)
+
entities = []
for h in hunks:
# convert JSON to a nice list
View
2  apps/base/templates/500.html
@@ -1 +1 @@
-500 Error - needs design :)
+500: Server Error
View
9 apps/base/templates/base.html
@@ -3,13 +3,14 @@
{% load base_helpers %}
<html lang="en">
<head>
+ <link rel="icon" href="/media/img/favicon.ico"/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="author" content="Mozilla Corporation" />
<link rel="stylesheet" href="/media/jetpack/css/UI.Base.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/media/jetpack/css/UI.Editor_Menu.css" type="text/css" media="screen">
{% block head_prejs %}{% endblock %}
- <script src="/media/js/lib/mootools-1.2.5-core-nc.js"></script>
- <script src="/media/js/lib/mootools-1.2.4.4-more.js"></script>
+ <script src="/media/js/lib/mootools-core-1.3.js"></script>
+ <script src="/media/js/lib/mootools-more-1.3.0.1.js"></script>
<script src="/media/js/lib/clientcide.2.2.0.js"></script>
<script src="/media/roar/Roar.js"></script>
<script src="/media/jetpack/js/Sidebar.js"></script>
@@ -42,7 +43,9 @@
DEBUG: {{ settings.DEBUG|yesno:"true,false" }}
};
window.addEvent('domready', function() {
- fd = new FlightDeck({'user': '{{ user.username }}'});
+ fd = new FlightDeck({
+ 'user': '{{ user.username }}'{% block flightdeck_options %}{% endblock %}
+ });
{% block create_init %}{% comment %}
create.init(
['create_addon', 'create_library'],
View
30 apps/base/templates/monitor.html
@@ -0,0 +1,30 @@
+{% extends 'base.html' %}
+
+{% block title %}Mozilla FlightDeck Site Status{% endblock title %}
+
+{% block app_content %}
+
+<h2 class="UI_Heading" style="padding-top: 3em">Filepaths</h2>
+<dl>
+{% for path, exists, permissions, extra in filepaths %}
+ <dt style="font-weight: bold">{{ path }}</dt>
+ <dd style="padding-bottom: 1em">
+ {% if exists %}
+ Exists...
+ {% else %}
+ <b style="color: red">Does not exist</b>
+ {% endif %}
+
+ {% if permissions %}
+ with proper permissions.
+ {% else %}
+ <b>and does not have the permissions we expect.</b>
+ {% endif %}
+ {% if extra %}
+ ({{ extra }})
+ {% endif %}
+ </dd>
+{% endfor %}
+</dl>
+
+{% endblock %}
View
15 apps/base/templates/settings.html
@@ -0,0 +1,15 @@
+{% extends 'base.html' %}
+
+{% block title %}Mozilla FlightDeck Site Settings{% endblock title %}
+
+{% block app_content %}
+
+<h2 class="UI_Heading" style="padding-top: 3em">Settings</h2>
+<dl>
+{% for k, v in settings %}
+ <dt style="font-weight: bold">{{ k }}</dt>
+ <dd style="padding-bottom: 1em">{{ v }}</dd>
+{% endfor %}
+</dl>
+
+{% endblock %}
View
12 apps/base/templatetags/safe_csrf.py
@@ -5,23 +5,31 @@
register = Library()
+
class CsrfTokenNode(Node):
+ """ Provide safer CSRF Token Node """
def render(self, context):
csrf_token = context.get('csrf_token', None)
if csrf_token:
if csrf_token == 'NOTPROVIDED':
return mark_safe(u"")
else:
- return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % escape(csrf_token))
+ return mark_safe(u"<div style='display:none'>"
+ "<input type='hidden' name='csrfmiddlewaretoken' "
+ "value='%s' /></div>" % escape(csrf_token))
else:
# It's very probable that the token is missing because of
# misconfiguration, so we raise a warning
from django.conf import settings
if settings.DEBUG:
import warnings
- warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.")
+ warnings.warn("A {% csrf_token %} was used in a template, "
+ "but the context did not provide the value. "
+ "This is usually caused by not using RequestContext.")
return u''
+
def safe_csrf_token(parser, token):
+ """ Safe csrf tag command """
return CsrfTokenNode()
register.tag(safe_csrf_token)
View
6 apps/base/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls.defaults import patterns, url
+
+urlpatterns = patterns('',
+ url('^services/monitor$', 'base.views.monitor', name='monitor'),
+ url('^services/settings$', 'base.views.site_settings', name='settings'),
+)
View
57 apps/base/views.py
@@ -1,8 +1,59 @@
-from django.template import RequestContext
-from django.shortcuts import render_to_response
+import os
+
from django.conf import settings
+from django.contrib.auth.decorators import user_passes_test
+from django.http import HttpResponse
+from django.shortcuts import render_to_response
+from django.template import RequestContext, loader
+from django.views.debug import get_safe_settings
+
+
+from jetpack.models import Package, SDK
+
+
+@user_passes_test(lambda u: u.is_superuser)
+def site_settings(request):
+ safe = sorted(list(get_safe_settings().items()))
+ return render_to_response(
+ 'settings.html',
+ {'settings': safe},
+ context_instance=RequestContext(request))
+
+
+def monitor(request):
+ status = True
+ data = {}
+
+ filepaths = [
+ (settings.UPLOAD_DIR, os.R_OK | os.W_OK, 'We want read + write.'),
+ ]
+
+ if hasattr(settings, 'XPI_TARGETDIR'):
+ filepaths.append((settings.XPI_TARGETDIR, os.R_OK | os.W_OK,
+ 'We want read + write. Should be a shared directory '
+ 'on multiserver installations'))
+
+ for sdk in SDK.objects.all():
+ filepaths.append((sdk.get_source_dir(), os.R_OK,
+ 'We want read on %s' % sdk.version),)
+
+ filepath_results = []
+ filepath_status = True
+
+ for path, perms, notes in filepaths:
+ path_exists = os.path.isdir(path)
+ path_perms = os.access(path, perms)
+ filepath_status = filepath_status and path_exists and path_perms
+ if not filepath_status and status:
+ status = False
+ filepath_results.append((path, path_exists, path_perms, notes))
+
+ data['filepaths'] = filepath_results
+ template = loader.get_template('monitor.html')
+ context = RequestContext(request, data)
+ status = 200 if status else 500
-from jetpack.models import Package
+ return HttpResponse(template.render(context), status=status)
def homepage(r):
View
3  apps/cronjobs/management/commands/cron.py
@@ -31,7 +31,8 @@ def handle(self, *args, **opts):
script, args = args[0], args[1:]
if script not in registered:
- log.error("Cron called with unrecognized command: %s %s" % (script, args))
+ log.error("Cron called with unrecognized command: %s %s" % (
+ script, args))
print 'Unrecognized name: %s' % script
sys.exit(1)
View
14 apps/jetpack/cron.py
@@ -1,5 +1,4 @@
import os
-import shutil
import stat
import time
@@ -8,17 +7,16 @@
import commonware
import cronjobs
-length = 60 * 60 * 24 # one day
-log = commonware.log.getLogger('z.cron')
+length = 60 * 60 * 24 # one day
+log = commonware.log.getLogger('f.cron')
def find_files():
files = []
- tmp_dir = os.path.dirname(settings.SDKDIR_PREFIX)
+ tmp_dir = settings.XPI_TARGETDIR
for filename in os.listdir(tmp_dir):
full = os.path.join(tmp_dir, filename)
-
- if full.startswith(settings.SDKDIR_PREFIX):
+ if os.path.isfile(full) and full.endswith("xpi"):
files.append(full)
return files
@@ -27,6 +25,6 @@ def find_files():
def clean_tmp(length=length):
older = time.time() - length
for filename in find_files():
- if (os.stat(filename)[stat.ST_MTIME] < older):
- shutil.rmtree(filename)
+ if os.stat(filename)[stat.ST_MTIME] < older:
+ os.remove(filename)
log.info('Deleted: %s' % filename)
View
5 apps/jetpack/errors.py
@@ -2,6 +2,7 @@
Special Exception classes
"""
+
class SimpleException(Exception):
" Exception extended with a value "
@@ -39,3 +40,7 @@ class AddingModuleDenied(SimpleException):
class SingletonCopyException(SimpleException):
" Singleton may not be copied "
+
+
+class ManifestNotValid(SimpleException):
+ " Upload failed due to package.json malfunction "
View
23 apps/jetpack/management/__init__.py
@@ -7,7 +7,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
-from jetpack.models import Package, Module, PackageRevision, SDK
+from jetpack.models import Package, PackageRevision, SDK
from person.models import Profile
@@ -79,7 +79,6 @@ def get_core_manifest(sdk_source):
', '.join(ALLOWED_CORE_NAMES.keys()))
-
def get_or_create_core_author():
" create or get Mozilla author "
try:
@@ -94,10 +93,10 @@ def get_or_create_core_author():
def _get_code(path):
- handle = open(path, 'r')
- code = handle.read()
- handle.close()
- return code
+ handle = open(path, 'r')
+ code = handle.read()
+ handle.close()
+ return code
def add_core_modules(sdk_source, core_revision, core_author,
@@ -133,7 +132,7 @@ def add_core_modules(sdk_source, core_revision, core_author,
)
except Exception, err:
print ("Warning: There was a problem with importing module "
- "from file %s/%s\n%s") % (core_lib_dir,module_file, err)
+ "from file %s/%s\n%s") % (core_lib_dir, module_file, err)
def add_core_attachments(sdk_source, sdk_name, core_revision, core_author,
@@ -165,7 +164,6 @@ def add_core_attachments(sdk_source, sdk_name, core_revision, core_author,
(att_path, str(err)))
-
def check_SDK_dir(sdk_dir_name):
" check if SDK dir is valid "
@@ -203,6 +201,7 @@ def _update_lib(package, author, manifest):
package_revision.set_version(manifest['version'])
return package_revision
+
def _create_lib(author, manifest, full_name, name, id_number):
check_SDK_manifest(manifest)
@@ -253,10 +252,10 @@ def update_SDK(sdk_dir_name):
kit = Package.objects.get(
id_number=settings.MINIMUM_PACKAGE_ID - 1)
kit_revision = _update_lib(kit, core_author, kit_manifest)
- except Exception: # TODO: be precise
+ except Exception: # TODO: be precise
kit_revision = _create_lib(
core_author, kit_manifest, 'Addon Kit', kit_name,
- settings.MINIMUM_PACKAGE_ID-1)
+ settings.MINIMUM_PACKAGE_ID - 1)
add_core_modules(sdk_source, kit_revision, core_author, kit_name)
add_core_attachments(sdk_source, sdk_dir_name, kit_revision,
@@ -293,8 +292,8 @@ def create_SDK(sdk_dir_name='jetpack-sdk'):
if kit_manifest:
kit_revision = _create_lib(
core_author, kit_manifest, 'Addon Kit', 'addon-kit',
- settings.MINIMUM_PACKAGE_ID-1)
- add_core_modules(sdk_source, kit_revision, core_author,kit_name)
+ settings.MINIMUM_PACKAGE_ID - 1)
+ add_core_modules(sdk_source, kit_revision, core_author, kit_name)
add_core_attachments(sdk_source, sdk_dir_name, kit_revision,
core_author, kit_name)
View
5 apps/jetpack/management/commands/loaddata.py
@@ -0,0 +1,5 @@
+from django.core.management.commands.loaddata import Command as BaseCommand
+
+
+class Command(BaseCommand):
+ pass
View
37 apps/jetpack/media/css/UI.Layout.css
@@ -188,11 +188,10 @@ a:hover {
background: -moz-linear-gradient(270deg, #3A86DA, #022052);
padding-right: 1px;
right: -1px;
- cursor: pointer;
width: 230px;
}
- #package-info #ji-toggler a {
+ #package-info #ji-toggler span {
display: block;
background: -moz-linear-gradient(270deg, #59A1F2, #1C5AAB);
border-top: solid 1px #9CC7F8;
@@ -203,17 +202,6 @@ a:hover {
position: relative;
}
- #package-info #ji-toggler a span {
- display: block;
- height: 12px;
- width: 11px;
- position: absolute;
- top: 50%;
- right: 8px;
- margin-top: -5px;
- background: url(../img/ico_info.png) 0 50% no-repeat;
- }
-
/*
=========================================================================== */
.UI_Heading {
@@ -539,4 +527,27 @@ a:hover {
display: none;
}
+/* SPINNER ############################################ */
+.spinner {
+ position: absolute;
+ opacity: 0.9;
+ filter:. alpha(opacity=90);
+ /*-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=90);*/
+ z-index: 999;
+ background: #fff;
+}
+.spinner-msg {
+ text-align: center;
+ color: #999;
+ margin-bottom: 3px;
+}
+
+.spinner-img {
+ background: url(../img/spinner.gif) no-repeat;
+ width: 24px;
+ height: 24px;
+ margin: 0 auto;
+}
+
+
/* @end */
View
BIN  apps/jetpack/media/img/spinner.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
17 apps/jetpack/media/js/FlightDeck.Bespin.js
@@ -3,7 +3,7 @@
var FDBespin = new Class({
Implements: [Options, Events],
options: {
- validSyntaxes: ['js', 'html', 'plain']
+ validSyntaxes: ['js', 'html', 'plain', 'css']
},
initialize: function(element, options) {
var self = this;
@@ -25,6 +25,10 @@ var FDBespin = new Class({
},
ready: function() {
$log('FD: Bespin is ready');
+ if (!fd.item.options.modules.length) {
+ this.setContent('\n\n\n<-- Please create a module in the left '
+ +'sidebar to start entering code for this library.');
+ }
this.fireEvent('ready');
var self = this;
this.element.textChanged.add(function() {
@@ -43,15 +47,10 @@ var FDBespin = new Class({
},
setSyntax: function(syntax) {
if (!this.options.validSyntaxes.contains(syntax)) {
- if (syntax == 'css') {
- syntax = 'html'
+ if (syntax == 'json') {
+ syntax = 'js'
} else {
- if (syntax == 'json') {
- syntax = 'js'
-
- } else {
- syntax = 'plain'
- }
+ syntax = 'plain'
}
}
// XXX Switched off as incompatible with MooTools
View
4 apps/jetpack/media/js/FlightDeck.Browser.js
@@ -18,9 +18,11 @@ FlightDeck = Class.refactor(FlightDeck,{
var testThisXpi = function() {
new Request.JSON({
url: el.get('href'),
+ useSpinner: true,
+ spinnerTarget: this.getParent('li.UI_Item'),
onSuccess: fd.testXPI.bind(fd)
}).send();
- }
+ }.bind(this);
if (fd.alertIfNoAddOn()) {
if (el.getParent('li').hasClass('pressed')) {
fd.uninstallXPI(el.get('rel'));
View
6 apps/jetpack/media/js/FlightDeck.Editor.js
@@ -1,5 +1,5 @@
/*
- * Extending Flightdeck with Editor functionality
+ * Extending Flightdeck with Editor functionality
*/
FlightDeck = Class.refactor(FlightDeck,{
@@ -59,7 +59,7 @@ FlightDeck = Class.refactor(FlightDeck,{
} else {
assignToEl(selector);
}
- $('modules').addEvent('click:relay(.{file_listing_class} li a)'.substitute(this.options),
+ $('modules').addEvent('click:relay(.{file_listing_class} li a)'.substitute(this.options),
function(e, el) {
var li = $(el).getParent('li');
// assign switch_mode_on to newly created modules
@@ -87,5 +87,5 @@ FlightDeck = Class.refactor(FlightDeck,{
}
}, this);
}
-
+
});
View
488 apps/jetpack/media/js/Package.js
@@ -31,14 +31,18 @@ var Package = new Class({
// origin_url: '', // link to a revision used to created this one
// revision_author: '',
// modules: [], // a list of module filename, author pairs
+ attachments: [],
readonly: false,
- package_info_el: 'package-info',
+ package_info_el: 'package-properties',
+ copy_el: 'package-copy',
test_el: 'try_in_browser'
},
modules: {},
+ attachments: {},
initialize: function(options) {
this.setOptions(options);
this.instantiate_modules();
+ this.instantiate_attachments();
$('revisions_list').addEvent('click', this.show_revision_list);
// testing
@@ -47,30 +51,27 @@ var Package = new Class({
this.test_url = $(this.options.test_el).get('href');
$(this.options.test_el).addEvent('click', this.boundTestAddon)
}
- if ($('attachments')) $('attachments').addEvent(
- 'click:relay(.UI_File_Listing a)',
- function(e, target) {
- e.stop();
- var url = target.get('href');
- var ext = target.get('rel');
- var filename = target.get('text').escapeAll();
- var template_start = '<div id="attachment_view"><h3>'+filename+'</h3><div class="UI_Modal_Section">';
- var template_end = '</div><div class="UI_Modal_Actions"><ul><li><input type="reset" value="Close" class="closeModal"/></li></ul></div></div>';
- var template_middle = 'Download <a href="'+url+'">'+filename+'</a>';
- if (['jpg', 'gif', 'png'].contains(ext)) template_middle = '<img src="'+url+'"/>';
- if (['css', 'js', 'txt'].contains(ext)) {
- new Request({
- url: url,
- onSuccess: function(response) {
- template_middle = '<pre>'+response.escapeAll()+'</pre>';
- this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
- }
- }).send();
- } else {
- this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
- }
- }.bind(this)
- )
+ this.copy_el = $(this.options.copy_el)
+ if (this.copy_el) {
+ this.copy_el.addEvent('click', this.copyPackage.bind(this));
+ }
+ },
+ /*
+ * Method: copyPackage
+ * create a new Package with the same name for the current user
+ */
+ copyPackage: function(e) {
+ e.stop();
+ if (!settings.user) {
+ fd.alertNotAuthenticated();
+ return;
+ }
+ new Request.JSON({
+ url: this.options.copy_url,
+ onSuccess: function(response) {
+ window.location.href = response.view_url;
+ }
+ }).send();
},
testAddon: function(e){
var el;
@@ -89,18 +90,20 @@ var Package = new Class({
} else {
fd.whenAddonInstalled(function() {
fd.message.alert(
- 'Add-on Builder Helper',
+ 'Add-on Builder Helper',
'Now that you have installed the Add-ons Builder Helper, loading the add-on into your browser for testing...'
);
this.testAddon();
}.bind(this));
-
+
}
},
installAddon: function() {
new Request.JSON({
url: this.test_url,
data: this.data || {},
+ useSpinner: true,
+ spinnerTarget: $(this.options.test_el).getParent('div'),
onSuccess: fd.testXPI.bind(fd)
}).send();
},
@@ -119,6 +122,13 @@ var Package = new Class({
this.modules[module.filename] = new Module(this,module);
}, this);
},
+ instantiate_attachments: function() {
+ // iterate through attachments
+ this.options.attachments.each(function(attachment) {
+ attachment.readonly = this.options.readonly;
+ this.attachments[attachment.uid] = new Attachment(this,attachment);
+ }, this);
+ },
show_revision_list: function(e) {
if (e) e.stop();
new Request({
@@ -130,8 +140,169 @@ var Package = new Class({
}
});
+var File = Class({
+ destroy: function() {
+ // refactor me
+ if (this.textarea) this.textarea.destroy();
+ this.trigger.getParent('li').destroy();
+ $('attachments-counter').set('text', '('+ $(this.options.counter).getElements('.UI_File_Listing li').length +')')
+ this.on_destroy();
+ delete fd.editor_contents[this.get_editor_id()];
+ if (this.active) {
+ // switch editor!
+ mod = null;
+ // try to switch to first element
+ first = false;
+ $each(fd.getItem().modules, function(mod) {
+ if (!first) {
+ first = true;
+ mod.switchBespin();
+ mod.trigger.getParent('li').switch_mode_on();
+ }
+ });
+ if (!first) {
+ fd.cleanBespin();
+ }
+ }
+ },
+ switchBespin: function() {
+ if (!fd.editor_contents[this.get_editor_id()]) {
+ this.loadCode();
+ }
+ fd.switchBespinEditor(this.get_editor_id(), this.options.type);
+ if (fd.getItem()) {
+ $each(fd.getItem().modules, function(mod) {
+ mod.active = false;
+ });
+ }
+ this.active = true;
+ },
+ get_editor_id: function() {
+ if (!this._editor_id)
+ this._editor_id = this.get_css_id() + this.options.code_editor_suffix;
+ return this._editor_id;
+ },
+ get_trigger_id: function() {
+ if (!this._trigger_id)
+ this._trigger_id = this.get_css_id() + this.options.code_trigger_suffix;
+ return this._trigger_id;
+ },
+});
+
+var Attachment = new Class({
+ Extends: File,
+ Implements: [Options, Events],
+ options: {
+ code_trigger_suffix: '_attachment_switch', // id of an element which is used to switch editors
+ code_editor_suffix: '_attachment_textarea', // id of the textarea
+ active: false,
+ type: 'js',
+ append: false,
+ readonly: false,
+ counter: 'attachments'
+ },
+ is_editable: function() {
+ return ['css', 'txt', 'js', 'html'].contains(this.options.type);
+ },
+ initialize: function(pack, options) {
+ this.setOptions(options);
+ this.pack = pack;
+ if (this.options.append) {
+ this.append();
+ }
+
+ // connect trigger with editor
+ if ($(this.get_trigger_id())) {
+ this.trigger = $(this.get_trigger_id());
+ this.trigger.store('Attachment', this);
+ this.editor = new FDEditor({
+ element: this.get_editor_id(),
+ activate: this.options.main || this.options.executable,
+ type: this.options.type,
+ readonly: this.options.readonly
+ });
+ // connect trigger
+ this.trigger.addEvent('click', function(e) {
+ if (e) e.preventDefault();
+ if (this.trigger == e.target) {
+ if (this.is_editable()) {
+ this.switchBespin();
+ this.highlightMenu();
+ } else {
+ var target = e.target;
+ var url = target.get('href');
+ var ext = target.get('rel');
+ var filename = target.get('text').escapeAll();
+ var template_start = '<div id="attachment_view"><h3>'+filename+'</h3><div class="UI_Modal_Section">';
+ var template_end = '</div><div class="UI_Modal_Actions"><ul><li><input type="reset" value="Close" class="closeModal"/></li></ul></div></div>';
+ var template_middle = 'Download <a href="'+url+'">'+filename+'</a>';
+ if (['jpg', 'gif', 'png'].contains(ext)) template_middle += '<p><img src="'+url+'"/></p>';
+ this.attachmentWindow = fd.displayModal(template_start+template_middle+template_end);
+ }
+ }
+ }.bind(this));
+ if (this.options.active && this.is_editable()) {
+ this.switchBespin();
+ this.highlightMenu();
+ }
+ if (!this.options.readonly) {
+ // here special functionality for edit page
+ var rm_mod_trigger = this.trigger.getElement('span.File_close');
+ if (rm_mod_trigger) {
+ rm_mod_trigger.addEvent('click', function(e) {
+ this.pack.removeAttachmentAction(e);
+ }.bind(this));
+ }
+ }
+ }
+ },
+ highlightMenu: function() {
+ var li = this.trigger.getParent('li')
+ fd.assignModeSwitch(li);
+ li.switch_mode_on();
+ },
+ loadCode: function() {
+ // load data synchronously
+ new Request({
+ url: this.options.get_url,
+ async: false,
+ useSpinner: true,
+ spinnerTarget: 'editor-wrapper',
+ onSuccess: function(text, html) {
+ fd.editor_contents[this.get_editor_id()] = text;
+ }.bind(this)
+ }).send();
+ },
+ get_css_id: function() {
+ return this.options.uid;
+ },
+ on_destroy: function() {
+ delete fd.getItem().attachments[this.options.uid];
+ },
+ append: function() {
+ var html = '<a title="" href="'+ this.options.get_url + '" class="Module_file" id="' + this.get_trigger_id() + '">'+
+ '{filename}.{ext}<span class="File_status"></span>'+
+ '<span class="File_close"></span>'+
+ '</a>';
+ var li = new Element('li',{
+ 'class': 'UI_File_normal',
+ 'html': html.substitute(this.options)
+ }).inject($('add_attachment_div').getPrevious('ul'));
+ $('attachments-counter').set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
+
+ if (this.is_editable()) {
+ var textarea = new Element('textarea', {
+ 'id': this.get_editor_id(),
+ 'class': 'UI_Editor_Area',
+ 'name': this.get_editor_id(),
+ 'html': ''
+ }).inject('editor-wrapper');
+ }
+ }
+});
var Module = new Class({
+ Extends: File,
Implements: [Options, Events],
options: {
// data
@@ -146,7 +317,8 @@ var Module = new Class({
executable: false,
active: false,
type: 'js',
- append: false
+ append: false,
+ counter: 'modules'
},
initialize: function(pack, options) {
this.setOptions(options);
@@ -171,80 +343,35 @@ var Module = new Class({
}.bind(this));
if (this.options.main || this.options.executable) {
this.trigger.getParent('li').switch_mode_on();
- }
+ }
if (this.options.active) {
this.switchBespin();
var li = this.trigger.getParent('li')
fd.assignModeSwitch(li);
li.switch_mode_on();
}
- if (!this.options.readonly) {
- // here special functionality for edit page
- var rm_mod_trigger = this.trigger.getElement('span.File_close');
- if (rm_mod_trigger) {
- rm_mod_trigger.addEvent('click', function(e) {
- this.pack.removeModuleAction(e);
- }.bind(this));
- }
+ if (!this.options.readonly) {
+ // here special functionality for edit page
+ var rm_mod_trigger = this.trigger.getElement('span.File_close');
+ if (rm_mod_trigger) {
+ rm_mod_trigger.addEvent('click', function(e) {
+ this.pack.removeModuleAction(e);
+ }.bind(this));
+ }
}
}
},
- loadCode: function() {
- // load data synchronously
- new Request.JSON({
- url: this.options.get_url,
- async: false,
- useSpinner: true,
- spinnerTarget: 'editor-wrapper',
- onSuccess: function(mod) {
- fd.editor_contents[this.get_editor_id()] = mod.code;
- }.bind(this)
- }).send();
- },
- switchBespin: function() {
- if (!fd.editor_contents[this.get_editor_id()]) {
- this.loadCode();
- }
- fd.switchBespinEditor(this.get_editor_id(), this.options.type);
- if (fd.getItem()) {
- $each(fd.getItem().modules, function(mod) {
- mod.active = false;
- });
- }
- this.active = true;
- },
- destroy: function() {
- if (this.textarea) this.textarea.destroy();
- this.trigger.getParent('li').destroy();
- $('modules-counter').set('text', '('+ $('modules').getElements('.UI_File_Listing li').length +')')
- delete fd.getItem().modules[this.options.filename];
- delete fd.editor_contents[this.get_editor_id()];
- if (this.active) {
- // switch editor!
- mod = null;
- // try to switch to first element
- first = false;
- $each(fd.getItem().modules, function(mod) {
- if (!first) {
- first = true;
- mod.switchBespin();
- mod.trigger.getParent('li').switch_mode_on();
- }
- });
- if (!first) {
- fd.cleanBespin();
- }
- }
- },
- get_editor_id: function() {
- if (!this._editor_id)
- this._editor_id = this.options.filename + this.options.code_editor_suffix;
- return this._editor_id;
- },
- get_trigger_id: function() {
- if (!this._trigger_id)
- this._trigger_id = this.options.filename + this.options.code_trigger_suffix;
- return this._trigger_id;
+ loadCode: function() {
+ // load data synchronously
+ new Request.JSON({
+ url: this.options.get_url,
+ async: false,
+ useSpinner: true,
+ spinnerTarget: 'editor-wrapper',
+ onSuccess: function(mod) {
+ fd.editor_contents[this.get_editor_id()] = mod.code;
+ }.bind(this)
+ }).send();
},
append: function() {
var html = '<a title="" href="#" class="Module_file" id="{filename}_switch">'+
@@ -256,33 +383,33 @@ var Module = new Class({
'html': html.substitute(this.options)
}).inject($('add_module_div').getPrevious('ul'));
$('modules-counter').set('text', '('+ $('modules').getElements('.UI_File_Listing li').length +')')
-
+
var textarea = new Element('textarea', {
'id': this.options.filename + '_textarea',
'class': 'UI_Editor_Area',
'name': this.options.filename + '_textarea',
'html': this.options.code
}).inject('editor-wrapper');
- }
+ },
+ get_css_id: function() {
+ return this.options.filename;
+ },
+ on_destroy: function() {
+ delete fd.getItem().modules[this.options.filename];
+ },
})
-
Package.View = new Class({
Extends: Package,
Implements: [Options, Events],
options: {
readonly: true,
- copy_el: 'package-copy',
// copy_url: '',
},
initialize: function(options) {
this.setOptions(options);
this.parent(options);
$(this.options.package_info_el).addEvent('click', this.showInfo.bind(this));
- this.copy_el = $(this.options.copy_el)
- if (this.copy_el) {
- this.copy_el.addEvent('click', this.copyPackage.bind(this));
- }
},
/*
* Method: showInfo
@@ -292,23 +419,6 @@ Package.View = new Class({
e.stop();
fd.displayModal(this.options.package_info);
},
- /*
- * Method: copyPackage
- * create a new Package with the same name for the current user
- */
- copyPackage: function(e) {
- e.stop();
- if (!settings.user) {
- fd.alertNotAuthenticated();
- return;
- }
- new Request.JSON({
- url: this.options.copy_url,
- onSuccess: function(response) {
- window.location.href = response.edit_url;
- }
- }).send();
- }
});
@@ -351,51 +461,48 @@ Package.Edit = new Class({
// assign menu items
this.appSidebarValidator = new Form.Validator.Inline('app-sidebar-form');
$(this.options.package_info_el).addEvent('click', this.editInfo.bind(this));
-
+
// save
this.boundSaveAction = this.saveAction.bind(this);
$(this.options.save_el).addEvent('click', this.boundSaveAction);
-
+
// submit Info
this.boundSubmitInfo = this.submitInfo.bind(this);
-
+
// add/remove module
this.boundAddModuleAction = this.addModuleAction.bind(this);
this.boundRemoveModuleAction = this.removeModuleAction.bind(this);
- $(this.options.add_module_el).addEvent('click',
+ $(this.options.add_module_el).addEvent('click',
this.boundAddModuleAction);
-
+
// assign/remove library
this.boundAssignLibraryAction = this.assignLibraryAction.bind(this);
this.boundRemoveLibraryAction = this.removeLibraryAction.bind(this);
$(this.options.assign_library_el).addEvent('click',
this.boundAssignLibraryAction);
- $$('#libraries .UI_File_Listing .File_close').each(function(close) {
+ $$('#libraries .UI_File_Listing .File_close').each(function(close) {
close.addEvent('click', this.boundRemoveLibraryAction);
},this);
-
+
// add attachments
this.add_attachment_el = $('add_attachment');
- this.attachment_template = '<a title="" rel="{ext}" href="{display_url}" class="Module_file" id="{filename}{ext}_display">'+
- '{basename}<span class="File_close"></span>'+
- '</a>';
this.add_attachment_el.addEvent('change', this.sendMultipleFiles.bind(this));
this.boundRemoveAttachmentAction = this.removeAttachmentAction.bind(this);
- $$('#attachments .UI_File_Listing .File_close').each(function(close) {
+ $$('#attachments .UI_File_Listing .File_close').each(function(close) {
close.addEvent('click', this.boundRemoveAttachmentAction);
},this);
this.attachments_counter = $('attachments-counter');
-
+
var fakeFileInput = $('add_attachment_fake'), fakeFileSubmit = $('add_attachment_action_fake');
this.add_attachment_el.addEvents({
change: function(){
fakeFileInput.set('value', this.get('value'));
},
-
+
mouseover: function(){
fakeFileSubmit.addClass('hover');
},
-
+
mouseout: function(){
fakeFileSubmit.removeClass('hover');
}
@@ -406,14 +513,14 @@ Package.Edit = new Class({
url: this.options.switch_sdk_url,
data: {'id': $('jetpack_core_sdk_version').get('value')},
onSuccess: function(response) {
- // set the redirect data to edit_url of the new revision
- fd.setURIRedirect(response.edit_url);
+ // set the redirect data to view_url of the new revision
+ fd.setURIRedirect(response.view_url);
// set data changed by save
this.setUrls(response);
// change url to the SDK lib code
$('core_library_lib').getElement('a').set(
'href', response.lib_url);
- // change name of the SDK lib
+ // change name of the SDK lib
$('core_library_lib').getElement('span').set(
'text', response.lib_name);
fd.message.alert(response.message_title, response.message);
@@ -421,6 +528,7 @@ Package.Edit = new Class({
}).send();
}.bind(this));
}
+ this.bind_keyboard();
},
get_add_attachment_url: function() {
@@ -429,78 +537,90 @@ Package.Edit = new Class({
sendMultipleFiles: function() {
self = this;
+ self.spinner = false;
sendMultipleFiles({
url: this.get_add_attachment_url.bind(this),
-
+
// list of files to upload
files: this.add_attachment_el.files,
-
+
// clear the container
- //onloadstart:function(){
- // $log('loadstart')
- //},
-
+ onloadstart:function(){
+ if (self.spinner) {
+ self.spinner.position();
+ } else {
+ self.spinner = new Spinner($('attachments')).show();
+ }
+ },
+
// do something during upload ...
//onprogress:function(rpe){
// $log('progress');
//},
onpartialload: function(rpe, xhr) {
- $log('FD: file uploaded');
- // here parse xhr.responseText and append a DOM Element
+ $log('FD: attachment uploaded');
response = JSON.parse(xhr.responseText);
- new Element('li',{
- 'class': 'UI_File_Normal',
- 'html': self.attachment_template.substitute(response)
- }).inject($('attachments_ul'));
- $(response.filename+response.ext+'_display').getElement('.File_close').addEvent('click', self.boundRemoveAttachmentAction);
- fd.setURIRedirect(response.edit_url);
+ fd.message.alert(response.message_title, response.message);
+ var attachment = new Attachment(self,{
+ append: true,
+ active: true,
+ filename: response.filename,
+ ext: response.ext,
+ author: response.author,
+ code: response.code,
+ get_url: response.get_url,
+ uid: response.uid,
+ type: response.ext
+ });
self.setUrls(response);
-
- self.attachments_counter.set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
+ self.attachments[response.uid] = attachment;
},
-
+
// fired when last file has been uploaded
onload:function(rpe, xhr){
+ if (self.spinner) self.spinner.destroy();
$log('FD: all files uploaded');
$(self.add_attachment_el).set('value','');
$('add_attachment_fake').set('value','')
},
-
+
// if something is wrong ... (from native instance or because of size)
onerror:function(){
+ if (self.spinner) self.spinner.destroy();
fd.error.alert(
- 'Error {status}'.substitute(xhr),
+ 'Error {status}'.substitute(xhr),
'{statusText}<br/>{responseText}'.substitute(xhr)
- );
+ );
}
});
},
removeAttachmentAction: function(e) {
- e.stop();
- var trigger = e.target.getParent('a');
- var filename = trigger.get('text');
+ var trigger = e.target.getParent('a'),
+ filename = trigger.get('text'),
+ uid = trigger.get('id').split('_')[0];
this.question = fd.showQuestion({
title: 'Are you sure you want to remove "'+filename+'"?',
message: '',
ok: 'Remove',
id: 'remove_attachment_button',
callback: function() {
- this.removeAttachment(filename);
+ this.removeAttachment(uid);
this.question.destroy();
}.bind(this)
});
+
},
- removeAttachment: function(filename) {
+ removeAttachment: function(uid) {
var self = this;
new Request.JSON({
url: self.remove_attachment_url || self.options.remove_attachment_url,
- data: {filename: filename},
+ data: {uid: uid},
onSuccess: function(response) {
- fd.setURIRedirect(response.edit_url);
+ fd.setURIRedirect(response.view_url);
self.setUrls(response);
- $(response.filename+response.ext+'_display').getParent('li').destroy();
- self.attachments_counter.set('text', '('+ $('attachments').getElements('.UI_File_Listing li').length +')')
+ var attachment = self.attachments[uid];
+ attachment.destroy();
}
}).send();
},
@@ -525,8 +645,8 @@ Package.Edit = new Class({
url: this.add_module_url || this.options.add_module_url,
data: {'filename': filename},
onSuccess: function(response) {
- // set the redirect data to edit_url of the new revision
- fd.setURIRedirect(response.edit_url);
+ // set the redirect data to view_url of the new revision
+ fd.setURIRedirect(response.view_url);
// set data changed by save
this.setUrls(response);
fd.message.alert(response.message_title, response.message);
@@ -537,7 +657,7 @@ Package.Edit = new Class({
filename: response.filename,
author: response.author,
code: response.code,
- get_url: response.get_url
+ get_url: response.get_url
});
this.modules[response.filename] = mod;
}.bind(this)
@@ -567,7 +687,7 @@ Package.Edit = new Class({
url: this.remove_module_url || this.options.remove_module_url,
data: module.options,
onSuccess: function(response) {
- fd.setURIRedirect(response.edit_url);
+ fd.setURIRedirect(response.view_url);
this.setUrls(response);
var mod = this.modules[response.filename];
mod.destroy();
@@ -590,8 +710,8 @@ Package.Edit = new Class({
// clear the library search field
$(this.options.assign_library_input).set('value','');
$(this.autocomplete.options.display_el).set('value','');
- // set the redirect data to edit_url of the new revision
- fd.setURIRedirect(response.edit_url);
+ // set the redirect data to view_url of the new revision
+ fd.setURIRedirect(response.view_url);
// set data changed by save
this.setUrls(response);
//fd.message.alert('Library assigned', response.message);
@@ -611,7 +731,7 @@ Package.Edit = new Class({
'class': 'UI_File_Normal',
'html': html.substitute(lib)
}).inject($('assign_library_div').getPrevious('ul'));
- $$('#library_{library_name} .File_close'.substitute(lib)).each(function(close) {
+ $$('#library_{library_name} .File_close'.substitute(lib)).each(function(close) {
close.addEvent('click', this.boundRemoveLibraryAction);
},this);
$('libraries-counter').set('text', '('+ $('libraries').getElements('.UI_File_Listing li').length +')')
@@ -637,7 +757,7 @@ Package.Edit = new Class({
url: this.remove_library_url || this.options.remove_library_url,
data: {'id_number': id_number},
onSuccess: function(response) {
- fd.setURIRedirect(response.edit_url);
+ fd.setURIRedirect(response.view_url);
this.setUrls(response);
$('library_{name}'.substitute(response)).getParent('li').destroy();
$('libraries-counter').set('text', '('+ $('libraries').getElements('.UI_File_Listing li').length +')')
@@ -653,7 +773,7 @@ Package.Edit = new Class({
this.savenow = false;
fd.editPackageInfoModal = fd.displayModal(settings.edit_package_info_template.substitute(this.data || this.options));
$('package-info_form').addEvent('submit', this.boundSubmitInfo);
-
+
// XXX: this will change after moving the content to other forms
$('version_name').addEvent('change', function() { fd.fireEvent('change'); });
$('full_name').addEvent('change', function() { fd.fireEvent('change'); });
@@ -701,6 +821,9 @@ Package.Edit = new Class({
$each(this.modules, function(module, filename) {
this.data[filename] = fd.editor_contents[filename + module.options.code_editor_suffix]
}, this);
+ $each(this.attachments, function(attachment) {
+ this.data[attachment.options.uid] = fd.editor_contents[attachment.options.uid + attachment.options.code_editor_suffix]
+ }, this);
},
testAddon: function(e){
this.collectData();
@@ -718,11 +841,11 @@ Package.Edit = new Class({
url: this.save_url || this.options.save_url,
data: this.data,
onSuccess: function(response) {
- // set the redirect data to edit_url of the new revision
+ // set the redirect data to view_url of the new revision
if (response.reload) {
- window.location.href = response.edit_url;
+ window.location.href = response.view_url;
}
- fd.setURIRedirect(response.edit_url);
+ fd.setURIRedirect(response.view_url);
// set data changed by save
this.setUrls(response);
fd.message.alert(response.message_title, response.message);
@@ -737,14 +860,23 @@ Package.Edit = new Class({
if ($(this.options.test_el).getParent('li').hasClass('pressed')) {
// only one add-on of the same id should be allowed on the Helper side
this.installAddon();
- }
+ }
fd.fireEvent('save');
}.bind(this),
- onFailure: function() {
+ addOnFailure: function() {
this.saving = false;
}.bind(this)
}).send();
},
+ bind_keyboard: function() {
+ this.keyboard = new Keyboard({
+ defaultEventType: 'keyup',
+ events: {
+ 'ctrl+s': this.boundSaveAction
+ }
+ });
+ this.keyboard.activate();
+ },
setUrls: function(urls) {
this.save_url = urls.save_url;
this.test_url = urls.test_url;
View
2  apps/jetpack/media/js/Sidebar.js
@@ -41,7 +41,7 @@ var Sidebar = new Class({
// show site item if it was toggled open before reloading
if (this.sideContStatus && this.sideContStatus[index]){
this.slideFx[index].show();
- this.containers[index].getParent().setStyle('height', 'auto');
+ //this.containers[index].getParent().setStyle('height', 'auto'); //removing this fixes 623353
this.togglers[index].getParent().removeClass('closed');
}
View
465 apps/jetpack/models.py
@@ -1,10 +1,12 @@
-" Models definition for jetpack application "
+"""Models definition for jetpack application."""
# TODO: split module and create the models package
import os
import csv
import shutil
+import time
+import commonware
from copy import deepcopy
@@ -22,7 +24,12 @@
from jetpack.managers import PackageManager
from jetpack.errors import SelfDependencyException, FilenameExistException, \
UpdateDeniedException, SingletonCopyException, DependencyException
-from jetpack.xpi_utils import sdk_copy, xpi_build, xpi_remove
+# ManifestNotValid
+
+from xpi import xpi_utils
+from utils.os_utils import make_path
+
+log = commonware.log.getLogger('f.jetpack')
PERMISSION_CHOICES = (
@@ -81,6 +88,7 @@ class Package(models.Model):
# Revision which is setting the version name
version = models.ForeignKey('PackageRevision', blank=True, null=True,
related_name='package_deprecated')
+
# latest saved PackageRevision
latest = models.ForeignKey('PackageRevision', blank=True, null=True,
related_name='package_deprecated2')
@@ -100,6 +108,7 @@ class Package(models.Model):
class Meta:
" Set the ordering of objects "
ordering = ('-last_update', '-created_at')
+ unique_together = ('author', 'name')
objects = PackageManager()
@@ -120,11 +129,6 @@ def get_latest_url(self):
return reverse('jp_%s_latest' % self.get_type_name(),
args=[self.id_number])
- def get_edit_latest_url(self):
- " returns the URL to edit the latest saved Revision "
- return reverse('jp_%s_edit_latest' % self.get_type_name(),
- args=[self.id_number])
-
def get_disable_url(self):
" returns URL to the disable package functionality "
return reverse('jp_package_disable',
@@ -171,14 +175,6 @@ def get_data_dir(self):
# TODO: YAGNI!
return settings.JETPACK_DATA_DIR
- def get_unique_package_name(self):
- """
- returns a unique package name
- it's name (slugified full_name)
- accompanied with id as name is not unique
- """
- return "%s-%s" % (self.name, self.id_number)
-
def set_full_name(self):
"""
setting automated full name of the Package item
@@ -199,15 +195,18 @@ def _get_full_name(full_name, username, type_id, i=0):
packages = Package.objects.filter(author__username=username,
full_name=new_full_name,
type=type_id)
- if len(packages.all()) == 0:
+ if packages.count() == 0:
return new_full_name
i = i + 1
return _get_full_name(full_name, username, type_id, i)
- self.full_name = _get_full_name(
- settings.DEFAULT_PACKAGE_FULLNAME[self.type],
- self.author.username, self.type)
+ username = self.author.username
+ if self.author.get_profile():
+ username = self.author.get_profile().nickname or username
+
+ name = settings.DEFAULT_PACKAGE_FULLNAME.get(self.type, username)
+ self.full_name = _get_full_name(name, self.author.username, self.type)
def set_name(self):
" set's the name from full_name "
@@ -236,7 +235,7 @@ def make_dir(self, packages_dir):
create package directories inside packages
return package directory name
"""
- package_dir = '%s/%s' % (packages_dir, self.get_unique_package_name())
+ package_dir = '%s/%s' % (packages_dir, self.name)
os.mkdir(package_dir)
os.mkdir('%s/%s' % (package_dir, self.get_lib_dir()))
if not os.path.isdir('%s/%s' % (package_dir, self.get_data_dir())):
@@ -271,6 +270,75 @@ def copy(self, author):
new_p.save()
return new_p
+ def create_revision_from_xpi(self, packed, manifest, author, jid,
+ new_revision=False):
+ """
+ Create new package revision by reading XPI
+
+ Args:
+ packed (ZipFile): XPI
+ manifest (dict): parsed package.json
+ jid (String): jid name from XPI
+ author (auth.User): owner of PackageRevision
+ new_revision (Boolean): should new revision be created?
+
+ Returns:
+ PackageRevision object
+ """
+
+ revision = self.latest
+ if 'contributors' in manifest:
+ revision.contributors = manifest['contributors']
+
+ main = manifest['main'] if 'main' in manifest else 'main'
+ lib_dir = 'resources/%s-%s-%s' % (jid.lower(), manifest['name'],
+ manifest['lib'] if 'lib' in manifest else 'lib')
+ att_dir = 'resources/%s-%s-%s' % (
+ jid.lower(), manifest['name'], 'data')
+
+ revision.add_mods_and_atts_from_archive(packed, main, lib_dir, att_dir)
+
+ if new_revision:
+ revision.save()
+ else:
+ super(PackageRevision, revision).save()
+
+ revision.set_version(manifest['version'])
+ return revision
+
+ def create_revision_from_archive(self, packed, manifest, author,
+ new_revision=False):
+ """
+ Create new package revision vy reading the archive.
+
+ Args:
+ packed (ZipFile): archive containing Revision data
+ manifest (dict): parsed package.json
+ author (auth.User): owner of PackageRevision
+ new_revision (Boolean): should new revision be created?
+
+ Returns:
+ PackageRevision object
+ """
+
+ revision = self.latest
+ if 'contributors' in manifest:
+ revision.contributors = manifest['contributors']
+
+ main = manifest['main'] if 'main' in manifest else 'main'
+ lib_dir = manifest['lib'] if 'lib' in manifest else 'lib'
+ att_dir = 'data'
+
+ revision.add_mods_and_atts_from_archive(packed, main, lib_dir, att_dir)
+
+ if new_revision:
+ revision.save()
+ else:
+ super(PackageRevision, revision).save()
+
+ revision.set_version(manifest['version'])
+ return revision
+
class PackageRevision(models.Model):
"""
@@ -334,12 +402,6 @@ def get_absolute_url(self):
% settings.PACKAGE_SINGULAR_NAMES[self.package.type],
args=[self.package.id_number, self.revision_number])
- def get_edit_url(self):
- " returns URL to edit the package revision "
- return reverse(
- 'jp_%s_revision_edit' % self.package.get_type_name(),
- args=[self.package.id_number, self.revision_number])
-
def get_save_url(self):
" returns URL to save the package revision "
return reverse(
@@ -419,13 +481,17 @@ def get_contributors_list(self):
for contributors in csv_r:
return contributors
- def get_dependencies_list(self):
+ def get_dependencies_list(self, sdk=None):
" returns a list of dependencies names extended by default core "
if self.package.is_addon() and self.sdk.kit_lib:
deps = ['addon-kit']
else:
- deps = ['jetpack-core']
- deps.extend([dep.package.get_unique_package_name() \
+ if sdk and sdk.kit_lib:
+ deps = ['api-utils']
+ else:
+ # jetpack-core or api-utils
+ deps = ['jetpack-core']
+ deps.extend([dep.package.name \
for dep in self.dependencies.all()])
return deps
@@ -440,23 +506,21 @@ def get_full_rendered_description(self):
" return description prepared for rendering "
return "<p>%s</p>" % self.get_full_description().replace("\n", "<br/>")
- def get_manifest(self, test_in_browser=False):
+ def get_manifest(self, test_in_browser=False, sdk=None):
" returns manifest dictionary "
version = self.get_version_name()
if test_in_browser:
version = "%s - test" % version
- name = self.package.name if self.package.is_addon() \
- else self.package.get_unique_package_name()
manifest = {
'fullName': self.package.full_name,
- 'name': name,
+ 'name': self.package.name,
'description': self.get_full_description(),
'author': self.package.author.username,
'id': self.package.jid if self.package.is_addon() \
else self.package.id_number,
'version': version,
- 'dependencies': self.get_dependencies_list(),
+ 'dependencies': self.get_dependencies_list(sdk),
'license': self.package.license,
'url': str(self.package.url),
'contributors': self.get_contributors_list(),
@@ -467,9 +531,9 @@ def get_manifest(self, test_in_browser=False):
return manifest
- def get_manifest_json(self, **kwargs):
+ def get_manifest_json(self, sdk=sdk, **kwargs):
" returns manifest as JSOIN object "
- return simplejson.dumps(self.get_manifest(**kwargs))
+ return simplejson.dumps(self.get_manifest(sdk=sdk, **kwargs))
def get_main_module(self):
" return executable Module for Add-ons "
@@ -548,13 +612,12 @@ def set_version(self, version_name, current=True):
Set current Package:version_name and Package:version if current
"""
# check if there isn't a version with such a name
- revisions = PackageRevision.objects.filter(package__pk=self.package.pk)
- for revision in revisions:
- if revision.version_name == version_name:
- version_name = ''
- #raise Exception("There is already a revision with that name")
+ if PackageRevision.objects.filter(package__pk=self.package.pk,
+ version_name=version_name).count() > 0:
+ # reset version_name
+ version_name = ''
self.version_name = version_name
- if current:
+ if current and version_name:
self.package.version_name = version_name
self.package.version = self
self.package.save()
@@ -566,7 +629,7 @@ def validate_module_filename(self, filename):
returns False if the package revision contains a module with given
filename
"""
- if self.modules.filter(filename=filename).count() > 0:
+ if self.modules.filter(filename=filename).count():
return False
return True
@@ -575,7 +638,7 @@ def validate_attachment_filename(self, filename, ext):
returns False if the package revision contains a module with given
filename
"""
- if self.attachments.filter(filename=filename, ext=ext).count() > 0:
+ if self.attachments.filter(filename=filename, ext=ext).count():
return False
return True
@@ -594,7 +657,6 @@ def module_create(self, save=True, **kwargs):
def module_add(self, mod, save=True):
" copy to new revision, add module "
- # save as new version
# validate if given filename is valid
if not self.validate_module_filename(mod.filename):
raise FilenameExistException(
@@ -602,13 +664,6 @@ def module_add(self, mod, save=True):
'with the name "%s". Each module in your add-on '
'needs to have a unique name.') % mod.filename
)
- # I think it's not necessary
- # TODO: check integration
- #for rev in mod.revisions.all():
- # if rev.package.id_number != self.package.id_number:
- # raise AddingModuleDenied(
- # ('this module is already assigned to other'
- # 'Library - %s') % rev.package.get_unique_package_name())
if save:
self.save()
@@ -616,57 +671,106 @@ def module_add(self, mod, save=True):
def module_remove(self, mod):
" copy to new revision, remove module "
- # save as new version
self.save()
return self.modules.remove(mod)
- def module_update(self, mod, save=True):
+ def update(self, change, save=True):
" to update a module, new package revision has to be created "
if save:
self.save()
- self.modules.remove(mod)
- mod.id = None
- mod.save()
- self.modules.add(mod)
- def modules_update(self, modules):
- " update more than one module "
+ change.increment(self)
+
+ def updates(self, changes):
+ """Changes from the server."""
self.save()
- for mod in modules:
- self.module_update(mod, False)
+ for change in changes:
+ self.update(change, False)
- def attachment_create(self, **kwargs):
- " create attachment and add to attachments "
- # validate if given filename is valid
- if not self.validate_attachment_filename(kwargs['filename'],
- kwargs['ext']):
+ def add_mods_and_atts_from_archive(self, packed, main, lib_dir, att_dir):
+ """
+ Read packed archive and search for modules and attachments
+ """
+ for path in packed.namelist():
+ # add Modules
+ if path.startswith(lib_dir):
+ module_path = path.split('%s/' % lib_dir)[1]
+ if module_path and not module_path.endswith('/'):
+ module_path = os.path.splitext(module_path)[0]
+ code = packed.read(path)
+ if module_path in [m.filename for m in self.modules.all()]:
+ mod = self.modules.get(filename=module_path)
+ mod.code = code
+ self.update(mod, save=False)
+ else:
+ self.module_create(
+ save=False,
+ filename=module_path,
+ author=self.author,
+ code=code)
+
+ # add Attachments
+ if path.startswith(att_dir):
+ att_path = path.split('%s/' % att_dir)[1]
+ if att_path and not att_path.endswith('/'):
+ code = packed.read(path)
+ filename, ext = os.path.splitext(att_path)
+ if ext.startswith('.'):
+ ext = ext.split('.')[1]
+
+ if (filename, ext) in [(a.filename, a.ext)
+ for a in self.attachments.all()]:
+ att = self.attachments.get(filename=filename, ext=ext)
+ att.data = code
+ self.update(att, save=False)
+ else:
+ att = self.attachment_create(
+ save=False,
+ filename=filename,
+ ext=ext,
+ path='temp',
+ author=self.author)
+ att.data = code
+ att.write()
+ self.attachments.add(att)
+
+ def attachment_create_by_filename(self, author, filename):
+ """ find out the filename and ext and call attachment_create """
+ filename, ext = os.path.splitext(filename)
+ ext = ext.split('.')[1].lower() if ext else ''
+
+ return self.attachment_create(
+ author=author,
+ filename=filename,
+ ext=ext)
+
+ def attachment_create(self, save=True, **kwargs):
+ """ create attachment and add to attachments """
+ filename, ext = kwargs['filename'], kwargs.get('ext', '')
+
+ if not self.validate_attachment_filename(filename, ext):
raise FilenameExistException(
('Sorry, there is already an attachment in your add-on with '
'the name "%s.%s". Each attachment in your add-on needs to '
- 'have a unique name.') % (kwargs['filename'], kwargs['ext'])
+ 'have a unique name.') % (filename, ext)
)
att = Attachment.objects.create(**kwargs)
- self.attachment_add(att)
+ self.attachment_add(att, save=save)
return att
- def attachment_add(self, att):
+ def attachment_add(self, att, check=True, save=True):
" copy to new revision, add attachment "
# save as new version
# validate if given filename is valid
- if not self.validate_attachment_filename(att.filename, att.ext):
+ if (check and
+ not self.validate_attachment_filename(att.filename, att.ext)):
raise FilenameExistException(
'Attachment with filename %s.%s already exists' % (
att.filename, att.ext)
)
- # TODO: check integration
- #for rev in att.revisions.all():
- # if rev.package.id_number != self.package.id_number:
- # raise AddingAttachmentDenied(
- # ('this attachment is already assigned to other Library '
- # '- %s') % rev.package.get_unique_package_name())
-
- self.save()
+ if save:
+ self.save()
return self.attachments.add(att)
def attachment_remove(self, dep):
@@ -675,7 +779,7 @@ def attachment_remove(self, dep):
self.save()
return self.attachments.remove(dep)
- def dependency_add(self, dep):
+ def dependency_add(self, dep, save=True):
"""
copy to new revision,
add dependency (existing Library - PackageVersion)
@@ -691,13 +795,15 @@ def dependency_add(self, dep):
'A Library can not depend on itself!')
# dependency have to be unique in the PackageRevision
- if self.dependencies.filter(package__pk=dep.package.pk).count() > 0:
+ if self.dependencies.filter(
+ package__name=dep.package.name).count() > 0:
raise DependencyException(
'Your add-on already depends on "%s" by %s.' % (
dep.package.full_name,
dep.package.author.get_profile()))
- # save as new version
- self.save()
+ if save:
+ # save as new version
+ self.save()
return self.dependencies.add(dep)
def dependency_remove(self, dep):
@@ -710,7 +816,6 @@ def dependency_remove(self, dep):
'There is no such library in this %s' \
% self.package.get_type_name())
-
def dependency_remove_by_id_number(self, id_number):
" find dependency by its id_number call dependency_remove "