This commit is contained in:
Akhilesh Darjee 2013-05-01 11:30:24 +05:30
commit 13944dd970
42 changed files with 1713 additions and 524 deletions

View file

@ -1,6 +1,6 @@
[
{
"creation": "2013-01-10 16:34:00",
"creation": "2013-01-28 17:06:59",
"docstatus": 0,
"modified": "2013-01-28 15:29:13",
"modified_by": "Administrator",
@ -26,14 +26,11 @@
"permlevel": 0
},
{
"cancel": 1,
"create": 1,
"doctype": "DocPerm",
"name": "__common__",
"parent": "Communication",
"parentfield": "permissions",
"parenttype": "DocType",
"permlevel": 0,
"read": 1,
"report": 1,
"submit": 0,
@ -243,16 +240,6 @@
"fieldtype": "Date",
"label": "Date"
},
{
"doctype": "DocField",
"fieldname": "file_list",
"fieldtype": "Text",
"hidden": 1,
"in_list_view": 1,
"label": "File List",
"no_copy": 1,
"print_hide": 1
},
{
"doctype": "DocField",
"fieldname": "_user_tags",
@ -263,26 +250,53 @@
"print_hide": 1
},
{
"amend": 0,
"create": 1,
"doctype": "DocPerm",
"permlevel": 0,
"role": "Support Team"
},
{
"amend": 0,
"create": 1,
"doctype": "DocPerm",
"permlevel": 0,
"role": "Sales Manager"
},
{
"create": 1,
"doctype": "DocPerm",
"permlevel": 0,
"role": "Sales User"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"doctype": "DocPerm",
"match": "",
"permlevel": 1,
"role": "Sales Manager"
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"doctype": "DocPerm",
"role": "Sales User"
},
{
"doctype": "DocPerm",
"match": "",
"permlevel": 1,
"role": "Support Manager"
},
{
"cancel": 1,
"create": 1,
"doctype": "DocPerm",
"permlevel": 0,
"role": "Support Manager"
},
{
"cancel": 1,
"create": 1,
"doctype": "DocPerm",
"permlevel": 0,
"role": "System Manager"
}
]

View file

@ -40,7 +40,8 @@ class DocType:
sql('UPDATE tabDocType SET modified="%s" WHERE `name`="%s"' % (now(), p[0]))
def scrub_field_names(self):
restricted = ('name','parent','idx','owner','creation','modified','modified_by','parentfield','parenttype')
restricted = ('name','parent','idx','owner','creation','modified','modified_by',
'parentfield','parenttype',"file_list")
for d in self.doclist:
if d.parent and d.fieldtype:
if (not d.fieldname):
@ -80,7 +81,6 @@ class DocType:
validate_permissions(self.doclist.get({"doctype":"DocPerm"}))
self.set_version()
self.make_amendable()
self.make_file_list()
self.check_link_replacement_error()
def on_update(self):
@ -139,24 +139,6 @@ class DocType:
"doctype_template.py"), 'r') as srcfile:
pyfile.write(srcfile.read())
def make_file_list(self):
"""
if allow_attach is checked and the column file_list doesn't exist,
create a new field 'file_list'
"""
if self.doc.allow_attach:
if not webnotes.conn.sql("""select name from tabDocField
where fieldname = 'file_list' and parent = %s""", self.doc.name):
new = self.doc.addchild('fields', 'DocField', self.doclist)
new.label = 'File List'
new.fieldtype = 'Text'
new.fieldname = 'file_list'
new.hidden = 1
new.permlevel = 0
new.print_hide = 1
new.no_copy = 1
new.idx = self.get_max_idx() + 1
def make_amendable(self):
"""
if is_submittable is set, add amended_from docfields

View file

@ -27,45 +27,27 @@ record of files
naming for same name files: file.gif, file-1.gif, file-2.gif etc
"""
import webnotes
import webnotes, webnotes.utils, os
class DocType():
def __init__(self, d, dl):
self.doc, self.doclist = d, dl
def autoname(self):
"""save file by its name"""
if not self.doc.file_name:
raise Exception, 'file name missing'
if not '.' in self.doc.file_name:
raise Exception, 'file name must have extension (.)'
self.doc.file_name = self.doc.file_name.replace('-', '')
parts = self.doc.file_name.split('.')
same = webnotes.conn.sql("""select name from `tabFile Data`
where name=%s""", self.doc.file_name)
if same:
# check for more
other_list = webnotes.conn.sql("""select name from `tabFile Data`
where name like '%s-%%.%s'""" % (parts[0], '.'.join(parts[1:])))
def on_update(self):
# check duplicate assignement
n_records = webnotes.conn.sql("""select count(*) from `tabFile Data`
where file_name=%s
and attached_to_doctype=%s
and attached_to_name=%s""", (self.doc.file_name, self.doc.attached_to_doctype,
self.doc.attached_to_name))[0][0]
if n_records > 1:
webnotes.msgprint(webnotes._("Same file has already been attached to the record"))
raise webnotes.DuplicateEntryError
if other_list:
from webnotes.utils import cint
# gets the max number from format like name-###.ext
last_num = max(
(cint(other[0].split('.')[0].split('-')[-1])
for other in other_list)
)
else:
last_num = 0
new_id = "%03d" % (last_num + 1)
# new name
self.doc.file_name = parts[0] + '-' + new_id + '.' + '.'.join(parts[1:])
self.doc.name = self.doc.file_name
def on_trash(self):
if webnotes.conn.sql("""select count(*) from `tabFile Data`
where file_name=%s""", self.doc.file_name)[0][0]==1:
path = webnotes.utils.get_path("public", "files", self.doc.file_name)
if os.path.exists(path):
os.remove(path)

View file

@ -1,51 +1,83 @@
[
{
"owner": "Administrator",
"creation": "2012-12-12 11:19:22",
"docstatus": 0,
"creation": "2012-11-30 18:13:34",
"modified": "2013-04-10 13:34:29",
"modified_by": "Administrator",
"modified": "2012-12-11 14:56:34"
"owner": "Administrator"
},
{
"read_only": 0,
"autoname": "FileData/.#####",
"name": "__common__",
"autoname": "File.######",
"doctype": "DocType",
"module": "Core"
"module": "Core",
"name": "__common__",
"read_only": 0
},
{
"doctype": "DocField",
"name": "__common__",
"parent": "File Data",
"doctype": "DocField",
"parentfield": "fields",
"parenttype": "DocType",
"permlevel": 0,
"parentfield": "fields"
"read_only": 1
},
{
"name": "File Data",
"doctype": "DocType"
"cancel": 1,
"doctype": "DocPerm",
"name": "__common__",
"parent": "File Data",
"parentfield": "permissions",
"parenttype": "DocType",
"permlevel": 0,
"read": 1,
"role": "System Manager",
"write": 1
},
{
"doctype": "DocType",
"name": "File Data"
},
{
"oldfieldtype": "Data",
"doctype": "DocField",
"fieldname": "file_name",
"fieldtype": "Data",
"label": "File Name",
"oldfieldname": "file_name",
"fieldname": "file_name",
"fieldtype": "Data"
"oldfieldtype": "Data"
},
{
"doctype": "DocField",
"label": "File URL",
"fieldname": "file_url",
"fieldtype": "Data"
"fieldtype": "Data",
"in_list_view": 1,
"label": "File URL"
},
{
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Module",
"oldfieldname": "module",
"fieldname": "module",
"fieldname": "attached_to_doctype",
"fieldtype": "Link",
"options": "Module Def"
"in_list_view": 1,
"label": "Attached To DocType",
"options": "DocType",
"search_index": 1
},
{
"doctype": "DocField",
"fieldname": "attached_to_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Attached To Name",
"search_index": 1
},
{
"doctype": "DocField",
"fieldname": "file_size",
"fieldtype": "Int",
"in_list_view": 1,
"label": "File Size"
},
{
"doctype": "DocPerm"
}
]

View file

@ -57,8 +57,10 @@ cur_frm.cscript['set_from_image'] = function(doc, dt, dn) {
if(!confirm('Are you sure you want to overwrite the existing HTML?'))
return;
}
var file_name = doc.file_list.split(',')[1]
var file_name = cur_frm.get_files()[0];
if(!in_list(['gif','jpg','jpeg','png'], file_name.split('.')[1].toLowerCase())) {
msgprint("Please upload a web friendly (GIF, JPG or PNG) image file for the letter head");

View file

@ -1,21 +1,18 @@
[
{
"creation": "2012-11-19 12:06:53",
"creation": "2012-11-22 17:45:46",
"docstatus": 0,
"modified": "2012-11-21 17:39:17",
"modified_by": "Administrator",
"owner": "Administrator"
},
{
"_last_update": "1307340319",
"allow_attach": 1,
"autoname": "field:letter_head_name",
"doctype": "DocType",
"max_attachments": 3,
"module": "Core",
"name": "__common__",
"section_style": "Simple",
"version": 1
"name": "__common__"
},
{
"doctype": "DocField",
@ -26,6 +23,7 @@
"permlevel": 0
},
{
"cancel": 1,
"create": 1,
"doctype": "DocPerm",
"name": "__common__",
@ -34,7 +32,9 @@
"parenttype": "DocType",
"permlevel": 0,
"read": 1,
"report": 1,
"role": "System Manager",
"submit": 0,
"write": 1
},
{
@ -83,8 +83,7 @@
"doctype": "DocField",
"fieldname": "set_from_image",
"fieldtype": "Button",
"label": "Set From Image",
"trigger": "Client"
"label": "Set From Image"
},
{
"depends_on": "letter_head_name",
@ -103,15 +102,6 @@
"hidden": 1,
"label": "URL"
},
{
"doctype": "DocField",
"fieldname": "file_list",
"fieldtype": "Text",
"hidden": 1,
"label": "File LIst",
"oldfieldname": "file_list",
"oldfieldtype": "Text"
},
{
"doctype": "DocPerm"
}

View file

@ -187,7 +187,10 @@ Thank you,<br>
'product': startup.product_name,
'user_fullname': get_user_fullname(webnotes.session['user'])
}
sendmail_md(self.doc.email, subject=subject, msg=txt % args)
sender = webnotes.session.user != "Administrator" and webnotes.session.user or None
sendmail_md(recipients=self.doc.email, sender=sender, subject=subject, msg=txt % args)
def on_trash(self):
if self.doc.name in ["Administrator", "Guest"]:

View file

@ -1,6 +1,6 @@
[
{
"creation": "2013-02-14 17:37:36",
"creation": "2013-03-07 12:26:21",
"docstatus": 0,
"modified": "2013-03-01 10:18:03",
"modified_by": "Administrator",
@ -387,16 +387,6 @@
"oldfieldtype": "Read Only",
"read_only": 1
},
{
"doctype": "DocField",
"fieldname": "file_list",
"fieldtype": "Text",
"hidden": 1,
"label": "File List",
"no_copy": 1,
"oldfieldname": "file_list",
"oldfieldtype": "Text"
},
{
"doctype": "DocField",
"fieldname": "roles_assigned_to_user",

View file

@ -1,8 +1,8 @@
[
{
"creation": "2013-02-25 13:11:50",
"creation": "2013-03-09 15:45:57",
"docstatus": 0,
"modified": "2013-02-25 14:18:36",
"modified": "2013-04-30 17:50:00",
"modified_by": "Administrator",
"owner": "Administrator"
},
@ -41,6 +41,7 @@
"fieldname": "report_name",
"fieldtype": "Data",
"label": "Report Name",
"read_only": 0,
"reqd": 1
},
{
@ -50,6 +51,7 @@
"in_list_view": 1,
"label": "Ref DocType",
"options": "DocType",
"read_only": 0,
"reqd": 1
},
{
@ -59,12 +61,20 @@
"in_list_view": 1,
"label": "Is Standard",
"options": "No\nYes",
"read_only": 0,
"reqd": 1
},
{
"doctype": "DocField",
"fieldname": "add_total_row",
"fieldtype": "Check",
"label": "Add Total Row"
},
{
"doctype": "DocField",
"fieldname": "column_break_4",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"read_only": 0
},
{
"doctype": "DocField",
@ -72,25 +82,29 @@
"fieldtype": "Select",
"label": "Report Type",
"options": "Report Builder\nQuery Report\nScript Report",
"read_only": 0,
"reqd": 1
},
{
"doctype": "DocField",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
"label": "Disabled",
"read_only": 0
},
{
"doctype": "DocField",
"fieldname": "section_break_6",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"read_only": 0
},
{
"depends_on": "eval:doc.report_type==\"Query Report\"",
"doctype": "DocField",
"fieldname": "query",
"fieldtype": "Code",
"label": "Query"
"label": "Query",
"read_only": 0
},
{
"depends_on": "eval:doc.report_type==\"Report Builder\"",

View file

@ -26,7 +26,7 @@ cur_frm.cscript['server_python'] = function(doc, dt, dn) {
$c_obj(make_doclist(doc.doctype, doc.name), 'execute_server', '', function(r, rt) {
doc = locals[doc.doctype][doc.name];
if(r.exc) {
doc.response = r.exc;
doc.response = (r.exc || []).join("\n");
} else {
doc.response = 'Worked!'.bold()
}

View file

@ -141,7 +141,7 @@ wn.pages['data-import-tool'].onload = function(wrapper) {
});
// add overwrite option
var $submit_btn = $('#dit-upload-area form input[type="submit"]');
var $submit_btn = $('#dit-upload-area input[type="submit"]');
$('<input type="checkbox" name="overwrite" style="margin-top: -3px">\
<span> Overwrite</span>\
<p class="help">If you are uploading a child table (for example Item Price), the all the entries of that table will be deleted (for that parent record) and new entries will be made.</p><br>')

View file

@ -93,7 +93,7 @@ def get_template():
docfield = doctype_dl.get_field(t)
if docfield and ((mandatory and docfield.reqd) or not (mandatory or docfield.reqd)) \
and (t not in ('parenttype', 'trash_reason', 'file_list')) and not docfield.hidden:
and (t not in ('parenttype', 'trash_reason')) and not docfield.hidden:
fieldrow.append(t)
labelrow.append(docfield.label)
mandatoryrow.append(docfield.reqd and 'Yes' or 'No')
@ -176,8 +176,12 @@ def upload():
return columns
# extra input params
import json
params = json.loads(webnotes.form_dict.get("params") or '{}')
# header
rows = read_csv_content_from_uploaded_file()
rows = read_csv_content_from_uploaded_file(params.get("ignore_encoding_errors"))
start_row = get_start_row()
header = rows[:start_row]
data = rows[start_row:]
@ -195,7 +199,7 @@ def upload():
webnotes.conn.begin()
overwrite = webnotes.form_dict.get('overwrite')
overwrite = params.get('overwrite')
doctype_dl = webnotes.model.doctype.get(doctype)
# delete child rows (if parenttype)
@ -226,8 +230,7 @@ def upload():
ret.append('Inserted row for %s at #%s' % (getlink(parenttype,
doc.parent), unicode(doc.idx)))
else:
ret.append(import_doc(d, doctype, overwrite, row_idx,
webnotes.form_dict.get("_submit")=="on"))
ret.append(import_doc(d, doctype, overwrite, row_idx, params.get("_submit")))
except Exception, e:
error = True
ret.append('Error for row (#%d) %s : %s' % (row_idx,
@ -307,13 +310,10 @@ def delete_child_rows(rows, doctype):
def import_doc(d, doctype, overwrite, row_idx, submit=False):
"""import main (non child) document"""
from webnotes.model.bean import Bean
if webnotes.conn.exists(doctype, d['name']):
if overwrite:
doclist = webnotes.model.doc.get(doctype, d['name'])
doclist[0].fields.update(d)
bean = Bean(doclist)
bean = webnotes.bean(doctype, d['name'])
bean.doc.fields.update(d)
if d.get("docstatus") == 1:
bean.update_after_submit()
else:
@ -323,12 +323,11 @@ def import_doc(d, doctype, overwrite, row_idx, submit=False):
return 'Ignored row (#%d) %s (exists)' % (row_idx,
getlink(doctype, d['name']))
else:
d['__islocal'] = 1
dl = Bean([webnotes.model.doc.Document(fielddata = d)])
dl.save()
bean = webnotes.bean([d])
bean.insert()
if submit:
dl.submit()
bean.submit()
return 'Inserted row (#%d) %s' % (row_idx, getlink(doctype,
dl.doc.fields['name']))
bean.doc.fields['name']))

View file

@ -155,6 +155,7 @@
"lib/public/js/legacy/widgets/form/form_comments.js",
"lib/public/js/legacy/wn/widgets/form/sidebar.js",
"lib/public/js/legacy/wn/widgets/form/comments.js",
"lib/public/js/wn/form/editors.js",
"lib/public/js/wn/form/attachments.js",
"lib/public/js/wn/form/linked_with.js",
"lib/public/js/wn/form/states.js",

View file

@ -113,3 +113,18 @@ a {
font-size: 32px;
color: #888;
}
.wysiwyg-editor {
height: 400px;
background-color: white;
border-collapse: separate;
border: 1px solid rgb(204, 204, 204);
padding: 4px;
box-sizing: content-box;
-webkit-box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset;
box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset;
border-top-right-radius: 3px; border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px; border-top-left-radius: 3px;
overflow: scroll;
outline: none;
}

View file

@ -148,12 +148,12 @@ function replace_all(s, t1, t2) {
function keys(obj) {
var mykeys=[];
for (key in obj) mykeys[mykeys.length]=key;
for (var key in obj) mykeys[mykeys.length]=key;
return mykeys;
}
function values(obj) {
var myvalues=[];
for (key in obj) myvalues[myvalues.length]=obj[key];
for (var key in obj) myvalues[myvalues.length]=obj[key];
return myvalues;
}

View file

@ -219,9 +219,9 @@ _f.Frm.prototype.call_server = function(method, args, callback) {
}
_f.Frm.prototype.get_files = function() {
return $.map((cur_frm.doc.file_list || "").split("\n"), function(f) {
return f.split(",")[0] || null;
});
return cur_frm.attachments
? keys(cur_frm.attachments.get_file_list()).sort()
: [] ;
}
_f.Frm.prototype.set_query = function(fieldname, opt1, opt2) {

View file

@ -1202,14 +1202,15 @@ SelectField.prototype.make_input = function() {
if(!cur_frm) return;
var fl = cur_frm.doc.file_list;
if(fl) {
fl = JSON.parse(fl);
this.df.options = '';
var fl = fl.split('\n');
for(var i in fl) {
var fname = fl[i].split(',')[0];
for(var fname in fl) {
if(fname.substr(0,4)!="http")
fname = "files/" + fname;
this.df.options += '\n' + fname;
}
this.set_description("");
} else {
this.df.options = ''

View file

@ -218,143 +218,36 @@ _f.CodeField.prototype.make_input = function() {
this.label_span.innerHTML = this.df.label;
$(this.input_area).css({"min-height":"360px"});
if(this.df.fieldtype=='Text Editor') {
$(this.input_area).css({"min-height":"360px"});
this.input = $a(this.input_area, 'text_area', '', {fontSize:'12px'});
this.myid = wn.dom.set_unique_id(this.input);
// setup tiny mce
$(me.input).tinymce({
// Location of TinyMCE script
script_url : 'lib/js/lib/tiny_mce_3.5.7/tiny_mce.js',
// General options
theme : "advanced",
plugins : "style,inlinepopups,table,advimage",
extended_valid_elements: "script|embed",
// w/h
width: '100%',
height: '360px',
// buttons
theme_advanced_buttons1 : "bold,italic,underline,hr,|,justifyleft,justifycenter,|,formatselect,fontsizeselect,|,bullist,numlist,|,image,|,outdent,indent,|,link,|,forecolor,backcolor,|,code",
theme_advanced_buttons2 : "",
theme_advanced_buttons3 : "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location: "none",
theme_advanced_path: false,
valid_elements : "*[*]",
content_css: "lib/js/lib/tiny_mce_3.5.7/custom_content.css?q=1",
oninit: function() { me.init_editor(); },
setup: function(ed) {
ed.onChange.add(function(ed, l) {
me.set(l.content);
me.run_trigger();
});
}
this.input = new wn.editors.BootstrapWYSIWYG({
parent: this.input_area,
change: function(value) {
me.set_value_and_run_trigger(value);
},
field: this
});
this.input.set_input = function(v) {
if(me.editor) {
me.editor.setContent(v==null ? "" : v);
} else {
$(me.input).val(v);
}
}
this.get_value = function() {
return me.editor && me.editor.getContent(); // tinyMCE
}
} else {
// setup ace
wn.require('lib/js/lib/ace/ace.js');
$(this.input_area).css('border','1px solid #aaa');
this.pre = $("<pre style='position: relative; height: 400px; \
width: 100%; padding: 0px; border-radius: 0px;\
margin: 0px; background-color: #fff;'>").appendTo(this.input_area).get(0);
this.input = {};
this.myid = wn.dom.set_unique_id(this.pre);
this.editor = ace.edit(this.myid);
if(me.df.options=='Markdown' || me.df.options=='HTML') {
wn.require('lib/js/lib/ace/mode-html.js');
var HTMLMode = require("ace/mode/html").Mode;
me.editor.getSession().setMode(new HTMLMode());
}
else if(me.df.options=='Javascript') {
wn.require('lib/js/lib/ace/mode-javascript.js');
var JavascriptMode = require("ace/mode/javascript").Mode;
me.editor.getSession().setMode(new JavascriptMode());
}
else if(me.df.options=='Python') {
wn.require('lib/js/lib/ace/mode-python.js');
var PythonMode = require("ace/mode/python").Mode;
me.editor.getSession().setMode(new PythonMode());
}
this.input.set_input = function(v) {
// during field refresh in run trigger, set_input is called
// if called during on_change, setting doesn't make sense
// and causes cursor to shift back to first position
if(me.changing_value) return;
me.setting_value = true;
me.editor.getSession().setValue(v==null ? "" : v);
me.setting_value = false;
}
this.get_value = function() {
return me.editor.getSession().getValue(); // tinyMCE
}
$(cur_frm.wrapper).bind('render_complete', function() {
me.editor.resize();
me.editor.getSession().on('change', function() {
if(me.setting_value) return;
var val = me.get_value();
if(locals[cur_frm.doctype][cur_frm.docname][me.df.fieldname] != val) {
me.set(me.get_value());
me.changing_value = true;
me.run_trigger();
me.changing_value = false;
}
})
this.input = new wn.editors.ACE({
parent: this.input_area,
change: function(value) {
me.set_value_and_run_trigger(value);
},
field: this
});
this.onrefresh = function() {
me.editor && me.editor.resize();
}
}
this.get_value = function() {
return this.input.get_value();
}
}
_f.CodeField.prototype.init_editor = function() {
// attach onchange methods
var me = this;
this.editor = tinymce.get(this.myid);
this.editor.onKeyUp.add(function(ed, e) {
me.set(ed.getContent());
});
this.editor.onPaste.add(function(ed, e) {
me.set(ed.getContent());
});
this.editor.onSetContent.add(function(ed, e) {
me.set(ed.getContent());
});
// reset content
var c = locals[cur_frm.doctype][cur_frm.docname][this.df.fieldname];
if(cur_frm && c) {
this.editor.setContent(c);
_f.CodeField.prototype.set_value_and_run_trigger = function(value) {
if(locals[cur_frm.doctype][cur_frm.docname][this.df.fieldname] != value) {
this.set(value);
this.changing_value = true;
this.run_trigger();
this.changing_value = false;
}
}
@ -363,7 +256,8 @@ _f.CodeField.prototype.set_disp = function(val) {
if(this.df.fieldtype=='Text Editor') {
this.disp_area.innerHTML = val;
} else {
this.disp_area.innerHTML = '<textarea class="code_text" readonly=1>'+val+'</textarea>';
this.disp_area.innerHTML = '<textarea class="code_text" readonly=1>'
+val+'</textarea>';
}
}

View file

@ -0,0 +1,617 @@
/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
/*
The MIT License (MIT)
Copyright (c) 2007-2013 Einar Lielmanis and contributors.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Style HTML
---------------
Written by Nochum Sossonko, (nsossonko@hotmail.com)
Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv>
http://jsbeautifier.org/
Usage:
style_html(html_source);
style_html(html_source, options);
The options are:
indent_size (default 4) indentation size,
indent_char (default space) character to indent with,
max_char (default 250) - maximum amount of characters per line (0 = disable)
brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
indent_scripts (default normal) - "keep"|"separate"|"normal"
e.g.
style_html(html_source, {
'indent_size': 2,
'indent_char': ' ',
'max_char': 78,
'brace_style': 'expand',
'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u']
});
*/
(function() {
function style_html(html_source, options, js_beautify, css_beautify) {
//Wrapper function to invoke all the necessary constructors and deal with the output.
var multi_parser,
indent_size,
indent_character,
max_char,
brace_style,
unformatted;
options = options || {};
indent_size = options.indent_size || 4;
indent_character = options.indent_char || ' ';
brace_style = options.brace_style || 'collapse';
max_char = options.max_char === 0 ? Infinity : options.max_char || 250;
unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
function Parser() {
this.pos = 0; //Parser position
this.token = '';
this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
parent: 'parent1',
parentcount: 1,
parent1: ''
};
this.tag_type = '';
this.token_text = this.last_token = this.last_text = this.token_type = '';
this.Utils = { //Uilities made available to the various functions
whitespace: "\n\r\t ".split(''),
single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
in_array: function (what, arr) {
for (var i=0; i<arr.length; i++) {
if (what === arr[i]) {
return true;
}
}
return false;
}
};
this.get_content = function () { //function to capture regular content between tags
var input_char = '',
content = [],
space = false; //if a space is needed
while (this.input.charAt(this.pos) !== '<') {
if (this.pos >= this.input.length) {
return content.length?content.join(''):['', 'TK_EOF'];
}
input_char = this.input.charAt(this.pos);
this.pos++;
this.line_char_count++;
if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
if (content.length) {
space = true;
}
this.line_char_count--;
continue; //don't want to insert unnecessary space
}
else if (space) {
if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached
content.push('\n');
for (var i=0; i<this.indent_level; i++) {
content.push(this.indent_string);
}
this.line_char_count = 0;
}
else{
content.push(' ');
this.line_char_count++;
}
space = false;
}
content.push(input_char); //letter at-a-time (or string) inserted to an array
}
return content.length?content.join(''):'';
};
this.get_contents_to = function (name) { //get the full content of a script or style to pass to js_beautify
if (this.pos === this.input.length) {
return ['', 'TK_EOF'];
}
var input_char = '';
var content = '';
var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
reg_match.lastIndex = this.pos;
var reg_array = reg_match.exec(this.input);
var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script
if(this.pos < end_script) { //get everything in between the script tags
content = this.input.substring(this.pos, end_script);
this.pos = end_script;
}
return content;
};
this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object
if (this.tags[tag + 'count']) { //check for the existence of this tag type
this.tags[tag + 'count']++;
this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
}
else { //otherwise initialize this tag type
this.tags[tag + 'count'] = 1;
this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
}
this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
};
this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer
if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
var temp_parent = this.tags.parent; //check to see if it's a closable tag.
while (temp_parent) { //till we reach '' (the initial value);
if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
break;
}
temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
}
if (temp_parent) { //if we caught something
this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
}
delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
if (this.tags[tag + 'count'] === 1) {
delete this.tags[tag + 'count'];
}
else {
this.tags[tag + 'count']--;
}
}
};
this.get_tag = function (peek) { //function to get a full tag and parse its type
var input_char = '',
content = [],
comment = '',
space = false,
tag_start, tag_end,
orig_pos = this.pos,
orig_line_char_count = this.line_char_count;
peek = peek !== undefined ? peek : false;
do {
if (this.pos >= this.input.length) {
if (peek) {
this.pos = orig_pos;
this.line_char_count = orig_line_char_count;
}
return content.length?content.join(''):['', 'TK_EOF'];
}
input_char = this.input.charAt(this.pos);
this.pos++;
this.line_char_count++;
if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
space = true;
this.line_char_count--;
continue;
}
if (input_char === "'" || input_char === '"') {
if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially
input_char += this.get_unformatted(input_char);
space = true;
}
}
if (input_char === '=') { //no space before =
space = false;
}
if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) {
//no space after = or before >
if (this.line_char_count >= this.max_char) {
this.print_newline(false, content);
this.line_char_count = 0;
}
else {
content.push(' ');
this.line_char_count++;
}
space = false;
}
if (input_char === '<') {
tag_start = this.pos - 1;
}
content.push(input_char); //inserts character at-a-time (or string)
} while (input_char !== '>');
var tag_complete = content.join('');
var tag_index;
if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
tag_index = tag_complete.indexOf(' ');
}
else { //otherwise go with the tag ending
tag_index = tag_complete.indexOf('>');
}
var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
if (tag_complete.charAt(tag_complete.length-2) === '/' ||
this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
if ( ! peek) {
this.tag_type = 'SINGLE';
}
}
else if (tag_check === 'script') { //for later script handling
if ( ! peek) {
this.record_tag(tag_check);
this.tag_type = 'SCRIPT';
}
}
else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content)
if ( ! peek) {
this.record_tag(tag_check);
this.tag_type = 'STYLE';
}
}
else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
comment = this.get_unformatted('</'+tag_check+'>', tag_complete); //...delegate to get_unformatted function
content.push(comment);
// Preserve collapsed whitespace either before or after this tag.
if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){
content.splice(0, 0, this.input.charAt(tag_start - 1));
}
tag_end = this.pos - 1;
if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){
content.push(this.input.charAt(tag_end + 1));
}
this.tag_type = 'SINGLE';
}
else if (tag_check.charAt(0) === '!') { //peek for <!-- comment
if (tag_check.indexOf('[if') !== -1) { //peek for <!--[if conditional comment
if (tag_complete.indexOf('!IE') !== -1) { //this type needs a closing --> so...
comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted
content.push(comment);
}
if ( ! peek) {
this.tag_type = 'START';
}
}
else if (tag_check.indexOf('[endif') !== -1) {//peek for <!--[endif end conditional comment
this.tag_type = 'END';
this.unindent();
}
else if (tag_check.indexOf('[cdata[') !== -1) { //if it's a <[cdata[ comment...
comment = this.get_unformatted(']]>', tag_complete); //...delegate to get_unformatted function
content.push(comment);
if ( ! peek) {
this.tag_type = 'SINGLE'; //<![CDATA[ comments are treated like single tags
}
}
else {
comment = this.get_unformatted('-->', tag_complete);
content.push(comment);
this.tag_type = 'SINGLE';
}
}
else if ( ! peek) {
if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
this.tag_type = 'END';
}
else { //otherwise it's a start-tag
this.record_tag(tag_check); //push it on the tag stack
this.tag_type = 'START';
}
if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
this.print_newline(true, this.output);
}
}
if (peek) {
this.pos = orig_pos;
this.line_char_count = orig_line_char_count;
}
return content.join(''); //returns fully formatted tag
};
this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety
if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
return '';
}
var input_char = '';
var content = '';
var space = true;
do {
if (this.pos >= this.input.length) {
return content;
}
input_char = this.input.charAt(this.pos);
this.pos++;
if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
if (!space) {
this.line_char_count--;
continue;
}
if (input_char === '\n' || input_char === '\r') {
content += '\n';
/* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
for (var i=0; i<this.indent_level; i++) {
content += this.indent_string;
}
space = false; //...and make sure other indentation is erased
*/
this.line_char_count = 0;
continue;
}
}
content += input_char;
this.line_char_count++;
space = true;
} while (content.toLowerCase().indexOf(delimiter) === -1);
return content;
};
this.get_token = function () { //initial handler for token-retrieval
var token;
if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
var type = this.last_token.substr(7);
token = this.get_contents_to(type);
if (typeof token !== 'string') {
return token;
}
return [token, 'TK_' + type];
}
if (this.current_mode === 'CONTENT') {
token = this.get_content();
if (typeof token !== 'string') {
return token;
}
else {
return [token, 'TK_CONTENT'];
}
}
if (this.current_mode === 'TAG') {
token = this.get_tag();
if (typeof token !== 'string') {
return token;
}
else {
var tag_name_type = 'TK_TAG_' + this.tag_type;
return [token, tag_name_type];
}
}
};
this.get_full_indent = function (level) {
level = this.indent_level + level || 0;
if (level < 1) {
return '';
}
return Array(level + 1).join(this.indent_string);
};
this.is_unformatted = function(tag_check, unformatted) {
//is this an HTML5 block-level link?
if (!this.Utils.in_array(tag_check, unformatted)){
return false;
}
if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)){
return true;
}
//at this point we have an tag; is its first child something we want to remain
//unformatted?
var next_tag = this.get_tag(true /* peek. */);
// tets next_tag to see if it is just html tag (no external content)
var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
// if next_tag comes back but is not an isolated tag, then
// let's treat the 'a' tag as having content
// and respect the unformatted option
if (!tag || this.Utils.in_array(tag, unformatted)){
return true;
} else {
return false;
}
};
this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
this.input = js_source || ''; //gets the input for the Parser
this.output = [];
this.indent_character = indent_character;
this.indent_string = '';
this.indent_size = indent_size;
this.brace_style = brace_style;
this.indent_level = 0;
this.max_char = max_char;
this.line_char_count = 0; //count to see if max_char was exceeded
for (var i=0; i<this.indent_size; i++) {
this.indent_string += this.indent_character;
}
this.print_newline = function (ignore, arr) {
this.line_char_count = 0;
if (!arr || !arr.length) {
return;
}
if (!ignore) { //we might want the extra line
while (this.Utils.in_array(arr[arr.length-1], this.Utils.whitespace)) {
arr.pop();
}
}
arr.push('\n');
for (var i=0; i<this.indent_level; i++) {
arr.push(this.indent_string);
}
};
this.print_token = function (text) {
this.output.push(text);
};
this.indent = function () {
this.indent_level++;
};
this.unindent = function () {
if (this.indent_level > 0) {
this.indent_level--;
}
};
};
return this;
}
/*_____________________--------------------_____________________*/
multi_parser = new Parser(); //wrapping functions Parser
multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
while (true) {
var t = multi_parser.get_token();
multi_parser.token_text = t[0];
multi_parser.token_type = t[1];
if (multi_parser.token_type === 'TK_EOF') {
break;
}
switch (multi_parser.token_type) {
case 'TK_TAG_START':
multi_parser.print_newline(false, multi_parser.output);
multi_parser.print_token(multi_parser.token_text);
multi_parser.indent();
multi_parser.current_mode = 'CONTENT';
break;
case 'TK_TAG_STYLE':
case 'TK_TAG_SCRIPT':
multi_parser.print_newline(false, multi_parser.output);
multi_parser.print_token(multi_parser.token_text);
multi_parser.current_mode = 'CONTENT';
break;
case 'TK_TAG_END':
//Print new line only if the tag has no content and has child
if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
var tag_name = multi_parser.token_text.match(/\w+/)[0];
var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
multi_parser.print_newline(true, multi_parser.output);
}
}
multi_parser.print_token(multi_parser.token_text);
multi_parser.current_mode = 'CONTENT';
break;
case 'TK_TAG_SINGLE':
// Don't add a newline before elements that should remain unformatted.
var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
multi_parser.print_newline(false, multi_parser.output);
}
multi_parser.print_token(multi_parser.token_text);
multi_parser.current_mode = 'CONTENT';
break;
case 'TK_CONTENT':
if (multi_parser.token_text !== '') {
multi_parser.print_token(multi_parser.token_text);
}
multi_parser.current_mode = 'TAG';
break;
case 'TK_STYLE':
case 'TK_SCRIPT':
if (multi_parser.token_text !== '') {
multi_parser.output.push('\n');
var text = multi_parser.token_text,
_beautifier,
script_indent_level = 1;
if (multi_parser.token_type === 'TK_SCRIPT') {
_beautifier = typeof js_beautify === 'function' && js_beautify;
} else if (multi_parser.token_type === 'TK_STYLE') {
_beautifier = typeof css_beautify === 'function' && css_beautify;
}
if (options.indent_scripts === "keep") {
script_indent_level = 0;
} else if (options.indent_scripts === "separate") {
script_indent_level = -multi_parser.indent_level;
}
var indentation = multi_parser.get_full_indent(script_indent_level);
if (_beautifier) {
// call the Beautifier if avaliable
text = _beautifier(text.replace(/^\s*/, indentation), options);
} else {
// simply indent the string otherwise
var white = text.match(/^\s*/)[0];
var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
var reindent = multi_parser.get_full_indent(script_indent_level -_level);
text = text.replace(/^\s*/, indentation)
.replace(/\r\n|\r|\n/g, '\n' + reindent)
.replace(/\s*$/, '');
}
if (text) {
multi_parser.print_token(text);
multi_parser.print_newline(true, multi_parser.output);
}
}
multi_parser.current_mode = 'TAG';
break;
}
multi_parser.last_token = multi_parser.token_type;
multi_parser.last_text = multi_parser.token_text;
}
return multi_parser.output.join('');
}
// If we're running a web page and don't have either of the above, add our one global
window.html_beautify = function(html_source, options) {
return style_html(html_source, options, window.js_beautify, window.css_beautify);
};
}());

187
public/js/lib/bootstrap-wysiwyg.js vendored Normal file
View file

@ -0,0 +1,187 @@
/* http://github.com/mindmup/bootstrap-wysiwyg */
/*global jQuery, $, FileReader*/
/*jslint browser:true*/
jQuery(function ($) {
'use strict';
var readFileIntoDataUrl = function (fileInfo) {
var loader = $.Deferred(),
fReader = new FileReader();
wn.upload.upload_file(fileInfo, {
from_form: 1,
doctype: cur_frm.doctype,
docname: cur_frm.docname
}, function(fileid, filename, r) {
if(!r.exc) {
if(fileid)
cur_frm.attachments.update_attachment(fileid, filename);
loader.resolve("files/" + filename);
}
});
return loader.promise();
};
$.fn.cleanHtml = function () {
var html = $(this).html();
return html && html.replace(/(<br>|\s|<div><br><\/div>|&nbsp;)*$/, '');
};
$.fn.wysiwyg = function (userOptions) {
var editor = this,
selectedRange,
defaultOptions = {
hotKeys: {
'ctrl+b meta+b': 'bold',
'ctrl+i meta+i': 'italic',
'ctrl+u meta+u': 'underline',
'ctrl+z meta+z': 'undo',
'ctrl+y meta+y meta+shift+z': 'redo',
'ctrl+l meta+l': 'justifyleft',
'ctrl+e meta+e': 'justifycenter',
'ctrl+j meta+j': 'justifyfull',
'shift+tab': 'outdent',
'tab': 'indent'
},
toolbarSelector: '[data-role=editor-toolbar]',
commandRole: 'edit',
activeToolbarClass: 'btn-info',
selectionMarker: 'edit-focus-marker',
selectionColor: 'darkgrey'
},
options,
updateToolbar = function () {
if (options.activeToolbarClass) {
$(options.toolbarSelector).find('.btn[data-' + options.commandRole + ']').each(function () {
var command = $(this).data(options.commandRole);
if (document.queryCommandState(command)) {
$(this).addClass(options.activeToolbarClass);
} else {
$(this).removeClass(options.activeToolbarClass);
}
});
}
},
execCommand = function (commandWithArgs, valueArg) {
var commandArr = commandWithArgs.split(' '),
command = commandArr.shift(),
args = commandArr.join(' ') + (valueArg || '');
document.execCommand(command, 0, args);
updateToolbar();
},
bindHotkeys = function (hotKeys) {
$.each(hotKeys, function (hotkey, command) {
editor.keydown(hotkey, function (e) {
if (editor.attr('contenteditable') && editor.is(':visible')) {
e.preventDefault();
e.stopPropagation();
execCommand(command);
}
}).keyup(hotkey, function (e) {
if (editor.attr('contenteditable') && editor.is(':visible')) {
e.preventDefault();
e.stopPropagation();
}
});
});
},
getCurrentRange = function () {
var sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
return sel.getRangeAt(0);
}
},
saveSelection = function () {
selectedRange = getCurrentRange();
},
restoreSelection = function () {
var selection = window.getSelection();
if (selectedRange) {
selection.removeAllRanges();
selection.addRange(selectedRange);
}
},
insertFiles = function (files) {
editor.focus();
$.each(files, function (idx, fileInfo) {
if (/^image\//.test(fileInfo.type)) {
$.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) {
execCommand('insertimage', dataUrl);
});
}
});
},
markSelection = function (input, color) {
restoreSelection();
document.execCommand('hiliteColor', 0, color || 'transparent');
saveSelection();
input.data(options.selectionMarker, color);
},
bindToolbar = function (toolbar, options) {
toolbar.find('a[data-' + options.commandRole + ']').click(function () {
restoreSelection();
editor.focus();
execCommand($(this).data(options.commandRole));
saveSelection();
});
toolbar.find('[data-toggle=dropdown]').click(restoreSelection);
toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () {
var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */
this.value = '';
restoreSelection();
if (newValue) {
editor.focus();
execCommand($(this).data(options.commandRole), newValue);
}
saveSelection();
}).on('focus', function () {
var input = $(this);
if (!input.data(options.selectionMarker)) {
markSelection(input, options.selectionColor);
input.focus();
}
}).on('blur', function () {
var input = $(this);
if (input.data(options.selectionMarker)) {
markSelection(input, false);
}
});
toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () {
restoreSelection();
if (this.type === 'file' && this.files && this.files.length > 0) {
insertFiles(this.files);
}
saveSelection();
this.value = '';
});
},
initFileDrops = function () {
editor.on('dragenter dragover', false)
.on('drop', function (e) {
var dataTransfer = e.originalEvent.dataTransfer;
e.stopPropagation();
e.preventDefault();
if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) {
insertFiles(dataTransfer.files);
}
});
};
options = $.extend({}, defaultOptions, userOptions);
bindHotkeys(options.hotKeys);
initFileDrops();
bindToolbar($(options.toolbarSelector), options);
editor.attr('contenteditable', true)
.on('mouseup keyup mouseout', function () {
saveSelection();
updateToolbar();
});
$(window).bind('touchend', function (e) {
var isInside = (editor.is(e.target) || editor.has(e.target).length > 0),
currentRange = getCurrentRange(),
clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset);
if (!clear || isInside) {
saveSelection();
updateToolbar();
}
});
return this;
};
});

View file

@ -0,0 +1,100 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(jQuery){
jQuery.hotkeys = {
version: "0.8",
specialKeys: {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
},
shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
}
};
function keyHandler( handleObj ) {
// Only care when a possible input has been specified
if ( typeof handleObj.data !== "string" ) {
return;
}
var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" "),
textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"];
handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) {
return;
}
// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
character = String.fromCharCode( event.which ).toLowerCase(),
key, modif = "", possible = {};
// check combinations (alt|ctrl|shift+anything)
if ( event.altKey && special !== "alt" ) {
modif += "alt+";
}
if ( event.ctrlKey && special !== "ctrl" ) {
modif += "ctrl+";
}
// TODO: Need to make sure this works consistently across platforms
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta+";
}
if ( event.shiftKey && special !== "shift" ) {
modif += "shift+";
}
if ( special ) {
possible[ modif + special ] = true;
} else {
possible[ modif + character ] = true;
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if ( modif === "shift+" ) {
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});
})( jQuery );

View file

@ -41,7 +41,7 @@ wn.ui.form.Attachments = Class.extend({
},
max_reached: function() {
// no of attachments
var n = this.frm.doc.file_list ? this.frm.doc.file_list.split('\n').length : 0;
var n = keys(this.get_file_list()).length;
// button if the number of attachments is less than max
if(n < this.frm.meta.max_attachments || !this.frm.meta.max_attachments) {
@ -50,7 +50,8 @@ wn.ui.form.Attachments = Class.extend({
return true;
},
refresh: function() {
if(this.frm.doc.__islocal || !this.frm.meta.allow_attach) {
var doc = this.frm.doc;
if(doc.__islocal || !this.frm.meta.allow_attach) {
this.parent.toggle(false);
return;
}
@ -59,20 +60,19 @@ wn.ui.form.Attachments = Class.extend({
this.$list.empty();
var fl = this.get_filelist();
var file_list = this.get_file_list();
var file_names = keys(file_list).sort();
// add attachment objects
for(var i=0; i<fl.length; i++) {
this.add_attachment(fl[i]);
for(var i=0; i<file_names.length; i++) {
this.add_attachment(file_names[i], file_list);
}
},
get_filelist: function() {
return this.frm.doc.file_list ? this.frm.doc.file_list.split('\n') : [];
get_file_list: function() {
return this.frm.doc.file_list ? JSON.parse(this.frm.doc.file_list) : {};
},
add_attachment: function(fileinfo) {
fileinfo = fileinfo.split(',');
var filename = fileinfo[0];
var fileid = fileinfo[1];
add_attachment: function(filename, file_list) {
var fileid = file_list[filename];
var me = this;
$(repl('<div class="alert alert-info"><span style="display: inline-block; width: 90%;\
@ -99,7 +99,10 @@ wn.ui.form.Attachments = Class.extend({
dn: me.frm.docname
},
callback: function(r,rt) {
me.frm.doc.modified = r.message;
if(r.exc) {
msgprint("There were errors.");
return;
}
me.remove_fileid(data);
me.frm && me.frm.cscript.on_remove_attachment
&& me.frm.cscript.on_remove_attachment(me.frm.doc);
@ -111,6 +114,7 @@ wn.ui.form.Attachments = Class.extend({
});
},
new_attachment: function() {
var me = this;
if(!this.dialog) {
this.dialog = new wn.ui.Dialog({
title: wn._('Upload Attachment'),
@ -121,7 +125,7 @@ wn.ui.form.Attachments = Class.extend({
}
this.dialog.body.innerHTML = '';
this.dialog.show();
wn.upload.make({
parent: this.dialog.body,
args: {
@ -129,39 +133,31 @@ wn.ui.form.Attachments = Class.extend({
doctype: this.frm.doctype,
docname: this.frm.docname
},
callback: wn.ui.form.file_upload_done
callback: function(fileid, filename, r) {
me.update_attachment(fileid, filename, r);
}
});
},
update_attachment: function(fileid, filename, r) {
this.dialog && this.dialog.hide();
if(fileid) {
this.add_to_file_list(fileid, filename);
this.refresh();
}
},
add_to_file_list: function(fileid, filename) {
var doc = this.frm.doc;
var file_list = doc.file_list ? this.get_file_list() : {};
file_list[filename] = fileid;
doc.file_list = JSON.stringify(file_list);
},
remove_fileid: function(fileid) {
this.frm.doc.file_list = $.map(this.get_filelist(), function(f) {
if(f.split(',')[1]!=fileid) return f;
}).join('\n');
var file_list = this.get_file_list();
var new_file_list = {};
$.each(file_list, function(key, value) {
if(value!=fileid)
new_file_list[key] = value;
});
this.frm.doc.file_list = JSON.stringify(new_file_list);
}
});
// this function will be called after the upload is done
// from webnotes.utils.file_manager
wn.ui.form.file_upload_done = function(doctype, docname, fileid, filename, at_id,
new_timestamp) {
// add to file_list
var doc = locals[doctype][docname];
if(doc.file_list) {
var fl = doc.file_list.split('\n');
fl.push(filename + ',' + fileid);
doc.file_list = fl.join('\n');
} else {
doc.file_list = filename + ',' + fileid;
}
// update timestamp
doc.modified = new_timestamp;
// update file_list
var frm = wn.views.formview[doctype].frm;
frm.attachments.dialog.hide();
msgprint(wn._('File Uploaded Sucessfully.'));
frm.refresh();
};
});

View file

@ -0,0 +1,306 @@
// Options
// parent
// change (event)
// Properties
// set_input
// input
wn.provide("wn.editors");
wn.editors.BootstrapWYSIWYG = Class.extend({
init: function(opts) {
wn.require("lib/js/lib/jquery/jquery.hotkeys.js");
wn.require("lib/js/lib/bootstrap-wysiwyg.js");
this.opts = opts;
this.make_body();
this.make_bindings();
},
make_body: function() {
this.myid = "editor-" + wn.dom.set_unique_id();
$('<div class="for-rich-text">\
<div class="btn-toolbar" data-role="editor-toolbar" data-target="#'+ this.myid +'">\
<div class="btn-group">\
<a class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="Font"><i class="icon-font"></i><b class="caret"></b></a>\
<ul class="dropdown-menu">\
</ul>\
</div>\
<div class="btn-group">\
<a class="btn btn-small dropdown-toggle" data-toggle="dropdown" title="Font Size"><i class="icon-text-height"></i> <b class="caret"></b></a>\
<ul class="dropdown-menu">\
<li><a data-edit="formatBlock &lt;p&gt;"><p>Paragraph</p></a></li>\
<li><a data-edit="formatBlock &lt;h1&gt;"><h1>Heading 1</h1></a></li>\
<li><a data-edit="formatBlock &lt;h2&gt;"><h2>Heading 2</h2></a></li>\
<li><a data-edit="formatBlock &lt;h3&gt;"><h3>Heading 3</h3></a></li>\
<li><a data-edit="formatBlock &lt;h4&gt;"><h4>Heading 4</h4></a></li>\
<li><a data-edit="formatBlock &lt;h5&gt;"><h5>Heading 5</h5></a></li>\
</ul>\
</div>\
<div class="btn-group">\
<a class="btn btn-small" data-edit="bold" title="Bold (Ctrl/Cmd+B)"><i class="icon-bold"></i></a>\
<a class="btn btn-small" data-edit="italic" title="Italic (Ctrl/Cmd+I)"><i class="icon-italic"></i></a>\
<a class="btn btn-small" data-edit="underline" title="Underline (Ctrl/Cmd+U)"><i class="icon-underline"></i></a>\
</div>\
<div class="btn-group">\
<a class="btn btn-small" data-edit="insertunorderedlist" title="Bullet list"><i class="icon-list-ul"></i></a>\
<a class="btn btn-small" data-edit="insertorderedlist" title="Number list"><i class="icon-list-ol"></i></a>\
<a class="btn btn-small" data-edit="outdent" title="Reduce indent (Shift+Tab)"><i class="icon-indent-left"></i></a>\
<a class="btn btn-small" data-edit="indent" title="Indent (Tab)"><i class="icon-indent-right"></i></a>\
</div>\
<div class="btn-group">\
<a class="btn btn-small" data-edit="justifyleft" title="Align Left (Ctrl/Cmd+L)"><i class="icon-align-left"></i></a>\
<a class="btn btn-small" data-edit="justifycenter" title="Center (Ctrl/Cmd+E)"><i class="icon-align-center"></i></a>\
</div>\
<div class="btn-group">\
<a class="btn btn-small" title="Insert picture (or just drag & drop)" id="pictureBtn-'+this.myid+'"><i class="icon-picture"></i></a>\
<input type="file" data-role="magic-overlay" data-target="#pictureBtn-'+this.myid+'" data-edit="insertImage" />\
<a class="btn btn-small" data-edit="insertHorizontalRule" title="Horizontal Line Break">-</a>\
</div>\
<div class="btn-group">\
<a class="btn btn-small" data-edit="undo" title="Undo (Ctrl/Cmd+Z)"><i class="icon-undo"></i></a>\
<a class="btn btn-small" data-edit="redo" title="Redo (Ctrl/Cmd+Y)"><i class="icon-repeat"></i></a>\
</div>\
</div>\
<div id="'+this.myid+'" class="wysiwyg-editor">\
</div>\
</div>\
<div class="for-html" style="display:none">\
<textarea class="html-editor" style="width:95%; height: 440px;\
font-family: Monaco, Menlo, Consolas, Courier, monospace;\
font-size: 11px;"></textarea>\
</div>\
<div class="btn-toolbar pull-right">\
<div class="btn-group">\
<a class="btn btn-small btn-info btn-rich-text" title="Rich Text" disabled="disabled"><i class="icon-reorder"></i></a>\
<a class="btn btn-small btn-html" title="HTML"><i class="icon-wrench"></i></a>\
</div>\
</div>\
').appendTo(this.opts.parent);
this.$parent = $(this.opts.parent);
this.$editor = $("#" + this.myid)
this.$textarea = this.$parent.find(".html-editor");
this.input = this.$editor.get(0);
},
make_bindings: function() {
var me = this;
var fonts = ['Serif', 'Sans', 'Arial', 'Arial Black', 'Courier',
'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande',
'Lucida Sans', 'Tahoma', 'Times', 'Times New Roman', 'Verdana'],
fontTarget = this.$parent.find('[title=Font]').siblings('.dropdown-menu');
$.each(fonts, function (idx, fontName) {
fontTarget.append($('<li><a data-edit="fontName ' +
fontName +'" style="font-family:\''+ fontName +'\'">'+
fontName + '</a></li>'));
});
//this.$parent.find('a[title]').tooltip({container:'body'});
this.$parent.find('.dropdown-menu input').click(function() {return false;})
.change(function () {
$(this).parent('.dropdown-menu').siblings('.dropdown-toggle')
.dropdown('toggle');
})
.keydown('esc', function () {
this.value='';$(this).change();
});
// magic-overlay
this.$parent.find('[data-role=magic-overlay]').each(function () {
var overlay = $(this), target = $(overlay.data('target'));
overlay.css('opacity', 0).css('position', 'absolute')
.offset(target.offset())
.width(40).height(30);
});
this.$editor
.wysiwyg()
.on("mouseup keyup mouseout", function() {
var value = $(this).html();
if(value==null) value="";
me.opts.change(value);
})
this.$textarea
.on("change", function() {
var value = $(this).val();
if(value==null) value="";
me.opts.change(value);
});
this.current_editor = this.$editor;
this.$parent.find(".btn-html").click(function() {
if($(this).attr("disabled")=="disabled") return;
wn.require("lib/js/lib/beautify-html.js");
me.$textarea.val(html_beautify(me.$editor.cleanHtml()));
me.$parent.find(".for-rich-text").toggle(false);
me.$parent.find(".for-html").toggle(true);
me.$parent.find(".btn-html").addClass("btn-info").attr("disabled", "disabled");
me.$parent.find(".btn-rich-text").removeClass("btn-info").attr("disabled", false);
me.current_editor = me.$textarea;
});
this.$parent.find(".btn-rich-text").click(function() {
if($(this).attr("disabled")=="disabled") return;
me.$editor.html(me.$textarea.val());
me.$parent.find(".for-rich-text").toggle(true);
me.$parent.find(".for-html").toggle(false);
me.$parent.find(".btn-html").removeClass("btn-info").attr("disabled", false);
me.$parent.find(".btn-rich-text").addClass("btn-info").attr("disabled", "disabled");
me.current_editor = me.$editor;
});
},
set_input: function(value) {
if(this.value!=value) {
this.value = value==null ? "" : value;
this.$editor.html(this.value);
this.$textarea.val(this.value);
}
},
get_value: function() {
if(this.current_editor==this.$editor)
return this.$editor.cleanHtml();
else
return this.$textarea.val();
}
})
//// TinyMCE
wn.editors.TinyMCE = Class.extend({
init: function(opts) {
this.opts = opts;
this.make();
},
make: function() {
var me = this;
this.input = $("<textarea>").appendTo(this.opts.parent).get(0);
this.myid = wn.dom.set_unique_id(this.input);
// setup tiny mce
$(me.input).tinymce({
// Location of TinyMCE script
script_url : 'lib/js/lib/tiny_mce_3.5.7/tiny_mce.js',
// General options
theme : "advanced",
plugins : "style,inlinepopups,table,advimage",
extended_valid_elements: "script|embed",
// w/h
width: '100%',
height: '360px',
// buttons
theme_advanced_buttons1 : "bold,italic,underline,hr,|,justifyleft,justifycenter,|,formatselect,fontsizeselect,|,bullist,numlist,|,image,|,outdent,indent,|,link,|,forecolor,backcolor,|,code",
theme_advanced_buttons2 : "",
theme_advanced_buttons3 : "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left",
theme_advanced_statusbar_location: "none",
theme_advanced_path: false,
valid_elements : "*[*]",
content_css: "lib/js/lib/tiny_mce_3.5.7/custom_content.css?q=1",
oninit: function() {
me.init_editor();
},
setup: function(ed) {
ed.onChange.add(function(ed, l) {
me.opts.change(l.content);
});
}
});
},
set_input: function(value) {
if(this.editor)
this.editor.setContent(value==null ? "" : value);
else
$(this.input).val(value==null ? "" : value);
},
get_value: function() {
return this.editor && this.editor.getContent(); // tinyMCE
},
init_editor: function() {
// attach onchange methods
var me = this;
this.editor = tinymce.get(this.myid);
this.editor.onKeyUp.add(function(ed, e) {
me.set_value_and_run_trigger(ed.getContent());
});
this.editor.onPaste.add(function(ed, e) {
me.set_value_and_run_trigger(ed.getContent());
});
this.editor.onSetContent.add(function(ed, e) {
me.set_value_and_run_trigger(ed.getContent());
});
}
});
wn.editors.ACE = Class.extend({
init: function(opts) {
this.opts = opts;
// setup ace
wn.require('lib/js/lib/ace/ace.js');
this.make();
this.bind_form_load();
},
make: function() {
$(this.opts.parent).css('border','1px solid #aaa');
this.pre = $("<pre style='position: relative; height: 400px; \
width: 100%; padding: 0px; border-radius: 0px;\
margin: 0px; background-color: #fff;'>").appendTo(this.opts.parent).get(0);
this.input = {};
this.myid = wn.dom.set_unique_id(this.pre);
this.editor = ace.edit(this.myid);
if(this.opts.field.df.options=='Markdown' || this.opts.field.df.options=='HTML') {
wn.require('lib/js/lib/ace/mode-html.js');
var HTMLMode = require("ace/mode/html").Mode;
this.editor.getSession().setMode(new HTMLMode());
}
else if(this.opts.field.df.options=='Javascript') {
wn.require('lib/js/lib/ace/mode-javascript.js');
var JavascriptMode = require("ace/mode/javascript").Mode;
this.editor.getSession().setMode(new JavascriptMode());
}
else if(this.opts.field.df.options=='Python') {
wn.require('lib/js/lib/ace/mode-python.js');
var PythonMode = require("ace/mode/python").Mode;
this.editor.getSession().setMode(new PythonMode());
}
},
set_input: function(value) {
// during field refresh in run trigger, set_input is called
// if called during on_change, setting doesn't make sense
// and causes cursor to shift back to first position
if(this.opts.field.changing_value) return;
this.setting_value = true;
this.editor.getSession().setValue(value==null ? "" : value);
this.setting_value = false;
},
get_value: function() {
return this.editor.getSession().getValue();
},
bind_form_load: function() {
var me = this;
if(cur_frm) {
$(cur_frm.wrapper).bind('render_complete', function() {
me.editor.resize();
me.editor.getSession().on('change', function() {
if(me.setting_value) return;
me.opts.change(me.get_value())
})
});
}
}
})

View file

@ -85,7 +85,7 @@ wn.request.call = function(opts) {
opts.error && opts.error(xhr)
}
};
if(opts.progress_bar) {
var interval = null;
$.extend(ajax_args, {
@ -109,7 +109,7 @@ wn.request.call = function(opts) {
}
})
}
$.ajax(ajax_args);
}

View file

@ -4,45 +4,79 @@
// parent, args, callback
wn.upload = {
make: function(opts) {
var id = wn.dom.set_unique_id();
$(opts.parent).append(repl('<iframe id="%(id)s" name="%(id)s" src="blank.html" \
style="width:0px; height:0px; border:0px"></iframe>\
<form method="POST" enctype="multipart/form-data" \
action="%(action)s" target="%(id)s">\
'+wn._('Upload a file')+':<br>\
<input type="file" name="filedata" /><br><br>\
OR:<br><input type="text" name="file_url" /><br>\
<p class="help">'
+ (opts.sample_url || 'e.g. http://example.com/somefile.png')
+ '</p><br>\
<input type="submit" class="btn" value="'+wn._('Attach')+'" />\
</form>', {
id: id,
var $upload = $("<div class='file-upload'>" + repl(wn._('Upload a file')+':<br>\
<input type="file" name="filedata" /><br><br>\
OR:<br><input type="text" name="file_url" /><br>\
<p class="help">'
+ (opts.sample_url || 'e.g. http://example.com/somefile.png')
+ '</p><br>\
<input type="submit" class="btn btn-info btn-upload" value="'
+wn._('Attach')+'" /></div>', {
action: wn.request.url
}));
})).appendTo(opts.parent);
opts.args.cmd = 'uploadfile';
opts.args._id = id;
// add request parameters
for(key in opts.args) {
if(opts.args[key]) {
if(typeof val==="function") {
var val = opts.args[key]();
} else {
var val = opts.args[key];
}
$('<input type="hidden">')
.attr('name', key)
.attr('value', val)
.appendTo($(opts.parent).find('form'));
// get the first file
$upload.find(".btn-upload").click(function() {
// convert functions to values
for(key in opts.args) {
if(typeof val==="function")
opt.args[key] = opts.args[key]();
}
// add other inputs in the div as arguments
opts.args.params = {};
$upload.find("input[name]").each(function() {
var key = $(this).attr("name");
var type = $(this).attr("type");
if(key!="filedata" && key!="file_url") {
if(type === "checkbox") {
opts.args.params[key] = $(this).is(":checked");
} else {
opts.args.params[key] = $(this).val();
}
}
})
opts.args.file_url = $upload.find('[name="file_url"]').val();
var fileobj = $upload.find(":file").get(0).files[0];
wn.upload.upload_file(fileobj, opts.args, opts.callback);
})
},
upload_file: function(fileobj, args, callback) {
if(!fileobj && !args.file_url) {
msgprint(_("Please attach a file or set a URL"));
return;
}
$('#' + id).get(0).callback = opts.callback
var _upload_file = function() {
var msgbox = msgprint(wn._("Uploading..."));
wn.call({
"method": "uploadfile",
args: args,
callback: function(r) {
msgbox.hide();
if(r.exc) {
msgprint("There were errors in uploading.");
}
callback(r.message, args.filename || args.file_url, r);
}
});
}
},
callback: function(id, file_id, args) {
$('#' + id).get(0).callback(file_id, args);
if(args.file_url) {
_upload_file();
} else {
var freader = new FileReader();
freader.onload = function() {
args.filename = fileobj.name;
args.filedata = freader.result.split(",")[1];
_upload_file();
};
freader.readAsDataURL(fileobj);
}
}
}

View file

@ -114,7 +114,7 @@ wn.views.DocListView = wn.ui.Listing.extend({
},
setup_docstatus_filter: function() {
var me = this;
this.can_submit = $.map(locals.DocPerm, function(d) {
this.can_submit = $.map(locals.DocPerm || [], function(d) {
if(d.parent==me.meta.name && d.submit) return 1
else return null;
}).length;

View file

@ -123,7 +123,7 @@ wn.views.ListView = Class.extend({
// make table
$.each(this.columns, function(i, v) {
if(v.content && v.content.substr && v.content.substr(0,6)=="avatar") {
rowhtml += repl('<td style="max-width: 40px;"></td>');
rowhtml += repl('<td style="width: 7%;"></td>');
} else {
rowhtml += repl('<td style="width: %(width)s"></td>', v);
}

View file

@ -141,15 +141,14 @@ wn.views.QueryReport = Class.extend({
$(f.wrapper).find("input, button").css({"margin-top":"-4px"});
else if(f.df.fieldtype == "Date")
$(f.wrapper).css({"margin-right":"-15px"});
if(df.get_query) f.get_query = df.get_query;
});
this.set_filters_by_name();
},
clear_filters: function() {
this.filters = [];
this.appframe.toolbar.find(".filters").remove();
this.appframe.$w.find('.appframe-toolbar').find(".filters").remove();
},
set_filters_by_name: function() {
this.filters_by_name = {};

View file

@ -87,6 +87,7 @@ def cache():
_memc = MClient(['localhost:11211'])
return _memc
class DuplicateEntryError(Exception): pass
class ValidationError(Exception): pass
class AuthenticationError(Exception): pass
class PermissionError(Exception): pass
@ -106,14 +107,14 @@ def errprint(msg):
print repr(msg)
from utils import cstr
error_log.append(repr(msg))
error_log.append(cstr(msg))
def log(msg):
if not request_method:
import conf
if getattr(conf, "logging", False):
print repr(msg)
from utils import cstr
debug_log.append(cstr(msg))

View file

@ -101,25 +101,23 @@ def uploadfile():
import webnotes.utils.file_manager
import json
ret = []
try:
if webnotes.form_dict.get('from_form'):
webnotes.utils.file_manager.upload()
try:
ret = webnotes.utils.file_manager.upload()
except webnotes.DuplicateEntryError, e:
# ignore pass
ret = None
webnotes.conn.rollback()
else:
if webnotes.form_dict.get('method'):
ret = webnotes.get_method(webnotes.form_dict.method)()
except Exception, e:
webnotes.msgprint(e)
webnotes.errprint(webnotes.utils.getTraceback())
ret = None
webnotes.response['type'] = 'iframe'
if not webnotes.response.get('result'):
webnotes.response['result'] = """<script>
window.parent.wn.upload.callback("%s", %s);
</script>""" % (webnotes.form_dict.get('_id'),
json.dumps(ret))
return ret
@webnotes.whitelist(allow_guest=True)
def reset_password(user):
from webnotes.model.code import get_obj
@ -293,7 +291,8 @@ def make_logs():
import json, conf
from webnotes.utils import cstr
if webnotes.error_log:
webnotes.response['exc'] = json.dumps("\n".join([cstr(d) for d in webnotes.error_log]))
# webnotes.response['exc'] = json.dumps("\n".join([cstr(d) for d in webnotes.error_log]))
webnotes.response['exc'] = json.dumps([cstr(d) for d in webnotes.error_log])
if webnotes.message_log:
webnotes.response['_server_messages'] = json.dumps([cstr(d) for d in webnotes.message_log])

View file

@ -343,7 +343,7 @@ class Document:
for f in fields_list:
if (not (f in ('doctype', 'name', 'perm', 'localname',
'creation','_user_tags'))) and (not f.startswith('__')):
'creation','_user_tags', "file_list"))) and (not f.startswith('__')):
# fields not saved
# validate links
@ -688,4 +688,4 @@ def validate_name(doctype, name, case=None):
if f in name:
webnotes.msgprint('%s not allowed in ID (name)' % f, raise_exception =1)
return name
return name

View file

@ -34,11 +34,21 @@ def rename_doc(doctype, old, new, force=False, merge=False):
if doctype=='DocType':
rename_doctype(doctype, old, new, force)
update_attachments(doctype, old, new)
if merge:
webnotes.delete_doc(doctype, old)
return new
def update_attachments(doctype, old, new):
try:
webnotes.conn.sql("""update `tabFile Data` set attached_to_name=%s
where attached_to_name=%s and attached_to_doctype=%s""", (new, old, doctype))
except Exception, e:
if e.args[0]!=1054: # in patch?
raise e
def rename_parent_and_child(doctype, old, new, doclist):
# rename the doc
webnotes.conn.sql("update `tab%s` set name=%s where name=%s" \

View file

@ -178,6 +178,10 @@ def delete_doc(doctype=None, name=None, doclist = None, force=0, ignore_doctypes
raise e
# delete attachments
from webnotes.utils.file_manager import remove_all
remove_all(doctype, name)
return 'okay'
def check_permission_and_not_submitted(doctype, name):

View file

@ -26,20 +26,21 @@ import json
import csv, cStringIO
from webnotes.utils import encode, cstr
def read_csv_content_from_uploaded_file():
def read_csv_content_from_uploaded_file(ignore_encoding=False):
from webnotes.utils.file_manager import get_uploaded_content
fname, fcontent = get_uploaded_content()
return read_csv_content(fcontent)
return read_csv_content(fcontent, ignore_encoding)
def read_csv_content_from_attached_file(doc):
if not doc.file_list:
fileid = webnotes.conn.get_value("File Data", {"attached_to_doctype": doc.doctype,
"attached_to_name":doc.name}, "name")
if not fileid:
msgprint("File not attached!")
raise Exception
try:
from webnotes.utils.file_manager import get_file
fid = doc.file_list.split(",")[1]
fname, fcontent = get_file(fid)
fname, fcontent = get_file(fileid)
return read_csv_content(fcontent, webnotes.form_dict.get('ignore_encoding_errors'))
except Exception, e:
webnotes.msgprint("""Unable to open attached file. Please try again.""")

View file

@ -97,14 +97,17 @@ class IncomingMail:
})
def save_attachments_in_doc(self, doc):
from webnotes.utils.file_manager import save_file, add_file_list, MaxFileSizeReachedError
from webnotes.utils.file_manager import save_file, MaxFileSizeReachedError
for attachment in self.attachments:
try:
fid = save_file(attachment['filename'], attachment['content'])
status = add_file_list(doc.doctype, doc.name, fid, fid)
fid = save_file(attachment['filename'], attachment['content'],
doc.doctype, doc.name)
except MaxFileSizeReachedError:
# bypass max file size exception
pass
except webnotes.DuplicateEntryError:
# same file attached twice??
pass
def get_thread_id(self):
import re

View file

@ -23,7 +23,7 @@
from __future__ import unicode_literals
import webnotes
import os, conf
from webnotes.utils import cstr
from webnotes.utils import cstr, get_path
from webnotes import _
class MaxFileSizeReachedError(webnotes.ValidationError): pass
@ -34,112 +34,125 @@ def upload():
dn = webnotes.form_dict.docname
at_id = webnotes.form_dict.at_id
file_url = webnotes.form_dict.file_url
filename = webnotes.form['filedata'].filename
filename = webnotes.form_dict.filename
webnotes.response['type'] = 'iframe'
if not filename and not file_url:
webnotes.response['result'] = """
<script type='text/javascript'>
window.parent.wn.views.fomrview['%s'].frm.attachments.dialog.hide();
window.parent.msgprint("Please upload a file or copy-paste a link (http://...)");
</script>""" % dt
return
webnotes.msgprint(_("Please select a file or url"),
raise_exception=True)
# save
if filename:
fid, fname = save_uploaded()
fid, fname = save_uploaded(dt, dn)
elif file_url:
fid, fname = save_url(file_url)
# save it in the form
updated = False
if fid:
updated = add_file_list(dt, dn, fname, fid)
if fid and updated:
# refesh the form!
# with the new modified timestamp
webnotes.response['result'] = """
<script type='text/javascript'>
window.parent.wn.ui.form.file_upload_done('%(dt)s', '%(dn)s', '%(fid)s', '%(fname)s', '%(at_id)s', '%(mod)s');
window.parent.wn.views.formview['%(dt)s'].frm.show_doc('%(dn)s');
</script>
""" % {
'dt': dt,
'dn': dn,
'fid': fid,
'fname': fname.replace("'", "\\'"),
'at_id': at_id,
'mod': webnotes.conn.get_value(dt, dn, 'modified')
}
fid, fname = save_url(file_url, dt, dn)
def save_uploaded():
webnotes.response['type'] = 'iframe'
if fid:
return fid
def save_uploaded(dt, dn):
fname, content = get_uploaded_content()
if content:
fid = save_file(fname, content)
# fname is not valid
return fid, fid
fid = save_file(fname, content, dt, dn)
return fid, fname
else:
return None, fname
raise Exception
def save_url(file_url):
def save_url(file_url, dt, dn):
if not (file_url.startswith("http://") or file_url.startswith("https://")):
webnotes.msgprint("URL must start with 'http://' or 'https://'")
return None, None
f = webnotes.doc("File Data")
f.file_url = file_url
f.file_name = file_url.split('/')[-1]
f.save(new=1)
return f.name, file_url
f = webnotes.bean({
"doctype": "File Data",
"file_url": file_url,
"attached_to_doctype": dt,
"attached_to_name": dn
})
f.ignore_permissions = True
f.insert();
return f.doc.name, file_url
def get_uploaded_content():
# should not be unicode when reading a file, hence using webnotes.form
if 'filedata' in webnotes.form:
i = webnotes.form['filedata']
webnotes.uploaded_filename, webnotes.uploaded_content = cstr(i.filename), i.file.read()
if 'filedata' in webnotes.form_dict:
import base64
webnotes.uploaded_content = base64.b64decode(webnotes.form_dict.filedata)
webnotes.uploaded_filename = webnotes.form_dict.filename
return webnotes.uploaded_filename, webnotes.uploaded_content
else:
webnotes.msgprint('No File')
return None, None
def save_file(fname, content, module=None):
from webnotes.model.doc import Document
def save_file(fname, content, dt, dn):
from filecmp import cmp
files_path = get_files_path()
check_max_file_size(content)
new_fname = write_file(content)
file_size = check_max_file_size(content)
temp_fname = write_file(content)
fname = scrub_file_name(fname)
fpath = os.path.join(files_path, fname)
if os.path.exists(fpath):
if cmp(fpath, temp_fname):
# remove new file, already exists!
os.remove(temp_fname)
else:
# get_new_version name
fname = get_new_fname_based_on_version(files_path, fname)
# rename
os.rename(temp_fname, os.path.join(files_path, fname))
else:
# rename new file
os.rename(temp_fname, os.path.join(files_path, fname))
# some browsers return the full path
f = webnotes.bean({
"doctype": "File Data",
"file_name": fname,
"attached_to_doctype": dt,
"attached_to_name": dn,
"file_size": file_size
})
f.ignore_permissions = True
f.insert();
return f.doc.name
def get_new_fname_based_on_version(files_path, fname):
# new version of the file is being uploaded, add a revision number?
versions = filter(lambda f: f.startswith(fname), os.listdir(files_path))
versions.sort()
if "-" in versions[-1]:
version = int(versions[-1].split("-")[-1]) or 1
else:
version = 1
new_fname = fname + "-" + str(version)
while os.path.exists(os.path.join(files_path, new_fname)):
version += 1
new_fname = fname + "-" + str(version)
if version > 100:
break # let there be an exception
return new_fname
def scrub_file_name(fname):
if '\\' in fname:
fname = fname.split('\\')[-1]
if '/' in fname:
fname = fname.split('/')[-1]
# we use - for versions, so remove them from the name!
fname = fname.replace('-', '')
fpath = os.path.join(get_files_path(), fname)
if os.path.exists(fpath) and cmp(fpath, new_fname):
# remove new file, already exists!
os.remove(new_fname)
return fname
else:
# generate the ID (?)
f = Document('File Data')
f.file_name = fname
f.save(1)
# rename new file
os.rename(new_fname, os.path.join(get_files_path(), f.name))
return f.name
return fname
def check_max_file_size(content):
max_file_size = getattr(conf, 'max_file_size', 1000000)
file_size = len(content)
if len(content) > max_file_size:
if file_size > max_file_size:
webnotes.msgprint(_("File size exceeded the maximum allowed size"),
raise_exception=MaxFileSizeReachedError)
return file_size
def write_file(content):
"""write file to disk with a random name (to compare)"""
@ -153,66 +166,34 @@ def write_file(content):
return fname
def add_file_list(dt, dn, fname, fid):
fl = webnotes.conn.get_value(dt, dn, 'file_list') or ''
if fl: fl += '\n'
fl += fname + ',' + fid
webnotes.conn.set_value(dt, dn, 'file_list', fl)
return True
def remove_all(dt, dn):
"""remove all files in a transaction"""
file_list = webnotes.conn.get_value(dt, dn, 'file_list') or ''
for afile in file_list.split('\n'):
if afile:
fname, fid = afile.split(',')
remove_file(dt, dn, fid)
try:
for fid in webnotes.conn.sql_list("""select name from `tabFile Data` where
attached_to_doctype=%s and attached_to_name=%s""", (dt, dn)):
remove_file(fid)
except Exception, e:
if e.args[0]!=1054: raise e # (temp till for patched)
def remove_file(dt, dn, fid):
"""Remove fid from the give file_list"""
# get the old file_list
fl = webnotes.conn.get_value(dt, dn, 'file_list') or ''
new_fl = []
fl = fl.split('\n')
for f in fl:
if f and f.split(',')[1]!=fid:
new_fl.append(f)
# delete
delete_file(fid)
def remove_file(fid):
"""Remove file and File Data entry"""
webnotes.delete_doc("File Data", fid)
# update the file_list
webnotes.conn.set_value(dt, dn, 'file_list', '\n'.join(new_fl))
# return the new timestamp
return webnotes.conn.get_value(dt, dn, 'modified')
def get_file_system_name(fname):
# get system name from File Data table
return webnotes.conn.sql("""select name, file_name from `tabFile Data`
where name=%s or file_name=%s""", (fname, fname))
def delete_file(fid, verbose=0):
"""delete file from file system"""
import os
webnotes.conn.sql("delete from `tabFile Data` where name=%s", fid)
path = os.path.join(get_files_path(), fid.replace('/','-'))
if os.path.exists(path):
os.remove(path)
def get_file(fname):
f = get_file_system_name(fname)
if f:
file_id = f[0][0].replace('/','-')
file_name = f[0][1]
else:
file_id = fname
file_name = fname
# read the file
import os
with open(os.path.join(get_files_path(), file_id), 'r') as f:
with open(os.path.join(get_files_path(), file_name), 'r') as f:
content = f.read()
return [file_name, content]
@ -221,7 +202,5 @@ files_path = None
def get_files_path():
global files_path
if not files_path:
import os, conf
files_path = os.path.join(os.path.dirname(os.path.abspath(conf.__file__)),
'public', 'files')
files_path = get_path("public", "files")
return files_path

View file

@ -192,7 +192,6 @@ def delete_page_cache(page_name):
webnotes.cache().delete_value("page:" + page_name)
def get_hex_shade(color, percent):
def p(c):
v = int(c, 16) + int(int('ff', 16) * (float(percent)/100))
if v < 0:

View file

@ -100,7 +100,11 @@ def remove(doctype, name, assign_to):
if res and res[0]: notify_assignment(res[0][0], res[0][1], res[0][2], res[0][3])
return get({"doctype": doctype, "name": name})
def clear(doctype, name):
for assign_to in webnotes.conn.sql_list("""select owner from `tabToDo`
where reference_type=%(doctype)s and reference_name=%(name)s""", locals()):
remove(doctype, name, assign_to)
def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', notify=0):
"""

View file

@ -21,7 +21,7 @@
#
from __future__ import unicode_literals
import webnotes
import webnotes, json
import webnotes.model.doc
import webnotes.utils
@ -81,6 +81,9 @@ def load_single_doc(dt, dn, user):
try:
dl = webnotes.bean(dt, dn).doclist
# add file list
add_file_list(dt, dn, dl)
except Exception, e:
webnotes.errprint(webnotes.utils.getTraceback())
webnotes.msgprint('Error in script while loading')
@ -90,8 +93,17 @@ def load_single_doc(dt, dn, user):
webnotes.user.update_recent(dt, dn)
return dl
def add_file_list(dt, dn, dl):
file_list = {}
for f in webnotes.conn.sql("""select name, file_name, file_url from
`tabFile Data` where attached_to_name=%s and attached_to_doctype=%s""",
(dn, dt), as_dict=True):
file_list[f.file_url or f.file_name] = f.name
if file_list:
dl[0].file_list = json.dumps(file_list)
def get_search_criteria(dt):
"""bundle search criteria with doctype"""
import webnotes.model.doc

View file

@ -58,6 +58,9 @@ def cancel(doctype=None, name=None):
raise e
def send_updated_docs(wrapper):
from load import add_file_list
add_file_list(wrapper.doc.doctype, wrapper.doc.name, wrapper.doclist)
webnotes.response['main_doc_name'] = wrapper.doc.name
webnotes.response['doctype'] = wrapper.doc.doctype
webnotes.response['docname'] = wrapper.doc.name

View file

@ -30,8 +30,7 @@ def remove_attach():
fid = webnotes.form_dict.get('fid')
# remove from dt dn
return str(webnotes.utils.file_manager.remove_file(webnotes.form_dict.get('dt'), webnotes.form_dict.get('dn'), fid))
webnotes.utils.file_manager.remove_file(fid)
@webnotes.whitelist()
def get_fields():

View file

@ -27,6 +27,7 @@ import os, json
from webnotes import _
from webnotes.modules import scrub, get_module_path
from webnotes.utils import flt, cint
@webnotes.whitelist()
def get_script(report_name):
@ -67,7 +68,23 @@ def run(report_name, filters=None):
+ ".report." + scrub(report.name) + "." + scrub(report.name) + ".execute"
columns, result = webnotes.get_method(method_name)(filters or {})
if cint(report.add_total_row):
result = add_total_row(result, columns)
return {
"result": result,
"columns": columns
}
}
def add_total_row(result, columns):
total_row = [""]*len(columns)
for row in result:
for i, col in enumerate(columns):
if col.split(":")[1] in ["Currency", "Int", "Float"] and flt(row[i]):
total_row[i] = flt(total_row[i]) + flt(row[i])
if columns[0].split(":")[1] not in ["Currency", "Int", "Float"]:
total_row[0] = "Total"
result.append(total_row)
return result