From 422668a67f8607b7da851fef33ff1b267bdad632 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Sat, 20 Jun 2015 21:10:10 +0530
Subject: [PATCH 01/47] Async
---
frappe/__init__.py | 2 +
frappe/api.py | 4 +-
frappe/app.py | 27 +-
frappe/async.py | 175 +
frappe/build.py | 3 +-
frappe/celery_app.py | 3 +-
frappe/core/doctype/async_task/__init__.py | 0
.../core/doctype/async_task/async_task.json | 142 +
frappe/core/doctype/async_task/async_task.py | 10 +
.../doctype/async_task/test_async_task.py | 12 +
frappe/handler.py | 16 +-
frappe/hooks.py | 1 +
frappe/installer.py | 3 +-
frappe/public/build.json | 5 +-
frappe/public/js/frappe/request.js | 13 +-
frappe/public/js/frappe/socket.js | 60 +
frappe/public/js/lib/socket.io.min.js | 7000 +++++++++++++++++
frappe/sessions.py | 2 +-
frappe/tasks.py | 47 +
frappe/tests/test_async.py | 16 +
frappe/utils/__init__.py | 1 +
requirements.txt | 1 +
socketio.js | 67 +
23 files changed, 7600 insertions(+), 10 deletions(-)
create mode 100644 frappe/async.py
create mode 100644 frappe/core/doctype/async_task/__init__.py
create mode 100644 frappe/core/doctype/async_task/async_task.json
create mode 100644 frappe/core/doctype/async_task/async_task.py
create mode 100644 frappe/core/doctype/async_task/test_async_task.py
create mode 100644 frappe/public/js/frappe/socket.js
create mode 100644 frappe/public/js/lib/socket.io.min.js
create mode 100644 frappe/tests/test_async.py
create mode 100644 socketio.js
diff --git a/frappe/__init__.py b/frappe/__init__.py
index f5b5c29b64..877e109281 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -7,6 +7,7 @@ globals attached to frappe module
from __future__ import unicode_literals
from werkzeug.local import Local, release_local
+from functools import wraps
import os, importlib, inspect, logging, json
# public
@@ -14,6 +15,7 @@ from frappe.__version__ import __version__
from .exceptions import *
from .utils.jinja import get_jenv, get_template, render_template
+
local = Local()
class _dict(dict):
diff --git a/frappe/api.py b/frappe/api.py
index 425d48725c..5ec69d5f6d 100644
--- a/frappe/api.py
+++ b/frappe/api.py
@@ -58,13 +58,13 @@ def handle():
if frappe.local.request.method=="GET":
if not doc.has_permission("read"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
- doc.run_method(method, **frappe.local.form_dict)
+ frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
if frappe.local.request.method=="POST":
if not doc.has_permission("write"):
frappe.throw(_("Not permitted"), frappe.PermissionError)
- doc.run_method(method, **frappe.local.form_dict)
+ frappe.local.response.update({"data": doc.run_method(method, **frappe.local.form_dict)})
frappe.db.commit()
else:
diff --git a/frappe/app.py b/frappe/app.py
index d670cb8e56..adaabc8da6 100644
--- a/frappe/app.py
+++ b/frappe/app.py
@@ -12,6 +12,8 @@ from werkzeug.local import LocalManager
from werkzeug.exceptions import HTTPException, NotFound
from werkzeug.contrib.profiler import ProfilerMiddleware
from werkzeug.wsgi import SharedDataMiddleware
+from werkzeug.serving import run_with_reloader
+
import mimetypes
import frappe
@@ -20,9 +22,10 @@ import frappe.auth
import frappe.api
import frappe.utils.response
import frappe.website.render
-from frappe.utils import get_site_name
+from frappe.utils import get_site_name, get_site_path
from frappe.middlewares import StaticDataMiddleware
+
local_manager = LocalManager([frappe.local])
_site = None
@@ -30,6 +33,21 @@ _sites_path = os.environ.get("SITES_PATH", ".")
logger = frappe.get_logger()
+class RequestContext(object):
+
+ def __init__(self, environ):
+ self.request = Request(environ)
+
+ def __enter__(self):
+ frappe.local.request = self.request
+ init_site(self.request)
+ make_form_dict(self.request)
+ frappe.local.http_request = frappe.auth.HTTPRequest()
+
+ def __exit__(self, type, value, traceback):
+ frappe.destroy()
+
+
@Request.application
def application(request):
frappe.local.request = request
@@ -135,6 +153,8 @@ def make_form_dict(request):
frappe.local.form_dict.pop("_")
application = local_manager.make_middleware(application)
+application.debug = True
+
def serve(port=8000, profile=False, site=None, sites_path='.'):
global application, _site, _sites_path
@@ -155,5 +175,10 @@ def serve(port=8000, profile=False, site=None, sites_path='.'):
b'/files': os.path.abspath(sites_path).encode("utf-8")
})
+ application.debug = True
+ application.config = {
+ 'SERVER_NAME': 'localhost:8000'
+ }
+
run_simple('0.0.0.0', int(port), application, use_reloader=True,
use_debugger=True, use_evalex=True, threaded=True)
diff --git a/frappe/async.py b/frappe/async.py
new file mode 100644
index 0000000000..48ebdcea76
--- /dev/null
+++ b/frappe/async.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+
+
+import frappe
+import os
+import time
+from functools import wraps
+from frappe.utils import get_site_path
+import json
+from frappe import conf
+
+END_LINE = ''
+TASK_LOG_MAX_AGE = 86400 # 1 day in seconds
+redis_server = None
+
+
+def handler(f):
+ cmd = f.__module__ + '.' + f.__name__
+
+ def _run(args, set_in_response=True):
+ from frappe.tasks import run_async_task
+ args = frappe._dict(args)
+ task = run_async_task.delay(frappe.local.site,
+ (frappe.session and frappe.session.user) or 'Administrator', cmd, args)
+ if set_in_response:
+ frappe.local.response['task_id'] = task.id
+ return task.id
+
+ @wraps(f)
+ def _f(*args, **kwargs):
+ from frappe.tasks import run_async_task
+ task = run_async_task.delay(frappe.local.site,
+ (frappe.session and frappe.session.user) or 'Administrator', cmd,
+ frappe.local.form_dict)
+ frappe.local.response['task_id'] = task.id
+ return {
+ "status": "queued",
+ "task_id": task.id
+ }
+ _f.async = True
+ _f._f = f
+ _f.run = _run
+ frappe.whitelisted.append(f)
+ frappe.whitelisted.append(_f)
+ return _f
+
+
+def run_async_task(method, args, reference_doctype=None, reference_name=None, set_in_response=True):
+ if frappe.local.request and frappe.local.request.method == "GET":
+ frappe.throw("Cannot run task in a GET request")
+ task_id = method.run(args, set_in_response=set_in_response)
+ task = frappe.new_doc("Async Task")
+ task.celery_task_id = task_id
+ task.status = "Queued"
+ task.reference_doctype = reference_doctype
+ task.reference_name = reference_name
+ task.save()
+ return task_id
+
+
+@frappe.whitelist()
+def get_pending_tasks_for_doc(doctype, docname):
+ return frappe.db.sql_list("select name from `tabAsync Task` where status in ('Queued', 'Running') and reference_doctype='%s' and reference_name='%s'" % (doctype, docname))
+
+
+@handler
+def ping():
+ from time import sleep
+ sleep(6)
+ return "pong"
+
+
+@frappe.whitelist()
+def get_task_status(task_id):
+ from frappe.celery_app import get_celery
+ c = get_celery()
+ a = c.AsyncResult(task_id)
+ frappe.local.response['response'] = a.result
+ return {
+ "state": a.state,
+ "progress": 0
+ }
+
+
+def set_task_status(task_id, status, response=None):
+ frappe.db.set_value("Async Task", task_id, "status", status)
+ if not response:
+ response = {}
+ response.update({
+ "status": status,
+ "task_id": task_id
+ })
+ emit_via_redis("task_status_change", response, room="task:" + task_id)
+
+
+def remove_old_task_logs():
+ logs_path = get_site_path('task-logs')
+
+ def full_path(_file):
+ return os.path.join(logs_path, _file)
+
+ files_to_remove = [full_path(_file) for _file in os.listdir(logs_path)]
+ files_to_remove = [_file for _file in files_to_remove if is_file_old(_file) and os.path.isfile(_file)]
+ for _file in files_to_remove:
+ os.remove(_file)
+
+
+def is_file_old(file_path):
+ return ((time.time() - os.stat(file_path).st_mtime) > TASK_LOG_MAX_AGE)
+
+
+def emit_via_redis(event, message, room=None):
+ r = get_redis_server()
+ r.publish('events', json.dumps({'event': event, 'message': message, 'room': room}))
+
+
+def put_log(task_id, line_no, line):
+ r = get_redis_server()
+ print "task_log:" + task_id
+ r.hset("task_log:" + task_id, line_no, line)
+
+
+def get_redis_server():
+ """Returns memcache connection."""
+ global redis_server
+ if not redis_server:
+ from redis import Redis
+ redis_server = Redis.from_url(conf.get("cache_redis_server") or "redis://localhost:12311")
+ return redis_server
+
+
+class FileAndRedisStream(file):
+ def __init__(self, *args, **kwargs):
+ ret = super(FileAndRedisStream, self).__init__(*args, **kwargs)
+ self.count = 0
+ return ret
+
+ def write(self, data):
+ ret = super(FileAndRedisStream, self).write(data)
+ if frappe.local.task_id:
+ emit_via_redis('task_progress', {
+ "message": {
+ "lines": {self.count: data}
+ },
+ "task_id": frappe.local.task_id
+ }, room="task_progress:" + frappe.local.task_id)
+
+ put_log(frappe.local.task_id, self.count, data)
+ self.count += 1
+ return ret
+
+
+def get_std_streams(task_id):
+ stdout = FileAndRedisStream(get_task_log_file_path(task_id, 'stdout'), 'w')
+ # stderr = FileAndRedisStream(get_task_log_file_path(task_id, 'stderr'), 'w')
+ return stdout, stdout
+
+
+def get_task_log_file_path(task_id, stream_type):
+ logs_dir = frappe.utils.get_site_path('task-logs')
+ return os.path.join(logs_dir, task_id + '.' + stream_type)
+
+
+@frappe.whitelist(allow_guest=True)
+def can_subscribe_doc(doctype, docname, sid):
+ from frappe.sessions import Session
+ from frappe.exceptions import PermissionError
+ session = Session(None).get_session_data()
+ if not frappe.has_permission(user=session.user, doctype=doctype, doc=docname, ptype='read'):
+ raise PermissionError()
+ return True
diff --git a/frappe/build.py b/frappe/build.py
index 42136f59fa..d65efc538a 100644
--- a/frappe/build.py
+++ b/frappe/build.py
@@ -127,7 +127,8 @@ def pack(target, sources, no_compress, verbose):
tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
jsm.minify(tmpin, tmpout)
minified = tmpout.getvalue()
- outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'
+ if minified:
+ outtxt += unicode(minified or '', 'utf-8').strip('\n') + ';'
if verbose:
print "{0}: {1}k".format(f, int(len(minified) / 1024))
diff --git a/frappe/celery_app.py b/frappe/celery_app.py
index 715a1b8363..06a7a41b39 100644
--- a/frappe/celery_app.py
+++ b/frappe/celery_app.py
@@ -17,7 +17,7 @@ SITES_PATH = os.environ.get('SITES_PATH', '.')
# defaults
DEFAULT_CELERY_BROKER = "redis://localhost"
-DEFAULT_CELERY_BACKEND = None
+DEFAULT_CELERY_BACKEND = "redis://localhost"
DEFAULT_SCHEDULER_INTERVAL = 300
LONGJOBS_PREFIX = "longjobs@"
@@ -41,6 +41,7 @@ def setup_celery(app, conf):
app.conf.CELERY_TASK_SERIALIZER = 'json'
app.conf.CELERY_ACCEPT_CONTENT = ['json']
app.conf.CELERY_TIMEZONE = 'UTC'
+ app.conf.CELERY_RESULT_SERIALIZER = 'json'
if conf.celery_queue_per_site:
app.conf.CELERY_ROUTES = (SiteRouter(),)
diff --git a/frappe/core/doctype/async_task/__init__.py b/frappe/core/doctype/async_task/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/core/doctype/async_task/async_task.json b/frappe/core/doctype/async_task/async_task.json
new file mode 100644
index 0000000000..7563e70316
--- /dev/null
+++ b/frappe/core/doctype/async_task/async_task.json
@@ -0,0 +1,142 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "autoname": "field:celery_task_id",
+ "creation": "2015-07-03 11:28:03.496346",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Transaction",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "fieldname": "celery_task_id",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Celery Task ID",
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Status",
+ "no_copy": 0,
+ "options": "\nQueued\nRunning\nFinished\nFailed\n",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "fieldname": "stdout",
+ "fieldtype": "Long Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "stdout",
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_on_submit": 0,
+ "fieldname": "stderr",
+ "fieldtype": "Long Text",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "stderr",
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 1,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "label": "Reference DocType",
+ "options": "DocType",
+ "permlevel": 0,
+ "precision": ""
+ },
+ {
+ "fieldname": "reference_name",
+ "fieldtype": "Dynamic Link",
+ "label": "Reference Doc",
+ "options": "reference_doctype",
+ "permlevel": 0,
+ "precision": ""
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "modified": "2015-07-04 14:33:26.791024",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Async Task",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1
+ }
+ ],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/async_task/async_task.py b/frappe/core/doctype/async_task/async_task.py
new file mode 100644
index 0000000000..85d043524b
--- /dev/null
+++ b/frappe/core/doctype/async_task/async_task.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class AsyncTask(Document):
+ pass
diff --git a/frappe/core/doctype/async_task/test_async_task.py b/frappe/core/doctype/async_task/test_async_task.py
new file mode 100644
index 0000000000..1e885f27f9
--- /dev/null
+++ b/frappe/core/doctype/async_task/test_async_task.py
@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+import frappe
+import unittest
+
+# test_records = frappe.get_test_records('Async Task')
+
+class TestAsyncTask(unittest.TestCase):
+ pass
diff --git a/frappe/handler.py b/frappe/handler.py
index 25e28a063d..05f17b623f 100755
--- a/frappe/handler.py
+++ b/frappe/handler.py
@@ -70,7 +70,7 @@ def handle():
return build_response("json")
-def execute_cmd(cmd):
+def execute_cmd(cmd, async=False):
"""execute a request as python module"""
for hook in frappe.get_hooks("override_whitelisted_methods", {}).get(cmd, []):
# override using the first hook
@@ -78,6 +78,8 @@ def execute_cmd(cmd):
break
method = get_attr(cmd)
+ if async:
+ method = method._f
# check if whitelisted
if frappe.session['user'] == 'Guest':
@@ -103,3 +105,15 @@ def get_attr(cmd):
method = globals()[cmd]
frappe.log("method:" + cmd)
return method
+
+
+@frappe.whitelist()
+def get_async_task_status(task_id):
+ from frappe.celery_app import get_celery
+ c = get_celery()
+ a = c.AsyncResult(task_id)
+ frappe.local.response['response'] = a.result
+ return {
+ "state": a.state,
+ "progress": 0
+ }
diff --git a/frappe/hooks.py b/frappe/hooks.py
index a5c27a9962..bfbefba78f 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -147,6 +147,7 @@ scheduler_events = {
"frappe.desk.doctype.event.event.send_event_digest",
"frappe.sessions.clear_expired_sessions",
"frappe.email.doctype.email_alert.email_alert.trigger_daily_alerts",
+ "frappe.async.remove_old_task_logs",
]
}
diff --git a/frappe/installer.py b/frappe/installer.py
index 4c3c10e538..4f63a1404e 100755
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -214,7 +214,8 @@ def make_site_dirs():
site_private_path = os.path.join(frappe.local.site_path, 'private')
for dir_path in (
os.path.join(site_private_path, 'backups'),
- os.path.join(site_public_path, 'files')):
+ os.path.join(site_public_path, 'files'),
+ os.path.join(site_public_path, 'task-logs')):
if not os.path.exists(dir_path):
os.makedirs(dir_path)
locks_dir = frappe.get_site_path('locks')
diff --git a/frappe/public/build.json b/frappe/public/build.json
index 7d7cb8fa7a..63ba6d41d8 100644
--- a/frappe/public/build.json
+++ b/frappe/public/build.json
@@ -15,7 +15,8 @@
"public/js/lib/moment/moment.min.js",
"public/js/lib/highlight.pack.js",
"public/js/frappe/class.js",
- "website/js/website.js"
+ "website/js/website.js",
+ "public/js/lib/socket.io.min.js"
],
"js/editor.min.js": [
"public/js/lib/jquery/jquery.hotkeys.js",
@@ -49,6 +50,7 @@
"public/js/lib/nprogress.js",
"public/js/lib/moment/moment-with-locales.min.js",
"public/js/lib/moment/moment-timezone-with-data.min.js",
+ "public/js/lib/socket.io.min.js",
"public/js/frappe/provide.js",
"public/js/frappe/class.js",
@@ -61,6 +63,7 @@
"public/js/frappe/ui/messages.js",
"public/js/frappe/request.js",
+ "public/js/frappe/socket.js",
"public/js/frappe/router.js",
"public/js/frappe/defaults.js",
"public/js/lib/microtemplate.js",
diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js
index 5e200fc442..b437662c52 100644
--- a/frappe/public/js/frappe/request.js
+++ b/frappe/public/js/frappe/request.js
@@ -28,10 +28,21 @@ frappe.call = function(opts) {
args.cmd = opts.method;
}
+ var callback = function(data, xhr) {
+ if(data.task_id) {
+ // async call, subscribe
+ frappe.socket.subscribe(data.task_id, opts);
+ }
+ else {
+ // ajax
+ return opts.callback(data, xhr);
+ }
+ }
+
return frappe.request.call({
type: opts.type || "POST",
args: args,
- success: opts.callback,
+ success: callback,
error: opts.error,
always: opts.always,
btn: opts.btn,
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
new file mode 100644
index 0000000000..c264292788
--- /dev/null
+++ b/frappe/public/js/frappe/socket.js
@@ -0,0 +1,60 @@
+frappe.socket = {
+ open_tasks: {},
+ init: function() {
+ frappe.socket.socket = io.connect('http://' + document.domain + ':' + 3000);
+ frappe.socket.socket.on('msgprint', function(message) {
+ frappe.msgprint(message)
+ });
+
+ frappe.socket.setup_listeners();
+ frappe.socket.setup_reconnect();
+ },
+ subscribe: function(task_id, opts) {
+ frappe.socket.socket.emit('task_subscribe', task_id);
+ frappe.socket.socket.emit('progress_subscribe', task_id);
+
+ frappe.socket.open_tasks[task_id] = opts;
+ },
+ setup_listeners: function() {
+ frappe.socket.socket.on('task_status_change', function(data) {
+ if(data.status==="Running") {
+ frappe.socket.process_response(data, "running");
+ } else {
+ // failed or finished
+ frappe.socket.process_response(data, "callback");
+ // delete frappe.socket.open_tasks[data.task_id];
+ }
+ });
+ frappe.socket.socket.on('task_progress', function(data) {
+ frappe.socket.process_response(data, "progress");
+ });
+
+ },
+ setup_reconnect: function() {
+ // subscribe again to open_tasks
+ frappe.socket.socket.on("connect", function() {
+ $.each(frappe.socket.open_tasks, function(task_id, opts) {
+ frappe.socket.subscribe(task_id, opts);
+ });
+ });
+ },
+ process_response: function(data, method) {
+ if(!data) {
+ return;
+ }
+ if(data) {
+ var opts = frappe.socket.open_tasks[data.task_id];
+ if(opts[method]) opts[method](data.message);
+ }
+ if(opts.always) {
+ opts.always(data.message);
+ }
+ if(data.status_code && status_code > 400 && opts.error) {
+ opts.error(data.message);
+ return;
+ }
+
+ }
+}
+
+$(frappe.socket.init);
diff --git a/frappe/public/js/lib/socket.io.min.js b/frappe/public/js/lib/socket.io.min.js
new file mode 100644
index 0000000000..443b5d51b4
--- /dev/null
+++ b/frappe/public/js/lib/socket.io.min.js
@@ -0,0 +1,7000 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.io=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && !this.encoding) {
+ var pack = this.packetBuffer.shift();
+ this.packet(pack);
+ }
+};
+
+/**
+ * Clean up transport subscriptions and packet buffer.
+ *
+ * @api private
+ */
+
+Manager.prototype.cleanup = function(){
+ var sub;
+ while (sub = this.subs.shift()) sub.destroy();
+
+ this.packetBuffer = [];
+ this.encoding = false;
+
+ this.decoder.destroy();
+};
+
+/**
+ * Close the current socket.
+ *
+ * @api private
+ */
+
+Manager.prototype.close =
+Manager.prototype.disconnect = function(){
+ this.skipReconnect = true;
+ this.backoff.reset();
+ this.readyState = 'closed';
+ this.engine && this.engine.close();
+};
+
+/**
+ * Called upon engine close.
+ *
+ * @api private
+ */
+
+Manager.prototype.onclose = function(reason){
+ debug('close');
+ this.cleanup();
+ this.backoff.reset();
+ this.readyState = 'closed';
+ this.emit('close', reason);
+ if (this._reconnection && !this.skipReconnect) {
+ this.reconnect();
+ }
+};
+
+/**
+ * Attempt a reconnection.
+ *
+ * @api private
+ */
+
+Manager.prototype.reconnect = function(){
+ if (this.reconnecting || this.skipReconnect) return this;
+
+ var self = this;
+
+ if (this.backoff.attempts >= this._reconnectionAttempts) {
+ debug('reconnect failed');
+ this.backoff.reset();
+ this.emitAll('reconnect_failed');
+ this.reconnecting = false;
+ } else {
+ var delay = this.backoff.duration();
+ debug('will wait %dms before reconnect attempt', delay);
+
+ this.reconnecting = true;
+ var timer = setTimeout(function(){
+ if (self.skipReconnect) return;
+
+ debug('attempting reconnect');
+ self.emitAll('reconnect_attempt', self.backoff.attempts);
+ self.emitAll('reconnecting', self.backoff.attempts);
+
+ // check again for the case socket closed in above events
+ if (self.skipReconnect) return;
+
+ self.open(function(err){
+ if (err) {
+ debug('reconnect attempt error');
+ self.reconnecting = false;
+ self.reconnect();
+ self.emitAll('reconnect_error', err.data);
+ } else {
+ debug('reconnect success');
+ self.onreconnect();
+ }
+ });
+ }, delay);
+
+ this.subs.push({
+ destroy: function(){
+ clearTimeout(timer);
+ }
+ });
+ }
+};
+
+/**
+ * Called upon successful reconnect.
+ *
+ * @api private
+ */
+
+Manager.prototype.onreconnect = function(){
+ var attempt = this.backoff.attempts;
+ this.reconnecting = false;
+ this.backoff.reset();
+ this.updateSocketIds();
+ this.emitAll('reconnect', attempt);
+};
+
+},{"./on":4,"./socket":5,"./url":6,"backo2":7,"component-bind":8,"component-emitter":9,"debug":10,"engine.io-client":11,"indexof":42,"object-component":43,"socket.io-parser":46}],4:[function(_dereq_,module,exports){
+
+/**
+ * Module exports.
+ */
+
+module.exports = on;
+
+/**
+ * Helper for subscriptions.
+ *
+ * @param {Object|EventEmitter} obj with `Emitter` mixin or `EventEmitter`
+ * @param {String} event name
+ * @param {Function} callback
+ * @api public
+ */
+
+function on(obj, ev, fn) {
+ obj.on(ev, fn);
+ return {
+ destroy: function(){
+ obj.removeListener(ev, fn);
+ }
+ };
+}
+
+},{}],5:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var parser = _dereq_('socket.io-parser');
+var Emitter = _dereq_('component-emitter');
+var toArray = _dereq_('to-array');
+var on = _dereq_('./on');
+var bind = _dereq_('component-bind');
+var debug = _dereq_('debug')('socket.io-client:socket');
+var hasBin = _dereq_('has-binary');
+
+/**
+ * Module exports.
+ */
+
+module.exports = exports = Socket;
+
+/**
+ * Internal events (blacklisted).
+ * These events can't be emitted by the user.
+ *
+ * @api private
+ */
+
+var events = {
+ connect: 1,
+ connect_error: 1,
+ connect_timeout: 1,
+ disconnect: 1,
+ error: 1,
+ reconnect: 1,
+ reconnect_attempt: 1,
+ reconnect_failed: 1,
+ reconnect_error: 1,
+ reconnecting: 1
+};
+
+/**
+ * Shortcut to `Emitter#emit`.
+ */
+
+var emit = Emitter.prototype.emit;
+
+/**
+ * `Socket` constructor.
+ *
+ * @api public
+ */
+
+function Socket(io, nsp){
+ this.io = io;
+ this.nsp = nsp;
+ this.json = this; // compat
+ this.ids = 0;
+ this.acks = {};
+ if (this.io.autoConnect) this.open();
+ this.receiveBuffer = [];
+ this.sendBuffer = [];
+ this.connected = false;
+ this.disconnected = true;
+}
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Socket.prototype);
+
+/**
+ * Subscribe to open, close and packet events
+ *
+ * @api private
+ */
+
+Socket.prototype.subEvents = function() {
+ if (this.subs) return;
+
+ var io = this.io;
+ this.subs = [
+ on(io, 'open', bind(this, 'onopen')),
+ on(io, 'packet', bind(this, 'onpacket')),
+ on(io, 'close', bind(this, 'onclose'))
+ ];
+};
+
+/**
+ * "Opens" the socket.
+ *
+ * @api public
+ */
+
+Socket.prototype.open =
+Socket.prototype.connect = function(){
+ if (this.connected) return this;
+
+ this.subEvents();
+ this.io.open(); // ensure open
+ if ('open' == this.io.readyState) this.onopen();
+ return this;
+};
+
+/**
+ * Sends a `message` event.
+ *
+ * @return {Socket} self
+ * @api public
+ */
+
+Socket.prototype.send = function(){
+ var args = toArray(arguments);
+ args.unshift('message');
+ this.emit.apply(this, args);
+ return this;
+};
+
+/**
+ * Override `emit`.
+ * If the event is in `events`, it's emitted normally.
+ *
+ * @param {String} event name
+ * @return {Socket} self
+ * @api public
+ */
+
+Socket.prototype.emit = function(ev){
+ if (events.hasOwnProperty(ev)) {
+ emit.apply(this, arguments);
+ return this;
+ }
+
+ var args = toArray(arguments);
+ var parserType = parser.EVENT; // default
+ if (hasBin(args)) { parserType = parser.BINARY_EVENT; } // binary
+ var packet = { type: parserType, data: args };
+
+ // event ack callback
+ if ('function' == typeof args[args.length - 1]) {
+ debug('emitting packet with ack id %d', this.ids);
+ this.acks[this.ids] = args.pop();
+ packet.id = this.ids++;
+ }
+
+ if (this.connected) {
+ this.packet(packet);
+ } else {
+ this.sendBuffer.push(packet);
+ }
+
+ return this;
+};
+
+/**
+ * Sends a packet.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Socket.prototype.packet = function(packet){
+ packet.nsp = this.nsp;
+ this.io.packet(packet);
+};
+
+/**
+ * Called upon engine `open`.
+ *
+ * @api private
+ */
+
+Socket.prototype.onopen = function(){
+ debug('transport is open - connecting');
+
+ // write connect packet if necessary
+ if ('/' != this.nsp) {
+ this.packet({ type: parser.CONNECT });
+ }
+};
+
+/**
+ * Called upon engine `close`.
+ *
+ * @param {String} reason
+ * @api private
+ */
+
+Socket.prototype.onclose = function(reason){
+ debug('close (%s)', reason);
+ this.connected = false;
+ this.disconnected = true;
+ delete this.id;
+ this.emit('disconnect', reason);
+};
+
+/**
+ * Called with socket packet.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Socket.prototype.onpacket = function(packet){
+ if (packet.nsp != this.nsp) return;
+
+ switch (packet.type) {
+ case parser.CONNECT:
+ this.onconnect();
+ break;
+
+ case parser.EVENT:
+ this.onevent(packet);
+ break;
+
+ case parser.BINARY_EVENT:
+ this.onevent(packet);
+ break;
+
+ case parser.ACK:
+ this.onack(packet);
+ break;
+
+ case parser.BINARY_ACK:
+ this.onack(packet);
+ break;
+
+ case parser.DISCONNECT:
+ this.ondisconnect();
+ break;
+
+ case parser.ERROR:
+ this.emit('error', packet.data);
+ break;
+ }
+};
+
+/**
+ * Called upon a server event.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Socket.prototype.onevent = function(packet){
+ var args = packet.data || [];
+ debug('emitting event %j', args);
+
+ if (null != packet.id) {
+ debug('attaching ack callback to event');
+ args.push(this.ack(packet.id));
+ }
+
+ if (this.connected) {
+ emit.apply(this, args);
+ } else {
+ this.receiveBuffer.push(args);
+ }
+};
+
+/**
+ * Produces an ack callback to emit with an event.
+ *
+ * @api private
+ */
+
+Socket.prototype.ack = function(id){
+ var self = this;
+ var sent = false;
+ return function(){
+ // prevent double callbacks
+ if (sent) return;
+ sent = true;
+ var args = toArray(arguments);
+ debug('sending ack %j', args);
+
+ var type = hasBin(args) ? parser.BINARY_ACK : parser.ACK;
+ self.packet({
+ type: type,
+ id: id,
+ data: args
+ });
+ };
+};
+
+/**
+ * Called upon a server acknowlegement.
+ *
+ * @param {Object} packet
+ * @api private
+ */
+
+Socket.prototype.onack = function(packet){
+ debug('calling ack %s with %j', packet.id, packet.data);
+ var fn = this.acks[packet.id];
+ fn.apply(this, packet.data);
+ delete this.acks[packet.id];
+};
+
+/**
+ * Called upon server connect.
+ *
+ * @api private
+ */
+
+Socket.prototype.onconnect = function(){
+ this.connected = true;
+ this.disconnected = false;
+ this.emit('connect');
+ this.emitBuffered();
+};
+
+/**
+ * Emit buffered events (received and emitted).
+ *
+ * @api private
+ */
+
+Socket.prototype.emitBuffered = function(){
+ var i;
+ for (i = 0; i < this.receiveBuffer.length; i++) {
+ emit.apply(this, this.receiveBuffer[i]);
+ }
+ this.receiveBuffer = [];
+
+ for (i = 0; i < this.sendBuffer.length; i++) {
+ this.packet(this.sendBuffer[i]);
+ }
+ this.sendBuffer = [];
+};
+
+/**
+ * Called upon server disconnect.
+ *
+ * @api private
+ */
+
+Socket.prototype.ondisconnect = function(){
+ debug('server disconnect (%s)', this.nsp);
+ this.destroy();
+ this.onclose('io server disconnect');
+};
+
+/**
+ * Called upon forced client/server side disconnections,
+ * this method ensures the manager stops tracking us and
+ * that reconnections don't get triggered for this.
+ *
+ * @api private.
+ */
+
+Socket.prototype.destroy = function(){
+ if (this.subs) {
+ // clean subscriptions to avoid reconnections
+ for (var i = 0; i < this.subs.length; i++) {
+ this.subs[i].destroy();
+ }
+ this.subs = null;
+ }
+
+ this.io.destroy(this);
+};
+
+/**
+ * Disconnects the socket manually.
+ *
+ * @return {Socket} self
+ * @api public
+ */
+
+Socket.prototype.close =
+Socket.prototype.disconnect = function(){
+ if (this.connected) {
+ debug('performing disconnect (%s)', this.nsp);
+ this.packet({ type: parser.DISCONNECT });
+ }
+
+ // remove socket from pool
+ this.destroy();
+
+ if (this.connected) {
+ // fire events
+ this.onclose('io client disconnect');
+ }
+ return this;
+};
+
+},{"./on":4,"component-bind":8,"component-emitter":9,"debug":10,"has-binary":38,"socket.io-parser":46,"to-array":50}],6:[function(_dereq_,module,exports){
+(function (global){
+
+/**
+ * Module dependencies.
+ */
+
+var parseuri = _dereq_('parseuri');
+var debug = _dereq_('debug')('socket.io-client:url');
+
+/**
+ * Module exports.
+ */
+
+module.exports = url;
+
+/**
+ * URL parser.
+ *
+ * @param {String} url
+ * @param {Object} An object meant to mimic window.location.
+ * Defaults to window.location.
+ * @api public
+ */
+
+function url(uri, loc){
+ var obj = uri;
+
+ // default to window.location
+ var loc = loc || global.location;
+ if (null == uri) uri = loc.protocol + '//' + loc.host;
+
+ // relative path support
+ if ('string' == typeof uri) {
+ if ('/' == uri.charAt(0)) {
+ if ('/' == uri.charAt(1)) {
+ uri = loc.protocol + uri;
+ } else {
+ uri = loc.hostname + uri;
+ }
+ }
+
+ if (!/^(https?|wss?):\/\//.test(uri)) {
+ debug('protocol-less url %s', uri);
+ if ('undefined' != typeof loc) {
+ uri = loc.protocol + '//' + uri;
+ } else {
+ uri = 'https://' + uri;
+ }
+ }
+
+ // parse
+ debug('parse %s', uri);
+ obj = parseuri(uri);
+ }
+
+ // make sure we treat `localhost:80` and `localhost` equally
+ if (!obj.port) {
+ if (/^(http|ws)$/.test(obj.protocol)) {
+ obj.port = '80';
+ }
+ else if (/^(http|ws)s$/.test(obj.protocol)) {
+ obj.port = '443';
+ }
+ }
+
+ obj.path = obj.path || '/';
+
+ // define unique id
+ obj.id = obj.protocol + '://' + obj.host + ':' + obj.port;
+ // define href
+ obj.href = obj.protocol + '://' + obj.host + (loc && loc.port == obj.port ? '' : (':' + obj.port));
+
+ return obj;
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"debug":10,"parseuri":44}],7:[function(_dereq_,module,exports){
+
+/**
+ * Expose `Backoff`.
+ */
+
+module.exports = Backoff;
+
+/**
+ * Initialize backoff timer with `opts`.
+ *
+ * - `min` initial timeout in milliseconds [100]
+ * - `max` max timeout [10000]
+ * - `jitter` [0]
+ * - `factor` [2]
+ *
+ * @param {Object} opts
+ * @api public
+ */
+
+function Backoff(opts) {
+ opts = opts || {};
+ this.ms = opts.min || 100;
+ this.max = opts.max || 10000;
+ this.factor = opts.factor || 2;
+ this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0;
+ this.attempts = 0;
+}
+
+/**
+ * Return the backoff duration.
+ *
+ * @return {Number}
+ * @api public
+ */
+
+Backoff.prototype.duration = function(){
+ var ms = this.ms * Math.pow(this.factor, this.attempts++);
+ if (this.jitter) {
+ var rand = Math.random();
+ var deviation = Math.floor(rand * this.jitter * ms);
+ ms = (Math.floor(rand * 10) & 1) == 0 ? ms - deviation : ms + deviation;
+ }
+ return Math.min(ms, this.max) | 0;
+};
+
+/**
+ * Reset the number of attempts.
+ *
+ * @api public
+ */
+
+Backoff.prototype.reset = function(){
+ this.attempts = 0;
+};
+
+/**
+ * Set the minimum duration
+ *
+ * @api public
+ */
+
+Backoff.prototype.setMin = function(min){
+ this.ms = min;
+};
+
+/**
+ * Set the maximum duration
+ *
+ * @api public
+ */
+
+Backoff.prototype.setMax = function(max){
+ this.max = max;
+};
+
+/**
+ * Set the jitter
+ *
+ * @api public
+ */
+
+Backoff.prototype.setJitter = function(jitter){
+ this.jitter = jitter;
+};
+
+
+},{}],8:[function(_dereq_,module,exports){
+/**
+ * Slice reference.
+ */
+
+var slice = [].slice;
+
+/**
+ * Bind `obj` to `fn`.
+ *
+ * @param {Object} obj
+ * @param {Function|String} fn or string
+ * @return {Function}
+ * @api public
+ */
+
+module.exports = function(obj, fn){
+ if ('string' == typeof fn) fn = obj[fn];
+ if ('function' != typeof fn) throw new Error('bind() requires a function');
+ var args = slice.call(arguments, 2);
+ return function(){
+ return fn.apply(obj, args.concat(slice.call(arguments)));
+ }
+};
+
+},{}],9:[function(_dereq_,module,exports){
+
+/**
+ * Expose `Emitter`.
+ */
+
+module.exports = Emitter;
+
+/**
+ * Initialize a new `Emitter`.
+ *
+ * @api public
+ */
+
+function Emitter(obj) {
+ if (obj) return mixin(obj);
+};
+
+/**
+ * Mixin the emitter properties.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function mixin(obj) {
+ for (var key in Emitter.prototype) {
+ obj[key] = Emitter.prototype[key];
+ }
+ return obj;
+}
+
+/**
+ * Listen on the given `event` with `fn`.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.on =
+Emitter.prototype.addEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+ (this._callbacks[event] = this._callbacks[event] || [])
+ .push(fn);
+ return this;
+};
+
+/**
+ * Adds an `event` listener that will be invoked a single
+ * time then automatically removed.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.once = function(event, fn){
+ var self = this;
+ this._callbacks = this._callbacks || {};
+
+ function on() {
+ self.off(event, on);
+ fn.apply(this, arguments);
+ }
+
+ on.fn = fn;
+ this.on(event, on);
+ return this;
+};
+
+/**
+ * Remove the given callback for `event` or all
+ * registered callbacks.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.off =
+Emitter.prototype.removeListener =
+Emitter.prototype.removeAllListeners =
+Emitter.prototype.removeEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+
+ // all
+ if (0 == arguments.length) {
+ this._callbacks = {};
+ return this;
+ }
+
+ // specific event
+ var callbacks = this._callbacks[event];
+ if (!callbacks) return this;
+
+ // remove all handlers
+ if (1 == arguments.length) {
+ delete this._callbacks[event];
+ return this;
+ }
+
+ // remove specific handler
+ var cb;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ if (cb === fn || cb.fn === fn) {
+ callbacks.splice(i, 1);
+ break;
+ }
+ }
+ return this;
+};
+
+/**
+ * Emit `event` with the given args.
+ *
+ * @param {String} event
+ * @param {Mixed} ...
+ * @return {Emitter}
+ */
+
+Emitter.prototype.emit = function(event){
+ this._callbacks = this._callbacks || {};
+ var args = [].slice.call(arguments, 1)
+ , callbacks = this._callbacks[event];
+
+ if (callbacks) {
+ callbacks = callbacks.slice(0);
+ for (var i = 0, len = callbacks.length; i < len; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return array of callbacks for `event`.
+ *
+ * @param {String} event
+ * @return {Array}
+ * @api public
+ */
+
+Emitter.prototype.listeners = function(event){
+ this._callbacks = this._callbacks || {};
+ return this._callbacks[event] || [];
+};
+
+/**
+ * Check if this emitter has `event` handlers.
+ *
+ * @param {String} event
+ * @return {Boolean}
+ * @api public
+ */
+
+Emitter.prototype.hasListeners = function(event){
+ return !! this.listeners(event).length;
+};
+
+},{}],10:[function(_dereq_,module,exports){
+
+/**
+ * Expose `debug()` as the module.
+ */
+
+module.exports = debug;
+
+/**
+ * Create a debugger with the given `name`.
+ *
+ * @param {String} name
+ * @return {Type}
+ * @api public
+ */
+
+function debug(name) {
+ if (!debug.enabled(name)) return function(){};
+
+ return function(fmt){
+ fmt = coerce(fmt);
+
+ var curr = new Date;
+ var ms = curr - (debug[name] || curr);
+ debug[name] = curr;
+
+ fmt = name
+ + ' '
+ + fmt
+ + ' +' + debug.humanize(ms);
+
+ // This hackery is required for IE8
+ // where `console.log` doesn't have 'apply'
+ window.console
+ && console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+ }
+}
+
+/**
+ * The currently active debug mode names.
+ */
+
+debug.names = [];
+debug.skips = [];
+
+/**
+ * Enables a debug mode by name. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} name
+ * @api public
+ */
+
+debug.enable = function(name) {
+ try {
+ localStorage.debug = name;
+ } catch(e){}
+
+ var split = (name || '').split(/[\s,]+/)
+ , len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ name = split[i].replace('*', '.*?');
+ if (name[0] === '-') {
+ debug.skips.push(new RegExp('^' + name.substr(1) + '$'));
+ }
+ else {
+ debug.names.push(new RegExp('^' + name + '$'));
+ }
+ }
+};
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+debug.disable = function(){
+ debug.enable('');
+};
+
+/**
+ * Humanize the given `ms`.
+ *
+ * @param {Number} m
+ * @return {String}
+ * @api private
+ */
+
+debug.humanize = function(ms) {
+ var sec = 1000
+ , min = 60 * 1000
+ , hour = 60 * min;
+
+ if (ms >= hour) return (ms / hour).toFixed(1) + 'h';
+ if (ms >= min) return (ms / min).toFixed(1) + 'm';
+ if (ms >= sec) return (ms / sec | 0) + 's';
+ return ms + 'ms';
+};
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+debug.enabled = function(name) {
+ for (var i = 0, len = debug.skips.length; i < len; i++) {
+ if (debug.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (var i = 0, len = debug.names.length; i < len; i++) {
+ if (debug.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+};
+
+/**
+ * Coerce `val`.
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+// persist
+
+try {
+ if (window.localStorage) debug.enable(localStorage.debug);
+} catch(e){}
+
+},{}],11:[function(_dereq_,module,exports){
+
+module.exports = _dereq_('./lib/');
+
+},{"./lib/":12}],12:[function(_dereq_,module,exports){
+
+module.exports = _dereq_('./socket');
+
+/**
+ * Exports parser
+ *
+ * @api public
+ *
+ */
+module.exports.parser = _dereq_('engine.io-parser');
+
+},{"./socket":13,"engine.io-parser":25}],13:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies.
+ */
+
+var transports = _dereq_('./transports');
+var Emitter = _dereq_('component-emitter');
+var debug = _dereq_('debug')('engine.io-client:socket');
+var index = _dereq_('indexof');
+var parser = _dereq_('engine.io-parser');
+var parseuri = _dereq_('parseuri');
+var parsejson = _dereq_('parsejson');
+var parseqs = _dereq_('parseqs');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Socket;
+
+/**
+ * Noop function.
+ *
+ * @api private
+ */
+
+function noop(){}
+
+/**
+ * Socket constructor.
+ *
+ * @param {String|Object} uri or options
+ * @param {Object} options
+ * @api public
+ */
+
+function Socket(uri, opts){
+ if (!(this instanceof Socket)) return new Socket(uri, opts);
+
+ opts = opts || {};
+
+ if (uri && 'object' == typeof uri) {
+ opts = uri;
+ uri = null;
+ }
+
+ if (uri) {
+ uri = parseuri(uri);
+ opts.host = uri.host;
+ opts.secure = uri.protocol == 'https' || uri.protocol == 'wss';
+ opts.port = uri.port;
+ if (uri.query) opts.query = uri.query;
+ }
+
+ this.secure = null != opts.secure ? opts.secure :
+ (global.location && 'https:' == location.protocol);
+
+ if (opts.host) {
+ var pieces = opts.host.split(':');
+ opts.hostname = pieces.shift();
+ if (pieces.length) {
+ opts.port = pieces.pop();
+ } else if (!opts.port) {
+ // if no port is specified manually, use the protocol default
+ opts.port = this.secure ? '443' : '80';
+ }
+ }
+
+ this.agent = opts.agent || false;
+ this.hostname = opts.hostname ||
+ (global.location ? location.hostname : 'localhost');
+ this.port = opts.port || (global.location && location.port ?
+ location.port :
+ (this.secure ? 443 : 80));
+ this.query = opts.query || {};
+ if ('string' == typeof this.query) this.query = parseqs.decode(this.query);
+ this.upgrade = false !== opts.upgrade;
+ this.path = (opts.path || '/engine.io').replace(/\/$/, '') + '/';
+ this.forceJSONP = !!opts.forceJSONP;
+ this.jsonp = false !== opts.jsonp;
+ this.forceBase64 = !!opts.forceBase64;
+ this.enablesXDR = !!opts.enablesXDR;
+ this.timestampParam = opts.timestampParam || 't';
+ this.timestampRequests = opts.timestampRequests;
+ this.transports = opts.transports || ['polling', 'websocket'];
+ this.readyState = '';
+ this.writeBuffer = [];
+ this.callbackBuffer = [];
+ this.policyPort = opts.policyPort || 843;
+ this.rememberUpgrade = opts.rememberUpgrade || false;
+ this.binaryType = null;
+ this.onlyBinaryUpgrades = opts.onlyBinaryUpgrades;
+
+ // SSL options for Node.js client
+ this.pfx = opts.pfx || null;
+ this.key = opts.key || null;
+ this.passphrase = opts.passphrase || null;
+ this.cert = opts.cert || null;
+ this.ca = opts.ca || null;
+ this.ciphers = opts.ciphers || null;
+ this.rejectUnauthorized = opts.rejectUnauthorized || null;
+
+ this.open();
+}
+
+Socket.priorWebsocketSuccess = false;
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Socket.prototype);
+
+/**
+ * Protocol version.
+ *
+ * @api public
+ */
+
+Socket.protocol = parser.protocol; // this is an int
+
+/**
+ * Expose deps for legacy compatibility
+ * and standalone browser access.
+ */
+
+Socket.Socket = Socket;
+Socket.Transport = _dereq_('./transport');
+Socket.transports = _dereq_('./transports');
+Socket.parser = _dereq_('engine.io-parser');
+
+/**
+ * Creates transport of the given type.
+ *
+ * @param {String} transport name
+ * @return {Transport}
+ * @api private
+ */
+
+Socket.prototype.createTransport = function (name) {
+ debug('creating transport "%s"', name);
+ var query = clone(this.query);
+
+ // append engine.io protocol identifier
+ query.EIO = parser.protocol;
+
+ // transport name
+ query.transport = name;
+
+ // session id if we already have one
+ if (this.id) query.sid = this.id;
+
+ var transport = new transports[name]({
+ agent: this.agent,
+ hostname: this.hostname,
+ port: this.port,
+ secure: this.secure,
+ path: this.path,
+ query: query,
+ forceJSONP: this.forceJSONP,
+ jsonp: this.jsonp,
+ forceBase64: this.forceBase64,
+ enablesXDR: this.enablesXDR,
+ timestampRequests: this.timestampRequests,
+ timestampParam: this.timestampParam,
+ policyPort: this.policyPort,
+ socket: this,
+ pfx: this.pfx,
+ key: this.key,
+ passphrase: this.passphrase,
+ cert: this.cert,
+ ca: this.ca,
+ ciphers: this.ciphers,
+ rejectUnauthorized: this.rejectUnauthorized
+ });
+
+ return transport;
+};
+
+function clone (obj) {
+ var o = {};
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ o[i] = obj[i];
+ }
+ }
+ return o;
+}
+
+/**
+ * Initializes transport to use and starts probe.
+ *
+ * @api private
+ */
+Socket.prototype.open = function () {
+ var transport;
+ if (this.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf('websocket') != -1) {
+ transport = 'websocket';
+ } else if (0 == this.transports.length) {
+ // Emit error on next tick so it can be listened to
+ var self = this;
+ setTimeout(function() {
+ self.emit('error', 'No transports available');
+ }, 0);
+ return;
+ } else {
+ transport = this.transports[0];
+ }
+ this.readyState = 'opening';
+
+ // Retry with the next transport if the transport is disabled (jsonp: false)
+ var transport;
+ try {
+ transport = this.createTransport(transport);
+ } catch (e) {
+ this.transports.shift();
+ this.open();
+ return;
+ }
+
+ transport.open();
+ this.setTransport(transport);
+};
+
+/**
+ * Sets the current transport. Disables the existing one (if any).
+ *
+ * @api private
+ */
+
+Socket.prototype.setTransport = function(transport){
+ debug('setting transport %s', transport.name);
+ var self = this;
+
+ if (this.transport) {
+ debug('clearing existing transport %s', this.transport.name);
+ this.transport.removeAllListeners();
+ }
+
+ // set up transport
+ this.transport = transport;
+
+ // set up transport listeners
+ transport
+ .on('drain', function(){
+ self.onDrain();
+ })
+ .on('packet', function(packet){
+ self.onPacket(packet);
+ })
+ .on('error', function(e){
+ self.onError(e);
+ })
+ .on('close', function(){
+ self.onClose('transport close');
+ });
+};
+
+/**
+ * Probes a transport.
+ *
+ * @param {String} transport name
+ * @api private
+ */
+
+Socket.prototype.probe = function (name) {
+ debug('probing transport "%s"', name);
+ var transport = this.createTransport(name, { probe: 1 })
+ , failed = false
+ , self = this;
+
+ Socket.priorWebsocketSuccess = false;
+
+ function onTransportOpen(){
+ if (self.onlyBinaryUpgrades) {
+ var upgradeLosesBinary = !this.supportsBinary && self.transport.supportsBinary;
+ failed = failed || upgradeLosesBinary;
+ }
+ if (failed) return;
+
+ debug('probe transport "%s" opened', name);
+ transport.send([{ type: 'ping', data: 'probe' }]);
+ transport.once('packet', function (msg) {
+ if (failed) return;
+ if ('pong' == msg.type && 'probe' == msg.data) {
+ debug('probe transport "%s" pong', name);
+ self.upgrading = true;
+ self.emit('upgrading', transport);
+ if (!transport) return;
+ Socket.priorWebsocketSuccess = 'websocket' == transport.name;
+
+ debug('pausing current transport "%s"', self.transport.name);
+ self.transport.pause(function () {
+ if (failed) return;
+ if ('closed' == self.readyState) return;
+ debug('changing transport and sending upgrade packet');
+
+ cleanup();
+
+ self.setTransport(transport);
+ transport.send([{ type: 'upgrade' }]);
+ self.emit('upgrade', transport);
+ transport = null;
+ self.upgrading = false;
+ self.flush();
+ });
+ } else {
+ debug('probe transport "%s" failed', name);
+ var err = new Error('probe error');
+ err.transport = transport.name;
+ self.emit('upgradeError', err);
+ }
+ });
+ }
+
+ function freezeTransport() {
+ if (failed) return;
+
+ // Any callback called by transport should be ignored since now
+ failed = true;
+
+ cleanup();
+
+ transport.close();
+ transport = null;
+ }
+
+ //Handle any error that happens while probing
+ function onerror(err) {
+ var error = new Error('probe error: ' + err);
+ error.transport = transport.name;
+
+ freezeTransport();
+
+ debug('probe transport "%s" failed because of error: %s', name, err);
+
+ self.emit('upgradeError', error);
+ }
+
+ function onTransportClose(){
+ onerror("transport closed");
+ }
+
+ //When the socket is closed while we're probing
+ function onclose(){
+ onerror("socket closed");
+ }
+
+ //When the socket is upgraded while we're probing
+ function onupgrade(to){
+ if (transport && to.name != transport.name) {
+ debug('"%s" works - aborting "%s"', to.name, transport.name);
+ freezeTransport();
+ }
+ }
+
+ //Remove all listeners on the transport and on self
+ function cleanup(){
+ transport.removeListener('open', onTransportOpen);
+ transport.removeListener('error', onerror);
+ transport.removeListener('close', onTransportClose);
+ self.removeListener('close', onclose);
+ self.removeListener('upgrading', onupgrade);
+ }
+
+ transport.once('open', onTransportOpen);
+ transport.once('error', onerror);
+ transport.once('close', onTransportClose);
+
+ this.once('close', onclose);
+ this.once('upgrading', onupgrade);
+
+ transport.open();
+
+};
+
+/**
+ * Called when connection is deemed open.
+ *
+ * @api public
+ */
+
+Socket.prototype.onOpen = function () {
+ debug('socket open');
+ this.readyState = 'open';
+ Socket.priorWebsocketSuccess = 'websocket' == this.transport.name;
+ this.emit('open');
+ this.flush();
+
+ // we check for `readyState` in case an `open`
+ // listener already closed the socket
+ if ('open' == this.readyState && this.upgrade && this.transport.pause) {
+ debug('starting upgrade probes');
+ for (var i = 0, l = this.upgrades.length; i < l; i++) {
+ this.probe(this.upgrades[i]);
+ }
+ }
+};
+
+/**
+ * Handles a packet.
+ *
+ * @api private
+ */
+
+Socket.prototype.onPacket = function (packet) {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ debug('socket receive: type "%s", data "%s"', packet.type, packet.data);
+
+ this.emit('packet', packet);
+
+ // Socket is live - any packet counts
+ this.emit('heartbeat');
+
+ switch (packet.type) {
+ case 'open':
+ this.onHandshake(parsejson(packet.data));
+ break;
+
+ case 'pong':
+ this.setPing();
+ break;
+
+ case 'error':
+ var err = new Error('server error');
+ err.code = packet.data;
+ this.emit('error', err);
+ break;
+
+ case 'message':
+ this.emit('data', packet.data);
+ this.emit('message', packet.data);
+ break;
+ }
+ } else {
+ debug('packet received with socket readyState "%s"', this.readyState);
+ }
+};
+
+/**
+ * Called upon handshake completion.
+ *
+ * @param {Object} handshake obj
+ * @api private
+ */
+
+Socket.prototype.onHandshake = function (data) {
+ this.emit('handshake', data);
+ this.id = data.sid;
+ this.transport.query.sid = data.sid;
+ this.upgrades = this.filterUpgrades(data.upgrades);
+ this.pingInterval = data.pingInterval;
+ this.pingTimeout = data.pingTimeout;
+ this.onOpen();
+ // In case open handler closes socket
+ if ('closed' == this.readyState) return;
+ this.setPing();
+
+ // Prolong liveness of socket on heartbeat
+ this.removeListener('heartbeat', this.onHeartbeat);
+ this.on('heartbeat', this.onHeartbeat);
+};
+
+/**
+ * Resets ping timeout.
+ *
+ * @api private
+ */
+
+Socket.prototype.onHeartbeat = function (timeout) {
+ clearTimeout(this.pingTimeoutTimer);
+ var self = this;
+ self.pingTimeoutTimer = setTimeout(function () {
+ if ('closed' == self.readyState) return;
+ self.onClose('ping timeout');
+ }, timeout || (self.pingInterval + self.pingTimeout));
+};
+
+/**
+ * Pings server every `this.pingInterval` and expects response
+ * within `this.pingTimeout` or closes connection.
+ *
+ * @api private
+ */
+
+Socket.prototype.setPing = function () {
+ var self = this;
+ clearTimeout(self.pingIntervalTimer);
+ self.pingIntervalTimer = setTimeout(function () {
+ debug('writing ping packet - expecting pong within %sms', self.pingTimeout);
+ self.ping();
+ self.onHeartbeat(self.pingTimeout);
+ }, self.pingInterval);
+};
+
+/**
+* Sends a ping packet.
+*
+* @api public
+*/
+
+Socket.prototype.ping = function () {
+ this.sendPacket('ping');
+};
+
+/**
+ * Called on `drain` event
+ *
+ * @api private
+ */
+
+Socket.prototype.onDrain = function() {
+ for (var i = 0; i < this.prevBufferLen; i++) {
+ if (this.callbackBuffer[i]) {
+ this.callbackBuffer[i]();
+ }
+ }
+
+ this.writeBuffer.splice(0, this.prevBufferLen);
+ this.callbackBuffer.splice(0, this.prevBufferLen);
+
+ // setting prevBufferLen = 0 is very important
+ // for example, when upgrading, upgrade packet is sent over,
+ // and a nonzero prevBufferLen could cause problems on `drain`
+ this.prevBufferLen = 0;
+
+ if (this.writeBuffer.length == 0) {
+ this.emit('drain');
+ } else {
+ this.flush();
+ }
+};
+
+/**
+ * Flush write buffers.
+ *
+ * @api private
+ */
+
+Socket.prototype.flush = function () {
+ if ('closed' != this.readyState && this.transport.writable &&
+ !this.upgrading && this.writeBuffer.length) {
+ debug('flushing %d packets in socket', this.writeBuffer.length);
+ this.transport.send(this.writeBuffer);
+ // keep track of current length of writeBuffer
+ // splice writeBuffer and callbackBuffer on `drain`
+ this.prevBufferLen = this.writeBuffer.length;
+ this.emit('flush');
+ }
+};
+
+/**
+ * Sends a message.
+ *
+ * @param {String} message.
+ * @param {Function} callback function.
+ * @return {Socket} for chaining.
+ * @api public
+ */
+
+Socket.prototype.write =
+Socket.prototype.send = function (msg, fn) {
+ this.sendPacket('message', msg, fn);
+ return this;
+};
+
+/**
+ * Sends a packet.
+ *
+ * @param {String} packet type.
+ * @param {String} data.
+ * @param {Function} callback function.
+ * @api private
+ */
+
+Socket.prototype.sendPacket = function (type, data, fn) {
+ if ('closing' == this.readyState || 'closed' == this.readyState) {
+ return;
+ }
+
+ var packet = { type: type, data: data };
+ this.emit('packetCreate', packet);
+ this.writeBuffer.push(packet);
+ this.callbackBuffer.push(fn);
+ this.flush();
+};
+
+/**
+ * Closes the connection.
+ *
+ * @api private
+ */
+
+Socket.prototype.close = function () {
+ if ('opening' == this.readyState || 'open' == this.readyState) {
+ this.readyState = 'closing';
+
+ var self = this;
+
+ function close() {
+ self.onClose('forced close');
+ debug('socket closing - telling transport to close');
+ self.transport.close();
+ }
+
+ function cleanupAndClose() {
+ self.removeListener('upgrade', cleanupAndClose);
+ self.removeListener('upgradeError', cleanupAndClose);
+ close();
+ }
+
+ function waitForUpgrade() {
+ // wait for upgrade to finish since we can't send packets while pausing a transport
+ self.once('upgrade', cleanupAndClose);
+ self.once('upgradeError', cleanupAndClose);
+ }
+
+ if (this.writeBuffer.length) {
+ this.once('drain', function() {
+ if (this.upgrading) {
+ waitForUpgrade();
+ } else {
+ close();
+ }
+ });
+ } else if (this.upgrading) {
+ waitForUpgrade();
+ } else {
+ close();
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Called upon transport error
+ *
+ * @api private
+ */
+
+Socket.prototype.onError = function (err) {
+ debug('socket error %j', err);
+ Socket.priorWebsocketSuccess = false;
+ this.emit('error', err);
+ this.onClose('transport error', err);
+};
+
+/**
+ * Called upon transport close.
+ *
+ * @api private
+ */
+
+Socket.prototype.onClose = function (reason, desc) {
+ if ('opening' == this.readyState || 'open' == this.readyState || 'closing' == this.readyState) {
+ debug('socket close with reason: "%s"', reason);
+ var self = this;
+
+ // clear timers
+ clearTimeout(this.pingIntervalTimer);
+ clearTimeout(this.pingTimeoutTimer);
+
+ // clean buffers in next tick, so developers can still
+ // grab the buffers on `close` event
+ setTimeout(function() {
+ self.writeBuffer = [];
+ self.callbackBuffer = [];
+ self.prevBufferLen = 0;
+ }, 0);
+
+ // stop event from firing again for transport
+ this.transport.removeAllListeners('close');
+
+ // ensure transport won't stay open
+ this.transport.close();
+
+ // ignore further transport communication
+ this.transport.removeAllListeners();
+
+ // set ready state
+ this.readyState = 'closed';
+
+ // clear session id
+ this.id = null;
+
+ // emit close event
+ this.emit('close', reason, desc);
+ }
+};
+
+/**
+ * Filters upgrades, returning only those matching client transports.
+ *
+ * @param {Array} server upgrades
+ * @api private
+ *
+ */
+
+Socket.prototype.filterUpgrades = function (upgrades) {
+ var filteredUpgrades = [];
+ for (var i = 0, j = upgrades.length; i';
+ iframe = document.createElement(html);
+ } catch (e) {
+ iframe = document.createElement('iframe');
+ iframe.name = self.iframeId;
+ iframe.src = 'javascript:0';
+ }
+
+ iframe.id = self.iframeId;
+
+ self.form.appendChild(iframe);
+ self.iframe = iframe;
+ }
+
+ initIframe();
+
+ // escape \n to prevent it from being converted into \r\n by some UAs
+ // double escaping is required for escaped new lines because unescaping of new lines can be done safely on server-side
+ data = data.replace(rEscapedNewline, '\\\n');
+ this.area.value = data.replace(rNewline, '\\n');
+
+ try {
+ this.form.submit();
+ } catch(e) {}
+
+ if (this.iframe.attachEvent) {
+ this.iframe.onreadystatechange = function(){
+ if (self.iframe.readyState == 'complete') {
+ complete();
+ }
+ };
+ } else {
+ this.iframe.onload = complete;
+ }
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":18,"component-inherit":21}],17:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module requirements.
+ */
+
+var XMLHttpRequest = _dereq_('xmlhttprequest');
+var Polling = _dereq_('./polling');
+var Emitter = _dereq_('component-emitter');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling-xhr');
+
+/**
+ * Module exports.
+ */
+
+module.exports = XHR;
+module.exports.Request = Request;
+
+/**
+ * Empty function
+ */
+
+function empty(){}
+
+/**
+ * XHR Polling constructor.
+ *
+ * @param {Object} opts
+ * @api public
+ */
+
+function XHR(opts){
+ Polling.call(this, opts);
+
+ if (global.location) {
+ var isSSL = 'https:' == location.protocol;
+ var port = location.port;
+
+ // some user agents have empty `location.port`
+ if (!port) {
+ port = isSSL ? 443 : 80;
+ }
+
+ this.xd = opts.hostname != global.location.hostname ||
+ port != opts.port;
+ this.xs = opts.secure != isSSL;
+ }
+}
+
+/**
+ * Inherits from Polling.
+ */
+
+inherit(XHR, Polling);
+
+/**
+ * XHR supports binary
+ */
+
+XHR.prototype.supportsBinary = true;
+
+/**
+ * Creates a request.
+ *
+ * @param {String} method
+ * @api private
+ */
+
+XHR.prototype.request = function(opts){
+ opts = opts || {};
+ opts.uri = this.uri();
+ opts.xd = this.xd;
+ opts.xs = this.xs;
+ opts.agent = this.agent || false;
+ opts.supportsBinary = this.supportsBinary;
+ opts.enablesXDR = this.enablesXDR;
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ return new Request(opts);
+};
+
+/**
+ * Sends data.
+ *
+ * @param {String} data to send.
+ * @param {Function} called upon flush.
+ * @api private
+ */
+
+XHR.prototype.doWrite = function(data, fn){
+ var isBinary = typeof data !== 'string' && data !== undefined;
+ var req = this.request({ method: 'POST', data: data, isBinary: isBinary });
+ var self = this;
+ req.on('success', fn);
+ req.on('error', function(err){
+ self.onError('xhr post error', err);
+ });
+ this.sendXhr = req;
+};
+
+/**
+ * Starts a poll cycle.
+ *
+ * @api private
+ */
+
+XHR.prototype.doPoll = function(){
+ debug('xhr poll');
+ var req = this.request();
+ var self = this;
+ req.on('data', function(data){
+ self.onData(data);
+ });
+ req.on('error', function(err){
+ self.onError('xhr poll error', err);
+ });
+ this.pollXhr = req;
+};
+
+/**
+ * Request constructor
+ *
+ * @param {Object} options
+ * @api public
+ */
+
+function Request(opts){
+ this.method = opts.method || 'GET';
+ this.uri = opts.uri;
+ this.xd = !!opts.xd;
+ this.xs = !!opts.xs;
+ this.async = false !== opts.async;
+ this.data = undefined != opts.data ? opts.data : null;
+ this.agent = opts.agent;
+ this.isBinary = opts.isBinary;
+ this.supportsBinary = opts.supportsBinary;
+ this.enablesXDR = opts.enablesXDR;
+
+ // SSL options for Node.js client
+ this.pfx = opts.pfx;
+ this.key = opts.key;
+ this.passphrase = opts.passphrase;
+ this.cert = opts.cert;
+ this.ca = opts.ca;
+ this.ciphers = opts.ciphers;
+ this.rejectUnauthorized = opts.rejectUnauthorized;
+
+ this.create();
+}
+
+/**
+ * Mix in `Emitter`.
+ */
+
+Emitter(Request.prototype);
+
+/**
+ * Creates the XHR object and sends the request.
+ *
+ * @api private
+ */
+
+Request.prototype.create = function(){
+ var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ var xhr = this.xhr = new XMLHttpRequest(opts);
+ var self = this;
+
+ try {
+ debug('xhr open %s: %s', this.method, this.uri);
+ xhr.open(this.method, this.uri, this.async);
+ if (this.supportsBinary) {
+ // This has to be done after open because Firefox is stupid
+ // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
+ xhr.responseType = 'arraybuffer';
+ }
+
+ if ('POST' == this.method) {
+ try {
+ if (this.isBinary) {
+ xhr.setRequestHeader('Content-type', 'application/octet-stream');
+ } else {
+ xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
+ }
+ } catch (e) {}
+ }
+
+ // ie6 check
+ if ('withCredentials' in xhr) {
+ xhr.withCredentials = true;
+ }
+
+ if (this.hasXDR()) {
+ xhr.onload = function(){
+ self.onLoad();
+ };
+ xhr.onerror = function(){
+ self.onError(xhr.responseText);
+ };
+ } else {
+ xhr.onreadystatechange = function(){
+ if (4 != xhr.readyState) return;
+ if (200 == xhr.status || 1223 == xhr.status) {
+ self.onLoad();
+ } else {
+ // make sure the `error` event handler that's user-set
+ // does not throw in the same tick and gets caught here
+ setTimeout(function(){
+ self.onError(xhr.status);
+ }, 0);
+ }
+ };
+ }
+
+ debug('xhr data %s', this.data);
+ xhr.send(this.data);
+ } catch (e) {
+ // Need to defer since .create() is called directly fhrom the constructor
+ // and thus the 'error' event can only be only bound *after* this exception
+ // occurs. Therefore, also, we cannot throw here at all.
+ setTimeout(function() {
+ self.onError(e);
+ }, 0);
+ return;
+ }
+
+ if (global.document) {
+ this.index = Request.requestsCount++;
+ Request.requests[this.index] = this;
+ }
+};
+
+/**
+ * Called upon successful response.
+ *
+ * @api private
+ */
+
+Request.prototype.onSuccess = function(){
+ this.emit('success');
+ this.cleanup();
+};
+
+/**
+ * Called if we have data.
+ *
+ * @api private
+ */
+
+Request.prototype.onData = function(data){
+ this.emit('data', data);
+ this.onSuccess();
+};
+
+/**
+ * Called upon error.
+ *
+ * @api private
+ */
+
+Request.prototype.onError = function(err){
+ this.emit('error', err);
+ this.cleanup(true);
+};
+
+/**
+ * Cleans up house.
+ *
+ * @api private
+ */
+
+Request.prototype.cleanup = function(fromError){
+ if ('undefined' == typeof this.xhr || null === this.xhr) {
+ return;
+ }
+ // xmlhttprequest
+ if (this.hasXDR()) {
+ this.xhr.onload = this.xhr.onerror = empty;
+ } else {
+ this.xhr.onreadystatechange = empty;
+ }
+
+ if (fromError) {
+ try {
+ this.xhr.abort();
+ } catch(e) {}
+ }
+
+ if (global.document) {
+ delete Request.requests[this.index];
+ }
+
+ this.xhr = null;
+};
+
+/**
+ * Called upon load.
+ *
+ * @api private
+ */
+
+Request.prototype.onLoad = function(){
+ var data;
+ try {
+ var contentType;
+ try {
+ contentType = this.xhr.getResponseHeader('Content-Type').split(';')[0];
+ } catch (e) {}
+ if (contentType === 'application/octet-stream') {
+ data = this.xhr.response;
+ } else {
+ if (!this.supportsBinary) {
+ data = this.xhr.responseText;
+ } else {
+ data = 'ok';
+ }
+ }
+ } catch (e) {
+ this.onError(e);
+ }
+ if (null != data) {
+ this.onData(data);
+ }
+};
+
+/**
+ * Check if it has XDomainRequest.
+ *
+ * @api private
+ */
+
+Request.prototype.hasXDR = function(){
+ return 'undefined' !== typeof global.XDomainRequest && !this.xs && this.enablesXDR;
+};
+
+/**
+ * Aborts the request.
+ *
+ * @api public
+ */
+
+Request.prototype.abort = function(){
+ this.cleanup();
+};
+
+/**
+ * Aborts pending requests when unloading the window. This is needed to prevent
+ * memory leaks (e.g. when using IE) and to ensure that no spurious error is
+ * emitted.
+ */
+
+if (global.document) {
+ Request.requestsCount = 0;
+ Request.requests = {};
+ if (global.attachEvent) {
+ global.attachEvent('onunload', unloadHandler);
+ } else if (global.addEventListener) {
+ global.addEventListener('beforeunload', unloadHandler, false);
+ }
+}
+
+function unloadHandler() {
+ for (var i in Request.requests) {
+ if (Request.requests.hasOwnProperty(i)) {
+ Request.requests[i].abort();
+ }
+ }
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./polling":18,"component-emitter":9,"component-inherit":21,"debug":22,"xmlhttprequest":20}],18:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parseqs = _dereq_('parseqs');
+var parser = _dereq_('engine.io-parser');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:polling');
+
+/**
+ * Module exports.
+ */
+
+module.exports = Polling;
+
+/**
+ * Is XHR2 supported?
+ */
+
+var hasXHR2 = (function() {
+ var XMLHttpRequest = _dereq_('xmlhttprequest');
+ var xhr = new XMLHttpRequest({ xdomain: false });
+ return null != xhr.responseType;
+})();
+
+/**
+ * Polling interface.
+ *
+ * @param {Object} opts
+ * @api private
+ */
+
+function Polling(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (!hasXHR2 || forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(Polling, Transport);
+
+/**
+ * Transport name.
+ */
+
+Polling.prototype.name = 'polling';
+
+/**
+ * Opens the socket (triggers polling). We write a PING message to determine
+ * when the transport is open.
+ *
+ * @api private
+ */
+
+Polling.prototype.doOpen = function(){
+ this.poll();
+};
+
+/**
+ * Pauses polling.
+ *
+ * @param {Function} callback upon buffers are flushed and transport is paused
+ * @api private
+ */
+
+Polling.prototype.pause = function(onPause){
+ var pending = 0;
+ var self = this;
+
+ this.readyState = 'pausing';
+
+ function pause(){
+ debug('paused');
+ self.readyState = 'paused';
+ onPause();
+ }
+
+ if (this.polling || !this.writable) {
+ var total = 0;
+
+ if (this.polling) {
+ debug('we are currently polling - waiting to pause');
+ total++;
+ this.once('pollComplete', function(){
+ debug('pre-pause polling complete');
+ --total || pause();
+ });
+ }
+
+ if (!this.writable) {
+ debug('we are currently writing - waiting to pause');
+ total++;
+ this.once('drain', function(){
+ debug('pre-pause writing complete');
+ --total || pause();
+ });
+ }
+ } else {
+ pause();
+ }
+};
+
+/**
+ * Starts polling cycle.
+ *
+ * @api public
+ */
+
+Polling.prototype.poll = function(){
+ debug('polling');
+ this.polling = true;
+ this.doPoll();
+ this.emit('poll');
+};
+
+/**
+ * Overloads onData to detect payloads.
+ *
+ * @api private
+ */
+
+Polling.prototype.onData = function(data){
+ var self = this;
+ debug('polling got data %s', data);
+ var callback = function(packet, index, total) {
+ // if its the first message we consider the transport open
+ if ('opening' == self.readyState) {
+ self.onOpen();
+ }
+
+ // if its a close packet, we close the ongoing requests
+ if ('close' == packet.type) {
+ self.onClose();
+ return false;
+ }
+
+ // otherwise bypass onData and handle the message
+ self.onPacket(packet);
+ };
+
+ // decode payload
+ parser.decodePayload(data, this.socket.binaryType, callback);
+
+ // if an event did not trigger closing
+ if ('closed' != this.readyState) {
+ // if we got data we're not polling
+ this.polling = false;
+ this.emit('pollComplete');
+
+ if ('open' == this.readyState) {
+ this.poll();
+ } else {
+ debug('ignoring poll - transport state "%s"', this.readyState);
+ }
+ }
+};
+
+/**
+ * For polling, send a close packet.
+ *
+ * @api private
+ */
+
+Polling.prototype.doClose = function(){
+ var self = this;
+
+ function close(){
+ debug('writing close packet');
+ self.write([{ type: 'close' }]);
+ }
+
+ if ('open' == this.readyState) {
+ debug('transport open - closing');
+ close();
+ } else {
+ // in case we're trying to close while
+ // handshaking is in progress (GH-164)
+ debug('transport not open - deferring close');
+ this.once('open', close);
+ }
+};
+
+/**
+ * Writes a packets payload.
+ *
+ * @param {Array} data packets
+ * @param {Function} drain callback
+ * @api private
+ */
+
+Polling.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ var callbackfn = function() {
+ self.writable = true;
+ self.emit('drain');
+ };
+
+ var self = this;
+ parser.encodePayload(packets, this.supportsBinary, function(data) {
+ self.doWrite(data, callbackfn);
+ });
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+Polling.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'https' : 'http';
+ var port = '';
+
+ // cache busting is forced
+ if (false !== this.timestampRequests) {
+ query[this.timestampParam] = +new Date + '-' + Transport.timestamps++;
+ }
+
+ if (!this.supportsBinary && !query.sid) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // avoid port if default for schema
+ if (this.port && (('https' == schema && this.port != 443) ||
+ ('http' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+},{"../transport":14,"component-inherit":21,"debug":22,"engine.io-parser":25,"parseqs":35,"xmlhttprequest":20}],19:[function(_dereq_,module,exports){
+/**
+ * Module dependencies.
+ */
+
+var Transport = _dereq_('../transport');
+var parser = _dereq_('engine.io-parser');
+var parseqs = _dereq_('parseqs');
+var inherit = _dereq_('component-inherit');
+var debug = _dereq_('debug')('engine.io-client:websocket');
+
+/**
+ * `ws` exposes a WebSocket-compatible interface in
+ * Node, or the `WebSocket` or `MozWebSocket` globals
+ * in the browser.
+ */
+
+var WebSocket = _dereq_('ws');
+
+/**
+ * Module exports.
+ */
+
+module.exports = WS;
+
+/**
+ * WebSocket transport constructor.
+ *
+ * @api {Object} connection options
+ * @api public
+ */
+
+function WS(opts){
+ var forceBase64 = (opts && opts.forceBase64);
+ if (forceBase64) {
+ this.supportsBinary = false;
+ }
+ Transport.call(this, opts);
+}
+
+/**
+ * Inherits from Transport.
+ */
+
+inherit(WS, Transport);
+
+/**
+ * Transport name.
+ *
+ * @api public
+ */
+
+WS.prototype.name = 'websocket';
+
+/*
+ * WebSockets support binary
+ */
+
+WS.prototype.supportsBinary = true;
+
+/**
+ * Opens socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doOpen = function(){
+ if (!this.check()) {
+ // let probe timeout
+ return;
+ }
+
+ var self = this;
+ var uri = this.uri();
+ var protocols = void(0);
+ var opts = { agent: this.agent };
+
+ // SSL options for Node.js client
+ opts.pfx = this.pfx;
+ opts.key = this.key;
+ opts.passphrase = this.passphrase;
+ opts.cert = this.cert;
+ opts.ca = this.ca;
+ opts.ciphers = this.ciphers;
+ opts.rejectUnauthorized = this.rejectUnauthorized;
+
+ this.ws = new WebSocket(uri, protocols, opts);
+
+ if (this.ws.binaryType === undefined) {
+ this.supportsBinary = false;
+ }
+
+ this.ws.binaryType = 'arraybuffer';
+ this.addEventListeners();
+};
+
+/**
+ * Adds event listeners to the socket
+ *
+ * @api private
+ */
+
+WS.prototype.addEventListeners = function(){
+ var self = this;
+
+ this.ws.onopen = function(){
+ self.onOpen();
+ };
+ this.ws.onclose = function(){
+ self.onClose();
+ };
+ this.ws.onmessage = function(ev){
+ self.onData(ev.data);
+ };
+ this.ws.onerror = function(e){
+ self.onError('websocket error', e);
+ };
+};
+
+/**
+ * Override `onData` to use a timer on iOS.
+ * See: https://gist.github.com/mloughran/2052006
+ *
+ * @api private
+ */
+
+if ('undefined' != typeof navigator
+ && /iPad|iPhone|iPod/i.test(navigator.userAgent)) {
+ WS.prototype.onData = function(data){
+ var self = this;
+ setTimeout(function(){
+ Transport.prototype.onData.call(self, data);
+ }, 0);
+ };
+}
+
+/**
+ * Writes data to socket.
+ *
+ * @param {Array} array of packets.
+ * @api private
+ */
+
+WS.prototype.write = function(packets){
+ var self = this;
+ this.writable = false;
+ // encodePacket efficient as it uses WS framing
+ // no need for encodePayload
+ for (var i = 0, l = packets.length; i < l; i++) {
+ parser.encodePacket(packets[i], this.supportsBinary, function(data) {
+ //Sometimes the websocket has already been closed but the browser didn't
+ //have a chance of informing us about it yet, in that case send will
+ //throw an error
+ try {
+ self.ws.send(data);
+ } catch (e){
+ debug('websocket closed before onclose event');
+ }
+ });
+ }
+
+ function ondrain() {
+ self.writable = true;
+ self.emit('drain');
+ }
+ // fake drain
+ // defer to next tick to allow Socket to clear writeBuffer
+ setTimeout(ondrain, 0);
+};
+
+/**
+ * Called upon close
+ *
+ * @api private
+ */
+
+WS.prototype.onClose = function(){
+ Transport.prototype.onClose.call(this);
+};
+
+/**
+ * Closes socket.
+ *
+ * @api private
+ */
+
+WS.prototype.doClose = function(){
+ if (typeof this.ws !== 'undefined') {
+ this.ws.close();
+ }
+};
+
+/**
+ * Generates uri for connection.
+ *
+ * @api private
+ */
+
+WS.prototype.uri = function(){
+ var query = this.query || {};
+ var schema = this.secure ? 'wss' : 'ws';
+ var port = '';
+
+ // avoid port if default for schema
+ if (this.port && (('wss' == schema && this.port != 443)
+ || ('ws' == schema && this.port != 80))) {
+ port = ':' + this.port;
+ }
+
+ // append timestamp to URI
+ if (this.timestampRequests) {
+ query[this.timestampParam] = +new Date;
+ }
+
+ // communicate binary support capabilities
+ if (!this.supportsBinary) {
+ query.b64 = 1;
+ }
+
+ query = parseqs.encode(query);
+
+ // prepend ? to query
+ if (query.length) {
+ query = '?' + query;
+ }
+
+ return schema + '://' + this.hostname + port + this.path + query;
+};
+
+/**
+ * Feature detection for WebSocket.
+ *
+ * @return {Boolean} whether this transport is available.
+ * @api public
+ */
+
+WS.prototype.check = function(){
+ return !!WebSocket && !('__initialize' in WebSocket && this.name === WS.prototype.name);
+};
+
+},{"../transport":14,"component-inherit":21,"debug":22,"engine.io-parser":25,"parseqs":35,"ws":37}],20:[function(_dereq_,module,exports){
+// browser shim for xmlhttprequest module
+var hasCORS = _dereq_('has-cors');
+
+module.exports = function(opts) {
+ var xdomain = opts.xdomain;
+
+ // scheme must be same when usign XDomainRequest
+ // http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
+ var xscheme = opts.xscheme;
+
+ // XDomainRequest has a flow of not sending cookie, therefore it should be disabled as a default.
+ // https://github.com/Automattic/engine.io-client/pull/217
+ var enablesXDR = opts.enablesXDR;
+
+ // XMLHttpRequest can be disabled on IE
+ try {
+ if ('undefined' != typeof XMLHttpRequest && (!xdomain || hasCORS)) {
+ return new XMLHttpRequest();
+ }
+ } catch (e) { }
+
+ // Use XDomainRequest for IE8 if enablesXDR is true
+ // because loading bar keeps flashing when using jsonp-polling
+ // https://github.com/yujiosaka/socke.io-ie8-loading-example
+ try {
+ if ('undefined' != typeof XDomainRequest && !xscheme && enablesXDR) {
+ return new XDomainRequest();
+ }
+ } catch (e) { }
+
+ if (!xdomain) {
+ try {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ } catch(e) { }
+ }
+}
+
+},{"has-cors":40}],21:[function(_dereq_,module,exports){
+
+module.exports = function(a, b){
+ var fn = function(){};
+ fn.prototype = b.prototype;
+ a.prototype = new fn;
+ a.prototype.constructor = a;
+};
+},{}],22:[function(_dereq_,module,exports){
+
+/**
+ * This is the web browser implementation of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = _dereq_('./debug');
+exports.log = log;
+exports.formatArgs = formatArgs;
+exports.save = save;
+exports.load = load;
+exports.useColors = useColors;
+
+/**
+ * Colors.
+ */
+
+exports.colors = [
+ 'lightseagreen',
+ 'forestgreen',
+ 'goldenrod',
+ 'dodgerblue',
+ 'darkorchid',
+ 'crimson'
+];
+
+/**
+ * Currently only WebKit-based Web Inspectors, Firefox >= v31,
+ * and the Firebug extension (any Firefox version) are known
+ * to support "%c" CSS customizations.
+ *
+ * TODO: add a `localStorage` variable to explicitly enable/disable colors
+ */
+
+function useColors() {
+ // is webkit? http://stackoverflow.com/a/16459606/376773
+ return ('WebkitAppearance' in document.documentElement.style) ||
+ // is firebug? http://stackoverflow.com/a/398120/376773
+ (window.console && (console.firebug || (console.exception && console.table))) ||
+ // is firefox >= v31?
+ // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
+ (navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31);
+}
+
+/**
+ * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
+ */
+
+exports.formatters.j = function(v) {
+ return JSON.stringify(v);
+};
+
+
+/**
+ * Colorize log arguments if enabled.
+ *
+ * @api public
+ */
+
+function formatArgs() {
+ var args = arguments;
+ var useColors = this.useColors;
+
+ args[0] = (useColors ? '%c' : '')
+ + this.namespace
+ + (useColors ? ' %c' : ' ')
+ + args[0]
+ + (useColors ? '%c ' : ' ')
+ + '+' + exports.humanize(this.diff);
+
+ if (!useColors) return args;
+
+ var c = 'color: ' + this.color;
+ args = [args[0], c, 'color: inherit'].concat(Array.prototype.slice.call(args, 1));
+
+ // the final "%c" is somewhat tricky, because there could be other
+ // arguments passed either before or after the %c, so we need to
+ // figure out the correct index to insert the CSS into
+ var index = 0;
+ var lastC = 0;
+ args[0].replace(/%[a-z%]/g, function(match) {
+ if ('%' === match) return;
+ index++;
+ if ('%c' === match) {
+ // we only are interested in the *last* %c
+ // (the user may have provided their own)
+ lastC = index;
+ }
+ });
+
+ args.splice(lastC, 0, c);
+ return args;
+}
+
+/**
+ * Invokes `console.log()` when available.
+ * No-op when `console.log` is not a "function".
+ *
+ * @api public
+ */
+
+function log() {
+ // This hackery is required for IE8,
+ // where the `console.log` function doesn't have 'apply'
+ return 'object' == typeof console
+ && 'function' == typeof console.log
+ && Function.prototype.apply.call(console.log, console, arguments);
+}
+
+/**
+ * Save `namespaces`.
+ *
+ * @param {String} namespaces
+ * @api private
+ */
+
+function save(namespaces) {
+ try {
+ if (null == namespaces) {
+ localStorage.removeItem('debug');
+ } else {
+ localStorage.debug = namespaces;
+ }
+ } catch(e) {}
+}
+
+/**
+ * Load `namespaces`.
+ *
+ * @return {String} returns the previously persisted debug modes
+ * @api private
+ */
+
+function load() {
+ var r;
+ try {
+ r = localStorage.debug;
+ } catch(e) {}
+ return r;
+}
+
+/**
+ * Enable namespaces listed in `localStorage.debug` initially.
+ */
+
+exports.enable(load());
+
+},{"./debug":23}],23:[function(_dereq_,module,exports){
+
+/**
+ * This is the common logic for both the Node.js and web browser
+ * implementations of `debug()`.
+ *
+ * Expose `debug()` as the module.
+ */
+
+exports = module.exports = debug;
+exports.coerce = coerce;
+exports.disable = disable;
+exports.enable = enable;
+exports.enabled = enabled;
+exports.humanize = _dereq_('ms');
+
+/**
+ * The currently active debug mode names, and names to skip.
+ */
+
+exports.names = [];
+exports.skips = [];
+
+/**
+ * Map of special "%n" handling functions, for the debug "format" argument.
+ *
+ * Valid key names are a single, lowercased letter, i.e. "n".
+ */
+
+exports.formatters = {};
+
+/**
+ * Previously assigned color.
+ */
+
+var prevColor = 0;
+
+/**
+ * Previous log timestamp.
+ */
+
+var prevTime;
+
+/**
+ * Select a color.
+ *
+ * @return {Number}
+ * @api private
+ */
+
+function selectColor() {
+ return exports.colors[prevColor++ % exports.colors.length];
+}
+
+/**
+ * Create a debugger with the given `namespace`.
+ *
+ * @param {String} namespace
+ * @return {Function}
+ * @api public
+ */
+
+function debug(namespace) {
+
+ // define the `disabled` version
+ function disabled() {
+ }
+ disabled.enabled = false;
+
+ // define the `enabled` version
+ function enabled() {
+
+ var self = enabled;
+
+ // set `diff` timestamp
+ var curr = +new Date();
+ var ms = curr - (prevTime || curr);
+ self.diff = ms;
+ self.prev = prevTime;
+ self.curr = curr;
+ prevTime = curr;
+
+ // add the `color` if not set
+ if (null == self.useColors) self.useColors = exports.useColors();
+ if (null == self.color && self.useColors) self.color = selectColor();
+
+ var args = Array.prototype.slice.call(arguments);
+
+ args[0] = exports.coerce(args[0]);
+
+ if ('string' !== typeof args[0]) {
+ // anything else let's inspect with %o
+ args = ['%o'].concat(args);
+ }
+
+ // apply any `formatters` transformations
+ var index = 0;
+ args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
+ // if we encounter an escaped % then don't increase the array index
+ if (match === '%') return match;
+ index++;
+ var formatter = exports.formatters[format];
+ if ('function' === typeof formatter) {
+ var val = args[index];
+ match = formatter.call(self, val);
+
+ // now we need to remove `args[index]` since it's inlined in the `format`
+ args.splice(index, 1);
+ index--;
+ }
+ return match;
+ });
+
+ if ('function' === typeof exports.formatArgs) {
+ args = exports.formatArgs.apply(self, args);
+ }
+ var logFn = enabled.log || exports.log || console.log.bind(console);
+ logFn.apply(self, args);
+ }
+ enabled.enabled = true;
+
+ var fn = exports.enabled(namespace) ? enabled : disabled;
+
+ fn.namespace = namespace;
+
+ return fn;
+}
+
+/**
+ * Enables a debug mode by namespaces. This can include modes
+ * separated by a colon and wildcards.
+ *
+ * @param {String} namespaces
+ * @api public
+ */
+
+function enable(namespaces) {
+ exports.save(namespaces);
+
+ var split = (namespaces || '').split(/[\s,]+/);
+ var len = split.length;
+
+ for (var i = 0; i < len; i++) {
+ if (!split[i]) continue; // ignore empty strings
+ namespaces = split[i].replace(/\*/g, '.*?');
+ if (namespaces[0] === '-') {
+ exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
+ } else {
+ exports.names.push(new RegExp('^' + namespaces + '$'));
+ }
+ }
+}
+
+/**
+ * Disable debug output.
+ *
+ * @api public
+ */
+
+function disable() {
+ exports.enable('');
+}
+
+/**
+ * Returns true if the given mode name is enabled, false otherwise.
+ *
+ * @param {String} name
+ * @return {Boolean}
+ * @api public
+ */
+
+function enabled(name) {
+ var i, len;
+ for (i = 0, len = exports.skips.length; i < len; i++) {
+ if (exports.skips[i].test(name)) {
+ return false;
+ }
+ }
+ for (i = 0, len = exports.names.length; i < len; i++) {
+ if (exports.names[i].test(name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
+ * @api private
+ */
+
+function coerce(val) {
+ if (val instanceof Error) return val.stack || val.message;
+ return val;
+}
+
+},{"ms":24}],24:[function(_dereq_,module,exports){
+/**
+ * Helpers.
+ */
+
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var y = d * 365.25;
+
+/**
+ * Parse or format the given `val`.
+ *
+ * Options:
+ *
+ * - `long` verbose formatting [false]
+ *
+ * @param {String|Number} val
+ * @param {Object} options
+ * @return {String|Number}
+ * @api public
+ */
+
+module.exports = function(val, options){
+ options = options || {};
+ if ('string' == typeof val) return parse(val);
+ return options.long
+ ? long(val)
+ : short(val);
+};
+
+/**
+ * Parse the given `str` and return milliseconds.
+ *
+ * @param {String} str
+ * @return {Number}
+ * @api private
+ */
+
+function parse(str) {
+ var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec(str);
+ if (!match) return;
+ var n = parseFloat(match[1]);
+ var type = (match[2] || 'ms').toLowerCase();
+ switch (type) {
+ case 'years':
+ case 'year':
+ case 'y':
+ return n * y;
+ case 'days':
+ case 'day':
+ case 'd':
+ return n * d;
+ case 'hours':
+ case 'hour':
+ case 'h':
+ return n * h;
+ case 'minutes':
+ case 'minute':
+ case 'm':
+ return n * m;
+ case 'seconds':
+ case 'second':
+ case 's':
+ return n * s;
+ case 'ms':
+ return n;
+ }
+}
+
+/**
+ * Short format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function short(ms) {
+ if (ms >= d) return Math.round(ms / d) + 'd';
+ if (ms >= h) return Math.round(ms / h) + 'h';
+ if (ms >= m) return Math.round(ms / m) + 'm';
+ if (ms >= s) return Math.round(ms / s) + 's';
+ return ms + 'ms';
+}
+
+/**
+ * Long format for `ms`.
+ *
+ * @param {Number} ms
+ * @return {String}
+ * @api private
+ */
+
+function long(ms) {
+ return plural(ms, d, 'day')
+ || plural(ms, h, 'hour')
+ || plural(ms, m, 'minute')
+ || plural(ms, s, 'second')
+ || ms + ' ms';
+}
+
+/**
+ * Pluralization helper.
+ */
+
+function plural(ms, n, name) {
+ if (ms < n) return;
+ if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
+ return Math.ceil(ms / n) + ' ' + name + 's';
+}
+
+},{}],25:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Module dependencies.
+ */
+
+var keys = _dereq_('./keys');
+var hasBinary = _dereq_('has-binary');
+var sliceBuffer = _dereq_('arraybuffer.slice');
+var base64encoder = _dereq_('base64-arraybuffer');
+var after = _dereq_('after');
+var utf8 = _dereq_('utf8');
+
+/**
+ * Check if we are running an android browser. That requires us to use
+ * ArrayBuffer with polling transports...
+ *
+ * http://ghinda.net/jpeg-blob-ajax-android/
+ */
+
+var isAndroid = navigator.userAgent.match(/Android/i);
+
+/**
+ * Check if we are running in PhantomJS.
+ * Uploading a Blob with PhantomJS does not work correctly, as reported here:
+ * https://github.com/ariya/phantomjs/issues/11395
+ * @type boolean
+ */
+var isPhantomJS = /PhantomJS/i.test(navigator.userAgent);
+
+/**
+ * When true, avoids using Blobs to encode payloads.
+ * @type boolean
+ */
+var dontSendBlobs = isAndroid || isPhantomJS;
+
+/**
+ * Current protocol version.
+ */
+
+exports.protocol = 3;
+
+/**
+ * Packet types.
+ */
+
+var packets = exports.packets = {
+ open: 0 // non-ws
+ , close: 1 // non-ws
+ , ping: 2
+ , pong: 3
+ , message: 4
+ , upgrade: 5
+ , noop: 6
+};
+
+var packetslist = keys(packets);
+
+/**
+ * Premade error packet.
+ */
+
+var err = { type: 'error', data: 'parser error' };
+
+/**
+ * Create a blob api even for blob builder when vendor prefixes exist
+ */
+
+var Blob = _dereq_('blob');
+
+/**
+ * Encodes a packet.
+ *
+ * [ ]
+ *
+ * Example:
+ *
+ * 5hello world
+ * 3
+ * 4
+ *
+ * Binary is encoded in an identical principle
+ *
+ * @api private
+ */
+
+exports.encodePacket = function (packet, supportsBinary, utf8encode, callback) {
+ if ('function' == typeof supportsBinary) {
+ callback = supportsBinary;
+ supportsBinary = false;
+ }
+
+ if ('function' == typeof utf8encode) {
+ callback = utf8encode;
+ utf8encode = null;
+ }
+
+ var data = (packet.data === undefined)
+ ? undefined
+ : packet.data.buffer || packet.data;
+
+ if (global.ArrayBuffer && data instanceof ArrayBuffer) {
+ return encodeArrayBuffer(packet, supportsBinary, callback);
+ } else if (Blob && data instanceof global.Blob) {
+ return encodeBlob(packet, supportsBinary, callback);
+ }
+
+ // might be an object with { base64: true, data: dataAsBase64String }
+ if (data && data.base64) {
+ return encodeBase64Object(packet, callback);
+ }
+
+ // Sending data as a utf-8 string
+ var encoded = packets[packet.type];
+
+ // data fragment is optional
+ if (undefined !== packet.data) {
+ encoded += utf8encode ? utf8.encode(String(packet.data)) : String(packet.data);
+ }
+
+ return callback('' + encoded);
+
+};
+
+function encodeBase64Object(packet, callback) {
+ // packet data is an object { base64: true, data: dataAsBase64String }
+ var message = 'b' + exports.packets[packet.type] + packet.data.data;
+ return callback(message);
+}
+
+/**
+ * Encode packet helpers for binary types
+ */
+
+function encodeArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var data = packet.data;
+ var contentArray = new Uint8Array(data);
+ var resultBuffer = new Uint8Array(1 + data.byteLength);
+
+ resultBuffer[0] = packets[packet.type];
+ for (var i = 0; i < contentArray.length; i++) {
+ resultBuffer[i+1] = contentArray[i];
+ }
+
+ return callback(resultBuffer.buffer);
+}
+
+function encodeBlobAsArrayBuffer(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ var fr = new FileReader();
+ fr.onload = function() {
+ packet.data = fr.result;
+ exports.encodePacket(packet, supportsBinary, true, callback);
+ };
+ return fr.readAsArrayBuffer(packet.data);
+}
+
+function encodeBlob(packet, supportsBinary, callback) {
+ if (!supportsBinary) {
+ return exports.encodeBase64Packet(packet, callback);
+ }
+
+ if (dontSendBlobs) {
+ return encodeBlobAsArrayBuffer(packet, supportsBinary, callback);
+ }
+
+ var length = new Uint8Array(1);
+ length[0] = packets[packet.type];
+ var blob = new Blob([length.buffer, packet.data]);
+
+ return callback(blob);
+}
+
+/**
+ * Encodes a packet with binary data in a base64 string
+ *
+ * @param {Object} packet, has `type` and `data`
+ * @return {String} base64 encoded message
+ */
+
+exports.encodeBase64Packet = function(packet, callback) {
+ var message = 'b' + exports.packets[packet.type];
+ if (Blob && packet.data instanceof Blob) {
+ var fr = new FileReader();
+ fr.onload = function() {
+ var b64 = fr.result.split(',')[1];
+ callback(message + b64);
+ };
+ return fr.readAsDataURL(packet.data);
+ }
+
+ var b64data;
+ try {
+ b64data = String.fromCharCode.apply(null, new Uint8Array(packet.data));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply with typed arrays
+ var typed = new Uint8Array(packet.data);
+ var basic = new Array(typed.length);
+ for (var i = 0; i < typed.length; i++) {
+ basic[i] = typed[i];
+ }
+ b64data = String.fromCharCode.apply(null, basic);
+ }
+ message += global.btoa(b64data);
+ return callback(message);
+};
+
+/**
+ * Decodes a packet. Changes format to Blob if requested.
+ *
+ * @return {Object} with `type` and `data` (if any)
+ * @api private
+ */
+
+exports.decodePacket = function (data, binaryType, utf8decode) {
+ // String data
+ if (typeof data == 'string' || data === undefined) {
+ if (data.charAt(0) == 'b') {
+ return exports.decodeBase64Packet(data.substr(1), binaryType);
+ }
+
+ if (utf8decode) {
+ try {
+ data = utf8.decode(data);
+ } catch (e) {
+ return err;
+ }
+ }
+ var type = data.charAt(0);
+
+ if (Number(type) != type || !packetslist[type]) {
+ return err;
+ }
+
+ if (data.length > 1) {
+ return { type: packetslist[type], data: data.substring(1) };
+ } else {
+ return { type: packetslist[type] };
+ }
+ }
+
+ var asArray = new Uint8Array(data);
+ var type = asArray[0];
+ var rest = sliceBuffer(data, 1);
+ if (Blob && binaryType === 'blob') {
+ rest = new Blob([rest]);
+ }
+ return { type: packetslist[type], data: rest };
+};
+
+/**
+ * Decodes a packet encoded in a base64 string
+ *
+ * @param {String} base64 encoded message
+ * @return {Object} with `type` and `data` (if any)
+ */
+
+exports.decodeBase64Packet = function(msg, binaryType) {
+ var type = packetslist[msg.charAt(0)];
+ if (!global.ArrayBuffer) {
+ return { type: type, data: { base64: true, data: msg.substr(1) } };
+ }
+
+ var data = base64encoder.decode(msg.substr(1));
+
+ if (binaryType === 'blob' && Blob) {
+ data = new Blob([data]);
+ }
+
+ return { type: type, data: data };
+};
+
+/**
+ * Encodes multiple messages (payload).
+ *
+ * :data
+ *
+ * Example:
+ *
+ * 11:hello world2:hi
+ *
+ * If any contents are binary, they will be encoded as base64 strings. Base64
+ * encoded strings are marked with a b before the length specifier
+ *
+ * @param {Array} packets
+ * @api private
+ */
+
+exports.encodePayload = function (packets, supportsBinary, callback) {
+ if (typeof supportsBinary == 'function') {
+ callback = supportsBinary;
+ supportsBinary = null;
+ }
+
+ var isBinary = hasBinary(packets);
+
+ if (supportsBinary && isBinary) {
+ if (Blob && !dontSendBlobs) {
+ return exports.encodePayloadAsBlob(packets, callback);
+ }
+
+ return exports.encodePayloadAsArrayBuffer(packets, callback);
+ }
+
+ if (!packets.length) {
+ return callback('0:');
+ }
+
+ function setLengthHeader(message) {
+ return message.length + ':' + message;
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, !isBinary ? false : supportsBinary, true, function(message) {
+ doneCallback(null, setLengthHeader(message));
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(results.join(''));
+ });
+};
+
+/**
+ * Async array map using after
+ */
+
+function map(ary, each, done) {
+ var result = new Array(ary.length);
+ var next = after(ary.length, done);
+
+ var eachWithIndex = function(i, el, cb) {
+ each(el, function(error, msg) {
+ result[i] = msg;
+ cb(error, result);
+ });
+ };
+
+ for (var i = 0; i < ary.length; i++) {
+ eachWithIndex(i, ary[i], next);
+ }
+}
+
+/*
+ * Decodes data when a payload is maybe expected. Possible binary contents are
+ * decoded from their base64 representation
+ *
+ * @param {String} data, callback method
+ * @api public
+ */
+
+exports.decodePayload = function (data, binaryType, callback) {
+ if (typeof data != 'string') {
+ return exports.decodePayloadAsBinary(data, binaryType, callback);
+ }
+
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var packet;
+ if (data == '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var length = ''
+ , n, msg;
+
+ for (var i = 0, l = data.length; i < l; i++) {
+ var chr = data.charAt(i);
+
+ if (':' != chr) {
+ length += chr;
+ } else {
+ if ('' == length || (length != (n = Number(length)))) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ msg = data.substr(i + 1, n);
+
+ if (length != msg.length) {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ if (msg.length) {
+ packet = exports.decodePacket(msg, binaryType, true);
+
+ if (err.type == packet.type && err.data == packet.data) {
+ // parser error in individual packet - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+ var ret = callback(packet, i + n, l);
+ if (false === ret) return;
+ }
+
+ // advance cursor
+ i += n;
+ length = '';
+ }
+ }
+
+ if (length != '') {
+ // parser error - ignoring payload
+ return callback(err, 0, 1);
+ }
+
+};
+
+/**
+ * Encodes multiple messages (payload) as binary.
+ *
+ * <1 = binary, 0 = string>[...]
+ *
+ * Example:
+ * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
+ *
+ * @param {Array} packets
+ * @return {ArrayBuffer} encoded payload
+ * @api private
+ */
+
+exports.encodePayloadAsArrayBuffer = function(packets, callback) {
+ if (!packets.length) {
+ return callback(new ArrayBuffer(0));
+ }
+
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(data) {
+ return doneCallback(null, data);
+ });
+ }
+
+ map(packets, encodeOne, function(err, encodedPackets) {
+ var totalLength = encodedPackets.reduce(function(acc, p) {
+ var len;
+ if (typeof p === 'string'){
+ len = p.length;
+ } else {
+ len = p.byteLength;
+ }
+ return acc + len.toString().length + len + 2; // string/binary identifier + separator = 2
+ }, 0);
+
+ var resultArray = new Uint8Array(totalLength);
+
+ var bufferIndex = 0;
+ encodedPackets.forEach(function(p) {
+ var isString = typeof p === 'string';
+ var ab = p;
+ if (isString) {
+ var view = new Uint8Array(p.length);
+ for (var i = 0; i < p.length; i++) {
+ view[i] = p.charCodeAt(i);
+ }
+ ab = view.buffer;
+ }
+
+ if (isString) { // not true binary
+ resultArray[bufferIndex++] = 0;
+ } else { // true binary
+ resultArray[bufferIndex++] = 1;
+ }
+
+ var lenStr = ab.byteLength.toString();
+ for (var i = 0; i < lenStr.length; i++) {
+ resultArray[bufferIndex++] = parseInt(lenStr[i]);
+ }
+ resultArray[bufferIndex++] = 255;
+
+ var view = new Uint8Array(ab);
+ for (var i = 0; i < view.length; i++) {
+ resultArray[bufferIndex++] = view[i];
+ }
+ });
+
+ return callback(resultArray.buffer);
+ });
+};
+
+/**
+ * Encode as Blob
+ */
+
+exports.encodePayloadAsBlob = function(packets, callback) {
+ function encodeOne(packet, doneCallback) {
+ exports.encodePacket(packet, true, true, function(encoded) {
+ var binaryIdentifier = new Uint8Array(1);
+ binaryIdentifier[0] = 1;
+ if (typeof encoded === 'string') {
+ var view = new Uint8Array(encoded.length);
+ for (var i = 0; i < encoded.length; i++) {
+ view[i] = encoded.charCodeAt(i);
+ }
+ encoded = view.buffer;
+ binaryIdentifier[0] = 0;
+ }
+
+ var len = (encoded instanceof ArrayBuffer)
+ ? encoded.byteLength
+ : encoded.size;
+
+ var lenStr = len.toString();
+ var lengthAry = new Uint8Array(lenStr.length + 1);
+ for (var i = 0; i < lenStr.length; i++) {
+ lengthAry[i] = parseInt(lenStr[i]);
+ }
+ lengthAry[lenStr.length] = 255;
+
+ if (Blob) {
+ var blob = new Blob([binaryIdentifier.buffer, lengthAry.buffer, encoded]);
+ doneCallback(null, blob);
+ }
+ });
+ }
+
+ map(packets, encodeOne, function(err, results) {
+ return callback(new Blob(results));
+ });
+};
+
+/*
+ * Decodes data when a payload is maybe expected. Strings are decoded by
+ * interpreting each byte as a key code for entries marked to start with 0. See
+ * description of encodePayloadAsBinary
+ *
+ * @param {ArrayBuffer} data, callback method
+ * @api public
+ */
+
+exports.decodePayloadAsBinary = function (data, binaryType, callback) {
+ if (typeof binaryType === 'function') {
+ callback = binaryType;
+ binaryType = null;
+ }
+
+ var bufferTail = data;
+ var buffers = [];
+
+ var numberTooLong = false;
+ while (bufferTail.byteLength > 0) {
+ var tailArray = new Uint8Array(bufferTail);
+ var isString = tailArray[0] === 0;
+ var msgLength = '';
+
+ for (var i = 1; ; i++) {
+ if (tailArray[i] == 255) break;
+
+ if (msgLength.length > 310) {
+ numberTooLong = true;
+ break;
+ }
+
+ msgLength += tailArray[i];
+ }
+
+ if(numberTooLong) return callback(err, 0, 1);
+
+ bufferTail = sliceBuffer(bufferTail, 2 + msgLength.length);
+ msgLength = parseInt(msgLength);
+
+ var msg = sliceBuffer(bufferTail, 0, msgLength);
+ if (isString) {
+ try {
+ msg = String.fromCharCode.apply(null, new Uint8Array(msg));
+ } catch (e) {
+ // iPhone Safari doesn't let you apply to typed arrays
+ var typed = new Uint8Array(msg);
+ msg = '';
+ for (var i = 0; i < typed.length; i++) {
+ msg += String.fromCharCode(typed[i]);
+ }
+ }
+ }
+
+ buffers.push(msg);
+ bufferTail = sliceBuffer(bufferTail, msgLength);
+ }
+
+ var total = buffers.length;
+ buffers.forEach(function(buffer, i) {
+ callback(exports.decodePacket(buffer, binaryType, true), i, total);
+ });
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./keys":26,"after":27,"arraybuffer.slice":28,"base64-arraybuffer":29,"blob":30,"has-binary":31,"utf8":33}],26:[function(_dereq_,module,exports){
+
+/**
+ * Gets the keys for an object.
+ *
+ * @return {Array} keys
+ * @api private
+ */
+
+module.exports = Object.keys || function keys (obj){
+ var arr = [];
+ var has = Object.prototype.hasOwnProperty;
+
+ for (var i in obj) {
+ if (has.call(obj, i)) {
+ arr.push(i);
+ }
+ }
+ return arr;
+};
+
+},{}],27:[function(_dereq_,module,exports){
+module.exports = after
+
+function after(count, callback, err_cb) {
+ var bail = false
+ err_cb = err_cb || noop
+ proxy.count = count
+
+ return (count === 0) ? callback() : proxy
+
+ function proxy(err, result) {
+ if (proxy.count <= 0) {
+ throw new Error('after called too many times')
+ }
+ --proxy.count
+
+ // after first error, rest are passed to err_cb
+ if (err) {
+ bail = true
+ callback(err)
+ // future error callbacks will go to error handler
+ callback = err_cb
+ } else if (proxy.count === 0 && !bail) {
+ callback(null, result)
+ }
+ }
+}
+
+function noop() {}
+
+},{}],28:[function(_dereq_,module,exports){
+/**
+ * An abstraction for slicing an arraybuffer even when
+ * ArrayBuffer.prototype.slice is not supported
+ *
+ * @api public
+ */
+
+module.exports = function(arraybuffer, start, end) {
+ var bytes = arraybuffer.byteLength;
+ start = start || 0;
+ end = end || bytes;
+
+ if (arraybuffer.slice) { return arraybuffer.slice(start, end); }
+
+ if (start < 0) { start += bytes; }
+ if (end < 0) { end += bytes; }
+ if (end > bytes) { end = bytes; }
+
+ if (start >= bytes || start >= end || bytes === 0) {
+ return new ArrayBuffer(0);
+ }
+
+ var abv = new Uint8Array(arraybuffer);
+ var result = new Uint8Array(end - start);
+ for (var i = start, ii = 0; i < end; i++, ii++) {
+ result[ii] = abv[i];
+ }
+ return result.buffer;
+};
+
+},{}],29:[function(_dereq_,module,exports){
+/*
+ * base64-arraybuffer
+ * https://github.com/niklasvh/base64-arraybuffer
+ *
+ * Copyright (c) 2012 Niklas von Hertzen
+ * Licensed under the MIT license.
+ */
+(function(chars){
+ "use strict";
+
+ exports.encode = function(arraybuffer) {
+ var bytes = new Uint8Array(arraybuffer),
+ i, len = bytes.length, base64 = "";
+
+ for (i = 0; i < len; i+=3) {
+ base64 += chars[bytes[i] >> 2];
+ base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
+ base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
+ base64 += chars[bytes[i + 2] & 63];
+ }
+
+ if ((len % 3) === 2) {
+ base64 = base64.substring(0, base64.length - 1) + "=";
+ } else if (len % 3 === 1) {
+ base64 = base64.substring(0, base64.length - 2) + "==";
+ }
+
+ return base64;
+ };
+
+ exports.decode = function(base64) {
+ var bufferLength = base64.length * 0.75,
+ len = base64.length, i, p = 0,
+ encoded1, encoded2, encoded3, encoded4;
+
+ if (base64[base64.length - 1] === "=") {
+ bufferLength--;
+ if (base64[base64.length - 2] === "=") {
+ bufferLength--;
+ }
+ }
+
+ var arraybuffer = new ArrayBuffer(bufferLength),
+ bytes = new Uint8Array(arraybuffer);
+
+ for (i = 0; i < len; i+=4) {
+ encoded1 = chars.indexOf(base64[i]);
+ encoded2 = chars.indexOf(base64[i+1]);
+ encoded3 = chars.indexOf(base64[i+2]);
+ encoded4 = chars.indexOf(base64[i+3]);
+
+ bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
+ bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
+ bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
+ }
+
+ return arraybuffer;
+ };
+})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+},{}],30:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * Create a blob builder even when vendor prefixes exist
+ */
+
+var BlobBuilder = global.BlobBuilder
+ || global.WebKitBlobBuilder
+ || global.MSBlobBuilder
+ || global.MozBlobBuilder;
+
+/**
+ * Check if Blob constructor is supported
+ */
+
+var blobSupported = (function() {
+ try {
+ var b = new Blob(['hi']);
+ return b.size == 2;
+ } catch(e) {
+ return false;
+ }
+})();
+
+/**
+ * Check if BlobBuilder is supported
+ */
+
+var blobBuilderSupported = BlobBuilder
+ && BlobBuilder.prototype.append
+ && BlobBuilder.prototype.getBlob;
+
+function BlobBuilderConstructor(ary, options) {
+ options = options || {};
+
+ var bb = new BlobBuilder();
+ for (var i = 0; i < ary.length; i++) {
+ bb.append(ary[i]);
+ }
+ return (options.type) ? bb.getBlob(options.type) : bb.getBlob();
+};
+
+module.exports = (function() {
+ if (blobSupported) {
+ return global.Blob;
+ } else if (blobBuilderSupported) {
+ return BlobBuilderConstructor;
+ } else {
+ return undefined;
+ }
+})();
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],31:[function(_dereq_,module,exports){
+(function (global){
+
+/*
+ * Module requirements.
+ */
+
+var isArray = _dereq_('isarray');
+
+/**
+ * Module exports.
+ */
+
+module.exports = hasBinary;
+
+/**
+ * Checks for binary data.
+ *
+ * Right now only Buffer and ArrayBuffer are supported..
+ *
+ * @param {Object} anything
+ * @api public
+ */
+
+function hasBinary(data) {
+
+ function _hasBinary(obj) {
+ if (!obj) return false;
+
+ if ( (global.Buffer && global.Buffer.isBuffer(obj)) ||
+ (global.ArrayBuffer && obj instanceof ArrayBuffer) ||
+ (global.Blob && obj instanceof Blob) ||
+ (global.File && obj instanceof File)
+ ) {
+ return true;
+ }
+
+ if (isArray(obj)) {
+ for (var i = 0; i < obj.length; i++) {
+ if (_hasBinary(obj[i])) {
+ return true;
+ }
+ }
+ } else if (obj && 'object' == typeof obj) {
+ if (obj.toJSON) {
+ obj = obj.toJSON();
+ }
+
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key) && _hasBinary(obj[key])) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return _hasBinary(data);
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"isarray":32}],32:[function(_dereq_,module,exports){
+module.exports = Array.isArray || function (arr) {
+ return Object.prototype.toString.call(arr) == '[object Array]';
+};
+
+},{}],33:[function(_dereq_,module,exports){
+(function (global){
+/*! http://mths.be/utf8js v2.0.0 by @mathias */
+;(function(root) {
+
+ // Detect free variables `exports`
+ var freeExports = typeof exports == 'object' && exports;
+
+ // Detect free variable `module`
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+
+ // Detect free variable `global`, from Node.js or Browserified code,
+ // and use it as `root`
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var stringFromCharCode = String.fromCharCode;
+
+ // Taken from http://mths.be/punycode
+ function ucs2decode(string) {
+ var output = [];
+ var counter = 0;
+ var length = string.length;
+ var value;
+ var extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ // unmatched surrogate; only append this code unit, in case the next
+ // code unit is the high surrogate of a surrogate pair
+ output.push(value);
+ counter--;
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ // Taken from http://mths.be/punycode
+ function ucs2encode(array) {
+ var length = array.length;
+ var index = -1;
+ var value;
+ var output = '';
+ while (++index < length) {
+ value = array[index];
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ }
+ return output;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function createByte(codePoint, shift) {
+ return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80);
+ }
+
+ function encodeCodePoint(codePoint) {
+ if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence
+ return stringFromCharCode(codePoint);
+ }
+ var symbol = '';
+ if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0);
+ }
+ else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0);
+ symbol += createByte(codePoint, 6);
+ }
+ else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence
+ symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0);
+ symbol += createByte(codePoint, 12);
+ symbol += createByte(codePoint, 6);
+ }
+ symbol += stringFromCharCode((codePoint & 0x3F) | 0x80);
+ return symbol;
+ }
+
+ function utf8encode(string) {
+ var codePoints = ucs2decode(string);
+
+ // console.log(JSON.stringify(codePoints.map(function(x) {
+ // return 'U+' + x.toString(16).toUpperCase();
+ // })));
+
+ var length = codePoints.length;
+ var index = -1;
+ var codePoint;
+ var byteString = '';
+ while (++index < length) {
+ codePoint = codePoints[index];
+ byteString += encodeCodePoint(codePoint);
+ }
+ return byteString;
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ function readContinuationByte() {
+ if (byteIndex >= byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ var continuationByte = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ if ((continuationByte & 0xC0) == 0x80) {
+ return continuationByte & 0x3F;
+ }
+
+ // If we end up here, it’s not a continuation byte
+ throw Error('Invalid continuation byte');
+ }
+
+ function decodeSymbol() {
+ var byte1;
+ var byte2;
+ var byte3;
+ var byte4;
+ var codePoint;
+
+ if (byteIndex > byteCount) {
+ throw Error('Invalid byte index');
+ }
+
+ if (byteIndex == byteCount) {
+ return false;
+ }
+
+ // Read first byte
+ byte1 = byteArray[byteIndex] & 0xFF;
+ byteIndex++;
+
+ // 1-byte sequence (no continuation bytes)
+ if ((byte1 & 0x80) == 0) {
+ return byte1;
+ }
+
+ // 2-byte sequence
+ if ((byte1 & 0xE0) == 0xC0) {
+ var byte2 = readContinuationByte();
+ codePoint = ((byte1 & 0x1F) << 6) | byte2;
+ if (codePoint >= 0x80) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 3-byte sequence (may include unpaired surrogates)
+ if ((byte1 & 0xF0) == 0xE0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3;
+ if (codePoint >= 0x0800) {
+ return codePoint;
+ } else {
+ throw Error('Invalid continuation byte');
+ }
+ }
+
+ // 4-byte sequence
+ if ((byte1 & 0xF8) == 0xF0) {
+ byte2 = readContinuationByte();
+ byte3 = readContinuationByte();
+ byte4 = readContinuationByte();
+ codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) |
+ (byte3 << 0x06) | byte4;
+ if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) {
+ return codePoint;
+ }
+ }
+
+ throw Error('Invalid UTF-8 detected');
+ }
+
+ var byteArray;
+ var byteCount;
+ var byteIndex;
+ function utf8decode(byteString) {
+ byteArray = ucs2decode(byteString);
+ byteCount = byteArray.length;
+ byteIndex = 0;
+ var codePoints = [];
+ var tmp;
+ while ((tmp = decodeSymbol()) !== false) {
+ codePoints.push(tmp);
+ }
+ return ucs2encode(codePoints);
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ var utf8 = {
+ 'version': '2.0.0',
+ 'encode': utf8encode,
+ 'decode': utf8decode
+ };
+
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return utf8;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = utf8;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ var object = {};
+ var hasOwnProperty = object.hasOwnProperty;
+ for (var key in utf8) {
+ hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.utf8 = utf8;
+ }
+
+}(this));
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],34:[function(_dereq_,module,exports){
+(function (global){
+/**
+ * JSON parse.
+ *
+ * @see Based on jQuery#parseJSON (MIT) and JSON2
+ * @api private
+ */
+
+var rvalidchars = /^[\],:{}\s]*$/;
+var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
+var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
+var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
+var rtrimLeft = /^\s+/;
+var rtrimRight = /\s+$/;
+
+module.exports = function parsejson(data) {
+ if ('string' != typeof data || !data) {
+ return null;
+ }
+
+ data = data.replace(rtrimLeft, '').replace(rtrimRight, '');
+
+ // Attempt to parse using the native JSON parser first
+ if (global.JSON && JSON.parse) {
+ return JSON.parse(data);
+ }
+
+ if (rvalidchars.test(data.replace(rvalidescape, '@')
+ .replace(rvalidtokens, ']')
+ .replace(rvalidbraces, ''))) {
+ return (new Function('return ' + data))();
+ }
+};
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],35:[function(_dereq_,module,exports){
+/**
+ * Compiles a querystring
+ * Returns string representation of the object
+ *
+ * @param {Object}
+ * @api private
+ */
+
+exports.encode = function (obj) {
+ var str = '';
+
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ if (str.length) str += '&';
+ str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]);
+ }
+ }
+
+ return str;
+};
+
+/**
+ * Parses a simple querystring into an object
+ *
+ * @param {String} qs
+ * @api private
+ */
+
+exports.decode = function(qs){
+ var qry = {};
+ var pairs = qs.split('&');
+ for (var i = 0, l = pairs.length; i < l; i++) {
+ var pair = pairs[i].split('=');
+ qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+ }
+ return qry;
+};
+
+},{}],36:[function(_dereq_,module,exports){
+/**
+ * Parses an URI
+ *
+ * @author Steven Levithan (MIT license)
+ * @api private
+ */
+
+var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+var parts = [
+ 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
+];
+
+module.exports = function parseuri(str) {
+ var src = str,
+ b = str.indexOf('['),
+ e = str.indexOf(']');
+
+ if (b != -1 && e != -1) {
+ str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length);
+ }
+
+ var m = re.exec(str || ''),
+ uri = {},
+ i = 14;
+
+ while (i--) {
+ uri[parts[i]] = m[i] || '';
+ }
+
+ if (b != -1 && e != -1) {
+ uri.source = src;
+ uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':');
+ uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':');
+ uri.ipv6uri = true;
+ }
+
+ return uri;
+};
+
+},{}],37:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var global = (function() { return this; })();
+
+/**
+ * WebSocket constructor.
+ */
+
+var WebSocket = global.WebSocket || global.MozWebSocket;
+
+/**
+ * Module exports.
+ */
+
+module.exports = WebSocket ? ws : null;
+
+/**
+ * WebSocket constructor.
+ *
+ * The third `opts` options object gets ignored in web browsers, since it's
+ * non-standard, and throws a TypeError if passed to the constructor.
+ * See: https://github.com/einaros/ws/issues/227
+ *
+ * @param {String} uri
+ * @param {Array} protocols (optional)
+ * @param {Object) opts (optional)
+ * @api public
+ */
+
+function ws(uri, protocols, opts) {
+ var instance;
+ if (protocols) {
+ instance = new WebSocket(uri, protocols);
+ } else {
+ instance = new WebSocket(uri);
+ }
+ return instance;
+}
+
+if (WebSocket) ws.prototype = WebSocket.prototype;
+
+},{}],38:[function(_dereq_,module,exports){
+(function (global){
+
+/*
+ * Module requirements.
+ */
+
+var isArray = _dereq_('isarray');
+
+/**
+ * Module exports.
+ */
+
+module.exports = hasBinary;
+
+/**
+ * Checks for binary data.
+ *
+ * Right now only Buffer and ArrayBuffer are supported..
+ *
+ * @param {Object} anything
+ * @api public
+ */
+
+function hasBinary(data) {
+
+ function _hasBinary(obj) {
+ if (!obj) return false;
+
+ if ( (global.Buffer && global.Buffer.isBuffer(obj)) ||
+ (global.ArrayBuffer && obj instanceof ArrayBuffer) ||
+ (global.Blob && obj instanceof Blob) ||
+ (global.File && obj instanceof File)
+ ) {
+ return true;
+ }
+
+ if (isArray(obj)) {
+ for (var i = 0; i < obj.length; i++) {
+ if (_hasBinary(obj[i])) {
+ return true;
+ }
+ }
+ } else if (obj && 'object' == typeof obj) {
+ if (obj.toJSON) {
+ obj = obj.toJSON();
+ }
+
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key) && _hasBinary(obj[key])) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ return _hasBinary(data);
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"isarray":39}],39:[function(_dereq_,module,exports){
+module.exports=_dereq_(32)
+},{}],40:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var global = _dereq_('global');
+
+/**
+ * Module exports.
+ *
+ * Logic borrowed from Modernizr:
+ *
+ * - https://github.com/Modernizr/Modernizr/blob/master/feature-detects/cors.js
+ */
+
+try {
+ module.exports = 'XMLHttpRequest' in global &&
+ 'withCredentials' in new global.XMLHttpRequest();
+} catch (err) {
+ // if XMLHttp support is disabled in IE then it will throw
+ // when trying to create
+ module.exports = false;
+}
+
+},{"global":41}],41:[function(_dereq_,module,exports){
+
+/**
+ * Returns `this`. Execute this without a "context" (i.e. without it being
+ * attached to an object of the left-hand side), and `this` points to the
+ * "global" scope of the current JS execution.
+ */
+
+module.exports = (function () { return this; })();
+
+},{}],42:[function(_dereq_,module,exports){
+
+var indexOf = [].indexOf;
+
+module.exports = function(arr, obj){
+ if (indexOf) return arr.indexOf(obj);
+ for (var i = 0; i < arr.length; ++i) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+};
+},{}],43:[function(_dereq_,module,exports){
+
+/**
+ * HOP ref.
+ */
+
+var has = Object.prototype.hasOwnProperty;
+
+/**
+ * Return own keys in `obj`.
+ *
+ * @param {Object} obj
+ * @return {Array}
+ * @api public
+ */
+
+exports.keys = Object.keys || function(obj){
+ var keys = [];
+ for (var key in obj) {
+ if (has.call(obj, key)) {
+ keys.push(key);
+ }
+ }
+ return keys;
+};
+
+/**
+ * Return own values in `obj`.
+ *
+ * @param {Object} obj
+ * @return {Array}
+ * @api public
+ */
+
+exports.values = function(obj){
+ var vals = [];
+ for (var key in obj) {
+ if (has.call(obj, key)) {
+ vals.push(obj[key]);
+ }
+ }
+ return vals;
+};
+
+/**
+ * Merge `b` into `a`.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Object} a
+ * @api public
+ */
+
+exports.merge = function(a, b){
+ for (var key in b) {
+ if (has.call(b, key)) {
+ a[key] = b[key];
+ }
+ }
+ return a;
+};
+
+/**
+ * Return length of `obj`.
+ *
+ * @param {Object} obj
+ * @return {Number}
+ * @api public
+ */
+
+exports.length = function(obj){
+ return exports.keys(obj).length;
+};
+
+/**
+ * Check if `obj` is empty.
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ * @api public
+ */
+
+exports.isEmpty = function(obj){
+ return 0 == exports.length(obj);
+};
+},{}],44:[function(_dereq_,module,exports){
+/**
+ * Parses an URI
+ *
+ * @author Steven Levithan (MIT license)
+ * @api private
+ */
+
+var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
+
+var parts = [
+ 'source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host'
+ , 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'
+];
+
+module.exports = function parseuri(str) {
+ var m = re.exec(str || '')
+ , uri = {}
+ , i = 14;
+
+ while (i--) {
+ uri[parts[i]] = m[i] || '';
+ }
+
+ return uri;
+};
+
+},{}],45:[function(_dereq_,module,exports){
+(function (global){
+/*global Blob,File*/
+
+/**
+ * Module requirements
+ */
+
+var isArray = _dereq_('isarray');
+var isBuf = _dereq_('./is-buffer');
+
+/**
+ * Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.
+ * Anything with blobs or files should be fed through removeBlobs before coming
+ * here.
+ *
+ * @param {Object} packet - socket.io event packet
+ * @return {Object} with deconstructed packet and list of buffers
+ * @api public
+ */
+
+exports.deconstructPacket = function(packet){
+ var buffers = [];
+ var packetData = packet.data;
+
+ function _deconstructPacket(data) {
+ if (!data) return data;
+
+ if (isBuf(data)) {
+ var placeholder = { _placeholder: true, num: buffers.length };
+ buffers.push(data);
+ return placeholder;
+ } else if (isArray(data)) {
+ var newData = new Array(data.length);
+ for (var i = 0; i < data.length; i++) {
+ newData[i] = _deconstructPacket(data[i]);
+ }
+ return newData;
+ } else if ('object' == typeof data && !(data instanceof Date)) {
+ var newData = {};
+ for (var key in data) {
+ newData[key] = _deconstructPacket(data[key]);
+ }
+ return newData;
+ }
+ return data;
+ }
+
+ var pack = packet;
+ pack.data = _deconstructPacket(packetData);
+ pack.attachments = buffers.length; // number of binary 'attachments'
+ return {packet: pack, buffers: buffers};
+};
+
+/**
+ * Reconstructs a binary packet from its placeholder packet and buffers
+ *
+ * @param {Object} packet - event packet with placeholders
+ * @param {Array} buffers - binary buffers to put in placeholder positions
+ * @return {Object} reconstructed packet
+ * @api public
+ */
+
+exports.reconstructPacket = function(packet, buffers) {
+ var curPlaceHolder = 0;
+
+ function _reconstructPacket(data) {
+ if (data && data._placeholder) {
+ var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway)
+ return buf;
+ } else if (isArray(data)) {
+ for (var i = 0; i < data.length; i++) {
+ data[i] = _reconstructPacket(data[i]);
+ }
+ return data;
+ } else if (data && 'object' == typeof data) {
+ for (var key in data) {
+ data[key] = _reconstructPacket(data[key]);
+ }
+ return data;
+ }
+ return data;
+ }
+
+ packet.data = _reconstructPacket(packet.data);
+ packet.attachments = undefined; // no longer useful
+ return packet;
+};
+
+/**
+ * Asynchronously removes Blobs or Files from data via
+ * FileReader's readAsArrayBuffer method. Used before encoding
+ * data as msgpack. Calls callback with the blobless data.
+ *
+ * @param {Object} data
+ * @param {Function} callback
+ * @api private
+ */
+
+exports.removeBlobs = function(data, callback) {
+ function _removeBlobs(obj, curKey, containingObject) {
+ if (!obj) return obj;
+
+ // convert any blob
+ if ((global.Blob && obj instanceof Blob) ||
+ (global.File && obj instanceof File)) {
+ pendingBlobs++;
+
+ // async filereader
+ var fileReader = new FileReader();
+ fileReader.onload = function() { // this.result == arraybuffer
+ if (containingObject) {
+ containingObject[curKey] = this.result;
+ }
+ else {
+ bloblessData = this.result;
+ }
+
+ // if nothing pending its callback time
+ if(! --pendingBlobs) {
+ callback(bloblessData);
+ }
+ };
+
+ fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer
+ } else if (isArray(obj)) { // handle array
+ for (var i = 0; i < obj.length; i++) {
+ _removeBlobs(obj[i], i, obj);
+ }
+ } else if (obj && 'object' == typeof obj && !isBuf(obj)) { // and object
+ for (var key in obj) {
+ _removeBlobs(obj[key], key, obj);
+ }
+ }
+ }
+
+ var pendingBlobs = 0;
+ var bloblessData = data;
+ _removeBlobs(bloblessData);
+ if (!pendingBlobs) {
+ callback(bloblessData);
+ }
+};
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{"./is-buffer":47,"isarray":48}],46:[function(_dereq_,module,exports){
+
+/**
+ * Module dependencies.
+ */
+
+var debug = _dereq_('debug')('socket.io-parser');
+var json = _dereq_('json3');
+var isArray = _dereq_('isarray');
+var Emitter = _dereq_('component-emitter');
+var binary = _dereq_('./binary');
+var isBuf = _dereq_('./is-buffer');
+
+/**
+ * Protocol version.
+ *
+ * @api public
+ */
+
+exports.protocol = 4;
+
+/**
+ * Packet types.
+ *
+ * @api public
+ */
+
+exports.types = [
+ 'CONNECT',
+ 'DISCONNECT',
+ 'EVENT',
+ 'BINARY_EVENT',
+ 'ACK',
+ 'BINARY_ACK',
+ 'ERROR'
+];
+
+/**
+ * Packet type `connect`.
+ *
+ * @api public
+ */
+
+exports.CONNECT = 0;
+
+/**
+ * Packet type `disconnect`.
+ *
+ * @api public
+ */
+
+exports.DISCONNECT = 1;
+
+/**
+ * Packet type `event`.
+ *
+ * @api public
+ */
+
+exports.EVENT = 2;
+
+/**
+ * Packet type `ack`.
+ *
+ * @api public
+ */
+
+exports.ACK = 3;
+
+/**
+ * Packet type `error`.
+ *
+ * @api public
+ */
+
+exports.ERROR = 4;
+
+/**
+ * Packet type 'binary event'
+ *
+ * @api public
+ */
+
+exports.BINARY_EVENT = 5;
+
+/**
+ * Packet type `binary ack`. For acks with binary arguments.
+ *
+ * @api public
+ */
+
+exports.BINARY_ACK = 6;
+
+/**
+ * Encoder constructor.
+ *
+ * @api public
+ */
+
+exports.Encoder = Encoder;
+
+/**
+ * Decoder constructor.
+ *
+ * @api public
+ */
+
+exports.Decoder = Decoder;
+
+/**
+ * A socket.io Encoder instance
+ *
+ * @api public
+ */
+
+function Encoder() {}
+
+/**
+ * Encode a packet as a single string if non-binary, or as a
+ * buffer sequence, depending on packet type.
+ *
+ * @param {Object} obj - packet object
+ * @param {Function} callback - function to handle encodings (likely engine.write)
+ * @return Calls callback with Array of encodings
+ * @api public
+ */
+
+Encoder.prototype.encode = function(obj, callback){
+ debug('encoding packet %j', obj);
+
+ if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
+ encodeAsBinary(obj, callback);
+ }
+ else {
+ var encoding = encodeAsString(obj);
+ callback([encoding]);
+ }
+};
+
+/**
+ * Encode packet as string.
+ *
+ * @param {Object} packet
+ * @return {String} encoded
+ * @api private
+ */
+
+function encodeAsString(obj) {
+ var str = '';
+ var nsp = false;
+
+ // first is type
+ str += obj.type;
+
+ // attachments if we have them
+ if (exports.BINARY_EVENT == obj.type || exports.BINARY_ACK == obj.type) {
+ str += obj.attachments;
+ str += '-';
+ }
+
+ // if we have a namespace other than `/`
+ // we append it followed by a comma `,`
+ if (obj.nsp && '/' != obj.nsp) {
+ nsp = true;
+ str += obj.nsp;
+ }
+
+ // immediately followed by the id
+ if (null != obj.id) {
+ if (nsp) {
+ str += ',';
+ nsp = false;
+ }
+ str += obj.id;
+ }
+
+ // json data
+ if (null != obj.data) {
+ if (nsp) str += ',';
+ str += json.stringify(obj.data);
+ }
+
+ debug('encoded %j as %s', obj, str);
+ return str;
+}
+
+/**
+ * Encode packet as 'buffer sequence' by removing blobs, and
+ * deconstructing packet into object with placeholders and
+ * a list of buffers.
+ *
+ * @param {Object} packet
+ * @return {Buffer} encoded
+ * @api private
+ */
+
+function encodeAsBinary(obj, callback) {
+
+ function writeEncoding(bloblessData) {
+ var deconstruction = binary.deconstructPacket(bloblessData);
+ var pack = encodeAsString(deconstruction.packet);
+ var buffers = deconstruction.buffers;
+
+ buffers.unshift(pack); // add packet info to beginning of data list
+ callback(buffers); // write all the buffers
+ }
+
+ binary.removeBlobs(obj, writeEncoding);
+}
+
+/**
+ * A socket.io Decoder instance
+ *
+ * @return {Object} decoder
+ * @api public
+ */
+
+function Decoder() {
+ this.reconstructor = null;
+}
+
+/**
+ * Mix in `Emitter` with Decoder.
+ */
+
+Emitter(Decoder.prototype);
+
+/**
+ * Decodes an ecoded packet string into packet JSON.
+ *
+ * @param {String} obj - encoded packet
+ * @return {Object} packet
+ * @api public
+ */
+
+Decoder.prototype.add = function(obj) {
+ var packet;
+ if ('string' == typeof obj) {
+ packet = decodeString(obj);
+ if (exports.BINARY_EVENT == packet.type || exports.BINARY_ACK == packet.type) { // binary packet's json
+ this.reconstructor = new BinaryReconstructor(packet);
+
+ // no attachments, labeled binary but no binary data to follow
+ if (this.reconstructor.reconPack.attachments === 0) {
+ this.emit('decoded', packet);
+ }
+ } else { // non-binary full packet
+ this.emit('decoded', packet);
+ }
+ }
+ else if (isBuf(obj) || obj.base64) { // raw binary data
+ if (!this.reconstructor) {
+ throw new Error('got binary data when not reconstructing a packet');
+ } else {
+ packet = this.reconstructor.takeBinaryData(obj);
+ if (packet) { // received final buffer
+ this.reconstructor = null;
+ this.emit('decoded', packet);
+ }
+ }
+ }
+ else {
+ throw new Error('Unknown type: ' + obj);
+ }
+};
+
+/**
+ * Decode a packet String (JSON data)
+ *
+ * @param {String} str
+ * @return {Object} packet
+ * @api private
+ */
+
+function decodeString(str) {
+ var p = {};
+ var i = 0;
+
+ // look up type
+ p.type = Number(str.charAt(0));
+ if (null == exports.types[p.type]) return error();
+
+ // look up attachments if type binary
+ if (exports.BINARY_EVENT == p.type || exports.BINARY_ACK == p.type) {
+ var buf = '';
+ while (str.charAt(++i) != '-') {
+ buf += str.charAt(i);
+ if (i == str.length) break;
+ }
+ if (buf != Number(buf) || str.charAt(i) != '-') {
+ throw new Error('Illegal attachments');
+ }
+ p.attachments = Number(buf);
+ }
+
+ // look up namespace (if any)
+ if ('/' == str.charAt(i + 1)) {
+ p.nsp = '';
+ while (++i) {
+ var c = str.charAt(i);
+ if (',' == c) break;
+ p.nsp += c;
+ if (i == str.length) break;
+ }
+ } else {
+ p.nsp = '/';
+ }
+
+ // look up id
+ var next = str.charAt(i + 1);
+ if ('' !== next && Number(next) == next) {
+ p.id = '';
+ while (++i) {
+ var c = str.charAt(i);
+ if (null == c || Number(c) != c) {
+ --i;
+ break;
+ }
+ p.id += str.charAt(i);
+ if (i == str.length) break;
+ }
+ p.id = Number(p.id);
+ }
+
+ // look up json data
+ if (str.charAt(++i)) {
+ try {
+ p.data = json.parse(str.substr(i));
+ } catch(e){
+ return error();
+ }
+ }
+
+ debug('decoded %s as %j', str, p);
+ return p;
+}
+
+/**
+ * Deallocates a parser's resources
+ *
+ * @api public
+ */
+
+Decoder.prototype.destroy = function() {
+ if (this.reconstructor) {
+ this.reconstructor.finishedReconstruction();
+ }
+};
+
+/**
+ * A manager of a binary event's 'buffer sequence'. Should
+ * be constructed whenever a packet of type BINARY_EVENT is
+ * decoded.
+ *
+ * @param {Object} packet
+ * @return {BinaryReconstructor} initialized reconstructor
+ * @api private
+ */
+
+function BinaryReconstructor(packet) {
+ this.reconPack = packet;
+ this.buffers = [];
+}
+
+/**
+ * Method to be called when binary data received from connection
+ * after a BINARY_EVENT packet.
+ *
+ * @param {Buffer | ArrayBuffer} binData - the raw binary data received
+ * @return {null | Object} returns null if more binary data is expected or
+ * a reconstructed packet object if all buffers have been received.
+ * @api private
+ */
+
+BinaryReconstructor.prototype.takeBinaryData = function(binData) {
+ this.buffers.push(binData);
+ if (this.buffers.length == this.reconPack.attachments) { // done with buffer list
+ var packet = binary.reconstructPacket(this.reconPack, this.buffers);
+ this.finishedReconstruction();
+ return packet;
+ }
+ return null;
+};
+
+/**
+ * Cleans up binary packet reconstruction variables.
+ *
+ * @api private
+ */
+
+BinaryReconstructor.prototype.finishedReconstruction = function() {
+ this.reconPack = null;
+ this.buffers = [];
+};
+
+function error(data){
+ return {
+ type: exports.ERROR,
+ data: 'parser error'
+ };
+}
+
+},{"./binary":45,"./is-buffer":47,"component-emitter":9,"debug":10,"isarray":48,"json3":49}],47:[function(_dereq_,module,exports){
+(function (global){
+
+module.exports = isBuf;
+
+/**
+ * Returns true if obj is a buffer or an arraybuffer.
+ *
+ * @api private
+ */
+
+function isBuf(obj) {
+ return (global.Buffer && global.Buffer.isBuffer(obj)) ||
+ (global.ArrayBuffer && obj instanceof ArrayBuffer);
+}
+
+}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],48:[function(_dereq_,module,exports){
+module.exports=_dereq_(32)
+},{}],49:[function(_dereq_,module,exports){
+/*! JSON v3.2.6 | http://bestiejs.github.io/json3 | Copyright 2012-2013, Kit Cambridge | http://kit.mit-license.org */
+;(function (window) {
+ // Convenience aliases.
+ var getClass = {}.toString, isProperty, forEach, undef;
+
+ // Detect the `define` function exposed by asynchronous module loaders. The
+ // strict `define` check is necessary for compatibility with `r.js`.
+ var isLoader = typeof define === "function" && define.amd;
+
+ // Detect native implementations.
+ var nativeJSON = typeof JSON == "object" && JSON;
+
+ // Set up the JSON 3 namespace, preferring the CommonJS `exports` object if
+ // available.
+ var JSON3 = typeof exports == "object" && exports && !exports.nodeType && exports;
+
+ if (JSON3 && nativeJSON) {
+ // Explicitly delegate to the native `stringify` and `parse`
+ // implementations in CommonJS environments.
+ JSON3.stringify = nativeJSON.stringify;
+ JSON3.parse = nativeJSON.parse;
+ } else {
+ // Export for web browsers, JavaScript engines, and asynchronous module
+ // loaders, using the global `JSON` object if available.
+ JSON3 = window.JSON = nativeJSON || {};
+ }
+
+ // Test the `Date#getUTC*` methods. Based on work by @Yaffle.
+ var isExtended = new Date(-3509827334573292);
+ try {
+ // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
+ // results for certain dates in Opera >= 10.53.
+ isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
+ // Safari < 2.0.2 stores the internal millisecond time value correctly,
+ // but clips the values returned by the date methods to the range of
+ // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
+ isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
+ } catch (exception) {}
+
+ // Internal: Determines whether the native `JSON.stringify` and `parse`
+ // implementations are spec-compliant. Based on work by Ken Snyder.
+ function has(name) {
+ if (has[name] !== undef) {
+ // Return cached feature test result.
+ return has[name];
+ }
+
+ var isSupported;
+ if (name == "bug-string-char-index") {
+ // IE <= 7 doesn't support accessing string characters using square
+ // bracket notation. IE 8 only supports this for primitives.
+ isSupported = "a"[0] != "a";
+ } else if (name == "json") {
+ // Indicates whether both `JSON.stringify` and `JSON.parse` are
+ // supported.
+ isSupported = has("json-stringify") && has("json-parse");
+ } else {
+ var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
+ // Test `JSON.stringify`.
+ if (name == "json-stringify") {
+ var stringify = JSON3.stringify, stringifySupported = typeof stringify == "function" && isExtended;
+ if (stringifySupported) {
+ // A test function object with a custom `toJSON` method.
+ (value = function () {
+ return 1;
+ }).toJSON = value;
+ try {
+ stringifySupported =
+ // Firefox 3.1b1 and b2 serialize string, number, and boolean
+ // primitives as object literals.
+ stringify(0) === "0" &&
+ // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
+ // literals.
+ stringify(new Number()) === "0" &&
+ stringify(new String()) == '""' &&
+ // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
+ // does not define a canonical JSON representation (this applies to
+ // objects with `toJSON` properties as well, *unless* they are nested
+ // within an object or array).
+ stringify(getClass) === undef &&
+ // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
+ // FF 3.1b3 pass this test.
+ stringify(undef) === undef &&
+ // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
+ // respectively, if the value is omitted entirely.
+ stringify() === undef &&
+ // FF 3.1b1, 2 throw an error if the given value is not a number,
+ // string, array, object, Boolean, or `null` literal. This applies to
+ // objects with custom `toJSON` methods as well, unless they are nested
+ // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
+ // methods entirely.
+ stringify(value) === "1" &&
+ stringify([value]) == "[1]" &&
+ // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
+ // `"[null]"`.
+ stringify([undef]) == "[null]" &&
+ // YUI 3.0.0b1 fails to serialize `null` literals.
+ stringify(null) == "null" &&
+ // FF 3.1b1, 2 halts serialization if an array contains a function:
+ // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
+ // elides non-JSON values from objects and arrays, unless they
+ // define custom `toJSON` methods.
+ stringify([undef, getClass, null]) == "[null,null,null]" &&
+ // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
+ // where character escape codes are expected (e.g., `\b` => `\u0008`).
+ stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
+ // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
+ stringify(null, value) === "1" &&
+ stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
+ // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
+ // serialize extended years.
+ stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
+ // The milliseconds are optional in ES 5, but required in 5.1.
+ stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
+ // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
+ // four-digit years instead of six-digit years. Credits: @Yaffle.
+ stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
+ // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
+ // values less than 1000. Credits: @Yaffle.
+ stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
+ } catch (exception) {
+ stringifySupported = false;
+ }
+ }
+ isSupported = stringifySupported;
+ }
+ // Test `JSON.parse`.
+ if (name == "json-parse") {
+ var parse = JSON3.parse;
+ if (typeof parse == "function") {
+ try {
+ // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
+ // Conforming implementations should also coerce the initial argument to
+ // a string prior to parsing.
+ if (parse("0") === 0 && !parse(false)) {
+ // Simple parsing test.
+ value = parse(serialized);
+ var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
+ if (parseSupported) {
+ try {
+ // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
+ parseSupported = !parse('"\t"');
+ } catch (exception) {}
+ if (parseSupported) {
+ try {
+ // FF 4.0 and 4.0.1 allow leading `+` signs and leading
+ // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
+ // certain octal literals.
+ parseSupported = parse("01") !== 1;
+ } catch (exception) {}
+ }
+ if (parseSupported) {
+ try {
+ // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
+ // points. These environments, along with FF 3.1b1 and 2,
+ // also allow trailing commas in JSON objects and arrays.
+ parseSupported = parse("1.") !== 1;
+ } catch (exception) {}
+ }
+ }
+ }
+ } catch (exception) {
+ parseSupported = false;
+ }
+ }
+ isSupported = parseSupported;
+ }
+ }
+ return has[name] = !!isSupported;
+ }
+
+ if (!has("json")) {
+ // Common `[[Class]]` name aliases.
+ var functionClass = "[object Function]";
+ var dateClass = "[object Date]";
+ var numberClass = "[object Number]";
+ var stringClass = "[object String]";
+ var arrayClass = "[object Array]";
+ var booleanClass = "[object Boolean]";
+
+ // Detect incomplete support for accessing string characters by index.
+ var charIndexBuggy = has("bug-string-char-index");
+
+ // Define additional utility methods if the `Date` methods are buggy.
+ if (!isExtended) {
+ var floor = Math.floor;
+ // A mapping between the months of the year and the number of days between
+ // January 1st and the first of the respective month.
+ var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
+ // Internal: Calculates the number of days between the Unix epoch and the
+ // first day of the given month.
+ var getDay = function (year, month) {
+ return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
+ };
+ }
+
+ // Internal: Determines if a property is a direct property of the given
+ // object. Delegates to the native `Object#hasOwnProperty` method.
+ if (!(isProperty = {}.hasOwnProperty)) {
+ isProperty = function (property) {
+ var members = {}, constructor;
+ if ((members.__proto__ = null, members.__proto__ = {
+ // The *proto* property cannot be set multiple times in recent
+ // versions of Firefox and SeaMonkey.
+ "toString": 1
+ }, members).toString != getClass) {
+ // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
+ // supports the mutable *proto* property.
+ isProperty = function (property) {
+ // Capture and break the object's prototype chain (see section 8.6.2
+ // of the ES 5.1 spec). The parenthesized expression prevents an
+ // unsafe transformation by the Closure Compiler.
+ var original = this.__proto__, result = property in (this.__proto__ = null, this);
+ // Restore the original prototype chain.
+ this.__proto__ = original;
+ return result;
+ };
+ } else {
+ // Capture a reference to the top-level `Object` constructor.
+ constructor = members.constructor;
+ // Use the `constructor` property to simulate `Object#hasOwnProperty` in
+ // other environments.
+ isProperty = function (property) {
+ var parent = (this.constructor || constructor).prototype;
+ return property in this && !(property in parent && this[property] === parent[property]);
+ };
+ }
+ members = null;
+ return isProperty.call(this, property);
+ };
+ }
+
+ // Internal: A set of primitive types used by `isHostType`.
+ var PrimitiveTypes = {
+ 'boolean': 1,
+ 'number': 1,
+ 'string': 1,
+ 'undefined': 1
+ };
+
+ // Internal: Determines if the given object `property` value is a
+ // non-primitive.
+ var isHostType = function (object, property) {
+ var type = typeof object[property];
+ return type == 'object' ? !!object[property] : !PrimitiveTypes[type];
+ };
+
+ // Internal: Normalizes the `for...in` iteration algorithm across
+ // environments. Each enumerated key is yielded to a `callback` function.
+ forEach = function (object, callback) {
+ var size = 0, Properties, members, property;
+
+ // Tests for bugs in the current environment's `for...in` algorithm. The
+ // `valueOf` property inherits the non-enumerable flag from
+ // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
+ (Properties = function () {
+ this.valueOf = 0;
+ }).prototype.valueOf = 0;
+
+ // Iterate over a new instance of the `Properties` class.
+ members = new Properties();
+ for (property in members) {
+ // Ignore all properties inherited from `Object.prototype`.
+ if (isProperty.call(members, property)) {
+ size++;
+ }
+ }
+ Properties = members = null;
+
+ // Normalize the iteration algorithm.
+ if (!size) {
+ // A list of non-enumerable properties inherited from `Object.prototype`.
+ members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
+ // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
+ // properties.
+ forEach = function (object, callback) {
+ var isFunction = getClass.call(object) == functionClass, property, length;
+ var hasProperty = !isFunction && typeof object.constructor != 'function' && isHostType(object, 'hasOwnProperty') ? object.hasOwnProperty : isProperty;
+ for (property in object) {
+ // Gecko <= 1.0 enumerates the `prototype` property of functions under
+ // certain conditions; IE does not.
+ if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
+ callback(property);
+ }
+ }
+ // Manually invoke the callback for each non-enumerable property.
+ for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
+ };
+ } else if (size == 2) {
+ // Safari <= 2.0.4 enumerates shadowed properties twice.
+ forEach = function (object, callback) {
+ // Create a set of iterated properties.
+ var members = {}, isFunction = getClass.call(object) == functionClass, property;
+ for (property in object) {
+ // Store each property name to prevent double enumeration. The
+ // `prototype` property of functions is not enumerated due to cross-
+ // environment inconsistencies.
+ if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
+ callback(property);
+ }
+ }
+ };
+ } else {
+ // No bugs detected; use the standard `for...in` algorithm.
+ forEach = function (object, callback) {
+ var isFunction = getClass.call(object) == functionClass, property, isConstructor;
+ for (property in object) {
+ if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
+ callback(property);
+ }
+ }
+ // Manually invoke the callback for the `constructor` property due to
+ // cross-environment inconsistencies.
+ if (isConstructor || isProperty.call(object, (property = "constructor"))) {
+ callback(property);
+ }
+ };
+ }
+ return forEach(object, callback);
+ };
+
+ // Public: Serializes a JavaScript `value` as a JSON string. The optional
+ // `filter` argument may specify either a function that alters how object and
+ // array members are serialized, or an array of strings and numbers that
+ // indicates which properties should be serialized. The optional `width`
+ // argument may be either a string or number that specifies the indentation
+ // level of the output.
+ if (!has("json-stringify")) {
+ // Internal: A map of control characters and their escaped equivalents.
+ var Escapes = {
+ 92: "\\\\",
+ 34: '\\"',
+ 8: "\\b",
+ 12: "\\f",
+ 10: "\\n",
+ 13: "\\r",
+ 9: "\\t"
+ };
+
+ // Internal: Converts `value` into a zero-padded string such that its
+ // length is at least equal to `width`. The `width` must be <= 6.
+ var leadingZeroes = "000000";
+ var toPaddedString = function (width, value) {
+ // The `|| 0` expression is necessary to work around a bug in
+ // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
+ return (leadingZeroes + (value || 0)).slice(-width);
+ };
+
+ // Internal: Double-quotes a string `value`, replacing all ASCII control
+ // characters (characters with code unit values between 0 and 31) with
+ // their escaped equivalents. This is an implementation of the
+ // `Quote(value)` operation defined in ES 5.1 section 15.12.3.
+ var unicodePrefix = "\\u00";
+ var quote = function (value) {
+ var result = '"', index = 0, length = value.length, isLarge = length > 10 && charIndexBuggy, symbols;
+ if (isLarge) {
+ symbols = value.split("");
+ }
+ for (; index < length; index++) {
+ var charCode = value.charCodeAt(index);
+ // If the character is a control character, append its Unicode or
+ // shorthand escape sequence; otherwise, append the character as-is.
+ switch (charCode) {
+ case 8: case 9: case 10: case 12: case 13: case 34: case 92:
+ result += Escapes[charCode];
+ break;
+ default:
+ if (charCode < 32) {
+ result += unicodePrefix + toPaddedString(2, charCode.toString(16));
+ break;
+ }
+ result += isLarge ? symbols[index] : charIndexBuggy ? value.charAt(index) : value[index];
+ }
+ }
+ return result + '"';
+ };
+
+ // Internal: Recursively serializes an object. Implements the
+ // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
+ var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
+ var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
+ try {
+ // Necessary for host object support.
+ value = object[property];
+ } catch (exception) {}
+ if (typeof value == "object" && value) {
+ className = getClass.call(value);
+ if (className == dateClass && !isProperty.call(value, "toJSON")) {
+ if (value > -1 / 0 && value < 1 / 0) {
+ // Dates are serialized according to the `Date#toJSON` method
+ // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
+ // for the ISO 8601 date time string format.
+ if (getDay) {
+ // Manually compute the year, month, date, hours, minutes,
+ // seconds, and milliseconds if the `getUTC*` methods are
+ // buggy. Adapted from @Yaffle's `date-shim` project.
+ date = floor(value / 864e5);
+ for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
+ for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
+ date = 1 + date - getDay(year, month);
+ // The `time` value specifies the time within the day (see ES
+ // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
+ // to compute `A modulo B`, as the `%` operator does not
+ // correspond to the `modulo` operation for negative numbers.
+ time = (value % 864e5 + 864e5) % 864e5;
+ // The hours, minutes, seconds, and milliseconds are obtained by
+ // decomposing the time within the day. See section 15.9.1.10.
+ hours = floor(time / 36e5) % 24;
+ minutes = floor(time / 6e4) % 60;
+ seconds = floor(time / 1e3) % 60;
+ milliseconds = time % 1e3;
+ } else {
+ year = value.getUTCFullYear();
+ month = value.getUTCMonth();
+ date = value.getUTCDate();
+ hours = value.getUTCHours();
+ minutes = value.getUTCMinutes();
+ seconds = value.getUTCSeconds();
+ milliseconds = value.getUTCMilliseconds();
+ }
+ // Serialize extended years correctly.
+ value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
+ "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
+ // Months, dates, hours, minutes, and seconds should have two
+ // digits; milliseconds should have three.
+ "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
+ // Milliseconds are optional in ES 5.0, but required in 5.1.
+ "." + toPaddedString(3, milliseconds) + "Z";
+ } else {
+ value = null;
+ }
+ } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
+ // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
+ // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
+ // ignores all `toJSON` methods on these objects unless they are
+ // defined directly on an instance.
+ value = value.toJSON(property);
+ }
+ }
+ if (callback) {
+ // If a replacement function was provided, call it to obtain the value
+ // for serialization.
+ value = callback.call(object, property, value);
+ }
+ if (value === null) {
+ return "null";
+ }
+ className = getClass.call(value);
+ if (className == booleanClass) {
+ // Booleans are represented literally.
+ return "" + value;
+ } else if (className == numberClass) {
+ // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
+ // `"null"`.
+ return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
+ } else if (className == stringClass) {
+ // Strings are double-quoted and escaped.
+ return quote("" + value);
+ }
+ // Recursively serialize objects and arrays.
+ if (typeof value == "object") {
+ // Check for cyclic structures. This is a linear search; performance
+ // is inversely proportional to the number of unique nested objects.
+ for (length = stack.length; length--;) {
+ if (stack[length] === value) {
+ // Cyclic structures cannot be serialized by `JSON.stringify`.
+ throw TypeError();
+ }
+ }
+ // Add the object to the stack of traversed objects.
+ stack.push(value);
+ results = [];
+ // Save the current indentation level and indent one additional level.
+ prefix = indentation;
+ indentation += whitespace;
+ if (className == arrayClass) {
+ // Recursively serialize array elements.
+ for (index = 0, length = value.length; index < length; index++) {
+ element = serialize(index, value, callback, properties, whitespace, indentation, stack);
+ results.push(element === undef ? "null" : element);
+ }
+ result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
+ } else {
+ // Recursively serialize object members. Members are selected from
+ // either a user-specified list of property names, or the object
+ // itself.
+ forEach(properties || value, function (property) {
+ var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
+ if (element !== undef) {
+ // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
+ // is not the empty string, let `member` {quote(property) + ":"}
+ // be the concatenation of `member` and the `space` character."
+ // The "`space` character" refers to the literal space
+ // character, not the `space` {width} argument provided to
+ // `JSON.stringify`.
+ results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
+ }
+ });
+ result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
+ }
+ // Remove the object from the traversed object stack.
+ stack.pop();
+ return result;
+ }
+ };
+
+ // Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
+ JSON3.stringify = function (source, filter, width) {
+ var whitespace, callback, properties, className;
+ if (typeof filter == "function" || typeof filter == "object" && filter) {
+ if ((className = getClass.call(filter)) == functionClass) {
+ callback = filter;
+ } else if (className == arrayClass) {
+ // Convert the property names array into a makeshift set.
+ properties = {};
+ for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
+ }
+ }
+ if (width) {
+ if ((className = getClass.call(width)) == numberClass) {
+ // Convert the `width` to an integer and create a string containing
+ // `width` number of space characters.
+ if ((width -= width % 1) > 0) {
+ for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
+ }
+ } else if (className == stringClass) {
+ whitespace = width.length <= 10 ? width : width.slice(0, 10);
+ }
+ }
+ // Opera <= 7.54u2 discards the values associated with empty string keys
+ // (`""`) only if they are used directly within an object member list
+ // (e.g., `!("" in { "": 1})`).
+ return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
+ };
+ }
+
+ // Public: Parses a JSON source string.
+ if (!has("json-parse")) {
+ var fromCharCode = String.fromCharCode;
+
+ // Internal: A map of escaped control characters and their unescaped
+ // equivalents.
+ var Unescapes = {
+ 92: "\\",
+ 34: '"',
+ 47: "/",
+ 98: "\b",
+ 116: "\t",
+ 110: "\n",
+ 102: "\f",
+ 114: "\r"
+ };
+
+ // Internal: Stores the parser state.
+ var Index, Source;
+
+ // Internal: Resets the parser state and throws a `SyntaxError`.
+ var abort = function() {
+ Index = Source = null;
+ throw SyntaxError();
+ };
+
+ // Internal: Returns the next token, or `"$"` if the parser has reached
+ // the end of the source string. A token may be a string, number, `null`
+ // literal, or Boolean literal.
+ var lex = function () {
+ var source = Source, length = source.length, value, begin, position, isSigned, charCode;
+ while (Index < length) {
+ charCode = source.charCodeAt(Index);
+ switch (charCode) {
+ case 9: case 10: case 13: case 32:
+ // Skip whitespace tokens, including tabs, carriage returns, line
+ // feeds, and space characters.
+ Index++;
+ break;
+ case 123: case 125: case 91: case 93: case 58: case 44:
+ // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
+ // the current position.
+ value = charIndexBuggy ? source.charAt(Index) : source[Index];
+ Index++;
+ return value;
+ case 34:
+ // `"` delimits a JSON string; advance to the next character and
+ // begin parsing the string. String tokens are prefixed with the
+ // sentinel `@` character to distinguish them from punctuators and
+ // end-of-string tokens.
+ for (value = "@", Index++; Index < length;) {
+ charCode = source.charCodeAt(Index);
+ if (charCode < 32) {
+ // Unescaped ASCII control characters (those with a code unit
+ // less than the space character) are not permitted.
+ abort();
+ } else if (charCode == 92) {
+ // A reverse solidus (`\`) marks the beginning of an escaped
+ // control character (including `"`, `\`, and `/`) or Unicode
+ // escape sequence.
+ charCode = source.charCodeAt(++Index);
+ switch (charCode) {
+ case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
+ // Revive escaped control characters.
+ value += Unescapes[charCode];
+ Index++;
+ break;
+ case 117:
+ // `\u` marks the beginning of a Unicode escape sequence.
+ // Advance to the first character and validate the
+ // four-digit code point.
+ begin = ++Index;
+ for (position = Index + 4; Index < position; Index++) {
+ charCode = source.charCodeAt(Index);
+ // A valid sequence comprises four hexdigits (case-
+ // insensitive) that form a single hexadecimal value.
+ if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
+ // Invalid Unicode escape sequence.
+ abort();
+ }
+ }
+ // Revive the escaped character.
+ value += fromCharCode("0x" + source.slice(begin, Index));
+ break;
+ default:
+ // Invalid escape sequence.
+ abort();
+ }
+ } else {
+ if (charCode == 34) {
+ // An unescaped double-quote character marks the end of the
+ // string.
+ break;
+ }
+ charCode = source.charCodeAt(Index);
+ begin = Index;
+ // Optimize for the common case where a string is valid.
+ while (charCode >= 32 && charCode != 92 && charCode != 34) {
+ charCode = source.charCodeAt(++Index);
+ }
+ // Append the string as-is.
+ value += source.slice(begin, Index);
+ }
+ }
+ if (source.charCodeAt(Index) == 34) {
+ // Advance to the next character and return the revived string.
+ Index++;
+ return value;
+ }
+ // Unterminated string.
+ abort();
+ default:
+ // Parse numbers and literals.
+ begin = Index;
+ // Advance past the negative sign, if one is specified.
+ if (charCode == 45) {
+ isSigned = true;
+ charCode = source.charCodeAt(++Index);
+ }
+ // Parse an integer or floating-point value.
+ if (charCode >= 48 && charCode <= 57) {
+ // Leading zeroes are interpreted as octal literals.
+ if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
+ // Illegal octal literal.
+ abort();
+ }
+ isSigned = false;
+ // Parse the integer component.
+ for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
+ // Floats cannot contain a leading decimal point; however, this
+ // case is already accounted for by the parser.
+ if (source.charCodeAt(Index) == 46) {
+ position = ++Index;
+ // Parse the decimal component.
+ for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
+ if (position == Index) {
+ // Illegal trailing decimal.
+ abort();
+ }
+ Index = position;
+ }
+ // Parse exponents. The `e` denoting the exponent is
+ // case-insensitive.
+ charCode = source.charCodeAt(Index);
+ if (charCode == 101 || charCode == 69) {
+ charCode = source.charCodeAt(++Index);
+ // Skip past the sign following the exponent, if one is
+ // specified.
+ if (charCode == 43 || charCode == 45) {
+ Index++;
+ }
+ // Parse the exponential component.
+ for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
+ if (position == Index) {
+ // Illegal empty exponent.
+ abort();
+ }
+ Index = position;
+ }
+ // Coerce the parsed value to a JavaScript number.
+ return +source.slice(begin, Index);
+ }
+ // A negative sign may only precede numbers.
+ if (isSigned) {
+ abort();
+ }
+ // `true`, `false`, and `null` literals.
+ if (source.slice(Index, Index + 4) == "true") {
+ Index += 4;
+ return true;
+ } else if (source.slice(Index, Index + 5) == "false") {
+ Index += 5;
+ return false;
+ } else if (source.slice(Index, Index + 4) == "null") {
+ Index += 4;
+ return null;
+ }
+ // Unrecognized token.
+ abort();
+ }
+ }
+ // Return the sentinel `$` character if the parser has reached the end
+ // of the source string.
+ return "$";
+ };
+
+ // Internal: Parses a JSON `value` token.
+ var get = function (value) {
+ var results, hasMembers;
+ if (value == "$") {
+ // Unexpected end of input.
+ abort();
+ }
+ if (typeof value == "string") {
+ if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
+ // Remove the sentinel `@` character.
+ return value.slice(1);
+ }
+ // Parse object and array literals.
+ if (value == "[") {
+ // Parses a JSON array, returning a new JavaScript array.
+ results = [];
+ for (;; hasMembers || (hasMembers = true)) {
+ value = lex();
+ // A closing square bracket marks the end of the array literal.
+ if (value == "]") {
+ break;
+ }
+ // If the array literal contains elements, the current token
+ // should be a comma separating the previous element from the
+ // next.
+ if (hasMembers) {
+ if (value == ",") {
+ value = lex();
+ if (value == "]") {
+ // Unexpected trailing `,` in array literal.
+ abort();
+ }
+ } else {
+ // A `,` must separate each array element.
+ abort();
+ }
+ }
+ // Elisions and leading commas are not permitted.
+ if (value == ",") {
+ abort();
+ }
+ results.push(get(value));
+ }
+ return results;
+ } else if (value == "{") {
+ // Parses a JSON object, returning a new JavaScript object.
+ results = {};
+ for (;; hasMembers || (hasMembers = true)) {
+ value = lex();
+ // A closing curly brace marks the end of the object literal.
+ if (value == "}") {
+ break;
+ }
+ // If the object literal contains members, the current token
+ // should be a comma separator.
+ if (hasMembers) {
+ if (value == ",") {
+ value = lex();
+ if (value == "}") {
+ // Unexpected trailing `,` in object literal.
+ abort();
+ }
+ } else {
+ // A `,` must separate each object member.
+ abort();
+ }
+ }
+ // Leading commas are not permitted, object property names must be
+ // double-quoted strings, and a `:` must separate each property
+ // name and value.
+ if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
+ abort();
+ }
+ results[value.slice(1)] = get(lex());
+ }
+ return results;
+ }
+ // Unexpected token encountered.
+ abort();
+ }
+ return value;
+ };
+
+ // Internal: Updates a traversed object member.
+ var update = function(source, property, callback) {
+ var element = walk(source, property, callback);
+ if (element === undef) {
+ delete source[property];
+ } else {
+ source[property] = element;
+ }
+ };
+
+ // Internal: Recursively traverses a parsed JSON object, invoking the
+ // `callback` function for each value. This is an implementation of the
+ // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
+ var walk = function (source, property, callback) {
+ var value = source[property], length;
+ if (typeof value == "object" && value) {
+ // `forEach` can't be used to traverse an array in Opera <= 8.54
+ // because its `Object#hasOwnProperty` implementation returns `false`
+ // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
+ if (getClass.call(value) == arrayClass) {
+ for (length = value.length; length--;) {
+ update(value, length, callback);
+ }
+ } else {
+ forEach(value, function (property) {
+ update(value, property, callback);
+ });
+ }
+ }
+ return callback.call(source, property, value);
+ };
+
+ // Public: `JSON.parse`. See ES 5.1 section 15.12.2.
+ JSON3.parse = function (source, callback) {
+ var result, value;
+ Index = 0;
+ Source = "" + source;
+ result = get(lex());
+ // If a JSON string contains multiple tokens, it is invalid.
+ if (lex() != "$") {
+ abort();
+ }
+ // Reset the parser state.
+ Index = Source = null;
+ return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
+ };
+ }
+ }
+
+ // Export for asynchronous module loaders.
+ if (isLoader) {
+ define(function () {
+ return JSON3;
+ });
+ }
+}(this));
+
+},{}],50:[function(_dereq_,module,exports){
+module.exports = toArray
+
+function toArray(list, index) {
+ var array = []
+
+ index = index || 0
+
+ for (var i = index || 0; i < list.length; i++) {
+ array[i - index] = list[i]
+ }
+
+ return array
+}
+
+},{}]},{},[1])
+(1)
+});
diff --git a/frappe/sessions.py b/frappe/sessions.py
index d4d98f7c18..850c6c8cd2 100644
--- a/frappe/sessions.py
+++ b/frappe/sessions.py
@@ -163,7 +163,7 @@ class Session:
"full_name": self.full_name,
"user_type": self.user_type,
"device": self.device,
- "session_country": get_geo_ip_country(frappe.local.request_ip)
+ "session_country": get_geo_ip_country(frappe.local.request_ip) if frappe.local.request_ip else None
})
# insert session
diff --git a/frappe/tasks.py b/frappe/tasks.py
index 60e5cfe7dd..52b1a33740 100644
--- a/frappe/tasks.py
+++ b/frappe/tasks.py
@@ -7,6 +7,10 @@ from frappe.utils.scheduler import enqueue_events
from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX
from frappe.utils import get_sites
from frappe.utils.file_lock import create_lock, delete_lock
+from frappe.handler import execute_cmd
+from frappe.async import set_task_status, END_LINE, get_std_streams
+import frappe.utils.response
+import sys
@celery_task()
def sync_queues():
@@ -122,3 +126,46 @@ def pull_from_email_account(site, email_account):
frappe.db.commit()
finally:
frappe.destroy()
+
+
+@celery_task(bind=True)
+def run_async_task(self, site, user, cmd, form_dict):
+ ret = {}
+ frappe.init(site)
+ frappe.connect()
+ sys.stdout, sys.stderr = get_std_streams(self.request.id)
+ frappe.local.stdout, frappe.local.stderr = sys.stdout, sys.stderr
+ frappe.local.task_id = self.request.id
+ frappe.cache()
+ try:
+ set_task_status(self.request.id, "Running")
+ frappe.db.commit()
+ frappe.set_user(user)
+ # sleep(60)
+ frappe.local.form_dict = frappe._dict(form_dict)
+ execute_cmd(cmd, async=True)
+ ret = frappe.local.response
+ except Exception, e:
+ frappe.db.rollback()
+ set_task_status(self.request.id, "Failed")
+ if not frappe.flags.in_test:
+ frappe.db.commit()
+
+ ret = frappe.local.response
+ http_status_code = getattr(e, "http_status_code", 500)
+ ret['status_code'] = http_status_code
+ ret['exc'] = frappe.get_traceback()
+ task_logger.error('Exception in running {}: {}'.format(cmd, ret['exc']))
+ else:
+ set_task_status(self.request.id, "Finished", response=ret)
+ if not frappe.flags.in_test:
+ frappe.db.commit()
+ finally:
+ sys.stdout.write('\n' + END_LINE)
+ sys.stderr.write('\n' + END_LINE)
+ if not frappe.flags.in_test:
+ frappe.destroy()
+ sys.stdout.close()
+ sys.stderr.close()
+ sys.stdout, sys.stderr = 1, 0
+ return ret
diff --git a/frappe/tests/test_async.py b/frappe/tests/test_async.py
new file mode 100644
index 0000000000..67077a1a2d
--- /dev/null
+++ b/frappe/tests/test_async.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import unittest
+import frappe
+
+from frappe.tasks import run_async_task
+
+class TestAsync(unittest.TestCase):
+
+ def test_response(self):
+ result = run_async_task(frappe.local.site, 'Administrator', 'async_ping', frappe._dict())
+ self.assertEquals(result.message, "pong")
diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py
index fee581762f..f9fa9966e6 100644
--- a/frappe/utils/__init__.py
+++ b/frappe/utils/__init__.py
@@ -400,3 +400,4 @@ def get_request_session(max_retries=3):
session.mount("http://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
session.mount("https://", requests.adapters.HTTPAdapter(max_retries=Retry(total=5, status_forcelist=[500])))
return session
+
diff --git a/requirements.txt b/requirements.txt
index ba31f3ce9d..8520a37397 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -28,3 +28,4 @@ html2text
email_reply_parser
click
num2words
+gevent-socketio
diff --git a/socketio.js b/socketio.js
new file mode 100644
index 0000000000..47068197c4
--- /dev/null
+++ b/socketio.js
@@ -0,0 +1,67 @@
+var app = require('express')();
+var http = require('http').Server(app);
+var io = require('socket.io')(http);
+var cookie = require('cookie')
+
+var redis = require("redis")
+var subscriber = redis.createClient(12311);
+var r = redis.createClient(12311);
+
+var request = require('superagent')
+
+app.get('/', function(req, res){
+ res.sendfile('index.html');
+});
+
+io.on('connection', function(socket){
+ socket.on('task_subscribe', function(task_id) {
+ var room = 'task:' + task_id;
+ socket.join(room);
+ })
+ socket.on('progress_subscribe', function(task_id) {
+ var room = 'task_progress:' + task_id;
+ socket.join(room);
+ send_existing_lines(task_id, socket);
+ })
+ socket.on('doc_subscribe', function(doctype, docname) {
+ var sid = cookie.parse(socket.request.headers.cookie).sid
+ if(!sid) {
+ return;
+ }
+ request.post('http://localhost:8000/api/method/frappe.async.can_subscribe_doc')
+ .type('form')
+ .send({
+ sid: sid,
+ doctype: doctype,
+ docname: docname
+ })
+ .end(function(err, res) {
+ if(res.status == 200) {
+ socket.join('doc:'+ doctype + '/' + docname);
+ }
+ })
+ })
+});
+
+function send_existing_lines(task_id, socket) {
+ r.hgetall('task_log:' + task_id, function(err, lines) {
+ socket.emit('task_progress', {
+ "task_id": task_id,
+ "message": {
+ "lines": lines
+ }
+ })
+ })
+}
+
+
+subscriber.on("message", function(channel, message) {
+ message = JSON.parse(message);
+ io.to(message.room).emit(message.event, message.message);
+});
+
+subscriber.subscribe("events");
+
+http.listen(3000, function(){
+ console.log('listening on *:3000');
+});
From b0d333d85b3a8c34277afa9b8e96f41ac577d376 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 16 Jul 2015 12:55:10 +0530
Subject: [PATCH 02/47] Join and leave doc room on form-load and form-unload
---
frappe/public/js/frappe/socket.js | 20 ++++++++++++++++++++
frappe/public/js/legacy/form.js | 6 ++++++
socketio.js | 13 +++++++++++--
3 files changed, 37 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index c264292788..7dffe9ce04 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -8,12 +8,27 @@ frappe.socket = {
frappe.socket.setup_listeners();
frappe.socket.setup_reconnect();
+ $(document).on('form-load', function(e, frm) {
+ frappe.socket.doc_subscribe(frm.doctype, frm.docname);
+ });
+
+ $(document).on('form-unload', function(e, frm) {
+ frappe.socket.doc_unsubscribe(frm.doctype, frm.docname);
+ });
},
subscribe: function(task_id, opts) {
frappe.socket.socket.emit('task_subscribe', task_id);
frappe.socket.socket.emit('progress_subscribe', task_id);
frappe.socket.open_tasks[task_id] = opts;
+ },
+ doc_subscribe: function(doctype, docname) {
+ frappe.socket.socket.emit('doc_subscribe', doctype, docname);
+ frappe.socket.open_doc = {doctype: doctype, docname: docname};
+ },
+ doc_unsubscribe: function(doctype, docname) {
+ frappe.socket.socket.emit('doc_unsubscribe', doctype, docname);
+ frappe.socket.open_doc = null;
},
setup_listeners: function() {
frappe.socket.socket.on('task_status_change', function(data) {
@@ -37,6 +52,10 @@ frappe.socket = {
frappe.socket.subscribe(task_id, opts);
});
});
+
+ if(frappe.socket.open_doc) {
+ frappe.socket.doc_subscribe(frappe.socket.open_doc.doctype, frappe.socket.open_doc.docname);
+ }
},
process_response: function(data, method) {
if(!data) {
@@ -58,3 +77,4 @@ frappe.socket = {
}
$(frappe.socket.init);
+
diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js
index a7ef72e5c8..9cb2c44446 100644
--- a/frappe/public/js/legacy/form.js
+++ b/frappe/public/js/legacy/form.js
@@ -391,8 +391,13 @@ _f.Frm.prototype.refresh = function(docname) {
// load the record for the first time, if not loaded (call 'onload')
cur_frm.cscript.is_onload = false;
if(!this.opendocs[this.docname]) {
+ var me = this;
cur_frm.cscript.is_onload = true;
this.setnewdoc();
+ $(document).trigger("form-load", [this]);
+ $(this.page.wrapper).on('hide', function(e) {
+ $(document).trigger("form-unload", [me]);
+ })
} else {
this.render_form(is_a_different_doc);
}
@@ -815,3 +820,4 @@ _f.Frm.prototype.validate_form_action = function(action) {
_f.Frm.prototype.get_handlers = function(fieldname, doctype, docname) {
return this.script_manager.get_handlers(fieldname, doctype || this.doctype, docname || this.docname)
}
+
diff --git a/socketio.js b/socketio.js
index 47068197c4..b84a5ba96a 100644
--- a/socketio.js
+++ b/socketio.js
@@ -37,10 +37,15 @@ io.on('connection', function(socket){
})
.end(function(err, res) {
if(res.status == 200) {
- socket.join('doc:'+ doctype + '/' + docname);
+ var room = get_doc_room(doctype, docname);
+ socket.join(room);
}
})
- })
+ });
+ socket.on('doc_unsubscribe', function(doctype, docname) {
+ var room = get_doc_room(doctype, docname);
+ socket.leave(room);
+ });
});
function send_existing_lines(task_id, socket) {
@@ -65,3 +70,7 @@ subscriber.subscribe("events");
http.listen(3000, function(){
console.log('listening on *:3000');
});
+
+function get_doc_room(doctype, docname) {
+ return 'doc:'+ doctype + '/' + docname;
+}
From dc3f3b7f0846ddccf3408498e92fdcd3d076f864 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 16 Jul 2015 14:21:01 +0530
Subject: [PATCH 03/47] Async comments WIP
---
frappe/async.py | 6 ++++++
frappe/hooks.py | 3 +++
frappe/public/js/frappe/socket.js | 6 ++++++
3 files changed, 15 insertions(+)
diff --git a/frappe/async.py b/frappe/async.py
index 48ebdcea76..38c367ce3c 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -173,3 +173,9 @@ def can_subscribe_doc(doctype, docname, sid):
if not frappe.has_permission(user=session.user, doctype=doctype, doc=docname, ptype='read'):
raise PermissionError()
return True
+
+def new_comment(doc, event):
+ emit_via_redis('new_comment', doc.as_dict(), room=get_doc_room(doc.comment_doctype, doc.comment_docname))
+
+def get_doc_room(doctype, docname):
+ return ''.join(['doc:', doctype, '/', docname])
diff --git a/frappe/hooks.py b/frappe/hooks.py
index bfbefba78f..110ee781e8 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -131,6 +131,9 @@ doc_events = {
"frappe.email.doctype.email_alert.email_alert.trigger_email_alerts"
],
"on_trash": "frappe.desk.notifications.clear_doctype_notifications"
+ },
+ "Comment": {
+ "after_insert": "frappe.async.new_comment"
}
}
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index 7dffe9ce04..37adc11130 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -43,6 +43,12 @@ frappe.socket = {
frappe.socket.socket.on('task_progress', function(data) {
frappe.socket.process_response(data, "progress");
});
+ frappe.socket.socket.on('new_comment', function(comment) {
+ if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) {
+ cur_frm.get_docinfo().comments = cur_frm.comments.get_comments().concat([comment]);
+ cur_frm.comments.refresh();
+ }
+ });
},
setup_reconnect: function() {
From 264144c2db534908610cac7042086b72514a02c5 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 16 Jul 2015 14:44:30 +0530
Subject: [PATCH 04/47] Avoid double addition of comment for sender
---
frappe/public/js/frappe/form/footer/timeline.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js
index 723c910e6c..8215f2ae79 100644
--- a/frappe/public/js/frappe/form/footer/timeline.js
+++ b/frappe/public/js/frappe/form/footer/timeline.js
@@ -227,8 +227,6 @@ frappe.ui.form.Comments = Class.extend({
btn: btn,
callback: function(r) {
if(!r.exc) {
- me.frm.get_docinfo().comments =
- me.get_comments().concat([r.message]);
me.input.val("");
me.refresh(true);
}
From f8bc9877b6fbc785a403475286c34163df4d4541 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Fri, 17 Jul 2015 02:08:56 +0530
Subject: [PATCH 05/47] SocketIO push messages
---
frappe/async.py | 23 +++++++++++++++++++++--
frappe/desk/page/messages/messages.js | 27 +--------------------------
frappe/public/js/frappe/desk.js | 4 ----
frappe/public/js/frappe/socket.js | 16 ++++++++++++++++
socketio.js | 25 +++++++++++++++++++++----
5 files changed, 59 insertions(+), 36 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index 38c367ce3c..edc030d088 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -9,7 +9,7 @@ import frappe
import os
import time
from functools import wraps
-from frappe.utils import get_site_path
+from frappe.utils import get_site_path, get_url
import json
from frappe import conf
@@ -174,8 +174,27 @@ def can_subscribe_doc(doctype, docname, sid):
raise PermissionError()
return True
+@frappe.whitelist(allow_guest=True)
+def get_user_info(sid):
+ from frappe.sessions import Session
+ session = Session(None).get_session_data()
+ return {
+ 'user': session.user,
+ }
+
def new_comment(doc, event):
- emit_via_redis('new_comment', doc.as_dict(), room=get_doc_room(doc.comment_doctype, doc.comment_docname))
+ if doc.comment_doctype == 'Message':
+ if doc.comment_docname == frappe.session.user:
+ message = doc.as_dict()
+ message['broadcast'] = True
+ emit_via_redis('new_message', message, room=get_url())
+ else:
+ emit_via_redis('new_message', doc.as_dict(), room=get_user_room(doc.comment_docname))
+ else:
+ emit_via_redis('new_comment', doc.as_dict(), room=get_doc_room(doc.comment_doctype, doc.comment_docname))
def get_doc_room(doctype, docname):
return ''.join(['doc:', doctype, '/', docname])
+
+def get_user_room(user):
+ return ''.join(['user:', user])
diff --git a/frappe/desk/page/messages/messages.js b/frappe/desk/page/messages/messages.js
index d7ead8937a..cc518fae83 100644
--- a/frappe/desk/page/messages/messages.js
+++ b/frappe/desk/page/messages/messages.js
@@ -37,7 +37,6 @@ frappe.desk.pages.Messages = Class.extend({
make: function() {
this.make_sidebar();
- this.set_next_refresh();
},
make_sidebar: function() {
@@ -155,31 +154,7 @@ frappe.desk.pages.Messages = Class.extend({
});
},
- refresh: function() {
- // check for updates every 5 seconds if page is active
- this.set_next_refresh();
-
- if(!frappe.session_alive) {
- // not in session
- return;
- }
-
- if(frappe.get_route()[0]!="messages") {
- // not on messages page
- return;
- }
-
- if (this.list) {
- this.list.run();
- }
- },
-
- set_next_refresh: function() {
- // 30 seconds
- setTimeout("frappe.desk.pages.messages.refresh()", 30000);
- },
-
- ////
+ refresh: function() {},
get_contact: function() {
var route = location.hash;
diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js
index 8e5c45559b..7e5f596d17 100644
--- a/frappe/public/js/frappe/desk.js
+++ b/frappe/public/js/frappe/desk.js
@@ -126,10 +126,6 @@ frappe.Application = Class.extend({
if(frappe.get_route()[0] != "messages") {
if(r.message.new_messages.length) {
- $.each(r.message.new_messages, function(i, m) {
- frappe.utils.notify(__("Message from {0}", [m.comment_by_fullname]),
- m.comment);
- });
frappe.utils.set_title_prefix("(" + r.message.new_messages.length + ")");
}
}
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index 37adc11130..9022164c54 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -49,6 +49,22 @@ frappe.socket = {
cur_frm.comments.refresh();
}
});
+ frappe.socket.socket.on('new_message', function(comment) {
+ frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
+ if ($(cur_page.page).data('page-route') === 'messages') {
+ console.log('messages page open');
+ var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
+ var on_broadcast_page = current_contact === user;
+ if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) {
+ var $row = $('');
+ frappe.desk.pages.messages.list.data.unshift(comment);
+ frappe.desk.pages.messages.list.render_row($row, comment);
+ frappe.desk.pages.messages.list.parent.prepend($row);
+ }
+ }
+ else {
+ }
+ });
},
setup_reconnect: function() {
diff --git a/socketio.js b/socketio.js
index b84a5ba96a..3fabac99b0 100644
--- a/socketio.js
+++ b/socketio.js
@@ -14,6 +14,22 @@ app.get('/', function(req, res){
});
io.on('connection', function(socket){
+ socket.join(socket.request.headers.origin);
+ var sid = cookie.parse(socket.request.headers.cookie).sid
+ if(!sid) {
+ return;
+ }
+ request.post('http://localhost:8000/api/method/frappe.async.get_user_info')
+ .type('form')
+ .send({
+ sid: sid
+ })
+ .end(function(err, res) {
+ if(res.status == 200) {
+ var room = get_user_room(res.body.message.user);
+ socket.join(room);
+ }
+ })
socket.on('task_subscribe', function(task_id) {
var room = 'task:' + task_id;
socket.join(room);
@@ -24,10 +40,6 @@ io.on('connection', function(socket){
send_existing_lines(task_id, socket);
})
socket.on('doc_subscribe', function(doctype, docname) {
- var sid = cookie.parse(socket.request.headers.cookie).sid
- if(!sid) {
- return;
- }
request.post('http://localhost:8000/api/method/frappe.async.can_subscribe_doc')
.type('form')
.send({
@@ -63,6 +75,7 @@ function send_existing_lines(task_id, socket) {
subscriber.on("message", function(channel, message) {
message = JSON.parse(message);
io.to(message.room).emit(message.event, message.message);
+ console.log(message.room, message.event, message.message)
});
subscriber.subscribe("events");
@@ -74,3 +87,7 @@ http.listen(3000, function(){
function get_doc_room(doctype, docname) {
return 'doc:'+ doctype + '/' + docname;
}
+
+function get_user_room(user) {
+ return 'user:' + user;
+}
From bd2cad08bc49db1f2b5284e5ca5c59d712f709d4 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Fri, 17 Jul 2015 10:20:45 +0530
Subject: [PATCH 06/47] Make data import tool async WIP
---
frappe/core/page/data_import_tool/importer.py | 3 ++-
frappe/public/js/frappe/socket.js | 6 +++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/frappe/core/page/data_import_tool/importer.py b/frappe/core/page/data_import_tool/importer.py
index f05a7638f4..8947d29032 100644
--- a/frappe/core/page/data_import_tool/importer.py
+++ b/frappe/core/page/data_import_tool/importer.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe, json
import frappe.permissions
+import frappe.async
from frappe import _
@@ -14,7 +15,7 @@ from frappe.utils.dateutils import parse_date
from frappe.utils import cint, cstr, flt
from frappe.core.page.data_import_tool.data_import_tool import get_data_keys
-@frappe.whitelist()
+@frappe.async.handler
def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=None,
ignore_links=False, pre_process=None):
"""upload data"""
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index 9022164c54..a2273fdfcf 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -85,13 +85,13 @@ frappe.socket = {
}
if(data) {
var opts = frappe.socket.open_tasks[data.task_id];
- if(opts[method]) opts[method](data.message);
+ if(opts[method]) opts[method](data);
}
if(opts.always) {
- opts.always(data.message);
+ opts.always(data);
}
if(data.status_code && status_code > 400 && opts.error) {
- opts.error(data.message);
+ opts.error(data);
return;
}
From 907841c33e0afd995b8b26758d8f7db63edf10cd Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Tue, 21 Jul 2015 13:55:28 +0530
Subject: [PATCH 07/47] Add router for Async tasks
---
frappe/celery_app.py | 11 +++++++++--
frappe/tasks.py | 13 ++++++++++---
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/frappe/celery_app.py b/frappe/celery_app.py
index 06a7a41b39..1165898c66 100644
--- a/frappe/celery_app.py
+++ b/frappe/celery_app.py
@@ -20,6 +20,7 @@ DEFAULT_CELERY_BROKER = "redis://localhost"
DEFAULT_CELERY_BACKEND = "redis://localhost"
DEFAULT_SCHEDULER_INTERVAL = 300
LONGJOBS_PREFIX = "longjobs@"
+ASYNC_TASKS_PREFIX = "async@"
_app = None
def get_celery():
@@ -29,7 +30,7 @@ def get_celery():
_app = Celery('frappe',
broker=conf.celery_broker or DEFAULT_CELERY_BROKER,
- backend=conf.celery_result_backend or DEFAULT_CELERY_BACKEND)
+ backend=conf.async_redis_server or DEFAULT_CELERY_BACKEND)
setup_celery(_app, conf)
@@ -42,9 +43,10 @@ def setup_celery(app, conf):
app.conf.CELERY_ACCEPT_CONTENT = ['json']
app.conf.CELERY_TIMEZONE = 'UTC'
app.conf.CELERY_RESULT_SERIALIZER = 'json'
+ app.CELERY_TASK_RESULT_EXPIRES = timedelta(0, 3600)
if conf.celery_queue_per_site:
- app.conf.CELERY_ROUTES = (SiteRouter(),)
+ app.conf.CELERY_ROUTES = (SiteRouter(), AsyncTaskRouter())
app.conf.CELERYBEAT_SCHEDULE = get_beat_schedule(conf)
@@ -62,6 +64,11 @@ class SiteRouter(object):
return get_queue(frappe.local.site)
return None
+
+class AsyncTaskRouter(object):
+ def route_for_task(self, task, args=None, kwargs=None):
+ if task == "frappe.tasks.run_async_task" and hasattr(frappe.local, 'site'):
+ return get_queue(frappe.local.site, ASYNC_TASKS_PREFIX)
def get_queue(site, prefix=None):
return {'queue': "{}{}".format(prefix or "", site)}
diff --git a/frappe/tasks.py b/frappe/tasks.py
index 52b1a33740..10561bb7b3 100644
--- a/frappe/tasks.py
+++ b/frappe/tasks.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
from frappe.utils.scheduler import enqueue_events
-from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX
+from frappe.celery_app import get_celery, celery_task, task_logger, LONGJOBS_PREFIX, ASYNC_TASKS_PREFIX
from frappe.utils import get_sites
from frappe.utils.file_lock import create_lock, delete_lock
from frappe.handler import execute_cmd
@@ -16,7 +16,7 @@ import sys
def sync_queues():
"""notifies workers to monitor newly added sites"""
app = get_celery()
- shortjob_workers, longjob_workers = get_workers(app)
+ shortjob_workers, longjob_workers, async_tasks_workers = get_workers(app)
if shortjob_workers:
for worker in shortjob_workers:
@@ -26,18 +26,25 @@ def sync_queues():
for worker in longjob_workers:
sync_worker(app, worker, prefix=LONGJOBS_PREFIX)
+ if async_tasks_workers:
+ for worker in async_tasks_workers:
+ sync_worker(app, worker, prefix=ASYNC_TASKS_PREFIX)
+
def get_workers(app):
longjob_workers = []
shortjob_workers = []
+ async_tasks_workers = []
active_queues = app.control.inspect().active_queues()
for worker in active_queues:
if worker.startswith(LONGJOBS_PREFIX):
longjob_workers.append(worker)
+ elif worker.startswith(ASYNC_TASKS_PREFIX):
+ async_tasks_workers.append(worker)
else:
shortjob_workers.append(worker)
- return shortjob_workers, longjob_workers
+ return shortjob_workers, longjob_workers, async_tasks_workers
def sync_worker(app, worker, prefix=''):
active_queues = set(get_active_queues(app, worker))
From 43360c54d5e30f93aaf3378021988b6a999e3f84 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Tue, 21 Jul 2015 13:56:47 +0530
Subject: [PATCH 08/47] Expire task logs in one hour
---
frappe/async.py | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index edc030d088..131c641939 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -118,10 +118,20 @@ def emit_via_redis(event, message, room=None):
r.publish('events', json.dumps({'event': event, 'message': message, 'room': room}))
-def put_log(task_id, line_no, line):
+def put_log(line_no, line, task_id=None):
r = get_redis_server()
- print "task_log:" + task_id
- r.hset("task_log:" + task_id, line_no, line)
+ if not task_id:
+ task_id = frappe.local.task_id
+ task_progress_room = "task_progress:" + frappe.local.task_id
+ task_log_key = "task_log:" + task_id
+ emit_via_redis('task_progress', {
+ "message": {
+ "lines": {line_no: line}
+ },
+ "task_id": task_id
+ }, room=task_progress_room)
+ r.hset(task_log_key, line_no, line)
+ r.expire(task_log_key, 3600)
def get_redis_server():
@@ -129,7 +139,7 @@ def get_redis_server():
global redis_server
if not redis_server:
from redis import Redis
- redis_server = Redis.from_url(conf.get("cache_redis_server") or "redis://localhost:12311")
+ redis_server = Redis.from_url(conf.get("async_redis_server") or "redis://localhost:12311")
return redis_server
From a2cba96938f731264b4a551d14cdd618f6e0b79b Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Wed, 22 Jul 2015 11:04:33 +0530
Subject: [PATCH 09/47] Fix write
---
frappe/async.py | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index 131c641939..0fa909ba56 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -152,14 +152,7 @@ class FileAndRedisStream(file):
def write(self, data):
ret = super(FileAndRedisStream, self).write(data)
if frappe.local.task_id:
- emit_via_redis('task_progress', {
- "message": {
- "lines": {self.count: data}
- },
- "task_id": frappe.local.task_id
- }, room="task_progress:" + frappe.local.task_id)
-
- put_log(frappe.local.task_id, self.count, data)
+ put_log(self.count, data, task_id=frappe.local.task_id)
self.count += 1
return ret
From bd8b0e96863e197237f9c2802645de5f463f0bac Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Wed, 22 Jul 2015 11:06:27 +0530
Subject: [PATCH 10/47] Add no_async
---
frappe/async.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/frappe/async.py b/frappe/async.py
index 0fa909ba56..c30db09694 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -23,6 +23,9 @@ def handler(f):
def _run(args, set_in_response=True):
from frappe.tasks import run_async_task
+ from frappe.handler import execute_cmd
+ if frappe.conf.no_async:
+ return execute_cmd(cmd, async=True)
args = frappe._dict(args)
task = run_async_task.delay(frappe.local.site,
(frappe.session and frappe.session.user) or 'Administrator', cmd, args)
@@ -33,6 +36,9 @@ def handler(f):
@wraps(f)
def _f(*args, **kwargs):
from frappe.tasks import run_async_task
+ from frappe.handler import execute_cmd
+ if frappe.conf.no_async:
+ return execute_cmd(cmd, async=True)
task = run_async_task.delay(frappe.local.site,
(frappe.session and frappe.session.user) or 'Administrator', cmd,
frappe.local.form_dict)
From 88f9281e3b5359f35d0c43c2b77b300c6b7557af Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Wed, 22 Jul 2015 11:44:09 +0530
Subject: [PATCH 11/47] Namespace rooms to site
---
frappe/async.py | 10 +++++---
socketio.js | 66 ++++++++++++++++++++++++++++++++++++++++++-------
2 files changed, 64 insertions(+), 12 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index c30db09694..27ffb0e1c6 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -196,14 +196,18 @@ def new_comment(doc, event):
if doc.comment_docname == frappe.session.user:
message = doc.as_dict()
message['broadcast'] = True
- emit_via_redis('new_message', message, room=get_url())
+ emit_via_redis('new_message', message, room=get_site_room())
else:
emit_via_redis('new_message', doc.as_dict(), room=get_user_room(doc.comment_docname))
else:
emit_via_redis('new_comment', doc.as_dict(), room=get_doc_room(doc.comment_doctype, doc.comment_docname))
def get_doc_room(doctype, docname):
- return ''.join(['doc:', doctype, '/', docname])
+ return ''.join([frappe.local.site, ':doc:', doctype, '/', docname])
def get_user_room(user):
- return ''.join(['user:', user])
+ return ''.join([frappe.local.site, ':user:', user])
+
+def get_site_room():
+ return ''.join([frappe.local.site, ':all'])
+
diff --git a/socketio.js b/socketio.js
index 3fabac99b0..2c361664d5 100644
--- a/socketio.js
+++ b/socketio.js
@@ -2,31 +2,46 @@ var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var cookie = require('cookie')
+var fs = require('fs');
var redis = require("redis")
var subscriber = redis.createClient(12311);
var r = redis.createClient(12311);
var request = require('superagent')
+var default_site;
+
+
+
+if(fs.existsSync('sites/currentsite.txt')) {
+ default_site = fs.readFileSync('sites/currentsite.txt').toString().trim();
+}
app.get('/', function(req, res){
res.sendfile('index.html');
});
io.on('connection', function(socket){
- socket.join(socket.request.headers.origin);
+ if (get_hostname(socket.request.headers.host) != get_hostname(socket.request.headers.origin)) {
+ return;
+ }
var sid = cookie.parse(socket.request.headers.cookie).sid
if(!sid) {
return;
}
- request.post('http://localhost:8000/api/method/frappe.async.get_user_info')
+ request.post(get_url(socket, '/api/method/frappe.async.get_user_info'))
.type('form')
.send({
sid: sid
})
.end(function(err, res) {
+ if(err) {
+ console.log(err);
+ return;
+ }
if(res.status == 200) {
- var room = get_user_room(res.body.message.user);
+ var room = get_user_room(socket, res.body.message.user);
+ // console.log('joining', room);
socket.join(room);
}
})
@@ -55,7 +70,7 @@ io.on('connection', function(socket){
})
});
socket.on('doc_unsubscribe', function(doctype, docname) {
- var room = get_doc_room(doctype, docname);
+ var room = get_doc_room(socket, doctype, docname);
socket.leave(room);
});
});
@@ -75,7 +90,7 @@ function send_existing_lines(task_id, socket) {
subscriber.on("message", function(channel, message) {
message = JSON.parse(message);
io.to(message.room).emit(message.event, message.message);
- console.log(message.room, message.event, message.message)
+ // console.log(message.room, message.event, message.message)
});
subscriber.subscribe("events");
@@ -84,10 +99,43 @@ http.listen(3000, function(){
console.log('listening on *:3000');
});
-function get_doc_room(doctype, docname) {
- return 'doc:'+ doctype + '/' + docname;
+function get_doc_room(socket, doctype, docname) {
+ return get_site_room(socket) + ':doc:'+ doctype + '/' + docname;
+}
+
+function get_user_room(socket, user) {
+ return get_site_room(socket) + ':user:' + user;
+}
+
+function get_site_room(socket) {
+ return get_site_name(socket) + ':all';
+}
+
+function get_site_name(socket) {
+ if (default_site) {
+ return default_site;
+ }
+ else if (socket.request.headers['x-frappe-site-name']) {
+ return get_hostname(socket.request.headers['x-frappe-site-name']);
+ }
+ else if (socket.request.headers.origin) {
+ return get_hostname(socket.request.headers.origin);
+ }
+ else {
+ return get_hostname(socket.request.headers.host);
+ }
+}
+
+function get_hostname(url) {
+ if (url.indexOf("://") > -1) {
+ url = url.split('/')[2];
+ }
+ return ( url.match(/:/g) ) ? url.slice( 0, url.indexOf(":") ) : url
}
-function get_user_room(user) {
- return 'user:' + user;
+function get_url(socket, path) {
+ if (!path) {
+ path = '';
+ }
+ return socket.request.headers.origin + path;
}
From e713d0060006fa72fee7ca16682635cd64d7e2d6 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Wed, 22 Jul 2015 11:45:25 +0530
Subject: [PATCH 12/47] Connect socketio to port 3000 if running a dev server
---
frappe/public/js/frappe/socket.js | 5 +++--
frappe/sessions.py | 2 ++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index a2273fdfcf..e869a0ccfd 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -1,7 +1,8 @@
frappe.socket = {
open_tasks: {},
- init: function() {
- frappe.socket.socket = io.connect('http://' + document.domain + ':' + 3000);
+ init: function() {
+ var socketio_server = frappe.boot.dev_server? '//' + document.domain + ':3000' : '//' + document.domain + ':' + window.location.port;
+ frappe.socket.socket = io.connect(socketio_server);
frappe.socket.socket.on('msgprint', function(message) {
frappe.msgprint(message)
});
diff --git a/frappe/sessions.py b/frappe/sessions.py
index 850c6c8cd2..cdc948b64a 100644
--- a/frappe/sessions.py
+++ b/frappe/sessions.py
@@ -17,6 +17,7 @@ import frappe.defaults
import frappe.translate
from frappe.utils.change_log import get_change_log
import redis
+import os
from urllib import unquote
@frappe.whitelist()
@@ -124,6 +125,7 @@ def get():
frappe.get_attr(hook)(bootinfo=bootinfo)
bootinfo["lang"] = frappe.translate.get_user_lang()
+ bootinfo["dev_server"] = os.environ.get('DEV_SERVER', False)
return bootinfo
class Session:
From d0dcb6110ec7a99c93b6f05d309d3fb3d68a2013 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 23 Jul 2015 11:45:45 +0530
Subject: [PATCH 13/47] Fix site room
---
socketio.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/socketio.js b/socketio.js
index 2c361664d5..8dbe0b9578 100644
--- a/socketio.js
+++ b/socketio.js
@@ -55,7 +55,8 @@ io.on('connection', function(socket){
send_existing_lines(task_id, socket);
})
socket.on('doc_subscribe', function(doctype, docname) {
- request.post('http://localhost:8000/api/method/frappe.async.can_subscribe_doc')
+ // console.log('trying to subscribe', doctype, docname)
+ request.post(get_url(socket, '/api/method/frappe.async.can_subscribe_doc'))
.type('form')
.send({
sid: sid,
@@ -63,8 +64,10 @@ io.on('connection', function(socket){
docname: docname
})
.end(function(err, res) {
+ console.log(err)
if(res.status == 200) {
- var room = get_doc_room(doctype, docname);
+ var room = get_doc_room(socket, doctype, docname);
+ // console.log('joining', room)
socket.join(room);
}
})
@@ -100,7 +103,7 @@ http.listen(3000, function(){
});
function get_doc_room(socket, doctype, docname) {
- return get_site_room(socket) + ':doc:'+ doctype + '/' + docname;
+ return get_site_name(socket) + ':doc:'+ doctype + '/' + docname;
}
function get_user_room(socket, user) {
From 40d1c608c5d251453bfb9b26ebbe94f4b80a1948 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 23 Jul 2015 12:28:32 +0530
Subject: [PATCH 14/47] Bump version to 6.0.0-wip
---
setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/setup.py b/setup.py
index cd68a5b602..796367e206 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = "5.1.3"
+version = "6.0.0-wip"
with open("requirements.txt", "r") as f:
install_requires = f.readlines()
From ebe66e8746f30455785b4c50ffd0c38659c8b274 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 23 Jul 2015 15:22:51 +0530
Subject: [PATCH 15/47] Fallback to ajax if no_async and append comments for
opendocs
---
frappe/public/js/frappe/form/footer/timeline.js | 6 ++++++
frappe/public/js/frappe/socket.js | 11 ++++++++---
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js
index 8215f2ae79..31b0346f71 100644
--- a/frappe/public/js/frappe/form/footer/timeline.js
+++ b/frappe/public/js/frappe/form/footer/timeline.js
@@ -227,6 +227,12 @@ frappe.ui.form.Comments = Class.extend({
btn: btn,
callback: function(r) {
if(!r.exc) {
+ var comment_exists = !!$.map(me.get_comments(), function(x) { return x.name == r.message.name? true : undefined}).length
+ if (comment_exists) {
+ return;
+ }
+ me.frm.get_docinfo().comments =
+ me.get_comments().concat([r.message]);
me.input.val("");
me.refresh(true);
}
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index e869a0ccfd..e23dabc3e7 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -45,15 +45,20 @@ frappe.socket = {
frappe.socket.process_response(data, "progress");
});
frappe.socket.socket.on('new_comment', function(comment) {
+ if (frappe.model.docinfo[comment.comment_doctype] && frappe.model.docinfo[comment.comment_doctype][comment.comment_docname]) {
+ var comments = frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments
+ var comment_exists = !!$.map(comments, function(x) { return x.name == comment.name? true : undefined}).length
+ if (!comment_exists) {
+ frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments = comments.concat([comment]);
+ }
+ }
if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) {
- cur_frm.get_docinfo().comments = cur_frm.comments.get_comments().concat([comment]);
- cur_frm.comments.refresh();
+ cur_frm.comments.refresh();
}
});
frappe.socket.socket.on('new_message', function(comment) {
frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
if ($(cur_page.page).data('page-route') === 'messages') {
- console.log('messages page open');
var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
var on_broadcast_page = current_contact === user;
if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) {
From 05eaba9cba51d6230f715c5ef36295f820415700 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 23 Jul 2015 15:23:47 +0530
Subject: [PATCH 16/47] Don't connect to socketio if no_async
---
frappe/public/js/frappe/socket.js | 3 +++
frappe/sessions.py | 1 +
2 files changed, 4 insertions(+)
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index e23dabc3e7..e1774f1c95 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -1,6 +1,9 @@
frappe.socket = {
open_tasks: {},
init: function() {
+ if (frappe.boot.no_async) {
+ return;
+ }
var socketio_server = frappe.boot.dev_server? '//' + document.domain + ':3000' : '//' + document.domain + ':' + window.location.port;
frappe.socket.socket = io.connect(socketio_server);
frappe.socket.socket.on('msgprint', function(message) {
diff --git a/frappe/sessions.py b/frappe/sessions.py
index cdc948b64a..b611ab3984 100644
--- a/frappe/sessions.py
+++ b/frappe/sessions.py
@@ -126,6 +126,7 @@ def get():
bootinfo["lang"] = frappe.translate.get_user_lang()
bootinfo["dev_server"] = os.environ.get('DEV_SERVER', False)
+ bootinfo["no_async"] = frappe.conf.no_async
return bootinfo
class Session:
From a6755663377a892a400e67bf2cca62ad8eccf95d Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Fri, 24 Jul 2015 09:57:06 +0530
Subject: [PATCH 17/47] Ignore ConnectionError in emit_via_redis
---
frappe/async.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/frappe/async.py b/frappe/async.py
index 27ffb0e1c6..629dc442c8 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -8,6 +8,7 @@ from __future__ import unicode_literals
import frappe
import os
import time
+import redis
from functools import wraps
from frappe.utils import get_site_path, get_url
import json
@@ -121,7 +122,10 @@ def is_file_old(file_path):
def emit_via_redis(event, message, room=None):
r = get_redis_server()
- r.publish('events', json.dumps({'event': event, 'message': message, 'room': room}))
+ try:
+ r.publish('events', json.dumps({'event': event, 'message': message, 'room': room}))
+ except redis.exceptions.ConnectionError:
+ pass
def put_log(line_no, line, task_id=None):
From c9a651fe13dabf24382162e6dd44975eeba36206 Mon Sep 17 00:00:00 2001
From: Tsutomu Mimori
Date: Thu, 23 Jul 2015 21:12:56 +0900
Subject: [PATCH 18/47] Removed HTML from messages
---
frappe/core/doctype/user/user.json | 6 +++---
frappe/custom/doctype/custom_field/custom_field.js | 2 +-
.../custom/doctype/customize_form/customize_form.json | 4 ++--
.../customize_form_field/customize_form_field.json | 4 ++--
frappe/public/js/frappe/ui/toolbar/awesome_bar.js | 10 +++++-----
.../doctype/website_settings/website_settings.json | 4 ++--
.../website/doctype/website_theme/website_theme.json | 6 +++---
frappe/workflow/doctype/workflow/workflow.json | 4 ++--
8 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 51fc55df99..78cd7451aa 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -154,7 +154,7 @@
"permlevel": 0
},
{
- "description": "Get your globally recognized avatar from Gravatar.com",
+ "description": "Get your globally recognized avatar from Gravatar.com",
"fieldname": "user_image",
"fieldtype": "Attach",
"hidden": 0,
@@ -326,7 +326,7 @@
"width": "50%"
},
{
- "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to Customize Form.",
+ "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".",
"fieldname": "defaults",
"fieldtype": "Table",
"hidden": 1,
@@ -516,4 +516,4 @@
],
"read_only": 0,
"search_fields": "first_name, last_name"
-}
\ No newline at end of file
+}
diff --git a/frappe/custom/doctype/custom_field/custom_field.js b/frappe/custom/doctype/custom_field/custom_field.js
index 6e154f6520..81c37ec3fc 100644
--- a/frappe/custom/doctype/custom_field/custom_field.js
+++ b/frappe/custom/doctype/custom_field/custom_field.js
@@ -52,7 +52,7 @@ cur_frm.cscript.fieldtype = function(doc, dt, dn) {
__('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer');
} else if(doc.fieldtype == 'Select') {
cur_frm.fields_dict['options_help'].disp_area.innerHTML =
- __('Options for select. Each option on a new line. e.g.:
Option 1
Option 2
Option 3
');
+ __('Options for select. Each option on a new line.')+' '+__('e.g.:')+'
'+__('Option 1')+'
'+__('Option 2')+'
'+__('Option 3')+'
';
} else if(doc.fieldtype == 'Dynamic Link') {
cur_frm.fields_dict['options_help'].disp_area.innerHTML =
__('Fieldname which will be the DocType for this link field.');
diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json
index 2334905ac4..5b0a3ec64a 100644
--- a/frappe/custom/doctype/customize_form/customize_form.json
+++ b/frappe/custom/doctype/customize_form/customize_form.json
@@ -33,7 +33,7 @@
"search_index": 0
},
{
- "description": "Fields separated by comma (,) will be included in the
Search By list of Search dialog box",
+ "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
"fieldname": "search_fields",
"fieldtype": "Data",
"in_list_view": 1,
@@ -133,4 +133,4 @@
}
],
"search_fields": "doc_type"
-}
\ No newline at end of file
+}
diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json
index 539178832d..6f7775385a 100644
--- a/frappe/custom/doctype/customize_form_field/customize_form_field.json
+++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json
@@ -119,7 +119,7 @@
"precision": ""
},
{
- "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples):
\nmyfield\neval:doc.myfield=='My Value'
\neval:doc.age>18",
+ "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
"fieldname": "depends_on",
"fieldtype": "Data",
"hidden": 0,
@@ -301,4 +301,4 @@
"owner": "Administrator",
"permissions": [],
"read_only": 0
-}
\ No newline at end of file
+}
diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js
index ff4955f084..c236e226c5 100644
--- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js
+++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js
@@ -84,15 +84,15 @@ frappe.search = {
onclick: function() {
var txt = '\
'+__("Make a new record")+' | '+
- __("new type of document")+' |
\
+ __("new type of document")+'\
'+__("List a document type")+' | '+
- __("document type..., e.g. customer")+' |
\
+ __("document type..., e.g. customer")+'\
'+__("Search in a document type")+' | '+
- __("text in document type")+' |
\
+ __("text in document type")+'\
'+__("Open a module or tool")+' | '+
- __("module name...")+' |
\
+ __("module name...")+'\
'+__("Calculate")+' | '+
- __("e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...")+' |
\
+ __("e.g. (55 + 434) / 4 or =Math.sin(Math.PI/2)...")+'\
'
msgprint(txt, "Search Help");
}
diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json
index 9df4654b72..ecb6f234bc 100644
--- a/frappe/website/doctype/website_settings/website_settings.json
+++ b/frappe/website/doctype/website_settings/website_settings.json
@@ -231,7 +231,7 @@
"permlevel": 0
},
{
- "description": "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]",
+ "description": "An icon file with .ico extension. Should be 16 x 16 px. Generated using a favicon generator. [favicon-generator.org]",
"fieldname": "favicon",
"fieldtype": "Attach",
"label": "FavIcon",
@@ -296,4 +296,4 @@
"submit": 0
}
]
-}
\ No newline at end of file
+}
diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json
index 5b8293ebce..8613ac9f65 100644
--- a/frappe/website/doctype/website_theme/website_theme.json
+++ b/frappe/website/doctype/website_theme/website_theme.json
@@ -110,7 +110,7 @@
"precision": ""
},
{
- "description": "Add the name of a Google Web Font e.g. \"Open Sans\"",
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
"fieldname": "text_webfont",
"fieldtype": "Data",
"label": "Google Font (Text)",
@@ -118,7 +118,7 @@
"precision": ""
},
{
- "description": "Add the name of a Google Web Font e.g. \"Open Sans\"",
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
"fieldname": "heading_webfont",
"fieldtype": "Data",
"label": "Google Font (Heading)",
@@ -301,4 +301,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"title_field": ""
-}
\ No newline at end of file
+}
diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json
index 00244cdd2e..2ef72fae8c 100644
--- a/frappe/workflow/doctype/workflow/workflow.json
+++ b/frappe/workflow/doctype/workflow/workflow.json
@@ -42,7 +42,7 @@
"permlevel": 0
},
{
- "description": "All possible Workflow States and roles of the workflow.
Docstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
+ "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
"fieldname": "states",
"fieldtype": "Table",
"label": "Document States",
@@ -97,4 +97,4 @@
"write": 1
}
]
-}
\ No newline at end of file
+}
From a6d3405ad41dc9ff35250c8d40741275ec3a2825 Mon Sep 17 00:00:00 2001
From: Tsutomu Mimori
Date: Thu, 23 Jul 2015 21:22:28 +0900
Subject: [PATCH 19/47] Removed HTML from messages 2
---
frappe/website/doctype/web_form/web_form.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index 54b3a42ba7..af2998fbfb 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -214,7 +214,7 @@
"precision": ""
},
{
- "description": "In JSON as [{\"title\":\"Jobs\", \"name\":\"jobs\"}]
",
+ "description": "In JSON as [{\"title\":\"Jobs\", \"name\":\"jobs\"}]",
"fieldname": "breadcrumbs",
"fieldtype": "Small Text",
"label": "Breadcrumbs",
@@ -262,4 +262,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title"
-}
\ No newline at end of file
+}
From 30993bd04a49a1516e9c471692ffd4354cd3a495 Mon Sep 17 00:00:00 2001
From: Anand Doshi
Date: Mon, 27 Jul 2015 12:15:05 +0530
Subject: [PATCH 20/47] [minor] changed modified for json files to affect #1215
---
frappe/core/doctype/user/user.json | 832 +++++++++---------
.../customize_form/customize_form.json | 202 ++---
.../customize_form_field.json | 490 +++++------
.../doctype/website_theme/website_theme.json | 474 +++++-----
.../workflow/doctype/workflow/workflow.json | 150 ++--
5 files changed, 1074 insertions(+), 1074 deletions(-)
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index 78cd7451aa..e1789d655d 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -1,519 +1,519 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "creation": "2014-03-11 14:55:00",
- "description": "Represents a User in the system.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
+ "allow_copy": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2014-03-11 14:55:00",
+ "description": "Represents a User in the system.",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Master",
"fields": [
{
- "fieldname": "sb0_5",
- "fieldtype": "Section Break",
- "label": "",
+ "fieldname": "sb0_5",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "default": "1",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "in_list_view": 0,
- "label": "Enabled",
- "no_copy": 0,
- "oldfieldname": "enabled",
- "oldfieldtype": "Check",
- "permlevel": 0,
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "in_list_view": 0,
+ "label": "Enabled",
+ "no_copy": 0,
+ "oldfieldname": "enabled",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "section_break_3",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
- {
- "fieldname": "email",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Email",
- "no_copy": 1,
- "oldfieldname": "email",
- "oldfieldtype": "Data",
- "options": "Email",
- "permlevel": 0,
- "reqd": 1,
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Email",
+ "no_copy": 1,
+ "oldfieldname": "email",
+ "oldfieldtype": "Data",
+ "options": "Email",
+ "permlevel": 0,
+ "reqd": 1,
"search_index": 0
- },
- {
- "fieldname": "first_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "First Name",
- "no_copy": 0,
- "oldfieldname": "first_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "First Name",
+ "no_copy": 0,
+ "oldfieldname": "first_name",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
"reqd": 1
- },
- {
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "label": "Middle Name (Optional)",
- "no_copy": 0,
- "oldfieldname": "middle_name",
- "oldfieldtype": "Data",
+ },
+ {
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "label": "Middle Name (Optional)",
+ "no_copy": 0,
+ "oldfieldname": "middle_name",
+ "oldfieldtype": "Data",
"permlevel": 0
- },
- {
- "fieldname": "last_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "Last Name",
- "oldfieldname": "last_name",
- "oldfieldtype": "Data",
+ },
+ {
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "Last Name",
+ "oldfieldname": "last_name",
+ "oldfieldtype": "Data",
"permlevel": 0
- },
- {
- "default": "1",
- "depends_on": "eval:doc.__islocal",
- "fieldname": "send_welcome_email",
- "fieldtype": "Check",
- "label": "Send Welcome Email",
- "permlevel": 0,
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:doc.__islocal",
+ "fieldname": "send_welcome_email",
+ "fieldtype": "Check",
+ "label": "Send Welcome Email",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "unsubscribed",
- "fieldtype": "Check",
- "hidden": 1,
- "label": "Unsubscribed",
- "no_copy": 1,
+ "fieldname": "unsubscribed",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Unsubscribed",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_width": "50%",
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "permlevel": 0,
+ "print_width": "50%",
"width": "50%"
- },
+ },
{
- "description": "",
- "fieldname": "language",
- "fieldtype": "Select",
- "label": "Language",
- "options": "Loading...",
+ "description": "",
+ "fieldname": "language",
+ "fieldtype": "Select",
+ "label": "Language",
+ "options": "Loading...",
"permlevel": 0
- },
+ },
{
- "description": "",
- "fieldname": "time_zone",
- "fieldtype": "Select",
- "label": "Timezone",
+ "description": "",
+ "fieldname": "time_zone",
+ "fieldtype": "Select",
+ "label": "Timezone",
"permlevel": 0
- },
+ },
{
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "change_password",
- "fieldtype": "Section Break",
- "label": "",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "change_password",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
+ },
{
- "fieldname": "new_password",
- "fieldtype": "Password",
- "label": "Set New Password",
- "no_copy": 1,
+ "fieldname": "new_password",
+ "fieldtype": "Password",
+ "label": "Set New Password",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "depends_on": "",
- "fieldname": "send_password_update_notification",
- "fieldtype": "Check",
- "label": "Send Password Update Notification",
- "permlevel": 0,
+ "depends_on": "",
+ "fieldname": "send_password_update_notification",
+ "fieldtype": "Check",
+ "label": "Send Password Update Notification",
+ "permlevel": 0,
"precision": ""
- },
- {
- "fieldname": "reset_password_key",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Reset Password Key",
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 1,
+ },
+ {
+ "fieldname": "reset_password_key",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Reset Password Key",
+ "no_copy": 1,
+ "permlevel": 0,
+ "print_hide": 1,
"read_only": 1
- },
+ },
{
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "display_settings",
- "fieldtype": "Section Break",
- "label": "",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "display_settings",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "description": "Get your globally recognized avatar from Gravatar.com",
- "fieldname": "user_image",
- "fieldtype": "Attach",
- "hidden": 0,
- "label": "User Image",
- "no_copy": 1,
+ },
+ {
+ "description": "Get your globally recognized avatar from Gravatar.com",
+ "fieldname": "user_image",
+ "fieldtype": "Attach",
+ "hidden": 0,
+ "label": "User Image",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "cb21",
- "fieldtype": "Column Break",
+ "fieldname": "cb21",
+ "fieldtype": "Column Break",
"permlevel": 0
- },
+ },
{
- "fieldname": "user_image_show",
- "fieldtype": "Image",
- "label": "user_image_show",
- "options": "user_image",
+ "fieldname": "user_image_show",
+ "fieldtype": "Image",
+ "label": "user_image_show",
+ "options": "user_image",
"permlevel": 0
- },
+ },
{
- "fieldname": "email_settings",
- "fieldtype": "Section Break",
- "label": "Email Settings",
- "permlevel": 0,
+ "fieldname": "email_settings",
+ "fieldtype": "Section Break",
+ "label": "Email Settings",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "1",
- "fieldname": "thread_notify",
- "fieldtype": "Check",
- "label": "Send Notifications for Transactions I Follow",
- "permlevel": 0,
+ "default": "1",
+ "fieldname": "thread_notify",
+ "fieldtype": "Check",
+ "label": "Send Notifications for Transactions I Follow",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "email_signature",
- "fieldtype": "Small Text",
- "label": "Email Signature",
- "no_copy": 1,
+ "fieldname": "email_signature",
+ "fieldtype": "Small Text",
+ "label": "Email Signature",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "background",
- "fieldtype": "Section Break",
- "label": "",
- "permlevel": 0,
+ "fieldname": "background",
+ "fieldtype": "Section Break",
+ "label": "",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_image",
- "fieldtype": "Attach",
- "label": "Background Image",
- "permlevel": 0,
+ "fieldname": "background_image",
+ "fieldtype": "Attach",
+ "label": "Background Image",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_style",
- "fieldtype": "Select",
- "label": "Background Style",
- "options": "Fill Screen\nTile",
- "permlevel": 0,
+ "fieldname": "background_style",
+ "fieldtype": "Select",
+ "label": "Background Style",
+ "options": "Fill Screen\nTile",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "short_bio",
- "fieldtype": "Section Break",
- "label": "",
+ "fieldname": "short_bio",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "fieldname": "gender",
- "fieldtype": "Select",
- "label": "Gender",
- "oldfieldname": "gender",
- "oldfieldtype": "Select",
- "options": "\nMale\nFemale\nOther",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "gender",
+ "fieldtype": "Select",
+ "label": "Gender",
+ "oldfieldname": "gender",
+ "oldfieldtype": "Select",
+ "options": "\nMale\nFemale\nOther",
+ "permlevel": 0,
"search_index": 0
- },
- {
- "fieldname": "birth_date",
- "fieldtype": "Date",
- "label": "Birth Date",
- "no_copy": 1,
- "oldfieldname": "birth_date",
- "oldfieldtype": "Date",
+ },
+ {
+ "fieldname": "birth_date",
+ "fieldtype": "Date",
+ "label": "Birth Date",
+ "no_copy": 1,
+ "oldfieldname": "birth_date",
+ "oldfieldtype": "Date",
"permlevel": 0
- },
+ },
{
- "fieldname": "location",
- "fieldtype": "Data",
- "label": "Location",
- "no_copy": 1,
+ "fieldname": "location",
+ "fieldtype": "Data",
+ "label": "Location",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break_22",
- "fieldtype": "Column Break",
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break",
"permlevel": 0
- },
+ },
{
- "fieldname": "bio",
- "fieldtype": "Small Text",
- "label": "Bio",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "bio",
+ "fieldtype": "Small Text",
+ "label": "Bio",
+ "no_copy": 1,
+ "permlevel": 0,
"set_only_once": 0
- },
+ },
{
- "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.",
- "fieldname": "sb1",
- "fieldtype": "Section Break",
- "label": "Roles",
- "permlevel": 1,
+ "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.",
+ "fieldname": "sb1",
+ "fieldtype": "Section Break",
+ "label": "Roles",
+ "permlevel": 1,
"read_only": 1
- },
+ },
{
- "fieldname": "roles_html",
- "fieldtype": "HTML",
- "label": "Roles HTML",
- "permlevel": 0,
+ "fieldname": "roles_html",
+ "fieldtype": "HTML",
+ "label": "Roles HTML",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "fieldname": "user_roles",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "Roles Assigned",
- "options": "UserRole",
- "permlevel": 1,
- "print_hide": 1,
+ },
+ {
+ "fieldname": "user_roles",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Roles Assigned",
+ "options": "UserRole",
+ "permlevel": 1,
+ "print_hide": 1,
"read_only": 1
- },
- {
- "default": "",
- "description": "Uncheck modules to hide from user's desktop",
- "fieldname": "modules_access",
- "fieldtype": "Section Break",
- "label": "Modules Access",
- "permlevel": 1,
+ },
+ {
+ "default": "",
+ "description": "Uncheck modules to hide from user's desktop",
+ "fieldname": "modules_access",
+ "fieldtype": "Section Break",
+ "label": "Modules Access",
+ "permlevel": 1,
"precision": ""
- },
+ },
{
- "fieldname": "modules_html",
- "fieldtype": "HTML",
- "label": "Modules HTML",
- "permlevel": 1,
+ "fieldname": "modules_html",
+ "fieldtype": "HTML",
+ "label": "Modules HTML",
+ "permlevel": 1,
"precision": ""
- },
- {
- "fieldname": "block_modules",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "Block Modules",
- "options": "Block Module",
- "permlevel": 1,
+ },
+ {
+ "fieldname": "block_modules",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Block Modules",
+ "options": "Block Module",
+ "permlevel": 1,
"precision": ""
- },
- {
- "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.",
- "fieldname": "sb2",
- "fieldtype": "Section Break",
- "hidden": 1,
- "label": "Defaults",
- "oldfieldtype": "Column Break",
- "permlevel": 1,
- "print_width": "50%",
- "read_only": 1,
+ },
+ {
+ "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.",
+ "fieldname": "sb2",
+ "fieldtype": "Section Break",
+ "hidden": 1,
+ "label": "Defaults",
+ "oldfieldtype": "Column Break",
+ "permlevel": 1,
+ "print_width": "50%",
+ "read_only": 1,
"width": "50%"
- },
- {
- "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".",
- "fieldname": "defaults",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "User Defaults",
- "no_copy": 1,
- "options": "DefaultValue",
+ },
+ {
+ "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".",
+ "fieldname": "defaults",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "User Defaults",
+ "no_copy": 1,
+ "options": "DefaultValue",
"permlevel": 0
- },
+ },
{
- "fieldname": "sb3",
- "fieldtype": "Section Break",
- "label": "Security Settings",
- "oldfieldtype": "Section Break",
- "permlevel": 0,
+ "fieldname": "sb3",
+ "fieldtype": "Section Break",
+ "label": "Security Settings",
+ "oldfieldtype": "Section Break",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "default": "System User",
- "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ",
- "fieldname": "user_type",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "User Type",
- "oldfieldname": "user_type",
- "oldfieldtype": "Select",
- "options": "System User\nWebsite User",
- "permlevel": 0,
- "read_only": 1,
+ },
+ {
+ "default": "System User",
+ "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ",
+ "fieldname": "user_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "User Type",
+ "oldfieldname": "user_type",
+ "oldfieldtype": "Select",
+ "options": "System User\nWebsite User",
+ "permlevel": 0,
+ "read_only": 1,
"reqd": 1
- },
+ },
{
- "description": "Allow user to login only after this hour (0-24)",
- "fieldname": "login_after",
- "fieldtype": "Int",
- "label": "Login After",
- "permlevel": 0,
+ "description": "Allow user to login only after this hour (0-24)",
+ "fieldname": "login_after",
+ "fieldtype": "Int",
+ "label": "Login After",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "description": "Allow user to login only before this hour (0-24)",
- "fieldname": "login_before",
- "fieldtype": "Int",
- "label": "Login Before",
- "permlevel": 0,
+ "description": "Allow user to login only before this hour (0-24)",
+ "fieldname": "login_before",
+ "fieldtype": "Int",
+ "label": "Login Before",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)",
- "fieldname": "restrict_ip",
- "fieldtype": "Data",
- "label": "Restrict IP",
- "permlevel": 0,
+ "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)",
+ "fieldname": "restrict_ip",
+ "fieldtype": "Data",
+ "label": "Restrict IP",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_width": "50%",
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "permlevel": 0,
+ "print_width": "50%",
"width": "50%"
- },
- {
- "fieldname": "last_login",
- "fieldtype": "Read Only",
- "hidden": 0,
- "label": "Last Login",
- "no_copy": 1,
- "oldfieldname": "last_login",
- "oldfieldtype": "Read Only",
- "permlevel": 0,
- "read_only": 1,
- "reqd": 0,
+ },
+ {
+ "fieldname": "last_login",
+ "fieldtype": "Read Only",
+ "hidden": 0,
+ "label": "Last Login",
+ "no_copy": 1,
+ "oldfieldname": "last_login",
+ "oldfieldtype": "Read Only",
+ "permlevel": 0,
+ "read_only": 1,
+ "reqd": 0,
"search_index": 0
- },
- {
- "fieldname": "last_ip",
- "fieldtype": "Read Only",
- "label": "Last IP",
- "no_copy": 1,
- "oldfieldname": "last_ip",
- "oldfieldtype": "Read Only",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "last_ip",
+ "fieldtype": "Read Only",
+ "label": "Last IP",
+ "no_copy": 1,
+ "oldfieldname": "last_ip",
+ "oldfieldtype": "Read Only",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.",
- "fieldname": "last_known_versions",
- "fieldtype": "Text",
- "hidden": 1,
- "label": "Last Known Versions",
- "permlevel": 0,
- "precision": "",
+ },
+ {
+ "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.",
+ "fieldname": "last_known_versions",
+ "fieldtype": "Text",
+ "hidden": 1,
+ "label": "Last Known Versions",
+ "permlevel": 0,
+ "precision": "",
"read_only": 1
- },
+ },
{
- "fieldname": "third_party_authentication",
- "fieldtype": "Section Break",
- "label": "Third Party Authentication",
+ "fieldname": "third_party_authentication",
+ "fieldtype": "Section Break",
+ "label": "Third Party Authentication",
"permlevel": 1
- },
+ },
{
- "fieldname": "fb_username",
- "fieldtype": "Data",
- "label": "Facebook Username",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "fb_username",
+ "fieldtype": "Data",
+ "label": "Facebook Username",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "fb_userid",
- "fieldtype": "Data",
- "label": "Facebook User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "fb_userid",
+ "fieldtype": "Data",
+ "label": "Facebook User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "google_userid",
- "fieldtype": "Data",
- "label": "Google User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "google_userid",
+ "fieldtype": "Data",
+ "label": "Google User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "column_break_49",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_49",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "github_userid",
- "fieldtype": "Data",
- "label": "Github User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "github_userid",
+ "fieldtype": "Data",
+ "label": "Github User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "github_username",
- "fieldtype": "Data",
- "label": "Github Username",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "github_username",
+ "fieldtype": "Data",
+ "label": "Github Username",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-user",
- "idx": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 5,
- "modified": "2015-06-01 01:00:32.901851",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "User",
- "owner": "Administrator",
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "icon": "icon-user",
+ "idx": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 5,
+ "modified": "2015-07-27 01:00:32.901851",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User",
+ "owner": "Administrator",
"permissions": [
{
- "create": 1,
- "delete": 1,
- "email": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 0,
"write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "permlevel": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "submit": 0,
+ },
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "permlevel": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "submit": 0,
"write": 1
}
- ],
- "read_only": 0,
+ ],
+ "read_only": 0,
"search_fields": "first_name, last_name"
}
diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json
index 5b0a3ec64a..8b45d72288 100644
--- a/frappe/custom/doctype/customize_form/customize_form.json
+++ b/frappe/custom/doctype/customize_form/customize_form.json
@@ -1,136 +1,136 @@
{
- "autoname": "DL.####",
- "creation": "2013-01-29 17:55:08",
- "docstatus": 0,
- "doctype": "DocType",
+ "autoname": "DL.####",
+ "creation": "2013-01-29 17:55:08",
+ "docstatus": 0,
+ "doctype": "DocType",
"fields": [
{
- "fieldname": "doc_type",
- "fieldtype": "Link",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Enter Form Type",
- "no_copy": 0,
- "options": "DocType",
- "permlevel": 0,
+ "fieldname": "doc_type",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Enter Form Type",
+ "no_copy": 0,
+ "options": "DocType",
+ "permlevel": 0,
"search_index": 0
- },
+ },
{
- "depends_on": "doc_type",
- "fieldname": "properties",
- "fieldtype": "Section Break",
- "label": "",
+ "depends_on": "doc_type",
+ "fieldname": "properties",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
+ },
{
- "fieldname": "default_print_format",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Default Print Format",
- "no_copy": 0,
- "options": "Print Format",
- "permlevel": 0,
+ "fieldname": "default_print_format",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Default Print Format",
+ "no_copy": 0,
+ "options": "Print Format",
+ "permlevel": 0,
"search_index": 0
- },
+ },
{
- "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
- "fieldname": "search_fields",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Search Fields",
- "no_copy": 0,
- "permlevel": 0,
+ "description": "Fields separated by comma (,) will be included in the \"Search By\" list of Search dialog box",
+ "fieldname": "search_fields",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Search Fields",
+ "no_copy": 0,
+ "permlevel": 0,
"search_index": 0
- },
+ },
{
- "fieldname": "column_break_5",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "",
- "fieldname": "max_attachments",
- "fieldtype": "Int",
- "label": "Max Attachments",
- "no_copy": 0,
- "permlevel": 0,
+ "depends_on": "",
+ "fieldname": "max_attachments",
+ "fieldtype": "Int",
+ "label": "Max Attachments",
+ "no_copy": 0,
+ "permlevel": 0,
"search_index": 0
- },
+ },
{
- "fieldname": "allow_copy",
- "fieldtype": "Check",
- "label": "Hide Copy",
- "no_copy": 0,
- "permlevel": 0,
+ "fieldname": "allow_copy",
+ "fieldtype": "Check",
+ "label": "Hide Copy",
+ "no_copy": 0,
+ "permlevel": 0,
"search_index": 0
- },
+ },
{
- "depends_on": "doc_type",
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "doc_type",
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "sort_field",
- "fieldtype": "Select",
- "label": "Sort Field",
+ "fieldname": "sort_field",
+ "fieldtype": "Select",
+ "label": "Sort Field",
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break_10",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "sort_order",
- "fieldtype": "Select",
- "label": "Sort Order",
- "options": "ASC\nDESC",
+ "fieldname": "sort_order",
+ "fieldtype": "Select",
+ "label": "Sort Order",
+ "options": "ASC\nDESC",
"permlevel": 0
- },
+ },
{
- "depends_on": "doc_type",
- "description": "Customize Label, Print Hide, Default etc.",
- "fieldname": "fields_section_break",
- "fieldtype": "Section Break",
- "label": "Fields",
+ "depends_on": "doc_type",
+ "description": "Customize Label, Print Hide, Default etc.",
+ "fieldname": "fields_section_break",
+ "fieldtype": "Section Break",
+ "label": "Fields",
"permlevel": 0
- },
+ },
{
- "fieldname": "fields",
- "fieldtype": "Table",
- "label": "Fields",
- "no_copy": 0,
- "options": "Customize Form Field",
- "permlevel": 0,
+ "fieldname": "fields",
+ "fieldtype": "Table",
+ "label": "Fields",
+ "no_copy": 0,
+ "options": "Customize Form Field",
+ "permlevel": 0,
"search_index": 0
}
- ],
- "hide_toolbar": 1,
- "icon": "icon-glass",
- "idx": 1,
- "issingle": 1,
- "modified": "2015-03-25 06:18:19.010091",
- "modified_by": "Administrator",
- "module": "Custom",
- "name": "Customize Form",
- "owner": "Administrator",
+ ],
+ "hide_toolbar": 1,
+ "icon": "icon-glass",
+ "idx": 1,
+ "issingle": 1,
+ "modified": "2015-07-27 01:00:32.901851",
+ "modified_by": "Administrator",
+ "module": "Custom",
+ "name": "Customize Form",
+ "owner": "Administrator",
"permissions": [
{
- "create": 1,
- "email": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 0,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 0,
"write": 1
}
- ],
+ ],
"search_fields": "doc_type"
}
diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json
index 6f7775385a..356fd1d1a0 100644
--- a/frappe/custom/doctype/customize_form_field/customize_form_field.json
+++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json
@@ -1,304 +1,304 @@
{
- "allow_copy": 0,
- "autoname": "hash",
- "creation": "2013-02-22 01:27:32",
- "docstatus": 0,
- "doctype": "DocType",
+ "allow_copy": 0,
+ "autoname": "hash",
+ "creation": "2013-02-22 01:27:32",
+ "docstatus": 0,
+ "doctype": "DocType",
"fields": [
{
- "fieldname": "label_and_type",
- "fieldtype": "Section Break",
- "label": "Label and Type",
- "permlevel": 0,
+ "fieldname": "label_and_type",
+ "fieldtype": "Section Break",
+ "label": "Label and Type",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "label",
- "fieldtype": "Data",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Label",
- "oldfieldname": "label",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 0,
+ "fieldname": "label",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Label",
+ "oldfieldname": "label",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 0,
"search_index": 1
- },
+ },
{
- "default": "Data",
- "fieldname": "fieldtype",
- "fieldtype": "Select",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Type",
- "oldfieldname": "fieldtype",
- "oldfieldtype": "Select",
- "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 1,
+ "default": "Data",
+ "fieldname": "fieldtype",
+ "fieldtype": "Select",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Type",
+ "oldfieldname": "fieldtype",
+ "oldfieldtype": "Select",
+ "options": "Attach\nButton\nCheck\nCode\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nHTML\nImage\nInt\nLink\nLong Text\nPassword\nPercent\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nText\nText Editor\nTime",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 1,
"search_index": 1
- },
+ },
{
- "fieldname": "fieldname",
- "fieldtype": "Data",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Name",
- "oldfieldname": "fieldname",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "read_only": 1,
- "reqd": 0,
+ "fieldname": "fieldname",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Name",
+ "oldfieldname": "fieldname",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
+ "read_only": 1,
+ "reqd": 0,
"search_index": 1
- },
+ },
{
- "fieldname": "reqd",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Mandatory",
- "oldfieldname": "reqd",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "print_width": "50px",
- "reqd": 0,
- "search_index": 0,
+ "fieldname": "reqd",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Mandatory",
+ "oldfieldname": "reqd",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_width": "50px",
+ "reqd": 0,
+ "search_index": 0,
"width": "50px"
- },
+ },
{
- "fieldname": "unique",
- "fieldtype": "Check",
- "label": "Unique",
- "permlevel": 0,
+ "fieldname": "unique",
+ "fieldtype": "Check",
+ "label": "Unique",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "in_list_view",
- "fieldtype": "Check",
- "label": "In List View",
+ "fieldname": "in_list_view",
+ "fieldtype": "Check",
+ "label": "In List View",
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break_7",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
- "description": "Set non-standard precision for a Float or Currency field",
- "fieldname": "precision",
- "fieldtype": "Select",
- "label": "Precision",
- "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9",
- "permlevel": 0,
+ "depends_on": "eval:in_list([\"Float\", \"Currency\", \"Percent\"], doc.fieldtype)",
+ "description": "Set non-standard precision for a Float or Currency field",
+ "fieldname": "precision",
+ "fieldtype": "Select",
+ "label": "Precision",
+ "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
- "fieldname": "options",
- "fieldtype": "Text",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Options",
- "oldfieldname": "options",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 0,
+ "description": "For Links, enter the DocType as range.\nFor Select, enter list of Options, each on a new line.",
+ "fieldname": "options",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Options",
+ "oldfieldname": "options",
+ "oldfieldtype": "Text",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 0,
"search_index": 0
- },
+ },
{
- "fieldname": "permissions",
- "fieldtype": "Section Break",
- "label": "Permissions",
- "permlevel": 0,
+ "fieldname": "permissions",
+ "fieldtype": "Section Break",
+ "label": "Permissions",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
- "fieldname": "depends_on",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Depends On",
- "oldfieldname": "depends_on",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
+ "description": "This field will appear only if the fieldname defined here has value OR the rules are true (examples): \nmyfield\neval:doc.myfield=='My Value'\neval:doc.age>18",
+ "fieldname": "depends_on",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Depends On",
+ "oldfieldname": "depends_on",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
"reqd": 0
- },
+ },
{
- "default": "0",
- "fieldname": "permlevel",
- "fieldtype": "Int",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Perm Level",
- "oldfieldname": "permlevel",
- "oldfieldtype": "Int",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 0,
+ "default": "0",
+ "fieldname": "permlevel",
+ "fieldtype": "Int",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Perm Level",
+ "oldfieldname": "permlevel",
+ "oldfieldtype": "Int",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 0,
"search_index": 0
- },
+ },
{
- "fieldname": "hidden",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Hidden",
- "oldfieldname": "hidden",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "print_width": "50px",
- "reqd": 0,
- "search_index": 0,
+ "fieldname": "hidden",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Hidden",
+ "oldfieldname": "hidden",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_width": "50px",
+ "reqd": 0,
+ "search_index": 0,
"width": "50px"
- },
+ },
{
- "fieldname": "column_break_14",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_14",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "ignore_user_permissions",
- "fieldtype": "Check",
- "label": "Ignore User Permissions",
+ "fieldname": "ignore_user_permissions",
+ "fieldtype": "Check",
+ "label": "Ignore User Permissions",
"permlevel": 0
- },
+ },
{
- "fieldname": "allow_on_submit",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Allow on Submit",
- "oldfieldname": "allow_on_submit",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
+ "fieldname": "allow_on_submit",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Allow on Submit",
+ "oldfieldname": "allow_on_submit",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
"reqd": 0
- },
+ },
{
- "fieldname": "report_hide",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Report Hide",
- "oldfieldname": "report_hide",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
+ "fieldname": "report_hide",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Report Hide",
+ "oldfieldname": "report_hide",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
"reqd": 0
- },
+ },
{
- "fieldname": "display",
- "fieldtype": "Section Break",
- "label": "Display",
- "permlevel": 0,
+ "fieldname": "display",
+ "fieldtype": "Section Break",
+ "label": "Display",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "default",
- "fieldtype": "Text",
- "hidden": 0,
- "label": "Default",
- "oldfieldname": "default",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 0,
+ "fieldname": "default",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "label": "Default",
+ "oldfieldname": "default",
+ "oldfieldtype": "Text",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 0,
"search_index": 0
- },
+ },
{
- "fieldname": "in_filter",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "In Filter",
- "oldfieldname": "in_filter",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "print_width": "50px",
- "reqd": 0,
+ "fieldname": "in_filter",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "In Filter",
+ "oldfieldname": "in_filter",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_width": "50px",
+ "reqd": 0,
"width": "50px"
- },
+ },
{
- "fieldname": "column_break_21",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_21",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "description",
- "fieldtype": "Text",
- "hidden": 0,
- "label": "Description",
- "oldfieldname": "description",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_width": "300px",
- "reqd": 0,
+ "fieldname": "description",
+ "fieldtype": "Text",
+ "hidden": 0,
+ "label": "Description",
+ "oldfieldname": "description",
+ "oldfieldtype": "Text",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_width": "300px",
+ "reqd": 0,
"width": "300px"
- },
+ },
{
- "fieldname": "print_hide",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Print Hide",
- "oldfieldname": "print_hide",
- "oldfieldtype": "Check",
- "permlevel": 0,
- "print_hide": 0,
- "reqd": 0,
+ "fieldname": "print_hide",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Print Hide",
+ "oldfieldname": "print_hide",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
+ "print_hide": 0,
+ "reqd": 0,
"search_index": 0
- },
+ },
{
- "description": "Print Width of the field, if the field is a column in a table",
- "fieldname": "print_width",
- "fieldtype": "Data",
- "label": "Print Width",
- "permlevel": 0,
- "print_width": "50px",
+ "description": "Print Width of the field, if the field is a column in a table",
+ "fieldname": "print_width",
+ "fieldtype": "Data",
+ "label": "Print Width",
+ "permlevel": 0,
+ "print_width": "50px",
"width": "50px"
- },
+ },
{
- "fieldname": "width",
- "fieldtype": "Data",
- "hidden": 0,
- "in_list_view": 1,
- "label": "Width",
- "oldfieldname": "width",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_width": "50px",
- "reqd": 0,
- "search_index": 0,
+ "fieldname": "width",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "in_list_view": 1,
+ "label": "Width",
+ "oldfieldname": "width",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
+ "print_hide": 0,
+ "print_width": "50px",
+ "reqd": 0,
+ "search_index": 0,
"width": "50px"
- },
+ },
{
- "fieldname": "is_custom_field",
- "fieldtype": "Check",
- "hidden": 1,
- "label": "Is Custom Field",
- "permlevel": 0,
- "precision": "",
+ "fieldname": "is_custom_field",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Is Custom Field",
+ "permlevel": 0,
+ "precision": "",
"read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "issingle": 0,
- "istable": 1,
- "modified": "2015-04-24 11:37:52.879004",
- "modified_by": "Administrator",
- "module": "Custom",
- "name": "Customize Form Field",
- "owner": "Administrator",
- "permissions": [],
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "idx": 1,
+ "issingle": 0,
+ "istable": 1,
+ "modified": "2015-07-27 01:00:32.901851",
+ "modified_by": "Administrator",
+ "module": "Custom",
+ "name": "Customize Form Field",
+ "owner": "Administrator",
+ "permissions": [],
"read_only": 0
}
diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json
index 8613ac9f65..b01c2576ab 100644
--- a/frappe/website/doctype/website_theme/website_theme.json
+++ b/frappe/website/doctype/website_theme/website_theme.json
@@ -1,304 +1,304 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "field:theme",
- "creation": "2015-02-18 12:46:38.168929",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
+ "allow_copy": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "field:theme",
+ "creation": "2015-02-18 12:46:38.168929",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Master",
"fields": [
{
- "allow_on_submit": 0,
- "fieldname": "theme",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Theme",
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
+ "allow_on_submit": 0,
+ "fieldname": "theme",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Theme",
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 1,
"set_only_once": 0
- },
+ },
{
- "allow_on_submit": 0,
- "default": "Website",
- "fieldname": "module",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Module",
- "no_copy": 0,
- "options": "Module Def",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
+ "allow_on_submit": 0,
+ "default": "Website",
+ "fieldname": "module",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Module",
+ "no_copy": 0,
+ "options": "Module Def",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
"set_only_once": 0
- },
+ },
{
- "default": "1",
- "description": "This must be checked if the below style settings are applicable",
- "fieldname": "apply_style",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Apply Style",
- "permlevel": 0,
+ "default": "1",
+ "description": "This must be checked if the below style settings are applicable",
+ "fieldname": "apply_style",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Apply Style",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "1",
- "fieldname": "custom",
- "fieldtype": "Check",
- "label": "Custom?",
- "permlevel": 0,
+ "default": "1",
+ "fieldname": "custom",
+ "fieldtype": "Check",
+ "label": "Custom?",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "allow_on_submit": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
+ "allow_on_submit": 0,
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
"set_only_once": 0
- },
+ },
{
- "description": "Link to your Bootstrap theme",
- "fieldname": "bootstrap",
- "fieldtype": "Small Text",
- "label": "Link to Bootstrap CSS",
- "permlevel": 0,
+ "description": "Link to your Bootstrap theme",
+ "fieldname": "bootstrap",
+ "fieldtype": "Small Text",
+ "label": "Link to Bootstrap CSS",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "",
- "fieldname": "font_size",
- "fieldtype": "Select",
- "label": "Font Size",
- "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px",
- "permlevel": 0,
+ "default": "",
+ "fieldname": "font_size",
+ "fieldtype": "Select",
+ "label": "Font Size",
+ "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
- "fieldname": "text_webfont",
- "fieldtype": "Data",
- "label": "Google Font (Text)",
- "permlevel": 0,
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
+ "fieldname": "text_webfont",
+ "fieldtype": "Data",
+ "label": "Google Font (Text)",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
- "fieldname": "heading_webfont",
- "fieldtype": "Data",
- "label": "Google Font (Heading)",
- "permlevel": 0,
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
+ "fieldname": "heading_webfont",
+ "fieldtype": "Data",
+ "label": "Google Font (Heading)",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_18",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "text_color",
- "fieldtype": "Data",
- "label": "Text Color",
- "permlevel": 0,
+ "fieldname": "text_color",
+ "fieldtype": "Data",
+ "label": "Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "link_color",
- "fieldtype": "Data",
- "label": "Link Color",
- "permlevel": 0,
+ "fieldname": "link_color",
+ "fieldtype": "Data",
+ "label": "Link Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "heading_style",
- "fieldtype": "Select",
- "label": "Heading Style",
- "options": "\nUPPERCASE\nTitle Case\nlowercase",
- "permlevel": 0,
+ "fieldname": "heading_style",
+ "fieldtype": "Select",
+ "label": "Heading Style",
+ "options": "\nUPPERCASE\nTitle Case\nlowercase",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "top_bar_color",
- "fieldtype": "Data",
- "label": "Top Bar Color",
- "permlevel": 0,
+ "fieldname": "top_bar_color",
+ "fieldtype": "Data",
+ "label": "Top Bar Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "top_bar_text_color",
- "fieldtype": "Data",
- "label": "Top Bar Text Color",
- "permlevel": 0,
+ "fieldname": "top_bar_text_color",
+ "fieldtype": "Data",
+ "label": "Top Bar Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "no_sidebar",
- "fieldtype": "Check",
- "label": "Hide Sidebar",
- "permlevel": 0,
+ "fieldname": "no_sidebar",
+ "fieldtype": "Check",
+ "label": "Hide Sidebar",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "footer_color",
- "fieldtype": "Data",
- "label": "Footer Color",
- "permlevel": 0,
+ "fieldname": "footer_color",
+ "fieldtype": "Data",
+ "label": "Footer Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "footer_text_color",
- "fieldtype": "Data",
- "label": "Footer Text Color",
- "permlevel": 0,
+ "fieldname": "footer_text_color",
+ "fieldtype": "Data",
+ "label": "Footer Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_color",
- "fieldtype": "Data",
- "label": "Background Color",
- "permlevel": 0,
+ "fieldname": "background_color",
+ "fieldtype": "Data",
+ "label": "Background Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "If image is selected, color will be ignored.",
- "fieldname": "background_image",
- "fieldtype": "Attach Image",
- "label": "Background Image",
- "permlevel": 0,
+ "description": "If image is selected, color will be ignored.",
+ "fieldname": "background_image",
+ "fieldtype": "Attach Image",
+ "label": "Background Image",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "",
- "fieldname": "section_break_21",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "",
+ "fieldname": "section_break_21",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "description": "",
- "fieldname": "css",
- "fieldtype": "Code",
- "label": "Style using CSS",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "description": "",
+ "fieldname": "css",
+ "fieldtype": "Code",
+ "label": "Style using CSS",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "js",
- "fieldtype": "Code",
- "label": "JavaScript",
- "permlevel": 0,
+ "fieldname": "js",
+ "fieldtype": "Code",
+ "label": "JavaScript",
+ "permlevel": 0,
"precision": ""
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "in_create": 0,
- "in_dialog": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "modified": "2015-07-13 04:45:02.429785",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Theme",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "modified": "2015-07-27 01:00:32.901851",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Theme",
+ "name_case": "",
+ "owner": "Administrator",
"permissions": [
{
- "create": 1,
- "delete": 1,
- "permlevel": 0,
- "read": 1,
- "role": "Website Manager",
+ "create": 1,
+ "delete": 1,
+ "permlevel": 0,
+ "read": 1,
+ "role": "Website Manager",
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 0,
- "export": 1,
- "import": 1,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 0,
+ "export": 1,
+ "import": 1,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "Administrator",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
"write": 1
}
- ],
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "",
- "sort_field": "modified",
- "sort_order": "DESC",
+ ],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "search_fields": "",
+ "sort_field": "modified",
+ "sort_order": "DESC",
"title_field": ""
}
diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json
index 2ef72fae8c..1d5b2232a8 100644
--- a/frappe/workflow/doctype/workflow/workflow.json
+++ b/frappe/workflow/doctype/workflow/workflow.json
@@ -1,99 +1,99 @@
{
- "allow_rename": 1,
- "autoname": "field:workflow_name",
- "creation": "2012-12-28 10:49:55",
- "description": "Defines workflow states and rules for a document.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Transaction",
+ "allow_rename": 1,
+ "autoname": "field:workflow_name",
+ "creation": "2012-12-28 10:49:55",
+ "description": "Defines workflow states and rules for a document.",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Transaction",
"fields": [
{
- "fieldname": "workflow_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "Workflow Name",
- "permlevel": 0,
- "read_only": 0,
+ "fieldname": "workflow_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "Workflow Name",
+ "permlevel": 0,
+ "read_only": 0,
"reqd": 1
- },
+ },
{
- "description": "DocType on which this Workflow is applicable.",
- "fieldname": "document_type",
- "fieldtype": "Link",
- "in_list_view": 0,
- "label": "Document Type",
- "options": "DocType",
- "permlevel": 0,
+ "description": "DocType on which this Workflow is applicable.",
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 0,
+ "label": "Document Type",
+ "options": "DocType",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "description": "If checked, all other workflows become inactive.",
- "fieldname": "is_active",
- "fieldtype": "Check",
- "in_list_view": 0,
- "label": "Is Active",
+ "description": "If checked, all other workflows become inactive.",
+ "fieldname": "is_active",
+ "fieldtype": "Check",
+ "in_list_view": 0,
+ "label": "Is Active",
"permlevel": 0
- },
+ },
{
- "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.",
- "fieldname": "states_head",
- "fieldtype": "Section Break",
- "label": "States",
+ "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.",
+ "fieldname": "states_head",
+ "fieldtype": "Section Break",
+ "label": "States",
"permlevel": 0
- },
+ },
{
- "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
- "fieldname": "states",
- "fieldtype": "Table",
- "label": "Document States",
- "options": "Workflow Document State",
- "permlevel": 0,
+ "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
+ "fieldname": "states",
+ "fieldtype": "Table",
+ "label": "Document States",
+ "options": "Workflow Document State",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.",
- "fieldname": "transition_rules",
- "fieldtype": "Section Break",
- "label": "Transition Rules",
+ "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.",
+ "fieldname": "transition_rules",
+ "fieldtype": "Section Break",
+ "label": "Transition Rules",
"permlevel": 0
- },
+ },
{
- "description": "Rules defining transition of state in the workflow.",
- "fieldname": "transitions",
- "fieldtype": "Table",
- "label": "Transitions",
- "options": "Workflow Transition",
- "permlevel": 0,
+ "description": "Rules defining transition of state in the workflow.",
+ "fieldname": "transitions",
+ "fieldtype": "Table",
+ "label": "Transitions",
+ "options": "Workflow Transition",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "default": "workflow_state",
- "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)",
- "fieldname": "workflow_state_field",
- "fieldtype": "Data",
- "label": "Workflow State Field",
+ "default": "workflow_state",
+ "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)",
+ "fieldname": "workflow_state_field",
+ "fieldtype": "Data",
+ "label": "Workflow State Field",
"permlevel": 0
}
- ],
- "icon": "icon-random",
- "idx": 1,
- "modified": "2015-02-05 05:11:49.280959",
- "modified_by": "Administrator",
- "module": "Workflow",
- "name": "Workflow",
- "owner": "Administrator",
+ ],
+ "icon": "icon-random",
+ "idx": 1,
+ "modified": "2015-07-27 01:00:32.901851",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow",
+ "owner": "Administrator",
"permissions": [
{
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "role": "System Manager",
- "share": 1,
- "submit": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 0,
"write": 1
}
]
From f4f688f71cb2ed3d784d4f0c3b7978c27f172205 Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Mon, 27 Jul 2015 15:54:44 +0530
Subject: [PATCH 21/47] Change log added
---
frappe/change_log/current/if_owner.md | 1 -
1 file changed, 1 deletion(-)
delete mode 100644 frappe/change_log/current/if_owner.md
diff --git a/frappe/change_log/current/if_owner.md b/frappe/change_log/current/if_owner.md
deleted file mode 100644
index e1f6019797..0000000000
--- a/frappe/change_log/current/if_owner.md
+++ /dev/null
@@ -1 +0,0 @@
-- Ability to set permissions based on owner by checking **If Owner** in Role Permissions Manager
From 89d5bf594293e02a64b579c77e16715538f0d4d9 Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Mon, 27 Jul 2015 16:26:33 +0600
Subject: [PATCH 22/47] bumped to version 5.1.4
---
frappe/__version__.py | 2 +-
frappe/hooks.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/__version__.py b/frappe/__version__.py
index 3ef413303b..284940332b 100644
--- a/frappe/__version__.py
+++ b/frappe/__version__.py
@@ -1,2 +1,2 @@
from __future__ import unicode_literals
-__version__ = "5.1.3"
+__version__ = "5.1.4"
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 110ee781e8..54523705df 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -26,7 +26,7 @@ to ERPNext.
"""
app_icon = "octicon octicon-circuit-board"
-app_version = "5.1.3"
+app_version = "5.1.4"
app_color = "orange"
github_link = "https://github.com/frappe/frappe"
From 5b84651a546af949da11ae90ce3f3a26dddbc9f7 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Mon, 27 Jul 2015 16:20:12 +0530
Subject: [PATCH 23/47] [comment] [fix] clear input
---
frappe/public/js/frappe/form/footer/timeline.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js
index 31b0346f71..9e7d7138ef 100644
--- a/frappe/public/js/frappe/form/footer/timeline.js
+++ b/frappe/public/js/frappe/form/footer/timeline.js
@@ -227,13 +227,14 @@ frappe.ui.form.Comments = Class.extend({
btn: btn,
callback: function(r) {
if(!r.exc) {
- var comment_exists = !!$.map(me.get_comments(), function(x) { return x.name == r.message.name? true : undefined}).length
+ var comment_exists = !!$.map(me.get_comments(), function(x) {
+ return x.name == r.message.name? true : undefined}).length;
+ me.input.val("");
if (comment_exists) {
return;
}
me.frm.get_docinfo().comments =
me.get_comments().concat([r.message]);
- me.input.val("");
me.refresh(true);
}
}
From 3932cecf850da165afe24896004cb022e8287d80 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Mon, 27 Jul 2015 18:22:29 +0530
Subject: [PATCH 24/47] [fixes] wiring exections for async requests
---
frappe/patches.txt | 2 +
frappe/patches/v6_0/__init__.py | 0
frappe/patches/v6_0/make_task_log_folder.py | 6 +
frappe/public/js/frappe/socket.js | 185 ++++++++++----------
frappe/public/js/frappe/upload.js | 5 +
frappe/tasks.py | 5 +-
frappe/utils/response.py | 11 +-
7 files changed, 117 insertions(+), 97 deletions(-)
create mode 100644 frappe/patches/v6_0/__init__.py
create mode 100644 frappe/patches/v6_0/make_task_log_folder.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 535f0c8feb..5435054e67 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -83,3 +83,5 @@ frappe.patches.v5_0.modify_session
frappe.patches.v5_0.expire_old_scheduler_logs
execute:frappe.permissions.reset_perms("DocType")
execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'")
+
+frappe.patches.v6_0.make_task_log_folder
diff --git a/frappe/patches/v6_0/__init__.py b/frappe/patches/v6_0/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/patches/v6_0/make_task_log_folder.py b/frappe/patches/v6_0/make_task_log_folder.py
new file mode 100644
index 0000000000..f557b5c8a1
--- /dev/null
+++ b/frappe/patches/v6_0/make_task_log_folder.py
@@ -0,0 +1,6 @@
+import frappe.utils, os
+
+def execute():
+ path = frappe.utils.get_site_path('task-logs')
+ if not os.path.exists(path):
+ os.makedirs(path)
diff --git a/frappe/public/js/frappe/socket.js b/frappe/public/js/frappe/socket.js
index e1774f1c95..0d5a636c3a 100644
--- a/frappe/public/js/frappe/socket.js
+++ b/frappe/public/js/frappe/socket.js
@@ -1,111 +1,114 @@
frappe.socket = {
- open_tasks: {},
+ open_tasks: {},
init: function() {
if (frappe.boot.no_async) {
return;
}
- var socketio_server = frappe.boot.dev_server? '//' + document.domain + ':3000' : '//' + document.domain + ':' + window.location.port;
- frappe.socket.socket = io.connect(socketio_server);
- frappe.socket.socket.on('msgprint', function(message) {
- frappe.msgprint(message)
- });
+ var socketio_server = frappe.boot.dev_server? '//' + document.domain + ':3000' : '//' + document.domain + ':' + window.location.port;
+ frappe.socket.socket = io.connect(socketio_server);
+ frappe.socket.socket.on('msgprint', function(message) {
+ frappe.msgprint(message)
+ });
- frappe.socket.setup_listeners();
- frappe.socket.setup_reconnect();
- $(document).on('form-load', function(e, frm) {
- frappe.socket.doc_subscribe(frm.doctype, frm.docname);
- });
+ frappe.socket.setup_listeners();
+ frappe.socket.setup_reconnect();
+ $(document).on('form-load', function(e, frm) {
+ frappe.socket.doc_subscribe(frm.doctype, frm.docname);
+ });
- $(document).on('form-unload', function(e, frm) {
- frappe.socket.doc_unsubscribe(frm.doctype, frm.docname);
- });
- },
- subscribe: function(task_id, opts) {
- frappe.socket.socket.emit('task_subscribe', task_id);
- frappe.socket.socket.emit('progress_subscribe', task_id);
+ $(document).on('form-unload', function(e, frm) {
+ frappe.socket.doc_unsubscribe(frm.doctype, frm.docname);
+ });
+ },
+ subscribe: function(task_id, opts) {
+ frappe.socket.socket.emit('task_subscribe', task_id);
+ frappe.socket.socket.emit('progress_subscribe', task_id);
- frappe.socket.open_tasks[task_id] = opts;
- },
+ frappe.socket.open_tasks[task_id] = opts;
+ },
doc_subscribe: function(doctype, docname) {
frappe.socket.socket.emit('doc_subscribe', doctype, docname);
frappe.socket.open_doc = {doctype: doctype, docname: docname};
- },
+ },
doc_unsubscribe: function(doctype, docname) {
frappe.socket.socket.emit('doc_unsubscribe', doctype, docname);
frappe.socket.open_doc = null;
- },
- setup_listeners: function() {
- frappe.socket.socket.on('task_status_change', function(data) {
- if(data.status==="Running") {
- frappe.socket.process_response(data, "running");
- } else {
- // failed or finished
- frappe.socket.process_response(data, "callback");
- // delete frappe.socket.open_tasks[data.task_id];
- }
- });
- frappe.socket.socket.on('task_progress', function(data) {
- frappe.socket.process_response(data, "progress");
- });
- frappe.socket.socket.on('new_comment', function(comment) {
- if (frappe.model.docinfo[comment.comment_doctype] && frappe.model.docinfo[comment.comment_doctype][comment.comment_docname]) {
- var comments = frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments
- var comment_exists = !!$.map(comments, function(x) { return x.name == comment.name? true : undefined}).length
- if (!comment_exists) {
- frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments = comments.concat([comment]);
- }
- }
- if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) {
- cur_frm.comments.refresh();
- }
- });
- frappe.socket.socket.on('new_message', function(comment) {
- frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
- if ($(cur_page.page).data('page-route') === 'messages') {
- var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
- var on_broadcast_page = current_contact === user;
- if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) {
- var $row = $('');
- frappe.desk.pages.messages.list.data.unshift(comment);
- frappe.desk.pages.messages.list.render_row($row, comment);
- frappe.desk.pages.messages.list.parent.prepend($row);
- }
- }
- else {
- }
- });
+ },
+ setup_listeners: function() {
+ frappe.socket.socket.on('task_status_change', function(data) {
+ if(data.status==="Running") {
+ frappe.socket.process_response(data, "running");
+ } else {
+ // failed or finished
+ frappe.socket.process_response(data, "callback");
+ // delete frappe.socket.open_tasks[data.task_id];
+ }
+ });
+ frappe.socket.socket.on('task_progress', function(data) {
+ frappe.socket.process_response(data, "progress");
+ });
+ frappe.socket.socket.on('new_comment', function(comment) {
+ if (frappe.model.docinfo[comment.comment_doctype] && frappe.model.docinfo[comment.comment_doctype][comment.comment_docname]) {
+ var comments = frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments
+ var comment_exists = !!$.map(comments, function(x) { return x.name == comment.name? true : undefined}).length
+ if (!comment_exists) {
+ frappe.model.docinfo[comment.comment_doctype][comment.comment_docname].comments = comments.concat([comment]);
+ }
+ }
+ if (cur_frm.doctype === comment.comment_doctype && cur_frm.docname === comment.comment_docname) {
+ cur_frm.comments.refresh();
+ }
+ });
+ frappe.socket.socket.on('new_message', function(comment) {
+ frappe.utils.notify(__("Message from {0}", [comment.comment_by_fullname]), comment.comment);
+ if ($(cur_page.page).data('page-route') === 'messages') {
+ var current_contact = $(cur_page.page).find('[data-contact]').data('contact');
+ var on_broadcast_page = current_contact === user;
+ if (current_contact == comment.owner || (on_broadcast_page && comment.broadcast)) {
+ var $row = $('');
+ frappe.desk.pages.messages.list.data.unshift(comment);
+ frappe.desk.pages.messages.list.render_row($row, comment);
+ frappe.desk.pages.messages.list.parent.prepend($row);
+ }
+ }
+ else {
+ }
+ });
+ },
+ setup_reconnect: function() {
+ // subscribe again to open_tasks
+ frappe.socket.socket.on("connect", function() {
+ $.each(frappe.socket.open_tasks, function(task_id, opts) {
+ frappe.socket.subscribe(task_id, opts);
+ });
+ });
- },
- setup_reconnect: function() {
- // subscribe again to open_tasks
- frappe.socket.socket.on("connect", function() {
- $.each(frappe.socket.open_tasks, function(task_id, opts) {
- frappe.socket.subscribe(task_id, opts);
- });
- });
+ if(frappe.socket.open_doc) {
+ frappe.socket.doc_subscribe(frappe.socket.open_doc.doctype, frappe.socket.open_doc.docname);
+ }
+ },
+ process_response: function(data, method) {
+ if(!data) {
+ return;
+ }
- if(frappe.socket.open_doc) {
- frappe.socket.doc_subscribe(frappe.socket.open_doc.doctype, frappe.socket.open_doc.docname);
- }
- },
- process_response: function(data, method) {
- if(!data) {
- return;
- }
- if(data) {
- var opts = frappe.socket.open_tasks[data.task_id];
- if(opts[method]) opts[method](data);
- }
- if(opts.always) {
- opts.always(data);
- }
- if(data.status_code && status_code > 400 && opts.error) {
- opts.error(data);
- return;
- }
+ // success
+ if(data) {
+ var opts = frappe.socket.open_tasks[data.task_id];
+ if(opts[method]) opts[method](data);
+ }
+
+ // always
+ frappe.request.cleanup(opts, data);
+ if(opts.always) {
+ opts.always(data);
+ }
- }
+ // error
+ if(data.status_code && data.status_code > 400 && opts.error) {
+ opts.error(data);
+ }
+ }
}
$(frappe.socket.init);
-
diff --git a/frappe/public/js/frappe/upload.js b/frappe/public/js/frappe/upload.js
index 1031937326..331f98a305 100644
--- a/frappe/public/js/frappe/upload.js
+++ b/frappe/public/js/frappe/upload.js
@@ -93,6 +93,11 @@ frappe.upload = {
opts.callback(attachment, r);
$(document).trigger("upload_complete", attachment);
},
+ error: function(r) {
+ // if no onerror, assume callback will handle errors
+ opts.onerror ? opts.onerror(r) : opts.callback(null, null, r);
+ return;
+ },
btn: opts.btn
});
}
diff --git a/frappe/tasks.py b/frappe/tasks.py
index 10561bb7b3..3192fd6041 100644
--- a/frappe/tasks.py
+++ b/frappe/tasks.py
@@ -154,14 +154,15 @@ def run_async_task(self, site, user, cmd, form_dict):
ret = frappe.local.response
except Exception, e:
frappe.db.rollback()
- set_task_status(self.request.id, "Failed")
if not frappe.flags.in_test:
frappe.db.commit()
ret = frappe.local.response
http_status_code = getattr(e, "http_status_code", 500)
ret['status_code'] = http_status_code
- ret['exc'] = frappe.get_traceback()
+ frappe.errprint(frappe.get_traceback())
+ frappe.utils.response.make_logs()
+ set_task_status(self.request.id, "Failed", response=ret)
task_logger.error('Exception in running {}: {}'.format(cmd, ret['exc']))
else:
set_task_status(self.request.id, "Finished", response=ret)
diff --git a/frappe/utils/response.py b/frappe/utils/response.py
index 7700cf4a6e..f4d1c3722b 100644
--- a/frappe/utils/response.py
+++ b/frappe/utils/response.py
@@ -64,18 +64,21 @@ def as_json():
response.data = json.dumps(frappe.local.response, default=json_handler, separators=(',',':'))
return response
-def make_logs():
+def make_logs(response = None):
"""make strings for msgprint and errprint"""
+ if not response:
+ response = frappe.local.response
+
if frappe.error_log:
# frappe.response['exc'] = json.dumps("\n".join([cstr(d) for d in frappe.error_log]))
- frappe.response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log])
+ response['exc'] = json.dumps([frappe.utils.cstr(d) for d in frappe.local.error_log])
if frappe.local.message_log:
- frappe.response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for
+ response['_server_messages'] = json.dumps([frappe.utils.cstr(d) for
d in frappe.local.message_log])
if frappe.debug_log and frappe.conf.get("logging") or False:
- frappe.response['_debug_messages'] = json.dumps(frappe.local.debug_log)
+ response['_debug_messages'] = json.dumps(frappe.local.debug_log)
def json_handler(obj):
"""serialize non-serializable data for json"""
From 6985d4127e4f02f8eb55c178f84b299d46be3d2e Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Tue, 28 Jul 2015 17:36:53 +0530
Subject: [PATCH 25/47] [fixes] document_type, added set-config in commands,
prompt in form.js
---
frappe/commands.py | 13 +
.../core/doctype/async_task/async_task.json | 4 +-
.../doctype/communication/communication.json | 4 +-
frappe/core/doctype/doctype/doctype.json | 7 +-
frappe/core/doctype/doctype/doctype.py | 2 +-
frappe/core/doctype/user/user.json | 834 +++++++++---------
frappe/core/doctype/version/version.json | 4 +-
frappe/desk/doctype/note/note.json | 4 +-
frappe/desk/moduleview.py | 12 +-
.../doctype/email_account/email_account.json | 4 +-
.../standard_reply/standard_reply.json | 9 +-
frappe/geo/doctype/country/country.json | 4 +-
frappe/installer.py | 32 +-
frappe/patches.txt | 1 +
frappe/patches/v6_0/document_type_rename.py | 7 +
frappe/public/js/frappe/form/layout.js | 2 +-
frappe/public/js/frappe/form/save.js | 60 +-
frappe/public/js/frappe/form/toolbar.js | 3 +
frappe/public/js/frappe/ui/dialog.js | 22 +-
frappe/public/js/frappe/ui/messages.js | 8 +
frappe/public/js/legacy/form.js | 3 +-
.../doctype/blog_category/blog_category.json | 4 +-
frappe/website/doctype/blogger/blogger.json | 4 +-
frappe/website/doctype/web_form/web_form.json | 6 +-
frappe/website/doctype/web_page/web_page.json | 4 +-
.../website_slideshow/website_slideshow.json | 4 +-
.../doctype/website_theme/website_theme.json | 476 +++++-----
.../workflow/doctype/workflow/workflow.json | 152 ++--
.../workflow_document_state.json | 118 +--
.../workflow_state/workflow_state.json | 4 +-
30 files changed, 940 insertions(+), 871 deletions(-)
create mode 100644 frappe/patches/v6_0/document_type_rename.py
diff --git a/frappe/commands.py b/frappe/commands.py
index 4f5c316af6..c99212a55d 100644
--- a/frappe/commands.py
+++ b/frappe/commands.py
@@ -759,6 +759,18 @@ def move(dest_dir, site):
frappe.destroy()
return final_new_path
+
+@click.command('set-config')
+@click.argument('key')
+@click.argument('value')
+@pass_context
+def set_config(context, key, value):
+ from frappe.installer import update_site_config
+ for site in context.sites:
+ frappe.init(site=site)
+ update_site_config(key, value)
+ frappe.destroy()
+
@click.command('drop-site')
@click.argument('site')
@click.option('--root-login', default='root')
@@ -837,4 +849,5 @@ commands = [
backup,
remove_from_installed_apps,
drop_site,
+ set_config,
]
diff --git a/frappe/core/doctype/async_task/async_task.json b/frappe/core/doctype/async_task/async_task.json
index 7563e70316..33b79ea85a 100644
--- a/frappe/core/doctype/async_task/async_task.json
+++ b/frappe/core/doctype/async_task/async_task.json
@@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
@@ -114,7 +114,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
- "modified": "2015-07-04 14:33:26.791024",
+ "modified": "2015-07-28 16:18:11.344349",
"modified_by": "Administrator",
"module": "Core",
"name": "Async Task",
diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json
index 24283f4728..1c4c33efd8 100644
--- a/frappe/core/doctype/communication/communication.json
+++ b/frappe/core/doctype/communication/communication.json
@@ -5,7 +5,7 @@
"description": "Keep a track of all communications",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"default": "COMM-",
@@ -182,7 +182,7 @@
"idx": 1,
"in_dialog": 0,
"issingle": 0,
- "modified": "2015-03-23 02:33:55.289739",
+ "modified": "2015-07-28 16:18:11.664740",
"modified_by": "Administrator",
"module": "Core",
"name": "Communication",
diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json
index e829d3af27..00a8ef9ff2 100644
--- a/frappe/core/doctype/doctype/doctype.json
+++ b/frappe/core/doctype/doctype/doctype.json
@@ -7,6 +7,7 @@
"description": "DocType is a Table / Form in the application.",
"docstatus": 0,
"doctype": "DocType",
+ "document_type": "Document",
"fields": [
{
"fieldname": "sb0",
@@ -65,7 +66,7 @@
"label": "Document Type",
"oldfieldname": "document_type",
"oldfieldtype": "Select",
- "options": "\nMaster\nTransaction\nSystem\nOther",
+ "options": "\nDocument\nSetup\nSystem\nOther",
"permlevel": 0
},
{
@@ -108,7 +109,7 @@
"oldfieldtype": "Table",
"options": "DocField",
"permlevel": 0,
- "reqd": 1,
+ "reqd": 0,
"search_index": 0
},
{
@@ -337,7 +338,7 @@
"idx": 1,
"issingle": 0,
"istable": 0,
- "modified": "2015-03-03 10:40:45.768116",
+ "modified": "2015-07-28 16:18:11.925264",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index c0145e4bec..3a8e345703 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -87,7 +87,7 @@ class DocType(Document):
if autoname and (not autoname.startswith('field:')) \
and (not autoname.startswith('eval:')) \
- and (not autoname in ('Prompt', 'hash')) \
+ and (not autoname.lower() in ('prompt', 'hash')) \
and (not autoname.startswith('naming_series:')):
prefix = autoname.split('.')[0]
diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json
index e1789d655d..f729a56afb 100644
--- a/frappe/core/doctype/user/user.json
+++ b/frappe/core/doctype/user/user.json
@@ -1,519 +1,519 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 1,
- "creation": "2014-03-11 14:55:00",
- "description": "Represents a User in the system.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
+ "allow_copy": 0,
+ "allow_import": 1,
+ "allow_rename": 1,
+ "creation": "2014-03-11 14:55:00",
+ "description": "Represents a User in the system.",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
"fields": [
{
- "fieldname": "sb0_5",
- "fieldtype": "Section Break",
- "label": "",
+ "fieldname": "sb0_5",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "default": "1",
- "fieldname": "enabled",
- "fieldtype": "Check",
- "in_list_view": 0,
- "label": "Enabled",
- "no_copy": 0,
- "oldfieldname": "enabled",
- "oldfieldtype": "Check",
- "permlevel": 0,
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "in_list_view": 0,
+ "label": "Enabled",
+ "no_copy": 0,
+ "oldfieldname": "enabled",
+ "oldfieldtype": "Check",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "section_break_3",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "fieldname": "section_break_3",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
- {
- "fieldname": "email",
- "fieldtype": "Data",
- "hidden": 0,
- "label": "Email",
- "no_copy": 1,
- "oldfieldname": "email",
- "oldfieldtype": "Data",
- "options": "Email",
- "permlevel": 0,
- "reqd": 1,
+ },
+ {
+ "fieldname": "email",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "label": "Email",
+ "no_copy": 1,
+ "oldfieldname": "email",
+ "oldfieldtype": "Data",
+ "options": "Email",
+ "permlevel": 0,
+ "reqd": 1,
"search_index": 0
- },
- {
- "fieldname": "first_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "First Name",
- "no_copy": 0,
- "oldfieldname": "first_name",
- "oldfieldtype": "Data",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "first_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "First Name",
+ "no_copy": 0,
+ "oldfieldname": "first_name",
+ "oldfieldtype": "Data",
+ "permlevel": 0,
"reqd": 1
- },
- {
- "fieldname": "middle_name",
- "fieldtype": "Data",
- "label": "Middle Name (Optional)",
- "no_copy": 0,
- "oldfieldname": "middle_name",
- "oldfieldtype": "Data",
+ },
+ {
+ "fieldname": "middle_name",
+ "fieldtype": "Data",
+ "label": "Middle Name (Optional)",
+ "no_copy": 0,
+ "oldfieldname": "middle_name",
+ "oldfieldtype": "Data",
"permlevel": 0
- },
- {
- "fieldname": "last_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "Last Name",
- "oldfieldname": "last_name",
- "oldfieldtype": "Data",
+ },
+ {
+ "fieldname": "last_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "Last Name",
+ "oldfieldname": "last_name",
+ "oldfieldtype": "Data",
"permlevel": 0
- },
- {
- "default": "1",
- "depends_on": "eval:doc.__islocal",
- "fieldname": "send_welcome_email",
- "fieldtype": "Check",
- "label": "Send Welcome Email",
- "permlevel": 0,
+ },
+ {
+ "default": "1",
+ "depends_on": "eval:doc.__islocal",
+ "fieldname": "send_welcome_email",
+ "fieldtype": "Check",
+ "label": "Send Welcome Email",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "unsubscribed",
- "fieldtype": "Check",
- "hidden": 1,
- "label": "Unsubscribed",
- "no_copy": 1,
+ "fieldname": "unsubscribed",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "label": "Unsubscribed",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break0",
- "fieldtype": "Column Break",
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_width": "50%",
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "permlevel": 0,
+ "print_width": "50%",
"width": "50%"
- },
+ },
{
- "description": "",
- "fieldname": "language",
- "fieldtype": "Select",
- "label": "Language",
- "options": "Loading...",
+ "description": "",
+ "fieldname": "language",
+ "fieldtype": "Select",
+ "label": "Language",
+ "options": "Loading...",
"permlevel": 0
- },
+ },
{
- "description": "",
- "fieldname": "time_zone",
- "fieldtype": "Select",
- "label": "Timezone",
+ "description": "",
+ "fieldname": "time_zone",
+ "fieldtype": "Select",
+ "label": "Timezone",
"permlevel": 0
- },
+ },
{
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "change_password",
- "fieldtype": "Section Break",
- "label": "",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "change_password",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
+ },
{
- "fieldname": "new_password",
- "fieldtype": "Password",
- "label": "Set New Password",
- "no_copy": 1,
+ "fieldname": "new_password",
+ "fieldtype": "Password",
+ "label": "Set New Password",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "depends_on": "",
- "fieldname": "send_password_update_notification",
- "fieldtype": "Check",
- "label": "Send Password Update Notification",
- "permlevel": 0,
+ "depends_on": "",
+ "fieldname": "send_password_update_notification",
+ "fieldtype": "Check",
+ "label": "Send Password Update Notification",
+ "permlevel": 0,
"precision": ""
- },
- {
- "fieldname": "reset_password_key",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "Reset Password Key",
- "no_copy": 1,
- "permlevel": 0,
- "print_hide": 1,
+ },
+ {
+ "fieldname": "reset_password_key",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Reset Password Key",
+ "no_copy": 1,
+ "permlevel": 0,
+ "print_hide": 1,
"read_only": 1
- },
+ },
{
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "display_settings",
- "fieldtype": "Section Break",
- "label": "",
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "display_settings",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "description": "Get your globally recognized avatar from Gravatar.com",
- "fieldname": "user_image",
- "fieldtype": "Attach",
- "hidden": 0,
- "label": "User Image",
- "no_copy": 1,
+ },
+ {
+ "description": "Get your globally recognized avatar from Gravatar.com",
+ "fieldname": "user_image",
+ "fieldtype": "Attach",
+ "hidden": 0,
+ "label": "User Image",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "cb21",
- "fieldtype": "Column Break",
+ "fieldname": "cb21",
+ "fieldtype": "Column Break",
"permlevel": 0
- },
+ },
{
- "fieldname": "user_image_show",
- "fieldtype": "Image",
- "label": "user_image_show",
- "options": "user_image",
+ "fieldname": "user_image_show",
+ "fieldtype": "Image",
+ "label": "user_image_show",
+ "options": "user_image",
"permlevel": 0
- },
+ },
{
- "fieldname": "email_settings",
- "fieldtype": "Section Break",
- "label": "Email Settings",
- "permlevel": 0,
+ "fieldname": "email_settings",
+ "fieldtype": "Section Break",
+ "label": "Email Settings",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "1",
- "fieldname": "thread_notify",
- "fieldtype": "Check",
- "label": "Send Notifications for Transactions I Follow",
- "permlevel": 0,
+ "default": "1",
+ "fieldname": "thread_notify",
+ "fieldtype": "Check",
+ "label": "Send Notifications for Transactions I Follow",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "email_signature",
- "fieldtype": "Small Text",
- "label": "Email Signature",
- "no_copy": 1,
+ "fieldname": "email_signature",
+ "fieldtype": "Small Text",
+ "label": "Email Signature",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "background",
- "fieldtype": "Section Break",
- "label": "",
- "permlevel": 0,
+ "fieldname": "background",
+ "fieldtype": "Section Break",
+ "label": "",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_image",
- "fieldtype": "Attach",
- "label": "Background Image",
- "permlevel": 0,
+ "fieldname": "background_image",
+ "fieldtype": "Attach",
+ "label": "Background Image",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_style",
- "fieldtype": "Select",
- "label": "Background Style",
- "options": "Fill Screen\nTile",
- "permlevel": 0,
+ "fieldname": "background_style",
+ "fieldtype": "Select",
+ "label": "Background Style",
+ "options": "Fill Screen\nTile",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "short_bio",
- "fieldtype": "Section Break",
- "label": "",
+ "fieldname": "short_bio",
+ "fieldtype": "Section Break",
+ "label": "",
"permlevel": 0
- },
- {
- "fieldname": "gender",
- "fieldtype": "Select",
- "label": "Gender",
- "oldfieldname": "gender",
- "oldfieldtype": "Select",
- "options": "\nMale\nFemale\nOther",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "gender",
+ "fieldtype": "Select",
+ "label": "Gender",
+ "oldfieldname": "gender",
+ "oldfieldtype": "Select",
+ "options": "\nMale\nFemale\nOther",
+ "permlevel": 0,
"search_index": 0
- },
- {
- "fieldname": "birth_date",
- "fieldtype": "Date",
- "label": "Birth Date",
- "no_copy": 1,
- "oldfieldname": "birth_date",
- "oldfieldtype": "Date",
+ },
+ {
+ "fieldname": "birth_date",
+ "fieldtype": "Date",
+ "label": "Birth Date",
+ "no_copy": 1,
+ "oldfieldname": "birth_date",
+ "oldfieldtype": "Date",
"permlevel": 0
- },
+ },
{
- "fieldname": "location",
- "fieldtype": "Data",
- "label": "Location",
- "no_copy": 1,
+ "fieldname": "location",
+ "fieldtype": "Data",
+ "label": "Location",
+ "no_copy": 1,
"permlevel": 0
- },
+ },
{
- "fieldname": "column_break_22",
- "fieldtype": "Column Break",
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break",
"permlevel": 0
- },
+ },
{
- "fieldname": "bio",
- "fieldtype": "Small Text",
- "label": "Bio",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "bio",
+ "fieldtype": "Small Text",
+ "label": "Bio",
+ "no_copy": 1,
+ "permlevel": 0,
"set_only_once": 0
- },
+ },
{
- "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.",
- "fieldname": "sb1",
- "fieldtype": "Section Break",
- "label": "Roles",
- "permlevel": 1,
+ "description": "Check / Uncheck roles assigned to the User. Click on the Role to find out what permissions that Role has.",
+ "fieldname": "sb1",
+ "fieldtype": "Section Break",
+ "label": "Roles",
+ "permlevel": 1,
"read_only": 1
- },
+ },
{
- "fieldname": "roles_html",
- "fieldtype": "HTML",
- "label": "Roles HTML",
- "permlevel": 0,
+ "fieldname": "roles_html",
+ "fieldtype": "HTML",
+ "label": "Roles HTML",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "fieldname": "user_roles",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "Roles Assigned",
- "options": "UserRole",
- "permlevel": 1,
- "print_hide": 1,
+ },
+ {
+ "fieldname": "user_roles",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Roles Assigned",
+ "options": "UserRole",
+ "permlevel": 1,
+ "print_hide": 1,
"read_only": 1
- },
- {
- "default": "",
- "description": "Uncheck modules to hide from user's desktop",
- "fieldname": "modules_access",
- "fieldtype": "Section Break",
- "label": "Modules Access",
- "permlevel": 1,
+ },
+ {
+ "default": "",
+ "description": "Uncheck modules to hide from user's desktop",
+ "fieldname": "modules_access",
+ "fieldtype": "Section Break",
+ "label": "Modules Access",
+ "permlevel": 1,
"precision": ""
- },
+ },
{
- "fieldname": "modules_html",
- "fieldtype": "HTML",
- "label": "Modules HTML",
- "permlevel": 1,
+ "fieldname": "modules_html",
+ "fieldtype": "HTML",
+ "label": "Modules HTML",
+ "permlevel": 1,
"precision": ""
- },
- {
- "fieldname": "block_modules",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "Block Modules",
- "options": "Block Module",
- "permlevel": 1,
+ },
+ {
+ "fieldname": "block_modules",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Block Modules",
+ "options": "Block Module",
+ "permlevel": 1,
"precision": ""
- },
- {
- "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.",
- "fieldname": "sb2",
- "fieldtype": "Section Break",
- "hidden": 1,
- "label": "Defaults",
- "oldfieldtype": "Column Break",
- "permlevel": 1,
- "print_width": "50%",
- "read_only": 1,
+ },
+ {
+ "description": "These values will be automatically updated in transactions and also will be useful to restrict permissions for this user on transactions containing these values.",
+ "fieldname": "sb2",
+ "fieldtype": "Section Break",
+ "hidden": 1,
+ "label": "Defaults",
+ "oldfieldtype": "Column Break",
+ "permlevel": 1,
+ "print_width": "50%",
+ "read_only": 1,
"width": "50%"
- },
- {
- "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".",
- "fieldname": "defaults",
- "fieldtype": "Table",
- "hidden": 1,
- "label": "User Defaults",
- "no_copy": 1,
- "options": "DefaultValue",
+ },
+ {
+ "description": "Enter default value fields (keys) and values. If you add multiple values for a field, the first one will be picked. These defaults are also used to set \"match\" permission rules. To see list of fields, go to \"Customize Form\".",
+ "fieldname": "defaults",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "User Defaults",
+ "no_copy": 1,
+ "options": "DefaultValue",
"permlevel": 0
- },
+ },
{
- "fieldname": "sb3",
- "fieldtype": "Section Break",
- "label": "Security Settings",
- "oldfieldtype": "Section Break",
- "permlevel": 0,
+ "fieldname": "sb3",
+ "fieldtype": "Section Break",
+ "label": "Security Settings",
+ "oldfieldtype": "Section Break",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "default": "System User",
- "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ",
- "fieldname": "user_type",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "User Type",
- "oldfieldname": "user_type",
- "oldfieldtype": "Select",
- "options": "System User\nWebsite User",
- "permlevel": 0,
- "read_only": 1,
+ },
+ {
+ "default": "System User",
+ "description": "User Type \"System User\" can access Desktop. \"Website User\" can only be logged into the website and portal pages. ",
+ "fieldname": "user_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "User Type",
+ "oldfieldname": "user_type",
+ "oldfieldtype": "Select",
+ "options": "System User\nWebsite User",
+ "permlevel": 0,
+ "read_only": 1,
"reqd": 1
- },
+ },
{
- "description": "Allow user to login only after this hour (0-24)",
- "fieldname": "login_after",
- "fieldtype": "Int",
- "label": "Login After",
- "permlevel": 0,
+ "description": "Allow user to login only after this hour (0-24)",
+ "fieldname": "login_after",
+ "fieldtype": "Int",
+ "label": "Login After",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "description": "Allow user to login only before this hour (0-24)",
- "fieldname": "login_before",
- "fieldtype": "Int",
- "label": "Login Before",
- "permlevel": 0,
+ "description": "Allow user to login only before this hour (0-24)",
+ "fieldname": "login_before",
+ "fieldtype": "Int",
+ "label": "Login Before",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)",
- "fieldname": "restrict_ip",
- "fieldtype": "Data",
- "label": "Restrict IP",
- "permlevel": 0,
+ "description": "Restrict user from this IP address only. Multiple IP addresses can be added by separating with commas. Also accepts partial IP addresses like (111.111.111)",
+ "fieldname": "restrict_ip",
+ "fieldtype": "Data",
+ "label": "Restrict IP",
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "oldfieldtype": "Column Break",
- "permlevel": 0,
- "print_width": "50%",
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "oldfieldtype": "Column Break",
+ "permlevel": 0,
+ "print_width": "50%",
"width": "50%"
- },
- {
- "fieldname": "last_login",
- "fieldtype": "Read Only",
- "hidden": 0,
- "label": "Last Login",
- "no_copy": 1,
- "oldfieldname": "last_login",
- "oldfieldtype": "Read Only",
- "permlevel": 0,
- "read_only": 1,
- "reqd": 0,
+ },
+ {
+ "fieldname": "last_login",
+ "fieldtype": "Read Only",
+ "hidden": 0,
+ "label": "Last Login",
+ "no_copy": 1,
+ "oldfieldname": "last_login",
+ "oldfieldtype": "Read Only",
+ "permlevel": 0,
+ "read_only": 1,
+ "reqd": 0,
"search_index": 0
- },
- {
- "fieldname": "last_ip",
- "fieldtype": "Read Only",
- "label": "Last IP",
- "no_copy": 1,
- "oldfieldname": "last_ip",
- "oldfieldtype": "Read Only",
- "permlevel": 0,
+ },
+ {
+ "fieldname": "last_ip",
+ "fieldtype": "Read Only",
+ "label": "Last IP",
+ "no_copy": 1,
+ "oldfieldname": "last_ip",
+ "oldfieldtype": "Read Only",
+ "permlevel": 0,
"read_only": 1
- },
- {
- "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.",
- "fieldname": "last_known_versions",
- "fieldtype": "Text",
- "hidden": 1,
- "label": "Last Known Versions",
- "permlevel": 0,
- "precision": "",
+ },
+ {
+ "description": "Stores the JSON of last known versions of various installed apps. It is used to show release notes.",
+ "fieldname": "last_known_versions",
+ "fieldtype": "Text",
+ "hidden": 1,
+ "label": "Last Known Versions",
+ "permlevel": 0,
+ "precision": "",
"read_only": 1
- },
+ },
{
- "fieldname": "third_party_authentication",
- "fieldtype": "Section Break",
- "label": "Third Party Authentication",
+ "fieldname": "third_party_authentication",
+ "fieldtype": "Section Break",
+ "label": "Third Party Authentication",
"permlevel": 1
- },
+ },
{
- "fieldname": "fb_username",
- "fieldtype": "Data",
- "label": "Facebook Username",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "fb_username",
+ "fieldtype": "Data",
+ "label": "Facebook Username",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "fb_userid",
- "fieldtype": "Data",
- "label": "Facebook User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "fb_userid",
+ "fieldtype": "Data",
+ "label": "Facebook User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "google_userid",
- "fieldtype": "Data",
- "label": "Google User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "google_userid",
+ "fieldtype": "Data",
+ "label": "Google User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "column_break_49",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_49",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "github_userid",
- "fieldtype": "Data",
- "label": "Github User ID",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "github_userid",
+ "fieldtype": "Data",
+ "label": "Github User ID",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
- },
+ },
{
- "fieldname": "github_username",
- "fieldtype": "Data",
- "label": "Github Username",
- "no_copy": 1,
- "permlevel": 0,
+ "fieldname": "github_username",
+ "fieldtype": "Data",
+ "label": "Github Username",
+ "no_copy": 1,
+ "permlevel": 0,
"read_only": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-user",
- "idx": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 5,
- "modified": "2015-07-27 01:00:32.901851",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "User",
- "owner": "Administrator",
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "icon": "icon-user",
+ "idx": 1,
+ "issingle": 0,
+ "istable": 0,
+ "max_attachments": 5,
+ "modified": "2015-07-28 16:18:12.510213",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User",
+ "owner": "Administrator",
"permissions": [
{
- "create": 1,
- "delete": 1,
- "email": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 0,
"write": 1
- },
- {
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "permlevel": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "submit": 0,
+ },
+ {
+ "amend": 0,
+ "cancel": 0,
+ "create": 0,
+ "delete": 0,
+ "permlevel": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "submit": 0,
"write": 1
}
- ],
- "read_only": 0,
+ ],
+ "read_only": 0,
"search_fields": "first_name, last_name"
-}
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/version/version.json b/frappe/core/doctype/version/version.json
index b0f4a8586d..2c218efc6c 100644
--- a/frappe/core/doctype/version/version.json
+++ b/frappe/core/doctype/version/version.json
@@ -3,7 +3,7 @@
"creation": "2014-02-20 17:22:37",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "ref_doctype",
@@ -33,7 +33,7 @@
],
"icon": "icon-copy",
"idx": 1,
- "modified": "2014-08-05 01:23:37.541856",
+ "modified": "2015-07-28 16:18:12.706419",
"modified_by": "Administrator",
"module": "Core",
"name": "Version",
diff --git a/frappe/desk/doctype/note/note.json b/frappe/desk/doctype/note/note.json
index 0d111bcc2c..ec30184f3f 100644
--- a/frappe/desk/doctype/note/note.json
+++ b/frappe/desk/doctype/note/note.json
@@ -4,7 +4,7 @@
"description": "Note is a free page where users can share documents / notes",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"fieldname": "title",
@@ -35,7 +35,7 @@
],
"icon": "icon-file-text",
"idx": 1,
- "modified": "2015-02-06 00:44:06.475116",
+ "modified": "2015-07-28 16:18:12.301520",
"modified_by": "Administrator",
"module": "Desk",
"name": "Note",
diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py
index 7c6e83c164..c82b786bc7 100644
--- a/frappe/desk/moduleview.py
+++ b/frappe/desk/moduleview.py
@@ -59,10 +59,10 @@ def build_standard_config(module, doctype_info):
data = []
add_section(data, _("Documents"), "icon-star",
- [d for d in doctype_info if in_document_section(d)])
+ [d for d in doctype_info if d.document_type in ("Document", "Transaction")])
add_section(data, _("Setup"), "icon-cog",
- [d for d in doctype_info if not in_document_section(d)])
+ [d for d in doctype_info if d.document_type in ("Master", "Setup", "")])
add_section(data, _("Standard Reports"), "icon-list",
get_report_list(module, is_standard="Yes"))
@@ -82,14 +82,10 @@ def add_section(data, label, icon, items):
def add_custom_doctypes(data, doctype_info):
"""Adds Custom DocTypes to modules setup via `config/desktop.py`."""
add_section(data, _("Documents"), "icon-star",
- [d for d in doctype_info if (d.custom and in_document_section(d))])
+ [d for d in doctype_info if (d.custom and d.document_type in ("Document", "Transaction"))])
add_section(data, _("Setup"), "icon-cog",
- [d for d in doctype_info if (d.custom and not in_document_section(d))])
-
-def in_document_section(d):
- """Returns True if `document_type` property is one of `Master`, `Transaction` or not set."""
- return d.document_type in ("Transaction", "Master", "")
+ [d for d in doctype_info if (d.custom and d.document_type in ("Setup", "Master", ""))])
def get_doctype_info(module):
"""Returns list of non child DocTypes for given module."""
diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json
index 3e0e666bc0..af3594ede4 100644
--- a/frappe/email/doctype/email_account/email_account.json
+++ b/frappe/email/doctype/email_account/email_account.json
@@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "email_settings",
@@ -429,7 +429,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
- "modified": "2015-07-16 10:11:06.466258",
+ "modified": "2015-07-28 16:18:12.116327",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",
diff --git a/frappe/email/doctype/standard_reply/standard_reply.json b/frappe/email/doctype/standard_reply/standard_reply.json
index 0bb51fc71b..4716237d32 100644
--- a/frappe/email/doctype/standard_reply/standard_reply.json
+++ b/frappe/email/doctype/standard_reply/standard_reply.json
@@ -4,7 +4,7 @@
"creation": "2014-06-19 05:20:26.331041",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"fieldname": "subject",
@@ -33,18 +33,13 @@
}
],
"icon": "icon-comment",
- "modified": "2015-02-05 05:11:46.922356",
+ "modified": "2015-07-28 16:18:12.432775",
"modified_by": "Administrator",
"module": "Email",
"name": "Standard Reply",
"name_case": "",
"owner": "Administrator",
"permissions": [
- {
- "permlevel": 0,
- "read": 1,
- "role": "All"
- },
{
"apply_user_permissions": 1,
"create": 1,
diff --git a/frappe/geo/doctype/country/country.json b/frappe/geo/doctype/country/country.json
index d763eb7818..8488c61055 100644
--- a/frappe/geo/doctype/country/country.json
+++ b/frappe/geo/doctype/country/country.json
@@ -5,7 +5,7 @@
"creation": "2013-01-19 10:23:30",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "country_name",
@@ -42,7 +42,7 @@
"icon": "icon-globe",
"idx": 1,
"in_create": 0,
- "modified": "2015-02-05 05:11:36.234753",
+ "modified": "2015-07-28 16:18:11.855617",
"modified_by": "Administrator",
"module": "Geo",
"name": "Country",
diff --git a/frappe/installer.py b/frappe/installer.py
index 4f63a1404e..f3efe5699e 100755
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -188,7 +188,7 @@ def make_conf(db_name=None, db_password=None, site_config=None):
def make_site_config(db_name=None, db_password=None, site_config=None):
frappe.create_folder(os.path.join(frappe.local.site_path))
- site_file = os.path.join(frappe.local.site_path, "site_config.json")
+ site_file = get_site_config_path()
if not os.path.exists(site_file):
if not (site_config and isinstance(site_config, dict)):
@@ -197,6 +197,34 @@ def make_site_config(db_name=None, db_password=None, site_config=None):
with open(site_file, "w") as f:
f.write(json.dumps(site_config, indent=1, sort_keys=True))
+def update_site_config(key, value):
+ """Update a value in site_config"""
+ with open(get_site_config_path(), "r") as f:
+ site_config = json.loads(f.read())
+
+ # int
+ try:
+ value = int(value)
+ except ValueError:
+ pass
+
+ # boolean
+ if value in ("False", "True"):
+ value = eval(value)
+
+ # remove key if value is None
+ if value == "None":
+ if key in site_config:
+ del site_config[key]
+ else:
+ site_config[key] = value
+
+ with open(get_site_config_path(), "w") as f:
+ f.write(json.dumps(site_config, indent=1, sort_keys=True))
+
+def get_site_config_path():
+ return os.path.join(frappe.local.site_path, "site_config.json")
+
def get_conf_params(db_name=None, db_password=None):
if not db_name:
db_name = raw_input("Database Name: ")
@@ -215,7 +243,7 @@ def make_site_dirs():
for dir_path in (
os.path.join(site_private_path, 'backups'),
os.path.join(site_public_path, 'files'),
- os.path.join(site_public_path, 'task-logs')):
+ os.path.join(frappe.local.site_path, 'task-logs')):
if not os.path.exists(dir_path):
os.makedirs(dir_path)
locks_dir = frappe.get_site_path('locks')
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 5435054e67..d69299a925 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -85,3 +85,4 @@ execute:frappe.permissions.reset_perms("DocType")
execute:frappe.db.sql("delete from `tabProperty Setter` where `property` = 'idx'")
frappe.patches.v6_0.make_task_log_folder
+frappe.patches.v6_0.document_type_rename
diff --git a/frappe/patches/v6_0/document_type_rename.py b/frappe/patches/v6_0/document_type_rename.py
new file mode 100644
index 0000000000..0d9ca7da0e
--- /dev/null
+++ b/frappe/patches/v6_0/document_type_rename.py
@@ -0,0 +1,7 @@
+import frappe
+
+def execute():
+ frappe.db.sql("""update tabDocType set document_type='Document'
+ where document_type='Transaction'""")
+ frappe.db.sql("""update tabDocType set document_type='Setup'
+ where document_type='Master'""")
diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js
index e3c4417057..3dce0ebd2c 100644
--- a/frappe/public/js/frappe/form/layout.js
+++ b/frappe/public/js/frappe/form/layout.js
@@ -85,7 +85,7 @@ frappe.ui.form.Layout = Class.extend({
this.section = null;
this.column = null;
- if(this.fields[0] && this.fields[0].fieldtype!="Section Break") {
+ if((this.fields[0] && this.fields[0].fieldtype!="Section Break") || !this.fields.length) {
this.make_section();
}
$.each(this.fields, function(i, df) {
diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js
index 91e17c8837..e366f2874c 100644
--- a/frappe/public/js/frappe/form/save.js
+++ b/frappe/public/js/frappe/form/save.js
@@ -18,21 +18,23 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
var freeze_message = working_label ? __(working_label) : "";
var save = function() {
- check_name();
- if(check_mandatory()) {
- _call({
- method: "frappe.desk.form.save.savedocs",
- args: { doc: frm.doc, action:action},
- callback: function(r) {
- $(document).trigger("save", [frm.doc]);
- callback(r);
- },
- btn: btn,
- freeze_message: freeze_message
- });
- } else {
- $(btn).prop("disabled", false);
- }
+ check_name(function() {
+ if(check_mandatory()) {
+ _call({
+ method: "frappe.desk.form.save.savedocs",
+ args: { doc: frm.doc, action:action},
+ callback: function(r) {
+ $(document).trigger("save", [frm.doc]);
+ callback(r);
+ },
+ btn: btn,
+ freeze_message: freeze_message
+ });
+ } else {
+ $(btn).prop("disabled", false);
+ }
+ });
+
};
var cancel = function() {
@@ -48,19 +50,33 @@ frappe.ui.form.save = function(frm, action, callback, btn) {
});
};
- var check_name = function() {
+ var check_name = function(callback) {
var doc = frm.doc;
var meta = locals.DocType[doc.doctype];
if(doc.__islocal && (meta && meta.autoname
&& meta.autoname.toLowerCase()=='prompt')) {
- var newname = prompt('Enter the name of the new '+ doc.doctype, '');
- if(newname) {
- doc.__newname = strip(newname);
- } else {
- msgprint(__("Name is required"));
+ var d = frappe.prompt(__("Name"), function(values) {
+ var newname = values.value;
+ if(newname) {
+ doc.__newname = strip(newname);
+ } else {
+ msgprint(__("Name is required"));
+ throw "name required";
+ }
+
+ callback();
+
+ }, __('Enter the name of the new {0}', [doc.doctype]), __("Create"));
+
+ if(doc.__newname) {
+ d.set_value("value", doc.__newname);
+ }
+
+ d.onhide = function() {
$(btn).prop("disabled", false);
- throw "name required";
}
+ } else {
+ callback();
}
};
diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js
index be83250861..9ac54d202a 100644
--- a/frappe/public/js/frappe/form/toolbar.js
+++ b/frappe/public/js/frappe/form/toolbar.js
@@ -220,6 +220,9 @@ frappe.ui.form.Toolbar = Class.extend({
} else {
var click = {
"Save": function() {
+ if(!frappe.dom.is_touchscreen()) {
+ show_alert(__("You can also use Ctrl+S to Save"));
+ }
me.frm.save('Save', null, this);
},
"Submit": function() {
diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js
index 284a7e8d9c..9529e12225 100644
--- a/frappe/public/js/frappe/ui/dialog.js
+++ b/frappe/public/js/frappe/ui/dialog.js
@@ -48,19 +48,21 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
me.display = true;
cur_dialog = me;
frappe.ui.open_dialogs.push(me);
- var first = $(me.body).find('.modal-content :input:first');
- if(first.length && first.attr("data-fieldtype")!="Date") {
- try {
- first.get(0).focus();
- } catch(e) {
- console.log("Dialog: unable to focus on first input: " + e);
- }
- }
+ me.focus_on_first_input();
me.on_page_show && me.on_page_show();
- })
-
+ });
},
+ focus_on_first_input: function() {
+ var first = $(this.body).find(':input:first');
+ if(first.length && first.attr("data-fieldtype")!="Date") {
+ try {
+ first.get(0).focus();
+ } catch(e) {
+ console.log("Dialog: unable to focus on first input: " + e);
+ }
+ }
+ },
get_primary_btn: function() {
return this.$wrapper.find(".modal-header .btn-primary");
},
diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js
index bb6a114a48..eb1997322d 100644
--- a/frappe/public/js/frappe/ui/messages.js
+++ b/frappe/public/js/frappe/ui/messages.js
@@ -44,6 +44,14 @@ frappe.confirm = function(message, ifyes, ifno) {
}
frappe.prompt = function(fields, callback, title, primary_label) {
+ if (typeof fields === "string") {
+ fields = [{
+ label: fields,
+ fieldname: "value",
+ fieldtype: "Data",
+ reqd: 1
+ }];
+ }
if(!$.isArray(fields)) fields = [fields];
var d = new frappe.ui.Dialog({
fields: fields,
diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js
index 9cb2c44446..0a0b4a6700 100644
--- a/frappe/public/js/legacy/form.js
+++ b/frappe/public/js/legacy/form.js
@@ -273,7 +273,7 @@ _f.Frm.prototype.rename_notify = function(dt, old, name) {
return;
// cleanup
- if(this && this.opendocs[old]) {
+ if(this && this.opendocs[old] && frappe.meta.docfield_copy[dt]) {
// delete docfield copy
frappe.meta.docfield_copy[dt][name] = frappe.meta.docfield_copy[dt][old];
delete frappe.meta.docfield_copy[dt][old];
@@ -820,4 +820,3 @@ _f.Frm.prototype.validate_form_action = function(action) {
_f.Frm.prototype.get_handlers = function(fieldname, doctype, docname) {
return this.script_manager.get_handlers(fieldname, doctype || this.doctype, docname || this.docname)
}
-
diff --git a/frappe/website/doctype/blog_category/blog_category.json b/frappe/website/doctype/blog_category/blog_category.json
index 22d23a3725..a2c2fddb15 100644
--- a/frappe/website/doctype/blog_category/blog_category.json
+++ b/frappe/website/doctype/blog_category/blog_category.json
@@ -4,7 +4,7 @@
"creation": "2013-03-08 09:41:11",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "category_name",
@@ -49,7 +49,7 @@
],
"icon": "icon-tag",
"idx": 1,
- "modified": "2015-02-05 05:11:34.877605",
+ "modified": "2015-07-28 16:18:11.486847",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Category",
diff --git a/frappe/website/doctype/blogger/blogger.json b/frappe/website/doctype/blogger/blogger.json
index 223ce5a675..2dbca5b001 100644
--- a/frappe/website/doctype/blogger/blogger.json
+++ b/frappe/website/doctype/blogger/blogger.json
@@ -5,7 +5,7 @@
"description": "User ID of a Blogger",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "disabled",
@@ -61,7 +61,7 @@
"icon": "icon-user",
"idx": 1,
"max_attachments": 1,
- "modified": "2015-02-19 09:29:25.836280",
+ "modified": "2015-07-28 16:18:11.567110",
"modified_by": "Administrator",
"module": "Website",
"name": "Blogger",
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index af2998fbfb..845ed7b392 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -7,7 +7,7 @@
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"allow_on_submit": 0,
@@ -230,7 +230,7 @@
"is_submittable": 0,
"issingle": 0,
"istable": 0,
- "modified": "2015-02-05 05:11:48.897157",
+ "modified": "2015-07-28 16:18:12.772231",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",
@@ -262,4 +262,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "title"
-}
+}
\ No newline at end of file
diff --git a/frappe/website/doctype/web_page/web_page.json b/frappe/website/doctype/web_page/web_page.json
index e9fa1bc3ea..efb3b3845e 100644
--- a/frappe/website/doctype/web_page/web_page.json
+++ b/frappe/website/doctype/web_page/web_page.json
@@ -3,7 +3,7 @@
"description": "Page to show on the website\n",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"fieldname": "section_title",
@@ -200,7 +200,7 @@
"icon": "icon-file-alt",
"idx": 1,
"max_attachments": 20,
- "modified": "2015-07-22 12:38:08.696692",
+ "modified": "2015-07-28 16:18:12.887565",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.json b/frappe/website/doctype/website_slideshow/website_slideshow.json
index d2ce23e9a9..e1f63a5926 100644
--- a/frappe/website/doctype/website_slideshow/website_slideshow.json
+++ b/frappe/website/doctype/website_slideshow/website_slideshow.json
@@ -4,7 +4,7 @@
"description": "Slideshow like display for the website",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Transaction",
+ "document_type": "Document",
"fields": [
{
"fieldname": "slideshow_name",
@@ -42,7 +42,7 @@
"icon": "icon-play",
"idx": 1,
"max_attachments": 10,
- "modified": "2015-02-20 05:04:19.614170",
+ "modified": "2015-07-28 16:18:13.013029",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Slideshow",
diff --git a/frappe/website/doctype/website_theme/website_theme.json b/frappe/website/doctype/website_theme/website_theme.json
index b01c2576ab..96fb5f0e01 100644
--- a/frappe/website/doctype/website_theme/website_theme.json
+++ b/frappe/website/doctype/website_theme/website_theme.json
@@ -1,304 +1,304 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "field:theme",
- "creation": "2015-02-18 12:46:38.168929",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
+ "allow_copy": 0,
+ "allow_import": 1,
+ "allow_rename": 0,
+ "autoname": "field:theme",
+ "creation": "2015-02-18 12:46:38.168929",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
"fields": [
{
- "allow_on_submit": 0,
- "fieldname": "theme",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Theme",
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 1,
+ "allow_on_submit": 0,
+ "fieldname": "theme",
+ "fieldtype": "Data",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Theme",
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 1,
"set_only_once": 0
- },
+ },
{
- "allow_on_submit": 0,
- "default": "Website",
- "fieldname": "module",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "label": "Module",
- "no_copy": 0,
- "options": "Module Def",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
+ "allow_on_submit": 0,
+ "default": "Website",
+ "fieldname": "module",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "label": "Module",
+ "no_copy": 0,
+ "options": "Module Def",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 1,
+ "search_index": 0,
"set_only_once": 0
- },
+ },
{
- "default": "1",
- "description": "This must be checked if the below style settings are applicable",
- "fieldname": "apply_style",
- "fieldtype": "Check",
- "hidden": 0,
- "label": "Apply Style",
- "permlevel": 0,
+ "default": "1",
+ "description": "This must be checked if the below style settings are applicable",
+ "fieldname": "apply_style",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "label": "Apply Style",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "1",
- "fieldname": "custom",
- "fieldtype": "Check",
- "label": "Custom?",
- "permlevel": 0,
+ "default": "1",
+ "fieldname": "custom",
+ "fieldtype": "Check",
+ "label": "Custom?",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "allow_on_submit": 0,
- "fieldname": "column_break_2",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
+ "allow_on_submit": 0,
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
"set_only_once": 0
- },
+ },
{
- "description": "Link to your Bootstrap theme",
- "fieldname": "bootstrap",
- "fieldtype": "Small Text",
- "label": "Link to Bootstrap CSS",
- "permlevel": 0,
+ "description": "Link to your Bootstrap theme",
+ "fieldname": "bootstrap",
+ "fieldtype": "Small Text",
+ "label": "Link to Bootstrap CSS",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_14",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_14",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "default": "",
- "fieldname": "font_size",
- "fieldtype": "Select",
- "label": "Font Size",
- "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px",
- "permlevel": 0,
+ "default": "",
+ "fieldname": "font_size",
+ "fieldtype": "Select",
+ "label": "Font Size",
+ "options": "\n12px\n13px\n14px\n15px\n16px\n17px\n18px",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
- "fieldname": "text_webfont",
- "fieldtype": "Data",
- "label": "Google Font (Text)",
- "permlevel": 0,
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
+ "fieldname": "text_webfont",
+ "fieldtype": "Data",
+ "label": "Google Font (Text)",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
- "fieldname": "heading_webfont",
- "fieldtype": "Data",
- "label": "Google Font (Heading)",
- "permlevel": 0,
+ "description": "Add the name of a \"Google Web Font\" e.g. \"Open Sans\"",
+ "fieldname": "heading_webfont",
+ "fieldtype": "Data",
+ "label": "Google Font (Heading)",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_18",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "text_color",
- "fieldtype": "Data",
- "label": "Text Color",
- "permlevel": 0,
+ "fieldname": "text_color",
+ "fieldtype": "Data",
+ "label": "Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "link_color",
- "fieldtype": "Data",
- "label": "Link Color",
- "permlevel": 0,
+ "fieldname": "link_color",
+ "fieldtype": "Data",
+ "label": "Link Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "heading_style",
- "fieldtype": "Select",
- "label": "Heading Style",
- "options": "\nUPPERCASE\nTitle Case\nlowercase",
- "permlevel": 0,
+ "fieldname": "heading_style",
+ "fieldtype": "Select",
+ "label": "Heading Style",
+ "options": "\nUPPERCASE\nTitle Case\nlowercase",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_8",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "top_bar_color",
- "fieldtype": "Data",
- "label": "Top Bar Color",
- "permlevel": 0,
+ "fieldname": "top_bar_color",
+ "fieldtype": "Data",
+ "label": "Top Bar Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "top_bar_text_color",
- "fieldtype": "Data",
- "label": "Top Bar Text Color",
- "permlevel": 0,
+ "fieldname": "top_bar_text_color",
+ "fieldtype": "Data",
+ "label": "Top Bar Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "no_sidebar",
- "fieldtype": "Check",
- "label": "Hide Sidebar",
- "permlevel": 0,
+ "fieldname": "no_sidebar",
+ "fieldtype": "Check",
+ "label": "Hide Sidebar",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_11",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_11",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "footer_color",
- "fieldtype": "Data",
- "label": "Footer Color",
- "permlevel": 0,
+ "fieldname": "footer_color",
+ "fieldtype": "Data",
+ "label": "Footer Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "footer_text_color",
- "fieldtype": "Data",
- "label": "Footer Text Color",
- "permlevel": 0,
+ "fieldname": "footer_text_color",
+ "fieldtype": "Data",
+ "label": "Footer Text Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "background_color",
- "fieldtype": "Data",
- "label": "Background Color",
- "permlevel": 0,
+ "fieldname": "background_color",
+ "fieldtype": "Data",
+ "label": "Background Color",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "column_break_6",
- "fieldtype": "Column Break",
- "permlevel": 0,
+ "fieldname": "column_break_6",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "description": "If image is selected, color will be ignored.",
- "fieldname": "background_image",
- "fieldtype": "Attach Image",
- "label": "Background Image",
- "permlevel": 0,
+ "description": "If image is selected, color will be ignored.",
+ "fieldname": "background_image",
+ "fieldtype": "Attach Image",
+ "label": "Background Image",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "",
- "fieldname": "section_break_21",
- "fieldtype": "Section Break",
- "permlevel": 0,
+ "depends_on": "",
+ "fieldname": "section_break_21",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "depends_on": "apply_style",
- "description": "",
- "fieldname": "css",
- "fieldtype": "Code",
- "label": "Style using CSS",
- "permlevel": 0,
+ "depends_on": "apply_style",
+ "description": "",
+ "fieldname": "css",
+ "fieldtype": "Code",
+ "label": "Style using CSS",
+ "permlevel": 0,
"precision": ""
- },
+ },
{
- "fieldname": "js",
- "fieldtype": "Code",
- "label": "JavaScript",
- "permlevel": 0,
+ "fieldname": "js",
+ "fieldtype": "Code",
+ "label": "JavaScript",
+ "permlevel": 0,
"precision": ""
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "in_create": 0,
- "in_dialog": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "modified": "2015-07-27 01:00:32.901851",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Theme",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 0,
+ "modified": "2015-07-28 16:18:13.081330",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Theme",
+ "name_case": "",
+ "owner": "Administrator",
"permissions": [
{
- "create": 1,
- "delete": 1,
- "permlevel": 0,
- "read": 1,
- "role": "Website Manager",
+ "create": 1,
+ "delete": 1,
+ "permlevel": 0,
+ "read": 1,
+ "role": "Website Manager",
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 0,
- "export": 1,
- "import": 1,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
+ "amend": 0,
+ "apply_user_permissions": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 0,
+ "export": 1,
+ "import": 1,
+ "permlevel": 0,
+ "print": 0,
+ "read": 1,
+ "report": 0,
+ "role": "Administrator",
+ "set_user_permissions": 0,
+ "share": 0,
+ "submit": 0,
"write": 1
}
- ],
- "read_only": 0,
- "read_only_onload": 0,
- "search_fields": "",
- "sort_field": "modified",
- "sort_order": "DESC",
+ ],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "search_fields": "",
+ "sort_field": "modified",
+ "sort_order": "DESC",
"title_field": ""
-}
+}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow/workflow.json b/frappe/workflow/doctype/workflow/workflow.json
index 1d5b2232a8..76a101412e 100644
--- a/frappe/workflow/doctype/workflow/workflow.json
+++ b/frappe/workflow/doctype/workflow/workflow.json
@@ -1,100 +1,100 @@
{
- "allow_rename": 1,
- "autoname": "field:workflow_name",
- "creation": "2012-12-28 10:49:55",
- "description": "Defines workflow states and rules for a document.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Transaction",
+ "allow_rename": 1,
+ "autoname": "field:workflow_name",
+ "creation": "2012-12-28 10:49:55",
+ "description": "Defines workflow states and rules for a document.",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Document",
"fields": [
{
- "fieldname": "workflow_name",
- "fieldtype": "Data",
- "in_list_view": 0,
- "label": "Workflow Name",
- "permlevel": 0,
- "read_only": 0,
+ "fieldname": "workflow_name",
+ "fieldtype": "Data",
+ "in_list_view": 0,
+ "label": "Workflow Name",
+ "permlevel": 0,
+ "read_only": 0,
"reqd": 1
- },
+ },
{
- "description": "DocType on which this Workflow is applicable.",
- "fieldname": "document_type",
- "fieldtype": "Link",
- "in_list_view": 0,
- "label": "Document Type",
- "options": "DocType",
- "permlevel": 0,
+ "description": "DocType on which this Workflow is applicable.",
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 0,
+ "label": "Document Type",
+ "options": "DocType",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "description": "If checked, all other workflows become inactive.",
- "fieldname": "is_active",
- "fieldtype": "Check",
- "in_list_view": 0,
- "label": "Is Active",
+ "description": "If checked, all other workflows become inactive.",
+ "fieldname": "is_active",
+ "fieldtype": "Check",
+ "in_list_view": 0,
+ "label": "Is Active",
"permlevel": 0
- },
+ },
{
- "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.",
- "fieldname": "states_head",
- "fieldtype": "Section Break",
- "label": "States",
+ "description": "Different \"States\" this document can exist in. Like \"Open\", \"Pending Approval\" etc.",
+ "fieldname": "states_head",
+ "fieldtype": "Section Break",
+ "label": "States",
"permlevel": 0
- },
+ },
{
- "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
- "fieldname": "states",
- "fieldtype": "Table",
- "label": "Document States",
- "options": "Workflow Document State",
- "permlevel": 0,
+ "description": "All possible Workflow States and roles of the workflow. \nDocstatus Options: 0 is\"Saved\", 1 is \"Submitted\" and 2 is \"Cancelled\"",
+ "fieldname": "states",
+ "fieldtype": "Table",
+ "label": "Document States",
+ "options": "Workflow Document State",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.",
- "fieldname": "transition_rules",
- "fieldtype": "Section Break",
- "label": "Transition Rules",
+ "description": "Rules for how states are transitions, like next state and which role is allowed to change state etc.",
+ "fieldname": "transition_rules",
+ "fieldtype": "Section Break",
+ "label": "Transition Rules",
"permlevel": 0
- },
+ },
{
- "description": "Rules defining transition of state in the workflow.",
- "fieldname": "transitions",
- "fieldtype": "Table",
- "label": "Transitions",
- "options": "Workflow Transition",
- "permlevel": 0,
+ "description": "Rules defining transition of state in the workflow.",
+ "fieldname": "transitions",
+ "fieldtype": "Table",
+ "label": "Transitions",
+ "options": "Workflow Transition",
+ "permlevel": 0,
"reqd": 1
- },
+ },
{
- "default": "workflow_state",
- "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)",
- "fieldname": "workflow_state_field",
- "fieldtype": "Data",
- "label": "Workflow State Field",
+ "default": "workflow_state",
+ "description": "Field that represents the Workflow State of the transaction (if field is not present, a new hidden Custom Field will be created)",
+ "fieldname": "workflow_state_field",
+ "fieldtype": "Data",
+ "label": "Workflow State Field",
"permlevel": 0
}
- ],
- "icon": "icon-random",
- "idx": 1,
- "modified": "2015-07-27 01:00:32.901851",
- "modified_by": "Administrator",
- "module": "Workflow",
- "name": "Workflow",
- "owner": "Administrator",
+ ],
+ "icon": "icon-random",
+ "idx": 1,
+ "modified": "2015-07-28 16:18:13.196607",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow",
+ "owner": "Administrator",
"permissions": [
{
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "role": "System Manager",
- "share": 1,
- "submit": 0,
+ "cancel": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "permlevel": 0,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
+ "submit": 0,
"write": 1
}
]
-}
+}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_document_state/workflow_document_state.json b/frappe/workflow/doctype/workflow_document_state/workflow_document_state.json
index cebb74de56..86d18c86ef 100644
--- a/frappe/workflow/doctype/workflow_document_state/workflow_document_state.json
+++ b/frappe/workflow/doctype/workflow_document_state/workflow_document_state.json
@@ -1,75 +1,75 @@
{
- "allow_import": 1,
- "creation": "2013-02-22 01:27:36",
- "description": "Represents the states allowed in one document and role assigned to change the state.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Master",
+ "allow_import": 1,
+ "creation": "2013-02-22 01:27:36",
+ "description": "Represents the states allowed in one document and role assigned to change the state.",
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "Setup",
"fields": [
{
- "fieldname": "state",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "State",
- "options": "Workflow State",
- "permlevel": 0,
- "print_width": "160px",
- "reqd": 1,
+ "fieldname": "state",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "State",
+ "options": "Workflow State",
+ "permlevel": 0,
+ "print_width": "160px",
+ "reqd": 1,
"width": "160px"
- },
+ },
{
- "description": "0 - Draft; 1 - Submitted; 2 - Cancelled",
- "fieldname": "doc_status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Doc Status",
- "options": "0\n1\n2",
- "permlevel": 0,
- "print_width": "80px",
+ "description": "0 - Draft; 1 - Submitted; 2 - Cancelled",
+ "fieldname": "doc_status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Doc Status",
+ "options": "0\n1\n2",
+ "permlevel": 0,
+ "print_width": "80px",
"width": "80px"
- },
+ },
{
- "fieldname": "update_field",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Update Field",
+ "fieldname": "update_field",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Update Field",
"permlevel": 0
- },
+ },
{
- "fieldname": "update_value",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Update Value",
+ "fieldname": "update_value",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Update Value",
"permlevel": 0
- },
+ },
{
- "fieldname": "allow_edit",
- "fieldtype": "Link",
- "in_list_view": 1,
- "label": "Only Allow Edit For",
- "options": "Role",
- "permlevel": 0,
- "print_width": "160px",
- "reqd": 1,
+ "fieldname": "allow_edit",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Only Allow Edit For",
+ "options": "Role",
+ "permlevel": 0,
+ "print_width": "160px",
+ "reqd": 1,
"width": "160px"
- },
+ },
{
- "fieldname": "message",
- "fieldtype": "Text",
- "in_list_view": 1,
- "label": "Message",
- "permlevel": 0,
- "print_width": "160px",
- "reqd": 0,
+ "fieldname": "message",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Message",
+ "permlevel": 0,
+ "print_width": "160px",
+ "reqd": 0,
"width": "160px"
}
- ],
- "idx": 1,
- "istable": 1,
- "modified": "2014-05-27 06:35:01.070158",
- "modified_by": "Administrator",
- "module": "Workflow",
- "name": "Workflow Document State",
- "owner": "Administrator",
+ ],
+ "idx": 1,
+ "istable": 1,
+ "modified": "2015-07-28 16:18:13.257862",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow Document State",
+ "owner": "Administrator",
"permissions": []
-}
+}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_state/workflow_state.json b/frappe/workflow/doctype/workflow_state/workflow_state.json
index fc1a9228f4..4568da229c 100644
--- a/frappe/workflow/doctype/workflow_state/workflow_state.json
+++ b/frappe/workflow/doctype/workflow_state/workflow_state.json
@@ -5,7 +5,7 @@
"description": "Workflow state represents the current state of a document.",
"docstatus": 0,
"doctype": "DocType",
- "document_type": "Master",
+ "document_type": "Setup",
"fields": [
{
"fieldname": "workflow_state_name",
@@ -36,7 +36,7 @@
],
"icon": "icon-flag",
"idx": 1,
- "modified": "2015-05-27 02:51:01.978973",
+ "modified": "2015-07-28 16:18:13.320514",
"modified_by": "Administrator",
"module": "Workflow",
"name": "Workflow State",
From f6bd49360c82babe8a036810368e23db57dce159 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Wed, 29 Jul 2015 10:56:36 +0530
Subject: [PATCH 26/47] [minor] don't give save tip everytime
---
frappe/public/js/frappe/form/toolbar.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js
index 9ac54d202a..2168121fe6 100644
--- a/frappe/public/js/frappe/form/toolbar.js
+++ b/frappe/public/js/frappe/form/toolbar.js
@@ -220,7 +220,7 @@ frappe.ui.form.Toolbar = Class.extend({
} else {
var click = {
"Save": function() {
- if(!frappe.dom.is_touchscreen()) {
+ if(!frappe.dom.is_touchscreen() && Math.random() < 0.30) {
show_alert(__("You can also use Ctrl+S to Save"));
}
me.frm.save('Save', null, this);
From d69d83b5f9149d9b378d3f54710259ffcda04f92 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Wed, 29 Jul 2015 12:33:01 +0530
Subject: [PATCH 27/47] [minor] grey out system messages frappe/erpnext#3707
---
frappe/desk/page/messages/messages.js | 5 +++--
frappe/desk/page/messages/messages_row.html | 5 +++--
frappe/public/css/common.css | 4 ++++
frappe/public/css/desk.css | 4 ++++
frappe/public/css/website.css | 4 ++++
frappe/public/less/common.less | 5 +++++
6 files changed, 23 insertions(+), 4 deletions(-)
diff --git a/frappe/desk/page/messages/messages.js b/frappe/desk/page/messages/messages.js
index cc518fae83..e6010d8deb 100644
--- a/frappe/desk/page/messages/messages.js
+++ b/frappe/desk/page/messages/messages.js
@@ -134,6 +134,9 @@ frappe.desk.pages.Messages = Class.extend({
hide_refresh: true,
freeze: false,
render_row: function(wrapper, data) {
+ if(data.parenttype==="Assignment" || data.comment_type==="Shared") {
+ data.is_system_message = 1;
+ }
var row = $(frappe.render_template("messages_row", {
data: data
})).appendTo(wrapper);
@@ -170,5 +173,3 @@ frappe.desk.pages.Messages = Class.extend({
});
-
-
diff --git a/frappe/desk/page/messages/messages_row.html b/frappe/desk/page/messages/messages_row.html
index 08a36ff29b..309174fd40 100644
--- a/frappe/desk/page/messages/messages_row.html
+++ b/frappe/desk/page/messages/messages_row.html
@@ -6,10 +6,11 @@
').appendTo(this.parent);
}
},
+ toggle_label: function(show) {
+ this.$wrapper.find(".control-label").toggleClass("hide", !show);
+ },
+ toggle_description: function(show) {
+ this.$wrapper.find(".help-box").toggleClass("hide", !show);
+ },
set_input_areas: function() {
if(this.only_input) {
this.input_area = this.wrapper;
@@ -637,7 +643,7 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({
this.input = this.$input.get(0);
this.set_input_attributes();
this.has_input = true;
- this.$wrapper.find(".control-label").addClass("hide");
+ this.toggle_label(false);
},
onclick: function() {
if(this.frm && this.frm.doc) {
From 9a95a660ad0f5f051a2bf79ceca08a92a4aa6619 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 31 Jul 2015 15:44:57 +0530
Subject: [PATCH 31/47] [fix] [minor] standard reply after salutation, fixes
frappe/erpnext#3552
---
frappe/public/js/frappe/views/communication.js | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js
index 70227ff056..01bad10a42 100644
--- a/frappe/public/js/frappe/views/communication.js
+++ b/frappe/public/js/frappe/views/communication.js
@@ -132,9 +132,18 @@ frappe.views.CommunicationComposer = Class.extend({
var prepend_reply = function() {
var content_field = me.dialog.fields_dict.content;
var content = content_field.get_value() || "";
- content_field.set_input(
- frappe.standard_replies[standard_reply]
- + "
" + content);
+
+ parts = content.split('');
+
+ if(parts.length===2) {
+ content = [parts[0], frappe.standard_replies[standard_reply],
+ "
", parts[1]];
+ } else {
+ content = [frappe.standard_replies[standard_reply],
+ "
", content];
+ }
+
+ content_field.set_input(content.join(''));
}
if(frappe.standard_replies[standard_reply]) {
prepend_reply();
@@ -369,7 +378,7 @@ frappe.views.CommunicationComposer = Class.extend({
if(this.real_name) {
this.message = ''+__('Dear') +' '
- + this.real_name + ",
" + (this.message || "");
+ + this.real_name + ",
" + (this.message || "");
}
var reply = (this.message || "")
From dd1743f36de7801448cfe14cea0ec5942bd6c0ea Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Sat, 1 Aug 2015 16:50:43 +0530
Subject: [PATCH 32/47] [fix] drag conflicts
---
.../js/lib/slickgrid/jquery.event.drag.js | 170 +++++++++---------
1 file changed, 88 insertions(+), 82 deletions(-)
diff --git a/frappe/public/js/lib/slickgrid/jquery.event.drag.js b/frappe/public/js/lib/slickgrid/jquery.event.drag.js
index f2c1d57e6d..f7d6245838 100755
--- a/frappe/public/js/lib/slickgrid/jquery.event.drag.js
+++ b/frappe/public/js/lib/slickgrid/jquery.event.drag.js
@@ -1,9 +1,9 @@
-/*!
+/*!
* jquery.event.drag - v 2.2
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
* Open Source MIT License - http://threedubmedia.com/code/license
*/
-// Created: 2008-06-04
+// Created: 2008-06-04
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x
@@ -16,7 +16,7 @@ $.fn.drag = function( str, arg, opts ){
// figure out the event handler...
fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
// fix the event type
- if ( type.indexOf("drag") !== 0 )
+ if ( type.indexOf("drag") !== 0 )
type = "drag"+ type;
// were options passed
opts = ( str == fn ? arg : opts ) || {};
@@ -25,11 +25,11 @@ $.fn.drag = function( str, arg, opts ){
};
// local refs (increase compression)
-var $event = $.event,
+var $event = $.event,
$special = $event.special,
-// configure the drag special event
+// configure the drag special event
drag = $special.drag = {
-
+
// these are the default settings
defaults: {
which: 1, // mouse button pressed to start drag sequence
@@ -40,82 +40,88 @@ drag = $special.drag = {
drop: true, // false to suppress drop events, true or selector to allow
click: false // false to suppress click events after dragend (no proxy)
},
-
+
// the key name for stored drag data
datakey: "dragdata",
-
+
// prevent bubbling for better performance
noBubble: true,
-
+
// count bound related events
- add: function( obj ){
+ add: function( obj ){
// read the interaction data
var data = $.data( this, drag.datakey ),
- // read any passed options
+ // read any passed options
opts = obj.data || {};
// count another realted event
data.related += 1;
// extend data options bound with this event
- // don't iterate "opts" in case it is a node
+ // don't iterate "opts" in case it is a node
$.each( drag.defaults, function( key, def ){
if ( opts[ key ] !== undefined )
data[ key ] = opts[ key ];
});
},
-
+
// forget unbound related events
remove: function(){
$.data( this, drag.datakey ).related -= 1;
},
-
+
// configure interaction, capture settings
setup: function(){
+
// check for related events
- if ( $.data( this, drag.datakey ) )
+ if ( $.data( this, drag.datakey ) )
return;
// initialize the drag data with copied defaults
var data = $.extend({ related:0 }, drag.defaults );
// store the interaction data
$.data( this, drag.datakey, data );
// bind the mousedown event, which starts drag interactions
+
+ // don't attached drag event via special for fullcalendar
+ // return false to attach the normal way
+ if(this===document) return false;
+
$event.add( this, "touchstart mousedown", drag.init, data );
// prevent image dragging in IE...
- if ( this.attachEvent )
- this.attachEvent("ondragstart", drag.dontstart );
+ if ( this.attachEvent )
+ this.attachEvent("ondragstart", drag.dontstart );
},
-
+
// destroy configured interaction
teardown: function(){
var data = $.data( this, drag.datakey ) || {};
// check for related events
- if ( data.related )
+ if ( data.related )
return;
// remove the stored data
$.removeData( this, drag.datakey );
// remove the mousedown event
$event.remove( this, "touchstart mousedown", drag.init );
// enable text selection
- drag.textselect( true );
+ drag.textselect( true );
// un-prevent image dragging in IE...
- if ( this.detachEvent )
- this.detachEvent("ondragstart", drag.dontstart );
+ if ( this.detachEvent )
+ this.detachEvent("ondragstart", drag.dontstart );
},
-
+
// initialize the interaction
- init: function( event ){
+ init: function( event ){
// sorry, only one touch at a time
- if ( drag.touched )
+ if ( drag.touched )
return;
// the drag/drop interaction data
var dd = event.data, results;
// check the which directive
- if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
- return;
+ if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
+ return;
// check for suppressed selector
- if ( $( event.target ).is( dd.not ) )
+ if ( $( event.target ).is( dd.not ) )
return;
// check for handle selector
- if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
+ if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
return;
drag.touched = event.type == 'touchstart' ? this : null;
@@ -126,7 +132,7 @@ drag = $special.drag = {
dd.pageX = event.pageX;
dd.pageY = event.pageY;
dd.dragging = null;
- // handle draginit event...
+ // handle draginit event...
results = drag.hijack( event, "draginit", dd );
// early cancel
if ( !dd.propagates )
@@ -143,43 +149,43 @@ drag = $special.drag = {
// remember how many interactions are propagating
dd.propagates = dd.interactions.length;
// locate and init the drop targets
- if ( dd.drop !== false && $special.drop )
+ if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd );
// disable text selection
- drag.textselect( false );
+ drag.textselect( false );
// bind additional events...
if ( drag.touched )
$event.add( drag.touched, "touchmove touchend", drag.handler, dd );
- else
+ else
$event.add( document, "mousemove mouseup", drag.handler, dd );
// helps prevent text selection or scrolling
if ( !drag.touched || dd.live )
return false;
- },
-
+ },
+
// returns an interaction object
interaction: function( elem, dd ){
var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
return {
- drag: elem,
- callback: new drag.callback(),
+ drag: elem,
+ callback: new drag.callback(),
droppable: [],
offset: offset
};
},
-
+
// handle drag-releatd DOM events
- handler: function( event ){
+ handler: function( event ){
// read the data before hijacking anything
- var dd = event.data;
+ var dd = event.data;
// handle various events
switch ( event.type ){
// mousemove, check distance, start dragging
- case !dd.dragging && 'touchmove':
+ case !dd.dragging && 'touchmove':
event.preventDefault();
case !dd.dragging && 'mousemove':
- // drag tolerance, x≤ + y≤ = distance≤
- if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
+ // drag tolerance, x² + y² = distance²
+ if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
break; // distance tolerance not reached
event.target = dd.target; // force target from "mousedown" event (fix distance issue)
drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
@@ -190,42 +196,42 @@ drag = $special.drag = {
event.preventDefault();
case 'mousemove':
if ( dd.dragging ){
- // trigger "drag"
+ // trigger "drag"
drag.hijack( event, "drag", dd );
if ( dd.propagates ){
// manage drop events
if ( dd.drop !== false && $special.drop )
- $special.drop.handler( event, dd ); // "dropstart", "dropend"
- break; // "drag" not rejected, stop
+ $special.drop.handler( event, dd ); // "dropstart", "dropend"
+ break; // "drag" not rejected, stop
}
event.type = "mouseup"; // helps "drop" handler behave
}
// mouseup, stop dragging
- case 'touchend':
- case 'mouseup':
+ case 'touchend':
+ case 'mouseup':
default:
if ( drag.touched )
$event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
- else
- $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
+ else
+ $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
if ( dd.dragging ){
if ( dd.drop !== false && $special.drop )
$special.drop.handler( event, dd ); // "drop"
- drag.hijack( event, "dragend", dd ); // trigger "dragend"
+ drag.hijack( event, "dragend", dd ); // trigger "dragend"
}
drag.textselect( true ); // enable text selection
// if suppressing click events...
if ( dd.click === false && dd.dragging )
$.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
- dd.dragging = drag.touched = false; // deactivate element
+ dd.dragging = drag.touched = false; // deactivate element
break;
}
},
-
+
// re-use event object for custom events
hijack: function( event, type, dd, x, elem ){
// not configured
- if ( !dd )
+ if ( !dd )
return;
// remember the original event and type
var orig = { event:event.originalEvent, type:event.type },
@@ -255,7 +261,7 @@ drag = $special.drag = {
callback.target = subject;
// force propagtion of the custom event
event.isPropagationStopped = function(){ return false; };
- // handle the event
+ // handle the event
result = subject ? $event.dispatch.call( subject, event, callback ) : null;
// stop the drag interaction for this element
if ( result === false ){
@@ -270,25 +276,25 @@ drag = $special.drag = {
// assign any dropinit elements
else if ( type == "dropinit" )
ia.droppable.push( drag.element( result ) || subject );
- // accept a returned proxy element
+ // accept a returned proxy element
if ( type == "dragstart" )
ia.proxy = $( drag.element( result ) || ia.drag )[0];
- // remember this result
+ // remember this result
ia.results.push( result );
// forget the event result, for recycling
delete event.result;
// break on cancelled handler
if ( type !== "dropinit" )
return result;
- });
- // flatten the results
- dd.results[ i ] = drag.flatten( ia.results );
+ });
+ // flatten the results
+ dd.results[ i ] = drag.flatten( ia.results );
// accept a set of valid drop targets
if ( type == "dropinit" )
ia.droppable = drag.flatten( ia.droppable );
// locate drop targets
if ( type == "dragstart" && !ia.cancelled )
- callback.update();
+ callback.update();
}
while ( ++i < len )
// restore the original event & type
@@ -297,9 +303,9 @@ drag = $special.drag = {
// return all handler results
return drag.flatten( dd.results );
},
-
+
// extend the callback object with drag/drop properties...
- properties: function( event, dd, ia ){
+ properties: function( event, dd, ia ){
var obj = ia.callback;
// elements
obj.drag = ia.drag;
@@ -314,44 +320,44 @@ drag = $special.drag = {
obj.originalX = ia.offset.left;
obj.originalY = ia.offset.top;
// adjusted element position
- obj.offsetX = obj.originalX + obj.deltaX;
+ obj.offsetX = obj.originalX + obj.deltaX;
obj.offsetY = obj.originalY + obj.deltaY;
// assign the drop targets information
obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
- return obj;
+ return obj;
},
-
+
// determine is the argument is an element or jquery instance
element: function( arg ){
if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
return arg;
},
-
+
// flatten nested jquery objects and arrays into a single dimension array
flatten: function( arr ){
return $.map( arr, function( member ){
- return member && member.jquery ? $.makeArray( member ) :
+ return member && member.jquery ? $.makeArray( member ) :
member && member.length ? drag.flatten( member ) : member;
});
},
-
+
// toggles text selection attributes ON (true) or OFF (false)
- textselect: function( bool ){
+ textselect: function( bool ){
$( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
.css("MozUserSelect", bool ? "" : "none" );
// .attr("unselectable", bool ? "off" : "on" )
- document.unselectable = bool ? "off" : "on";
+ document.unselectable = bool ? "off" : "on";
},
-
+
// suppress "selectstart" and "ondragstart" events
- dontstart: function(){
- return false;
+ dontstart: function(){
+ return false;
},
-
+
// a callback instance contructor
callback: function(){}
-
+
};
// callback methods
@@ -375,9 +381,9 @@ $event.dispatch = function( event ){
};
// event fix hooks for touch events...
-var touchHooks =
-$event.fixHooks.touchstart =
-$event.fixHooks.touchmove =
+var touchHooks =
+$event.fixHooks.touchstart =
+$event.fixHooks.touchmove =
$event.fixHooks.touchend =
$event.fixHooks.touchcancel = {
props: "clientX clientY pageX pageY screenX screenY".split( " " ),
@@ -385,9 +391,9 @@ $event.fixHooks.touchcancel = {
if ( orig ){
var touched = ( orig.touches && orig.touches[0] )
|| ( orig.changedTouches && orig.changedTouches[0] )
- || null;
+ || null;
// iOS webkit: touchstart, touchmove, touchend
- if ( touched )
+ if ( touched )
$.each( touchHooks.props, function( i, prop ){
event[ prop ] = touched[ prop ];
});
@@ -399,4 +405,4 @@ $event.fixHooks.touchcancel = {
// share the same special event configuration with related events...
$special.draginit = $special.dragstart = $special.dragend = drag;
-})( jQuery );
\ No newline at end of file
+})( jQuery );
From e41bfe300397b159d144e6c0db1278267b4a4263 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Mon, 3 Aug 2015 17:25:06 +0530
Subject: [PATCH 33/47] [minor] [ux] refresh button primary in reportview
---
frappe/async.py | 6 ++++--
frappe/model/base_document.py | 4 +++-
.../public/js/frappe/views/reports/reportview.js | 14 +++++++++++---
3 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index 629dc442c8..7cb837901a 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -10,7 +10,7 @@ import os
import time
import redis
from functools import wraps
-from frappe.utils import get_site_path, get_url
+from frappe.utils import get_site_path
import json
from frappe import conf
@@ -196,6 +196,8 @@ def get_user_info(sid):
}
def new_comment(doc, event):
+ if not doc.comment_doctype:
+ return
if doc.comment_doctype == 'Message':
if doc.comment_docname == frappe.session.user:
message = doc.as_dict()
@@ -214,4 +216,4 @@ def get_user_room(user):
def get_site_room():
return ''.join([frappe.local.site, ':all'])
-
+
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index c34b753cd1..c4c2768fa4 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -160,7 +160,9 @@ class BaseDocument(object):
value.parent = self.name
value.parenttype = self.doctype
value.parentfield = key
- value.docstatus = 0
+
+ if value.docstatus==None:
+ value.docstatus = 0
if not getattr(value, "idx", None):
value.idx = len(self.get(key) or []) + 1
diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js
index 72cb27132b..8e5cfef630 100644
--- a/frappe/public/js/frappe/views/reports/reportview.js
+++ b/frappe/public/js/frappe/views/reports/reportview.js
@@ -97,9 +97,9 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
parent: this.page.main,
start: 0,
show_filters: true,
- new_doctype: this.doctype,
allow_delete: true,
});
+ this.make_new_and_refresh();
this.make_delete();
this.make_column_picker();
this.make_sorter();
@@ -109,8 +109,16 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
this.make_save();
this.make_user_permissions();
this.set_tag_and_status_filter();
- this.page.add_menu_item(__("Refresh"), function() {
- me.refresh();
+ },
+
+ make_new_and_refresh: function() {
+ var me = this;
+ this.page.set_primary_action(__("Refresh"), function() {
+ me.run();
+ });
+
+ this.page.add_menu_item(__("New {0}", [this.doctype]), function() {
+ new_doc(me.doctype);
}, true);
},
From 263d94f217a69256ade2e58e22fdbbd8ca997198 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 7 Aug 2015 18:26:15 +0530
Subject: [PATCH 34/47] [mobile] [style] fix checkboxes
---
frappe/public/css/mobile.css | 45 ++++++++++++----
frappe/public/js/frappe/form/dashboard.js | 2 +-
.../public/js/frappe/list/list_item_row.html | 6 +--
.../js/frappe/list/list_item_row_head.html | 2 +-
frappe/public/js/frappe/ui/toolbar/toolbar.js | 1 -
.../frappe/views/module/module_section.html | 2 +-
frappe/public/less/mobile.less | 53 ++++++++++++++-----
frappe/public/less/variables.less | 2 +
8 files changed, 83 insertions(+), 30 deletions(-)
diff --git a/frappe/public/css/mobile.css b/frappe/public/css/mobile.css
index a7c3b17702..1997008361 100644
--- a/frappe/public/css/mobile.css
+++ b/frappe/public/css/mobile.css
@@ -166,6 +166,9 @@ body {
.timeline .timeline-item:last-child {
border-bottom: none;
}
+ .list-row {
+ padding: 13px 15px !important;
+ }
.doclist-row {
position: relative;
padding-right: 10px;
@@ -192,6 +195,15 @@ body {
.doclist-row .list-row-right {
float: right;
}
+ .doclist-row .list-row-right .list-row-indicator {
+ top: 4px;
+ }
+ .doclist-row .list-row-right .list-row-indicator .indicator::before,
+ .doclist-row .list-row-right .list-row-indicator .indicator::after {
+ height: 12px;
+ width: 12px;
+ border-radius: 12px;
+ }
.doclist-row .list-row-right.no-right-column {
position: absolute;
top: 0;
@@ -199,15 +211,6 @@ body {
left: -10px;
width: 100%;
}
- .doclist-row .list-row-right.no-right-column .list-row-indicator {
- top: 5px;
- }
- .doclist-row .list-row-right.no-right-column .list-row-indicator .indicator::before,
- .doclist-row .list-row-right.no-right-column .list-row-indicator .indicator::after {
- height: 14px;
- width: 14px;
- border-radius: 14px;
- }
body[data-route^="messages"] .navbar-center {
display: block !important;
position: absolute;
@@ -258,6 +261,28 @@ body {
}
}
@media (max-width: 991px) {
+ input[type='checkbox'] {
+ -webkit-appearance: none;
+ width: 18px;
+ height: 18px;
+ background: white;
+ border-radius: 9px;
+ margin-top: -2px;
+ margin-bottom: -5px;
+ border: 1px solid #d1d8dd;
+ display: inline-block;
+ }
+ input[type='checkbox']:checked {
+ background: #3b99fc;
+ border-color: #3b99fc;
+ }
+ input.list-select-all {
+ margin-top: 0px;
+ }
+ .input-area input[type='checkbox'] {
+ margin-top: 2px;
+ margin-left: -23px;
+ }
.intro-area,
.footnote-area {
padding: 15px 0px;
@@ -269,7 +294,7 @@ body {
position: relative;
}
.page-head .page-title h1 {
- font-size: 18px;
+ font-size: 22px;
margin-top: 22px;
}
body[data-route^="Form"] .page-title h1 {
diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js
index c20c436f18..240991addd 100644
--- a/frappe/public/js/frappe/form/dashboard.js
+++ b/frappe/public/js/frappe/form/dashboard.js
@@ -42,7 +42,7 @@ frappe.ui.form.Dashboard = Class.extend({
var badge = $(repl('', {label:label, icon: frappe.boot.doctype_icons[doctype]}))
.appendTo(this.body)
diff --git a/frappe/public/js/frappe/list/list_item_row.html b/frappe/public/js/frappe/list/list_item_row.html
index 09916c15a3..c5f71083a7 100644
--- a/frappe/public/js/frappe/list/list_item_row.html
+++ b/frappe/public/js/frappe/list/list_item_row.html
@@ -1,5 +1,5 @@
-
+
{% if (is_different) { %}
{%= data.name %}
@@ -24,7 +23,6 @@
{%= list.get_indicator_dot(data) %}
diff --git a/frappe/public/js/frappe/list/list_item_row_head.html b/frappe/public/js/frappe/list/list_item_row_head.html
index 647abfad78..86ea53f5e3 100644
--- a/frappe/public/js/frappe/list/list_item_row_head.html
+++ b/frappe/public/js/frappe/list/list_item_row_head.html
@@ -1,6 +1,6 @@
-
{% } %}
-
{%= item.description %}
+
{%= item.description %}
{% if (item.last_modified) { %}
diff --git a/frappe/public/less/mobile.less b/frappe/public/less/mobile.less
index 793bf48c52..eeef5d7152 100644
--- a/frappe/public/less/mobile.less
+++ b/frappe/public/less/mobile.less
@@ -157,6 +157,10 @@
}
// listviews
+ .list-row {
+ padding: 13px 15px !important;
+ }
+
.doclist-row& {
position: relative;
padding-right: 10px;
@@ -187,6 +191,19 @@
.list-row-right {
float: right;
+
+ .list-row-indicator {
+ top: 4px;
+
+ // bigger indicators for list
+ .indicator::before, .indicator::after {
+ height: 12px;
+ width: 12px;
+ border-radius: 12px;
+ }
+
+ }
+
}
.list-row-right.no-right-column {
@@ -196,17 +213,6 @@
left: -10px;
width: 100%;
- .list-row-indicator {
- top: 5px;
-
- // bigger indicators for list
- .indicator::before, .indicator::after {
- height: 14px;
- width: 14px;
- border-radius: 14px;
- }
-
- }
}
}
@@ -273,6 +279,29 @@
}
@media(max-width: 991px) {
+ input[type='checkbox'] {
+ -webkit-appearance:none;
+ width: 18px;
+ height: 18px;
+ background: white;
+ border-radius: 9px;
+ margin-top: -2px;
+ margin-bottom: -5px;
+ border: 1px solid @border-color;
+ display: inline-block;
+ }
+ input[type='checkbox']:checked {
+ background: @checkbox-color;
+ border-color: @checkbox-color;
+ }
+ input.list-select-all {
+ margin-top: 0px;
+ }
+ .input-area input[type='checkbox'] {
+ margin-top: 2px;
+ margin-left: -23px;
+ }
+
.intro-area,
.footnote-area {
padding: 15px 0px;
@@ -288,7 +317,7 @@
.page-head {
.page-title h1 {
- font-size: 18px;
+ font-size: 22px;
margin-top: 22px;
}
}
diff --git a/frappe/public/less/variables.less b/frappe/public/less/variables.less
index ac3bfc58e1..9f45f24f33 100644
--- a/frappe/public/less/variables.less
+++ b/frappe/public/less/variables.less
@@ -31,3 +31,5 @@
@label-info-bg: #E8DDFF;
@label-warning-bg: #FFE6BF;
@label-danger-bg: #FFDCDC;
+
+@checkbox-color: #3b99fc;
From 341e268a225d932204d65c9a789786babe9d925d Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Mon, 10 Aug 2015 11:31:18 +0530
Subject: [PATCH 35/47] [minor] related to pull #1207
---
frappe/installer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/installer.py b/frappe/installer.py
index 8b391766a5..d921897462 100755
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -286,7 +286,7 @@ def add_module_defs(app):
d = frappe.new_doc("Module Def")
d.app_name = app
d.module_name = module
- d.save()
+ d.save(ignore_permissions=True)
def remove_missing_apps():
apps = ('frappe_subscription', 'shopping_cart')
From c36accbc60303b42983513e54b7cb81cefd07140 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Mon, 10 Aug 2015 13:06:11 +0530
Subject: [PATCH 36/47] [minor] avoid adding standard reply twice
---
frappe/public/js/frappe/views/communication.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js
index 01bad10a42..52c6bd6007 100644
--- a/frappe/public/js/frappe/views/communication.js
+++ b/frappe/public/js/frappe/views/communication.js
@@ -130,6 +130,9 @@ frappe.views.CommunicationComposer = Class.extend({
this.dialog.get_input("standard_reply").on("change", function() {
var standard_reply = $(this).val();
var prepend_reply = function() {
+ if(me.reply_added===standard_reply) {
+ return;
+ }
var content_field = me.dialog.fields_dict.content;
var content = content_field.get_value() || "";
@@ -144,6 +147,8 @@ frappe.views.CommunicationComposer = Class.extend({
}
content_field.set_input(content.join(''));
+
+ me.reply_added = standard_reply;
}
if(frappe.standard_replies[standard_reply]) {
prepend_reply();
From 8c97be9e2ea9c38285a4dcb6da2e93baa84458c4 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Tue, 11 Aug 2015 14:47:19 +0530
Subject: [PATCH 37/47] [minor] return btn for frm.add_custom_button
---
frappe/public/js/legacy/form.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/legacy/form.js b/frappe/public/js/legacy/form.js
index 0a0b4a6700..b90a105a54 100644
--- a/frappe/public/js/legacy/form.js
+++ b/frappe/public/js/legacy/form.js
@@ -779,7 +779,7 @@ _f.Frm.prototype.set_footnote = function(txt) {
_f.Frm.prototype.add_custom_button = function(label, fn, icon, toolbar_or_class) {
- this.page.add_inner_button(label, fn);
+ return this.page.add_inner_button(label, fn);
}
_f.Frm.prototype.clear_custom_buttons = function() {
From 78b390eb4700a012130c322c9b35b0e02cf659eb Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Tue, 11 Aug 2015 15:10:29 +0530
Subject: [PATCH 38/47] [fix] check dynamic links before cancel
---
frappe/model/document.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 547bdc6932..7f6149dd40 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -578,9 +578,10 @@ class Document(BaseDocument):
def check_no_back_links_exist(self):
"""Check if document links to any active document before Cancel."""
- from frappe.model.delete_doc import check_if_doc_is_linked
+ from frappe.model.delete_doc import check_if_doc_is_linked, check_if_doc_is_dynamically_linked
if not self.flags.ignore_links:
check_if_doc_is_linked(self, method="Cancel")
+ check_if_doc_is_dynamically_linked(self)
@staticmethod
def whitelist(f):
From 04ccac6df35040b4da86689fa3ca8d15286c146e Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Thu, 13 Aug 2015 12:01:20 +0530
Subject: [PATCH 39/47] [minor] abs url #1247, fix for form message
---
frappe/boot.py | 2 +-
frappe/commands.py | 2 +-
frappe/public/js/frappe/form/layout.js | 20 ++++++++------------
frappe/public/js/frappe/misc/user.js | 2 +-
frappe/public/js/legacy/form.js | 2 +-
5 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/frappe/boot.py b/frappe/boot.py
index 78a498801d..20d5b2f0bf 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -69,7 +69,7 @@ def get_bootinfo():
bootinfo['versions'] = {k: v['version'] for k, v in get_versions().items()}
bootinfo.error_report_email = frappe.get_hooks("error_report_email")
- bootinfo.default_background_image = get_url("/assets/frappe/images/ui/into-the-dawn.jpg")
+ bootinfo.default_background_image = "/assets/frappe/images/ui/into-the-dawn.jpg"
bootinfo.calendars = sorted(frappe.get_hooks("calendars"))
return bootinfo
diff --git a/frappe/commands.py b/frappe/commands.py
index c58be53b20..f1fee3ae27 100644
--- a/frappe/commands.py
+++ b/frappe/commands.py
@@ -626,7 +626,7 @@ def run_tests(context, app=None, module=None, doctype=None, test=(), driver=None
site = get_single_site(context)
frappe.init(site=site)
- if frappe.conf.run_selenium_tests:
+ if frappe.conf.run_selenium_tests and False:
sel.start(context.verbose, driver)
try:
diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js
index 3dce0ebd2c..85812be228 100644
--- a/frappe/public/js/frappe/form/layout.js
+++ b/frappe/public/js/frappe/form/layout.js
@@ -44,24 +44,20 @@ frappe.ui.form.Layout = Class.extend({
$(this.frm.wrapper).trigger("refresh-fields");
}
- if (this.frm) {
- // show empty form notification
- setTimeout(function() {
- me.page.find(".empty-form-alert").remove();
- if(!(me.page.find(".frappe-control:visible").length)) {
- $(''
- +__("This form does not have any input")+'
')
- .appendTo(me.page);
- }
- }, 100);
- }
-
// dependent fields
this.refresh_dependency();
// refresh sections
this.refresh_sections();
},
+ show_empty_form_message: function() {
+ this.wrapper.find(".empty-form-alert").remove();
+ if(!(this.wrapper.find(".frappe-control:visible").length)) {
+ $(''
+ +__("This form does not have any input")+'
')
+ .appendTo(this.page);
+ }
+ },
attach_doc_and_docfields: function(refresh) {
var me = this;
for(var i=0, l=this.fields_list.length; i
Date: Thu, 13 Aug 2015 12:08:25 +0530
Subject: [PATCH 40/47] [enhancement] assign to myself, #1241
---
frappe/change_log/current/assign_to_myself.md | 1 +
frappe/public/js/frappe/form/footer/assign_to.js | 9 +++++++++
2 files changed, 10 insertions(+)
create mode 100644 frappe/change_log/current/assign_to_myself.md
diff --git a/frappe/change_log/current/assign_to_myself.md b/frappe/change_log/current/assign_to_myself.md
new file mode 100644
index 0000000000..18da7d65da
--- /dev/null
+++ b/frappe/change_log/current/assign_to_myself.md
@@ -0,0 +1 @@
+- You can now quickly assign a document to yourself by clicking on "Assign to me"
diff --git a/frappe/public/js/frappe/form/footer/assign_to.js b/frappe/public/js/frappe/form/footer/assign_to.js
index cd485aee95..ea7af7ed7e 100644
--- a/frappe/public/js/frappe/form/footer/assign_to.js
+++ b/frappe/public/js/frappe/form/footer/assign_to.js
@@ -92,6 +92,7 @@ frappe.ui.form.AssignTo = Class.extend({
me.dialog = new frappe.ui.Dialog({
title: __('Add to To Do'),
fields: [
+ {fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0},
{fieldtype:'Link', fieldname:'assign_to', options:'User',
label:__("Assign To"),
description:__("Add to To Do List Of"), reqd:true},
@@ -115,6 +116,14 @@ frappe.ui.form.AssignTo = Class.extend({
}
me.dialog.show();
+
+ me.dialog.get_input("myself").on("click", function() {
+ if($(this).prop("checked")) {
+ me.dialog.set_value("assign_to", user);
+ } else {
+ me.dialog.set_value("assign_to", "");
+ }
+ });
},
add_assignment: function() {
var me = this;
From 7e290587ef411d1c2d00a80dd9e4e53921aa4847 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Thu, 13 Aug 2015 12:17:29 +0530
Subject: [PATCH 41/47] [minor] fixes #1238
---
frappe/public/js/frappe/views/reports/reportview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js
index 8e5cfef630..33f6705870 100644
--- a/frappe/public/js/frappe/views/reports/reportview.js
+++ b/frappe/public/js/frappe/views/reports/reportview.js
@@ -252,7 +252,7 @@ frappe.views.ReportView = frappe.ui.Listing.extend({
if(docfield.fieldtype==="Link" && docfield.fieldname!=="name") {
docfield.link_onclick =
- repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").page.reportview.run()',
+ repl('frappe.container.page.reportview.set_filter("%(fieldname)s", "%(value)s").run()',
{fieldname:docfield.fieldname, value:value});
}
return frappe.format(value, docfield, {for_print: for_print}, dataContext);
From 5cbfecc299b690ce5851adc8a671fe9f7510263d Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Thu, 13 Aug 2015 13:01:40 +0530
Subject: [PATCH 42/47] [minor] added currency for Ghana, fixes
frappe/erpnext#3795
---
frappe/geo/country_info.json | 1 +
frappe/patches.txt | 1 +
frappe/patches/v6_0/fix_ghana_currency.py | 7 +++++++
frappe/utils/install.py | 6 ++++--
4 files changed, 13 insertions(+), 2 deletions(-)
create mode 100644 frappe/patches/v6_0/fix_ghana_currency.py
diff --git a/frappe/geo/country_info.json b/frappe/geo/country_info.json
index f33977c6b2..4f41e385a2 100644
--- a/frappe/geo/country_info.json
+++ b/frappe/geo/country_info.json
@@ -911,6 +911,7 @@
},
"Ghana": {
"code": "gh",
+ "currency": "GHS",
"currency_fraction": "Pesewa",
"currency_fraction_units": 100,
"currency_symbol": "\u20b5",
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 61f2ea4e23..fd4848aaed 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -88,3 +88,4 @@ frappe.patches.v5_3.rename_chinese_languages
frappe.patches.v6_0.make_task_log_folder
frappe.patches.v6_0.document_type_rename
+frappe.patches.v6_0.fix_ghana_currency
diff --git a/frappe/patches/v6_0/fix_ghana_currency.py b/frappe/patches/v6_0/fix_ghana_currency.py
new file mode 100644
index 0000000000..10365148b1
--- /dev/null
+++ b/frappe/patches/v6_0/fix_ghana_currency.py
@@ -0,0 +1,7 @@
+
+def execute():
+ from frappe.geo.country_info import get_all
+ import frappe.utils.install
+
+ countries = get_all()
+ frappe.utils.install.add_country_and_currency("Ghana", frappe._dict(countries["Ghana"]))
diff --git a/frappe/utils/install.py b/frappe/utils/install.py
index 204638e11a..10d30f5a2b 100644
--- a/frappe/utils/install.py
+++ b/frappe/utils/install.py
@@ -106,7 +106,8 @@ def add_country_and_currency(name, country):
"country_name": name,
"code": country.code,
"date_format": country.date_format or "dd-mm-yyyy",
- "time_zones": "\n".join(country.timezones or [])
+ "time_zones": "\n".join(country.timezones or []),
+ "docstatus": 0
}).db_insert()
if country.currency and not frappe.db.exists("Currency", country.currency):
@@ -116,6 +117,7 @@ def add_country_and_currency(name, country):
"fraction": country.currency_fraction,
"symbol": country.currency_symbol,
"fraction_units": country.currency_fraction_units,
- "number_format": country.number_format
+ "number_format": country.number_format,
+ "docstatus": 0
}).db_insert()
From c3229a58280eebaea59a8b754e61b668f83e2ba6 Mon Sep 17 00:00:00 2001
From: Pratik Vyas
Date: Thu, 13 Aug 2015 23:03:35 -0700
Subject: [PATCH 43/47] [v6] fix data import test case
---
frappe/tests/test_data_import.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/frappe/tests/test_data_import.py b/frappe/tests/test_data_import.py
index af7cc4fc15..fb81cdccb2 100644
--- a/frappe/tests/test_data_import.py
+++ b/frappe/tests/test_data_import.py
@@ -35,7 +35,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("Blog Category", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "", "test-category", "Test Cateogry"])
- importer.upload(content)
+ importer.upload._f(content)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "Test Category")
# export with data
@@ -44,7 +44,7 @@ class TestDataImport(unittest.TestCase):
# overwrite
content[-1][3] = "New Title"
- importer.upload(content, overwrite=True)
+ importer.upload._f(content, overwrite=True)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "New Title")
def test_import_only_children(self):
@@ -57,7 +57,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Blogger"])
- importer.upload(content)
+ importer.upload._f(content)
user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -67,7 +67,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Website Manager"])
- importer.upload(content, overwrite=True)
+ importer.upload._f(content, overwrite=True)
user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -81,7 +81,7 @@ class TestDataImport(unittest.TestCase):
content[-1][3] = "Private"
content[-1][4] = "2014-01-01 10:00:00.000000"
content[-1][content[15].index("role")] = "System Manager"
- importer.upload(content)
+ importer.upload._f(content)
ev = frappe.get_doc("Event", {"subject":"__Test Event"})
self.assertTrue("System Manager" in [d.role for d in ev.roles])
From 748cac0d99c0d1aeec1c40dc7fe743f243a1ce79 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 14 Aug 2015 12:19:35 +0530
Subject: [PATCH 44/47] [enhancement] added 'assigned by me' in todo list
---
frappe/desk/doctype/todo/todo_list.js | 20 +++++++++++++++++++
frappe/desk/form/meta.py | 15 +++++++++++---
frappe/public/css/form.css | 4 ++++
.../public/js/frappe/form/footer/assign_to.js | 2 ++
frappe/public/js/frappe/list/doclistview.js | 5 +++++
.../public/js/frappe/list/list_sidebar.html | 4 +++-
frappe/public/js/frappe/list/list_sidebar.js | 14 ++++++++++++-
frappe/public/js/frappe/ui/page.js | 15 ++++++++++++++
frappe/public/less/form.less | 5 +++++
9 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/frappe/desk/doctype/todo/todo_list.js b/frappe/desk/doctype/todo/todo_list.js
index 3bf70ec9f9..192057eb4e 100644
--- a/frappe/desk/doctype/todo/todo_list.js
+++ b/frappe/desk/doctype/todo/todo_list.js
@@ -5,6 +5,26 @@ frappe.listview_settings['ToDo'] = {
"status": "Open"
};
me.page.set_title(__("To Do"));
+
+ },
+ refresh: function(me) {
+ // override assigned to me by owner
+ me.page.sidebar.find(".assigned-to-me a").off("click").on("click", function() {
+ var assign_filter = me.filter_list.get_filter("assigned_by");
+ assign_filter && assign_filter.remove(true);
+
+ me.filter_list.add_filter(me.doctype, "owner", '=', user);
+ me.run();
+ });
+
+ // add assigned by me
+ me.page.add_sidebar_item(__("Assigned By Me"), function() {
+ var assign_filter = me.filter_list.get_filter("owner");
+ assign_filter && assign_filter.remove(true);
+
+ me.filter_list.add_filter(me.doctype, "assigned_by", '=', user);
+ me.run();
+ }, ".assigned-to-me");
},
add_fields: ["reference_type", "reference_name"],
}
diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py
index c993dce046..e0c03753db 100644
--- a/frappe/desk/form/meta.py
+++ b/frappe/desk/form/meta.py
@@ -121,7 +121,14 @@ class FormMeta(Meta):
df.search_fields = map(lambda sf: sf.strip(), search_fields.split(","))
def add_linked_with(self):
- """add list of doctypes this doctype is 'linked' with"""
+ """add list of doctypes this doctype is 'linked' with.
+
+ Example, for Customer:
+
+ {"Address": {"fieldname": "customer"}..}
+ """
+
+ # find fields where this doctype is linked
links = frappe.db.sql("""select parent, fieldname from tabDocField
where (fieldtype="Link" and options=%s)
or (fieldtype="Select" and options=%s)""", (self.name, "link:"+ self.name))
@@ -137,15 +144,17 @@ class FormMeta(Meta):
ret[dt] = { "fieldname": links[dt] }
if links:
- for grand_parent, options in frappe.db.sql("""select parent, options from tabDocField
+ # find out if linked in a child table
+ for parent, options in frappe.db.sql("""select parent, options from tabDocField
where fieldtype="Table"
and options in (select name from tabDocType
where istable=1 and name in (%s))""" % ", ".join(["%s"] * len(links)) ,tuple(links)):
- ret[grand_parent] = {"child_doctype": options, "fieldname": links[options] }
+ ret[parent] = {"child_doctype": options, "fieldname": links[options] }
if options in ret:
del ret[options]
+ # find links of parents
links = frappe.db.sql("""select dt from `tabCustom Field`
where (fieldtype="Table" and options=%s)""", (self.name))
links += frappe.db.sql("""select parent from tabDocField
diff --git a/frappe/public/css/form.css b/frappe/public/css/form.css
index a85f9ae799..63db368199 100644
--- a/frappe/public/css/form.css
+++ b/frappe/public/css/form.css
@@ -157,3 +157,7 @@ select.form-control {
-moz-appearance: none;
appearance: none;
}
+.form-control.bold {
+ font-weight: bold;
+ background-color: #fffce7;
+}
diff --git a/frappe/public/js/frappe/form/footer/assign_to.js b/frappe/public/js/frappe/form/footer/assign_to.js
index ea7af7ed7e..8df3b19914 100644
--- a/frappe/public/js/frappe/form/footer/assign_to.js
+++ b/frappe/public/js/frappe/form/footer/assign_to.js
@@ -120,8 +120,10 @@ frappe.ui.form.AssignTo = Class.extend({
me.dialog.get_input("myself").on("click", function() {
if($(this).prop("checked")) {
me.dialog.set_value("assign_to", user);
+ me.dialog.set_value("notify", 0);
} else {
me.dialog.set_value("assign_to", "");
+ me.dialog.set_value("notify", 1);
}
});
},
diff --git a/frappe/public/js/frappe/list/doclistview.js b/frappe/public/js/frappe/list/doclistview.js
index 2f1109b525..0fb4b59522 100644
--- a/frappe/public/js/frappe/list/doclistview.js
+++ b/frappe/public/js/frappe/list/doclistview.js
@@ -216,6 +216,11 @@ frappe.views.DocListView = frappe.ui.Listing.extend({
refresh: function() {
var me = this;
this.init_stats();
+
+ if(this.listview.settings.refresh) {
+ this.listview.settings.refresh(this);
+ }
+
if(frappe.route_options) {
me.set_route_options();
} else if(me.dirty) {
diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html
index 57ef1cff6b..f254d52698 100644
--- a/frappe/public/js/frappe/list/list_sidebar.html
+++ b/frappe/public/js/frappe/list/list_sidebar.html
@@ -6,7 +6,9 @@
{%= __("Report") %}
{%= __("Calendar") %}
{%= __("Gantt") %}
- {%= __("Assigned To Me") %}
+
+ {%= __("Assigned To Me") %}
+
{% if(frappe.help.has_help(doctype)) { %}
{{ __("Help") }}
{% } %}
diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js
index 9dbc5bf70a..296dac87bf 100644
--- a/frappe/public/js/frappe/list/list_sidebar.js
+++ b/frappe/public/js/frappe/list/list_sidebar.js
@@ -12,8 +12,8 @@ frappe.provide('frappe.views');
frappe.views.ListSidebar = Class.extend({
init: function(opts) {
$.extend(this, opts);
- this.get_stats();
this.make();
+ this.get_stats();
},
make: function() {
var sidebar_content = frappe.render_template("list_sidebar", {doctype: this.doclistview.doctype});
@@ -25,10 +25,22 @@ frappe.views.ListSidebar = Class.extend({
this.sidebar = this.page_sidebar.add(this.offcanvas_list_sidebar);
+ this.setup_assigned_to_me();
+
if(frappe.views.calendar[this.doctype]) {
this.sidebar.find(".calendar-link, .gantt-link").removeClass("hide");
}
},
+ setup_assigned_to_me: function() {
+ var me = this;
+ this.page.sidebar.find(".assigned-to-me a").on("click", function() {
+ me.doclistview.assigned_to_me();
+ });
+
+ this.offcanvas_list_sidebar.find(".assigned-to-me a").on("click", function() {
+ me.doclistview.assigned_to_me();
+ });
+ },
get_stats: function() {
var me = this
return frappe.call({
diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js
index 6e23e7ca6c..ddcabd34ff 100644
--- a/frappe/public/js/frappe/ui/page.js
+++ b/frappe/public/js/frappe/ui/page.js
@@ -232,6 +232,21 @@ frappe.ui.Page = Class.extend({
.on("click", action).appendTo(this.inner_toolbar.removeClass("hide"))
},
+ //-- Sidebar --//
+
+ add_sidebar_item: function(label, action, insert_after) {
+ var parent = this.sidebar.find(".sidebar-menu.standard-actions");
+ var li = $('');
+ var link = $('').html(label).on("click", action).appendTo(li);
+
+ if(insert_after) {
+ li.insertAfter(parent.find(insert_after));
+ } else {
+ li.appendTo(parent);
+ }
+ return link;
+ },
+
//---//
clear_user_actions: function() {
diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less
index a60dcf3c4a..917fdb16d1 100644
--- a/frappe/public/less/form.less
+++ b/frappe/public/less/form.less
@@ -204,3 +204,8 @@ select.form-control {
-moz-appearance: none;
appearance: none;
}
+
+.form-control.bold {
+ font-weight: bold;
+ background-color: @light-yellow;
+}
From 21d5aaa47c4714c3789cb01e3e0f6921d1c531fe Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 14 Aug 2015 14:22:18 +0530
Subject: [PATCH 45/47] [added] alternate desktop (listified) for ios app
---
frappe/core/page/desktop/desktop.js | 73 +++++++++++++------
.../core/page/desktop/desktop_list_view.html | 25 +++++++
.../page/desktop/desktop_module_icon.html | 4 +-
frappe/public/css/desktop.css | 12 +++
frappe/public/css/mobile.css | 11 +--
frappe/public/js/frappe/ui/toolbar/about.js | 4 +-
frappe/public/less/desktop.less | 18 +++++
frappe/public/less/mobile.less | 12 +--
8 files changed, 122 insertions(+), 37 deletions(-)
create mode 100644 frappe/core/page/desktop/desktop_list_view.html
diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js
index 31837ba7f5..42be800ffc 100644
--- a/frappe/core/page/desktop/desktop.js
+++ b/frappe/core/page/desktop/desktop.js
@@ -2,10 +2,18 @@ frappe.provide('frappe.desktop');
frappe.pages['desktop'].on_page_load = function(wrapper) {
// load desktop
- frappe.desktop.set_background();
+ if(!frappe.list_desktop) {
+ frappe.desktop.set_background();
+ }
frappe.desktop.refresh(wrapper);
};
+frappe.pages['desktop'].on_page_show = function(wrapper) {
+ if(frappe.list_desktop) {
+ $("body").attr("data-route", "list-desktop");
+ }
+};
+
$.extend(frappe.desktop, {
refresh: function(wrapper) {
if (wrapper) {
@@ -20,7 +28,10 @@ $.extend(frappe.desktop, {
var me = this;
frappe.utils.set_title("Desktop");
- this.wrapper.html(frappe.render_template("desktop_icon_grid", {
+ var template = frappe.list_desktop ? "desktop_list_view" : "desktop_icon_grid";
+
+
+ this.wrapper.html(frappe.render_template(template, {
// all visible icons
desktop_items: this.get_desktop_items(),
@@ -28,7 +39,7 @@ $.extend(frappe.desktop, {
user_desktop_items: this.get_user_desktop_items(),
}));
- this.setup_icon_click();
+ this.setup_module_click();
// notifications
this.show_pending_notifications();
@@ -96,29 +107,40 @@ $.extend(frappe.desktop, {
return out;
},
- setup_icon_click: function() {
- this.wrapper.on("click", ".app-icon", function() {
- var parent = $(this).parent();
- var link = parent.attr("data-link");
- if(link) {
- if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
- window.open(link, "_blank");
- } else {
- frappe.set_route(link);
- }
- return false;
+ setup_module_click: function() {
+ var me = this;
+
+ if(frappe.list_desktop) {
+ this.wrapper.on("click", ".desktop-list-item", function() {
+ me.open_module($(this));
+ });
+ } else {
+ this.wrapper.on("click", ".app-icon", function() {
+ me.open_module($(this).parent());
+ });
+ }
+ },
+
+ open_module: function(parent) {
+ var link = parent.attr("data-link");
+ if(link) {
+ if(link.substr(0, 1)==="/" || link.substr(0, 4)==="http") {
+ window.open(link, "_blank");
} else {
- module = frappe.get_module(parent.attr("data-name"));
- if (module && module.onclick) {
- module.onclick();
- return false;
- }
+ frappe.set_route(link);
}
- });
+ return false;
+ } else {
+ module = frappe.get_module(parent.attr("data-name"));
+ if (module && module.onclick) {
+ module.onclick();
+ return false;
+ }
+ }
},
make_sortable: function() {
- if (frappe.dom.is_touchscreen()) {
+ if (frappe.dom.is_touchscreen() || frappe.list_desktop) {
return;
}
@@ -215,10 +237,15 @@ $.extend(frappe.desktop, {
sum = frappe.boot.notification_info.open_count_module[module];
}
if (frappe.modules[module]) {
- var notifier = $("#module-count-" + frappe.get_module(module)._id);
+ var notifier = $(".module-count-" + frappe.get_module(module)._id);
if(notifier.length) {
notifier.toggle(sum ? true : false);
- notifier.find(".circle-text").html(sum || "");
+ var circle = notifier.find(".circle-text");
+ if(circle.length) {
+ circle.html(sum || "");
+ } else {
+ notifier.html(sum);
+ }
}
}
}
diff --git a/frappe/core/page/desktop/desktop_list_view.html b/frappe/core/page/desktop/desktop_list_view.html
new file mode 100644
index 0000000000..fc5f66f283
--- /dev/null
+++ b/frappe/core/page/desktop/desktop_list_view.html
@@ -0,0 +1,25 @@
+
+
+
+
+ {% for (var i=0, l=desktop_items.length; i < l; i++) {
+ var module = frappe.get_module(desktop_items[i]);
+ if (!module || (user_desktop_items.indexOf(module.name)===-1 && !module.force_show)
+ || frappe.user.is_module_blocked(module.name)) { continue; }
+ %}
+
+
+
+ {{ module._label }}
+
+
+
+ {% } %}
+
+
+
+
diff --git a/frappe/core/page/desktop/desktop_module_icon.html b/frappe/core/page/desktop/desktop_module_icon.html
index 49ae8f8e75..f5d685aef8 100644
--- a/frappe/core/page/desktop/desktop_module_icon.html
+++ b/frappe/core/page/desktop/desktop_module_icon.html
@@ -1,8 +1,8 @@
-
{%= app_icon %}
-
+
diff --git a/frappe/public/css/desktop.css b/frappe/public/css/desktop.css
index e09d50aca2..de7c2114b8 100644
--- a/frappe/public/css/desktop.css
+++ b/frappe/public/css/desktop.css
@@ -161,3 +161,15 @@ body[data-route="desktop"] .navbar-default {
margin-top: 3px;
margin-bottom: 3px;
}
+.desktop-list-item {
+ padding: 10px 15px;
+ border-bottom: 1px solid #d1d8dd;
+ cursor: pointer;
+}
+.desktop-list-item:hover,
+.desktop-list-item:focus {
+ background-color: #f7fafc;
+}
+.desktop-list-item h4 {
+ display: inline-block;
+}
diff --git a/frappe/public/css/mobile.css b/frappe/public/css/mobile.css
index 1997008361..1eb5f49de5 100644
--- a/frappe/public/css/mobile.css
+++ b/frappe/public/css/mobile.css
@@ -263,12 +263,10 @@ body {
@media (max-width: 991px) {
input[type='checkbox'] {
-webkit-appearance: none;
- width: 18px;
- height: 18px;
+ width: 12px;
+ height: 12px;
background: white;
- border-radius: 9px;
- margin-top: -2px;
- margin-bottom: -5px;
+ border-radius: 6px;
border: 1px solid #d1d8dd;
display: inline-block;
}
@@ -329,6 +327,9 @@ body {
.module-item {
padding: 7px 0px !important;
}
+ .module-item h4 {
+ font-weight: normal;
+ }
#navbar-breadcrumbs {
margin: 0px;
display: inline-block;
diff --git a/frappe/public/js/frappe/ui/toolbar/about.js b/frappe/public/js/frappe/ui/toolbar/about.js
index 5df44b395e..662cbc76e3 100644
--- a/frappe/public/js/frappe/ui/toolbar/about.js
+++ b/frappe/public/js/frappe/ui/toolbar/about.js
@@ -33,8 +33,8 @@ frappe.ui.misc.about = function() {
var $wrap = $("#about-app-versions").empty();
$.each(keys(versions).sort(), function(i, key) {
var v = versions[key];
- $($.format('
{0}: v{1}
{2}
',
- [v.title, v.version, v.description])).appendTo($wrap);
+ $($.format('
{0}: v{1}
',
+ [v.title, v.version])).appendTo($wrap);
});
frappe.versions = versions;
diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less
index 6a05009ec7..df52050ab0 100644
--- a/frappe/public/less/desktop.less
+++ b/frappe/public/less/desktop.less
@@ -200,3 +200,21 @@ body[data-route=""] .navbar-default, body[data-route="desktop"] .navbar-default
margin-bottom: 3px;
}
}
+
+.desktop-list {
+
+}
+
+.desktop-list-item& {
+ padding: 10px 15px;
+ border-bottom: 1px solid @border-color;
+ cursor: pointer;
+
+ &:hover, &:focus {
+ background-color: @panel-bg;
+ }
+
+ h4 {
+ display: inline-block;
+ }
+}
diff --git a/frappe/public/less/mobile.less b/frappe/public/less/mobile.less
index eeef5d7152..753d12d282 100644
--- a/frappe/public/less/mobile.less
+++ b/frappe/public/less/mobile.less
@@ -281,12 +281,10 @@
@media(max-width: 991px) {
input[type='checkbox'] {
-webkit-appearance:none;
- width: 18px;
- height: 18px;
+ width: 12px;
+ height: 12px;
background: white;
- border-radius: 9px;
- margin-top: -2px;
- margin-bottom: -5px;
+ border-radius: 6px;
border: 1px solid @border-color;
display: inline-block;
}
@@ -363,6 +361,10 @@
.module-item {
padding: 7px 0px !important;
+
+ h4 {
+ font-weight: normal;
+ }
}
#navbar-breadcrumbs {
From 74bc524416908954c86f4b08bc0e10f68d7b2871 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 14 Aug 2015 15:08:40 +0530
Subject: [PATCH 46/47] [test] fix test_async
---
frappe/async.py | 12 ++++++------
frappe/handler.py | 7 ++++++-
frappe/tests/test_async.py | 7 ++++---
frappe/tests/test_data_import.py | 10 +++++-----
4 files changed, 21 insertions(+), 15 deletions(-)
diff --git a/frappe/async.py b/frappe/async.py
index 7cb837901a..2a0c57dad8 100644
--- a/frappe/async.py
+++ b/frappe/async.py
@@ -35,7 +35,7 @@ def handler(f):
return task.id
@wraps(f)
- def _f(*args, **kwargs):
+ def queue(*args, **kwargs):
from frappe.tasks import run_async_task
from frappe.handler import execute_cmd
if frappe.conf.no_async:
@@ -48,12 +48,12 @@ def handler(f):
"status": "queued",
"task_id": task.id
}
- _f.async = True
- _f._f = f
- _f.run = _run
+ queue.async = True
+ queue.queue = f
+ queue.run = _run
frappe.whitelisted.append(f)
- frappe.whitelisted.append(_f)
- return _f
+ frappe.whitelisted.append(queue)
+ return queue
def run_async_task(method, args, reference_doctype=None, reference_name=None, set_in_response=True):
diff --git a/frappe/handler.py b/frappe/handler.py
index 05f17b623f..c2fe2119f9 100755
--- a/frappe/handler.py
+++ b/frappe/handler.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe
from frappe import _
import frappe.utils
+import frappe.async
import frappe.sessions
import frappe.utils.file_manager
import frappe.desk.form.run_method
@@ -18,6 +19,10 @@ def version():
def ping():
return "pong"
+@frappe.async.handler
+def async_ping():
+ return "pong"
+
@frappe.whitelist()
def runserverobj(method, docs=None, dt=None, dn=None, arg=None, args=None):
frappe.desk.form.run_method.runserverobj(method, docs=docs, dt=dt, dn=dn, arg=arg, args=args)
@@ -79,7 +84,7 @@ def execute_cmd(cmd, async=False):
method = get_attr(cmd)
if async:
- method = method._f
+ method = method.queue
# check if whitelisted
if frappe.session['user'] == 'Guest':
diff --git a/frappe/tests/test_async.py b/frappe/tests/test_async.py
index 67077a1a2d..e1f5c3e673 100644
--- a/frappe/tests/test_async.py
+++ b/frappe/tests/test_async.py
@@ -10,7 +10,8 @@ import frappe
from frappe.tasks import run_async_task
class TestAsync(unittest.TestCase):
-
def test_response(self):
- result = run_async_task(frappe.local.site, 'Administrator', 'async_ping', frappe._dict())
- self.assertEquals(result.message, "pong")
+ result = run_async_task.delay(frappe.local.site, 'Administrator', 'async_ping',
+ frappe._dict())
+ result = result.get()
+ self.assertEquals(result.get("message"), "pong")
diff --git a/frappe/tests/test_data_import.py b/frappe/tests/test_data_import.py
index fb81cdccb2..256fc15244 100644
--- a/frappe/tests/test_data_import.py
+++ b/frappe/tests/test_data_import.py
@@ -35,7 +35,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("Blog Category", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "", "test-category", "Test Cateogry"])
- importer.upload._f(content)
+ importer.upload.queue(content)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "Test Category")
# export with data
@@ -44,7 +44,7 @@ class TestDataImport(unittest.TestCase):
# overwrite
content[-1][3] = "New Title"
- importer.upload._f(content, overwrite=True)
+ importer.upload.queue(content, overwrite=True)
self.assertTrue(frappe.db.get_value("Blog Category", "test-category", "title"), "New Title")
def test_import_only_children(self):
@@ -57,7 +57,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Blogger"])
- importer.upload._f(content)
+ importer.upload.queue(content)
user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -67,7 +67,7 @@ class TestDataImport(unittest.TestCase):
exporter.get_template("UserRole", "User", all_doctypes="No", with_data="No")
content = read_csv_content(frappe.response.result)
content.append(["", "test_import_userrole@example.com", "Website Manager"])
- importer.upload._f(content, overwrite=True)
+ importer.upload.queue(content, overwrite=True)
user = frappe.get_doc("User", user_email)
self.assertEquals(len(user.get("user_roles")), 1)
@@ -81,7 +81,7 @@ class TestDataImport(unittest.TestCase):
content[-1][3] = "Private"
content[-1][4] = "2014-01-01 10:00:00.000000"
content[-1][content[15].index("role")] = "System Manager"
- importer.upload._f(content)
+ importer.upload.queue(content)
ev = frappe.get_doc("Event", {"subject":"__Test Event"})
self.assertTrue("System Manager" in [d.role for d in ev.roles])
From 9dbd96af27aa732824402c403b47ae179b5e2329 Mon Sep 17 00:00:00 2001
From: Rushabh Mehta
Date: Fri, 14 Aug 2015 15:31:58 +0530
Subject: [PATCH 47/47] [travis] use bench start & with cache
---
.travis.yml | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 464256b1dd..bae95835e3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -15,7 +15,6 @@ install:
- wget https://raw.githubusercontent.com/frappe/bench/master/install_scripts/setup_frappe.sh
- sudo bash setup_frappe.sh --skip-setup-bench --mysql-root-password travis
- sudo pip install --upgrade pip
- - sudo service redis-server start
- rm $TRAVIS_BUILD_DIR/.git/shallow
- cd ~/ && bench init frappe-bench --frappe-path $TRAVIS_BUILD_DIR
- cp -r $TRAVIS_BUILD_DIR/test_sites/test_site ~/frappe-bench/sites/
@@ -23,10 +22,13 @@ install:
script:
- cd ~/frappe-bench
- bench use test_site
+ - bench setup redis-cache
+ - bench setup redis-async-broker
+ - bench setup procfile --with-celery-broker
- bench reinstall
- bench build
- bench build-website
- - bench serve &
+ - bench start &
- sleep 10
- bench --verbose run-tests --driver Firefox