@@ -107,7 +107,7 @@ class LoginManager: | |||
def set_user_info(self): | |||
info = webnotes.conn.get_value("Profile", self.user, | |||
["user_type", "first_name", "last_name"], as_dict=1) | |||
["user_type", "first_name", "last_name", "user_image"], as_dict=1) | |||
if info.user_type=="Website User": | |||
webnotes._response.set_cookie("system_user", "no") | |||
webnotes.response["message"] = "No App" | |||
@@ -119,6 +119,7 @@ class LoginManager: | |||
webnotes.response["full_name"] = full_name | |||
webnotes._response.set_cookie("full_name", full_name) | |||
webnotes._response.set_cookie("user_id", self.user) | |||
webnotes._response.set_cookie("user_image", info.user_image or "") | |||
def make_session(self, resume=False): | |||
# start session | |||
@@ -347,6 +347,57 @@ def reset_password(user): | |||
else: | |||
return "No such user (%s)" % user | |||
@webnotes.whitelist(allow_guest=True) | |||
def facebook_login(data): | |||
data = json.loads(data) | |||
if not (data.get("id") and data.get("fb_access_token")): | |||
raise webnotes.ValidationError | |||
user = data["email"] | |||
if not get_fb_userid(data.get("fb_access_token")): | |||
# garbage | |||
raise webnotes.ValidationError | |||
if not webnotes.conn.exists("Profile", user): | |||
if data.get("birthday"): | |||
b = data.get("birthday").split("/") | |||
data["birthday"] = b[2] + "-" + b[0] + "-" + b[1] | |||
profile = webnotes.bean({ | |||
"doctype":"Profile", | |||
"first_name": data["first_name"], | |||
"last_name": data["last_name"], | |||
"email": data["email"], | |||
"enabled": 1, | |||
"new_password": webnotes.generate_hash(data["email"]), | |||
"fb_username": data["username"], | |||
"fb_userid": data["id"], | |||
"fb_location": data.get("location", {}).get("name"), | |||
"fb_hometown": data.get("hometown", {}).get("name"), | |||
"fb_age_range": data.get("age_range") and "{min}-{max}".format(**data.get("age_range")), | |||
"birth_date": data.get("birthday"), | |||
"fb_bio": data.get("bio"), | |||
"fb_education": data.get("education") and data.get("education")[-1].get("type"), | |||
"user_type": "Website User" | |||
}) | |||
profile.ignore_permissions = True | |||
profile.get_controller().no_welcome_mail = True | |||
profile.insert() | |||
webnotes.local.login_manager.user = user | |||
webnotes.local.login_manager.post_login() | |||
def get_fb_userid(fb_access_token): | |||
import requests | |||
response = requests.get("https://graph.facebook.com/me?access_token=" + fb_access_token) | |||
if response.status_code==200: | |||
print response.json() | |||
return response.json().get("id") | |||
else: | |||
return webnotes.AuthenticationError | |||
def profile_query(doctype, txt, searchfield, start, page_len, filters): | |||
from webnotes.widgets.reportview import get_match_cond | |||
return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name) | |||
@@ -7,10 +7,17 @@ | |||
], | |||
"js/webnotes-web.min.js": [ | |||
"public/js/lib/bootstrap.min.js", | |||
"public/js/wn/provide.js", | |||
"public/js/wn/misc/number_format.js", | |||
"public/js/lib/nprogress.js", | |||
"public/js/wn/translate.js", | |||
"website/js/website.js" | |||
"public/js/wn/misc/pretty_date.js", | |||
"website/js/website.js", | |||
"website/js/website_group.js" | |||
], | |||
"js/canvasResize.min.js": [ | |||
"website/js/jquery.exif.js", | |||
"website/js/jquery.canvasResize.js" | |||
], | |||
"js/editor.min.js": [ | |||
"public/js/lib/jquery/jquery.hotkeys.js", | |||
@@ -316,6 +316,11 @@ div#freeze { | |||
max-height: 15px; | |||
} | |||
.navbar-brand { | |||
min-height: 20px; | |||
height: auto; | |||
} | |||
.navbar #spinner { | |||
display: block; | |||
float: right; | |||
@@ -11,25 +11,23 @@ | |||
<ul class="nav nav-tabs view-selector"> | |||
{%- for t in views -%} | |||
<li class="{% if view.name==t.name -%} active {%- endif %} {% if t.hidden -%} hide {%- endif %}" | |||
data-view="{{ t.view }}"> | |||
data-view="{{ t.name }}"> | |||
<a href="{{ t.url or '' }}"><i class="{{ t.icon }}"></i> | |||
<span class="nav-label">{{ t.label }}</span></a> | |||
</li> | |||
{%- endfor -%} | |||
</ul> | |||
{% include view.template_path %} | |||
{%- if access -%} | |||
<script> | |||
$(function() { | |||
wn.provide("website"); | |||
{%- if access -%} | |||
website.access = {{ access|json }}; | |||
{%- endif -%} | |||
website.group = "{{ group.name }}"; | |||
website.view = "{{ view.name }}"; | |||
}) | |||
</script> | |||
{%- endif -%} | |||
{% include view.template_path %} | |||
{% endblock %} | |||
{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %} |
@@ -35,7 +35,7 @@ def get_context(context): | |||
def get_group_context(group, view, bean): | |||
cache_key = "website_group_context:{}:{}".format(group, view) | |||
views = get_views(bean.doc.group_type) | |||
view = views.get(view) | |||
view = webnotes._dict(views.get(view)) | |||
if can_cache(view.get("no_cache")): | |||
group_context = webnotes.cache().get_value(cache_key) | |||
@@ -86,9 +86,10 @@ def get_handler(group_type): | |||
return webnotes.get_module(handler[0]) | |||
def get_views(group_type): | |||
from copy import deepcopy | |||
handler = get_handler(group_type) | |||
if handler and hasattr(handler, "get_views"): | |||
return handler.get_views() | |||
return deepcopy(handler.get_views() or {}) | |||
return {} | |||
def has_access(group, view): | |||
@@ -20,10 +20,10 @@ | |||
</h4> | |||
{%- endif -%} | |||
<ul class="list-inline small text-muted post-options"> | |||
{% if view_options and view_options.upvote %}<li class="upvote"> | |||
{% if view and view.upvote %}<li class="upvote"> | |||
<a><span class="upvote-count {% if not post.upvotes %}hide{% endif %}">{{ post.upvotes }}</span> | |||
<i class="icon-thumbs-up"></i></a></li>{% endif %} | |||
{%- if not post.parent_post and view != "post" -%} | |||
{%- if not post.parent_post and view.name != "post" -%} | |||
<li><a itemprop="url" href="{{ post_url }}"> | |||
{% if not post.post_reply_count -%} | |||
<i class="icon-reply"></i> Reply | |||
@@ -62,26 +62,7 @@ login.do_login = function(){ | |||
url: "/", | |||
data: args, | |||
dataType: "json", | |||
statusCode: { | |||
200: function(data) { | |||
if(data.message=="Logged In") { | |||
window.location.href = "app"; | |||
} else if(data.message=="No App") { | |||
if(localStorage) { | |||
var last_visited = localStorage.getItem("last_visited") || "/index"; | |||
localStorage.removeItem("last_visited"); | |||
window.location.href = last_visited; | |||
} else { | |||
window.location.href = "/index"; | |||
} | |||
} else if(window.is_sign_up) { | |||
wn.msgprint(data.message); | |||
} | |||
}, | |||
401: function(xhr, data) { | |||
login.set_message("Invalid Login"); | |||
} | |||
} | |||
statusCode: login.login_handlers | |||
}).always(function(){ | |||
$("#login-spinner").toggle(false); | |||
$('#login_btn').prop("disabled", false); | |||
@@ -140,4 +121,88 @@ login.set_message = function(message, color) { | |||
wn.msgprint(message); | |||
return; | |||
//$('#login_message').html(message).toggle(true); | |||
} | |||
} | |||
login.login_handlers = { | |||
200: function(data) { | |||
if(data.message=="Logged In") { | |||
window.location.href = "app"; | |||
} else if(data.message=="No App") { | |||
if(localStorage) { | |||
var last_visited = localStorage.getItem("last_visited") || "/index"; | |||
localStorage.removeItem("last_visited"); | |||
window.location.href = last_visited; | |||
} else { | |||
window.location.href = "/index"; | |||
} | |||
} else if(window.is_sign_up) { | |||
wn.msgprint(data.message); | |||
} | |||
}, | |||
401: function(xhr, data) { | |||
login.set_message("Invalid Login"); | |||
} | |||
} | |||
{% if fb_app_id is defined -%} | |||
// facebook login | |||
$(document).ready(function() { | |||
$.ajaxSetup({ cache: true }); | |||
var user_id = wn.get_cookie("user_id"); | |||
var sid = wn.get_cookie("sid"); | |||
// logged in? | |||
if(!sid || sid==="Guest") { | |||
// fallback on facebook login -- no login again | |||
$(".btn-login").html("Login via Facebook").removeAttr("disabled"); | |||
} else { | |||
// get private stuff (if access) | |||
// app.setup_user({"user": user_id}); | |||
} | |||
}); | |||
$(function() { | |||
$login = $(".btn-login").prop("disabled", true); | |||
$.getScript('//connect.facebook.net/en_UK/all.js', function() { | |||
$login.prop("disabled", false); | |||
FB.init({ | |||
appId: '{{ fb_app_id }}', | |||
}); | |||
$login.click(function() { | |||
$login.prop("disabled", true).html("Logging In..."); | |||
login.via_facebook(); | |||
}); | |||
}); | |||
}); | |||
login.via_facebook = function() { | |||
// not logged in to facebook either | |||
FB.login(function(response) { | |||
if (response.authResponse) { | |||
// yes logged in via facebook | |||
console.log('Welcome! Fetching your information.... '); | |||
var fb_access_token = response.authResponse.accessToken; | |||
// get user graph | |||
FB.api('/me', function(response) { | |||
response.fb_access_token = fb_access_token || "[none]"; | |||
$.ajax({ | |||
url:"/", | |||
type: "POST", | |||
data: { | |||
cmd:"webnotes.core.doctype.profile.profile.facebook_login", | |||
data: JSON.stringify(response) | |||
}, | |||
statusCode: login.login_handlers | |||
}) | |||
}); | |||
} else { | |||
wn.msgprint("You have denied access to this application via Facebook. \ | |||
Please change your privacy settings in Facebook and try again. \ | |||
If you do not want to use Facebook login, <a href='/login'>sign-up</a> here"); | |||
} | |||
},{scope:"email"}); | |||
} | |||
{%- endif %} |
@@ -67,13 +67,7 @@ | |||
</li> | |||
</ul> | |||
<ul class="btn-login-area nav navbar-nav navbar-right"> | |||
<li class="dropdown"> | |||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Login <b class="caret"></b></a> | |||
<ul class="dropdown-menu"> | |||
<li><a href="#" class="btn-login" disabled=disabled>Login via Facebook</a></li> | |||
<li><a href="/login">Sign Up / Login</a></li> | |||
</ul> | |||
</li> | |||
<li><a href="/login">Sign Up / Login</a></li> | |||
</ul> | |||
</div> | |||
</div> | |||
@@ -0,0 +1,66 @@ | |||
{% set parent_post = post.parent_post if post else parent_post %} | |||
<div class="post-editor well" | |||
{% if parent_post %}data-parent-post="{{ parent_post }}"{% endif %} | |||
{% if post %}data-post="{{ post.name }}"{% endif %}> | |||
{%- if not (post and post.parent_post) and not parent_post-%} | |||
<input type="text" class="form-group form-control h3" placeholder="Title" | |||
data-fieldname="title" {%- if post and post.title -%}value="{{ post.title }}"{%- endif -%}> | |||
{%- endif -%} | |||
<textarea class="form-group form-control post-add-textarea" placeholder="Enter text" | |||
data-fieldname="content">{%- if post and post.content -%}{{ post.content }}{%- endif -%}</textarea> | |||
<!-- task and events related fields --> | |||
{%- if view.name != "post" and not (post and post.parent_post) -%} | |||
{%- if group.group_type == "Tasks" -%} | |||
<input type="text" class="form-group form-control control-assign" | |||
placeholder="Assign this task to someone" | |||
{%- if post and post.assigned_to_fullname -%}value="{{ post.assigned_to_fullname }}"{%- endif -%} /> | |||
<input type="hidden" class="form-group form-control hide" data-fieldname="assigned_to" | |||
{% if post and post.assigned_to %}value="{{ post.assigned_to }}"{% endif %}/> | |||
<div class="assigned-to alert alert-success {% if not (post and post.assigned_to) %}hide{% endif %}" | |||
style="margin: 10px 0px;"> | |||
<div class="row"> | |||
<div class="col-xs-10"> | |||
<div class="assigned-profile user-profile"> | |||
{%- if post and profile -%} | |||
{% include "templates/includes/profile_display.html" %} | |||
{%- endif -%} | |||
</div> | |||
</div> | |||
<div class="col-xs-2"> | |||
<a class="close">×</a> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="form-group task-status {% if not (post and post.assigned_to) %}hide{% endif %}"> | |||
<label>Status</label> | |||
<select class="form-control" data-fieldname="status"> | |||
{% for opt in ("Open", "Closed") %} | |||
<option value="{{ opt }}" {% if post and opt==post.status %}selected{% endif %}>{{ opt }}</option> | |||
{% endfor %} | |||
</select> | |||
</div> | |||
{%- elif group.group_type == "Events" -%} | |||
<input type="text" class="form-group form-control control-event" | |||
placeholder="Enter Event Date and Time" /> | |||
<input type="hidden" class="form-group form-control hide" data-fieldname="event_datetime" | |||
{% if post and post.event_datetime %}value="{{ post.event_datetime }}"{% endif %}/> | |||
{%- endif -%} | |||
{%- endif -%} | |||
<div class="text-muted small">tab + enter to post / <a target="_blank" class="text-muted" | |||
href="/markdown-cheatsheet" tabindex="-1">markdown formatting</a></div> | |||
<div class="post-picture hide" style="margin: 10px 0px;"> | |||
<img src="{{ post.picture_url|scrub_relative_url if post else ''}}" class="img-responsive" /> | |||
</div> | |||
<div class="clearfix"> | |||
<button class="btn btn-default btn-post-add pull-right"><i class="icon-plus"></i> Add Post</button> | |||
<button class="btn btn-default btn-post-save pull-right hide"><i class="icon-ok"></i> Save Post</button> | |||
<button class="btn btn-default btn-post-add-picture pull-right"> | |||
<i class="icon-camera"></i> Add Picture | |||
</button> | |||
<!-- hidden file input --> | |||
<input type="file" class="control-post-add-picture hide" | |||
style="position: absolute; top: 0; width: 0; height: 0;"> | |||
</div> | |||
</div> |
@@ -0,0 +1,9 @@ | |||
<div class="media"> | |||
<div class="pull-left"> | |||
<img class="media-object" src="{{ profile.user_image }}" style="width: 50px; min-height: 1px"/> | |||
</div> | |||
<div class="media-body"> | |||
<div>{{ profile.first_name or "" }} {{ profile.last_name or "" }}</div> | |||
<div class="text-muted"><small>{{ profile.fb_location or profile.fb_hometown or "" }}</small></div> | |||
</div> | |||
</div> |
@@ -1,7 +1,7 @@ | |||
{%- if children -%} | |||
{%- for child in children -%} | |||
<div class="sidebar-item"> | |||
<a href="/{{ child.url|lower }}"> | |||
<a href="/{{ child.name|lower }}"> | |||
{{ child.page_title }} | |||
{% if not child.public_read %} | |||
(<i class="icon-fixed-width icon-lock"></i>) | |||
@@ -0,0 +1,8 @@ | |||
<tr class="sitemap-permission" data-profile="{{ profile.name }}"> | |||
<td> | |||
{% include "templates/includes/profile_display.html" %} | |||
</td> | |||
<td><input type="checkbox" data-perm="read" {% if profile.read %}checked{% endif %}></td> | |||
<td><input type="checkbox" data-perm="write" {% if profile.write %}checked{% endif %}></td> | |||
<td><input type="checkbox" data-perm="admin" {% if profile.admin %}checked{% endif %}></td> | |||
</tr> |
@@ -33,6 +33,11 @@ | |||
<p id="forgot-link"></p> | |||
</div> | |||
<div class="col-sm-6"> | |||
{%- if fb_app_id is defined -%} | |||
<div id="fb-root"></div> | |||
<p><button type="button" class="btn btn-default btn-login"> | |||
Login via Facebook</button></p> | |||
{%- endif -%} | |||
<p id="switch-view"></p> | |||
</div> | |||
</div> | |||
@@ -13,7 +13,8 @@ def get_context(context): | |||
if hasattr(webnotes.local, "message"): | |||
message_context["title"] = webnotes.local.message_title | |||
message_context["message"] = webnotes.local.message | |||
message_context["success"] = webnotes.local.message_success | |||
if hasattr(webnotes.local, "message_success"): | |||
message_context["success"] = webnotes.local.message_success | |||
message_context.update(context) | |||
return render_blocks(message_context) |
@@ -0,0 +1,19 @@ | |||
{% include "templates/includes/post_editor.html" %} | |||
<script type="text/javascript" src="/assets/js/canvasResize.min.js"></script> | |||
<script> | |||
$(function() { | |||
website.bind_add_post(); | |||
{%- if view.name == "edit" -%} | |||
website.bind_save_post(); | |||
{% if post -%} website.post = "{{ post.name }}"; {%- endif %} | |||
{%- endif -%} | |||
{%- if group.group_type == "Events" -%} | |||
website.setup_event_editor(); | |||
{%- elif group.group_type == "Tasks" -%} | |||
website.setup_tasks_editor(); | |||
{%- endif -%} | |||
}); | |||
</script> |
@@ -0,0 +1,29 @@ | |||
<div class="small text-muted post-list-help"></div> | |||
<div class="post-list"> | |||
{{ post_list_html }} | |||
</div> | |||
<div class="text-center"> | |||
<button class="btn btn-default btn-more hide">More</button> | |||
</div> | |||
<script> | |||
$(function() { | |||
if($(".post").length===20) { | |||
wn.setup_pagination($(".btn-more"), { | |||
args: { | |||
cmd: "webnotes.templates.website_group.events.get_post_list_html", | |||
limit_start: $(".post").length, | |||
limit_length: 20, | |||
group: website.group, | |||
view: website.view | |||
}, | |||
$wrapper: $(".post-list") | |||
}); | |||
} | |||
website.toggle_edit(); | |||
website.setup_upvote(); | |||
website.toggle_upvote(); | |||
website.format_event_timestamps(); | |||
}); | |||
</script> |
@@ -0,0 +1,125 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.utils import now_datetime, get_datetime_str | |||
from webnotes.webutils import get_access | |||
from webnotes.templates.website_group.settings import get_settings_context | |||
from webnotes.templates.website_group.post import get_post_context | |||
def get_views(): | |||
return views | |||
def get_context(group_context): | |||
events_context = {} | |||
if group_context.view.name in ("upcoming", "past"): | |||
events_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"]) | |||
elif group_context.view.name == "edit": | |||
events_context["session_user"] = webnotes.session.user | |||
events_context["post"] = webnotes.doc("Post", webnotes.form_dict.name).fields | |||
elif group_context.view.name == "settings": | |||
events_context.update(get_settings_context(group_context)) | |||
elif group_context.view.name == "post": | |||
events_context.update(get_post_context(group_context)) | |||
return events_context | |||
@webnotes.whitelist(allow_guest=True) | |||
def get_post_list_html(group, view, limit_start=0, limit_length=20): | |||
access = get_access(group) | |||
if isinstance(view, basestring): | |||
view = get_views()[view] | |||
view = webnotes._dict(view) | |||
# verify permission for paging | |||
if webnotes.local.form_dict.cmd == "get_post_list_html": | |||
if not access.get("read"): | |||
return webnotes.PermissionError | |||
if view.name=="upcoming": | |||
condition = "and p.event_datetime >= %s" | |||
order_by = "p.event_datetime asc" | |||
else: | |||
condition = "and p.event_datetime < %s" | |||
order_by = "p.event_datetime desc" | |||
# should show based on time upto precision of hour | |||
# because the current hour should also be in upcoming | |||
now = now_datetime().replace(minute=0, second=0, microsecond=0) | |||
posts = webnotes.conn.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name, | |||
(select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count | |||
from `tabPost` p, `tabProfile` pr | |||
where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')='' | |||
and p.is_event=1 {condition} | |||
order by {order_by} limit %s, %s""".format(condition=condition, order_by=order_by), | |||
(group, now, int(limit_start), int(limit_length)), as_dict=True) | |||
context = {"posts": posts, "limit_start": limit_start, "view": view} | |||
return webnotes.get_template("templates/includes/post_list.html").render(context) | |||
views = { | |||
"upcoming": { | |||
"name": "upcoming", | |||
"template_path": "templates/website_group/events.html", | |||
"url": "/{group}", | |||
"label": "Upcoming", | |||
"icon": "icon-calendar", | |||
"default": True, | |||
"idx": 1 | |||
}, | |||
"past": { | |||
"name": "past", | |||
"template_path": "templates/website_group/events.html", | |||
"url": "/{group}?view=past", | |||
"label": "Past", | |||
"icon": "icon-time", | |||
"idx": 2 | |||
}, | |||
"post": { | |||
"name": "post", | |||
"template_path": "templates/website_group/post.html", | |||
"url": "/{group}?view=post&name={post}", | |||
"label": "Post", | |||
"icon": "icon-comments", | |||
"hidden": True, | |||
"no_cache": True, | |||
"idx": 3 | |||
}, | |||
"edit": { | |||
"name": "edit", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=edit&name={post}", | |||
"label": "Edit Post", | |||
"icon": "icon-pencil", | |||
"hidden": True, | |||
"no_cache": True, | |||
"idx": 4 | |||
}, | |||
"add": { | |||
"name": "add", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=add", | |||
"label": "Add Post", | |||
"icon": "icon-plus", | |||
"hidden": True, | |||
"idx": 5 | |||
}, | |||
"settings": { | |||
"name": "settings", | |||
"template_path": "templates/website_group/settings.html", | |||
"url": "/{group}?view=settings", | |||
"label": "Settings", | |||
"icon": "icon-cog", | |||
"hidden": True, | |||
"idx": 6 | |||
} | |||
} |
@@ -1,4 +1,3 @@ | |||
{%- block view -%} | |||
<div class="small text-muted post-list-help"></div> | |||
<div class="post-list"> | |||
{{ post_list_html }} | |||
@@ -6,17 +5,24 @@ | |||
<div class="text-center"> | |||
<button class="btn btn-default btn-more hide">More</button> | |||
</div> | |||
{%- endblock -%} | |||
{%- block group_script -%} | |||
<script> | |||
$(function() { | |||
app.setup_more_btn({ | |||
cmd: "webnotes.templates.website_group.forum.get_post_list_html" | |||
}); | |||
app.toggle_edit(true); | |||
app.setup_upvote(); | |||
app.toggle_upvote(); | |||
if($(".post").length===20) { | |||
wn.setup_pagination($(".btn-more"), { | |||
args: { | |||
cmd: "webnotes.templates.website_group.forum.get_post_list_html", | |||
limit_start: $(".post").length, | |||
limit_length: 20, | |||
group: website.group, | |||
view: website.view | |||
}, | |||
$wrapper: $(".post-list") | |||
}); | |||
} | |||
website.toggle_edit(true); | |||
website.setup_upvote(); | |||
website.toggle_upvote(); | |||
}); | |||
</script> | |||
{%- endblock -%} |
@@ -5,22 +5,36 @@ from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.utils import now_datetime, get_datetime_str | |||
from webnotes.webutils import get_access | |||
from webnotes.templates.generators.website_group import get_views | |||
from webnotes.templates.website_group.settings import get_settings_context | |||
from webnotes.templates.website_group.post import get_post_context | |||
def get_views(): | |||
return views | |||
def get_context(group_context): | |||
return { | |||
"post_list_html": get_post_list_html(group_context["group"]["name"], group_context["view"]) | |||
} | |||
forum_context = {} | |||
if group_context.view.name in ("popular", "feed"): | |||
forum_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"]) | |||
elif group_context.view.name == "edit": | |||
forum_context["session_user"] = webnotes.session.user | |||
forum_context["post"] = webnotes.doc("Post", webnotes.form_dict.name).fields | |||
elif group_context.view.name == "settings": | |||
forum_context.update(get_settings_context(group_context)) | |||
elif group_context.view.name == "post": | |||
forum_context.update(get_post_context(group_context)) | |||
return forum_context | |||
@webnotes.whitelist(allow_guest=True) | |||
def get_post_list_html(group, view, limit_start=0, limit_length=20): | |||
access = get_access(group) | |||
if isinstance(view, basestring): | |||
view = get_views(group)["view"] | |||
view = get_views()[view] | |||
view = webnotes._dict(view) | |||
@@ -50,7 +64,7 @@ def get_post_list_html(group, view, limit_start=0, limit_length=20): | |||
views = { | |||
"popular": { | |||
"name": "popular", | |||
"template_path": "templates/unit_templates/forum_list.html", | |||
"template_path": "templates/website_group/forum.html", | |||
"url": "/{group}", | |||
"label": "Popular", | |||
"icon": "icon-heart", | |||
@@ -60,7 +74,7 @@ views = { | |||
}, | |||
"feed": { | |||
"name": "feed", | |||
"template_path": "templates/unit_templates/forum_list.html", | |||
"template_path": "templates/website_group/forum.html", | |||
"url": "/{group}?view=feed", | |||
"label": "Feed", | |||
"icon": "icon-rss", | |||
@@ -69,7 +83,7 @@ views = { | |||
}, | |||
"post": { | |||
"name": "post", | |||
"template_path": "templates/unit_templates/base_post.html", | |||
"template_path": "templates/website_group/post.html", | |||
"url": "/{group}?view=post&name={post}", | |||
"label": "Post", | |||
"icon": "icon-comments", | |||
@@ -80,7 +94,7 @@ views = { | |||
}, | |||
"edit": { | |||
"name": "edit", | |||
"template_path": "templates/unit_templates/base_edit.html", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=edit&name={post}", | |||
"label": "Edit Post", | |||
"icon": "icon-pencil", | |||
@@ -90,7 +104,7 @@ views = { | |||
}, | |||
"add": { | |||
"name": "add", | |||
"template_path": "templates/unit_templates/base_edit.html", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=add", | |||
"label": "Add Post", | |||
"icon": "icon-plus", | |||
@@ -99,8 +113,8 @@ views = { | |||
}, | |||
"settings": { | |||
"name": "settings", | |||
"template_path": "templates/unit_templates/base_settings.html", | |||
"url": "/{group}&view=settings", | |||
"template_path": "templates/website_group/settings.html", | |||
"url": "/{group}?view=settings", | |||
"label": "Settings", | |||
"icon": "icon-cog", | |||
"hidden": True, | |||
@@ -0,0 +1,34 @@ | |||
<div class="small text-muted post-list-help"></div> | |||
<div class="parent-post">{{ parent_post_html }}</div> | |||
<div class="text-center"> | |||
<button type="button" style="margin-bottom: 15px" | |||
class="btn btn-default btn-earlier-replies hide"> | |||
<span class="btn-earlier-label">Show</span> Earlier Replies</button> | |||
</div> | |||
<div class="post-list"> | |||
{{ post_list_html }} | |||
</div> | |||
<div style="margin-top: 15px"> | |||
{% include "templates/includes/post_editor.html" %} | |||
</div> | |||
<script type="text/javascript" src="/assets/js/canvasResize.min.js"></script> | |||
<script> | |||
$(function() { | |||
website.toggle_edit(true); | |||
website.setup_upvote(); | |||
website.toggle_upvote(); | |||
website.bind_add_post(); | |||
// show/hide earlier replies | |||
website.toggle_earlier_replies(); | |||
{%- if group.group_type == "Events" -%} | |||
website.format_event_timestamps(); | |||
{%- endif -%} | |||
website.toggle_post_editor(); | |||
$('[data-view="post"]').removeClass("hide"); | |||
}); | |||
</script> |
@@ -0,0 +1,41 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.utils import get_fullname | |||
def get_post_context(group_context): | |||
post = webnotes.doc("Post", webnotes.form_dict.name) | |||
if post.parent_post: | |||
raise webnotes.PermissionError | |||
fullname = get_fullname(post.owner) | |||
return { | |||
"title": "{} by {}".format(post.title, fullname), | |||
# "group_title": group_context.get("unit_title") + " by {}".format(fullname), | |||
"parent_post_html": get_parent_post_html(post, group_context.get("view")), | |||
"post_list_html": get_child_posts_html(post, group_context.get("view")), | |||
"parent_post": post.name | |||
} | |||
def get_parent_post_html(post, view): | |||
profile = webnotes.bean("Profile", post.owner).doc | |||
for fieldname in ("first_name", "last_name", "user_image", "fb_hometown", "fb_location"): | |||
post.fields[fieldname] = profile.fields[fieldname] | |||
return webnotes.get_template("templates/includes/inline_post.html")\ | |||
.render({"post": post.fields, "view": view}) | |||
def get_child_posts_html(post, view): | |||
posts = webnotes.conn.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name | |||
from tabPost p, tabProfile pr | |||
where p.parent_post=%s and pr.name = p.owner | |||
order by p.creation asc""", (post.name,), as_dict=True) | |||
return webnotes.get_template("templates/includes/post_list.html")\ | |||
.render({ | |||
"posts": posts, | |||
"parent_post": post.name, | |||
"view": view | |||
}) |
@@ -0,0 +1,77 @@ | |||
<div class="permission-editor-area"> | |||
<div class="well permission-editor"> | |||
<h4>1. Edit Description</h4> | |||
<div class="row"> | |||
<div class="col-xs-12"> | |||
<p> | |||
<textarea class="form-control control-description" | |||
style="height: 100px;">{{ group.group_description or "" }}</textarea> | |||
</p> | |||
<div> | |||
<button class="btn btn-default btn-update-description" | |||
onclick="website.update_group_description()">Update</button> | |||
</div> | |||
</div> | |||
</div> | |||
<hr> | |||
<h4>2. Add Sub Groups</h4> | |||
<div class="row"> | |||
<div class="col-xs-12"> | |||
<div class="form-group"> | |||
<input class="form-control control-add-group" placeholder="New Group Name" /> | |||
<p class="help-block small">Only letters, numbers and spaces</p> | |||
</div> | |||
<div class="form-group"> | |||
<label>Group Type</label> | |||
<select class="form-control control-add-group-type" data-fieldname="group_type"> | |||
{%- for group_type in ("Forum", "Tasks", "Events") -%} | |||
<option value="{{ group_type }}">{{ group_type }}</option> | |||
{%- endfor -%} | |||
</select> | |||
</div> | |||
<div class="checkbox" style="position: static;"> | |||
<label> | |||
<input type="checkbox" class="control-add-group-public_read" | |||
{{ "checked" if public_read else "disabled" }}> <span>Allow all users to read</span> | |||
</label> | |||
<p class="help-block small">Private if unchecked, only users with explicit read access will be allowed to read</p> | |||
</div> | |||
<div class="checkbox" style="position: static;"> | |||
<label> | |||
<input type="checkbox" class="control-add-group-public_write" | |||
{{ "checked" if public_write else "disabled" }}> <span>Allow all users to write</span> | |||
<p class="help-block small">Public Forum</p> | |||
</label> | |||
</div> | |||
<div> | |||
<button class="btn btn-default btn-add-group"><i class="icon-plus"></i> Add</button> | |||
</div> | |||
</div> | |||
</div> | |||
<hr> | |||
<h4>3. Manage Users</h4> | |||
<input class="form-control add-user-control" type="text" placeholder="Select User" /> | |||
<br> | |||
<table class="table table-bordered"> | |||
<thead> | |||
<tr> | |||
<th style="width: 55%">User</th> | |||
<th style="width: 15%">Read</th> | |||
<th style="width: 15%">Write</th> | |||
<th style="width: 15%">Admin</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{% for profile in profiles %} | |||
{% include "templates/includes/sitemap_permission.html" %} | |||
{% endfor %} | |||
</tbody> | |||
</table> | |||
</div> | |||
</div> | |||
<script> | |||
$(function() { | |||
website.setup_settings(); | |||
}) | |||
</script> |
@@ -0,0 +1,105 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.webutils import get_access | |||
from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission import clear_permissions | |||
from webnotes.utils.email_lib.bulk import send | |||
def get_settings_context(group_context): | |||
if not get_access(group_context.group.name).get("admin"): | |||
raise webnotes.PermissionError | |||
return { | |||
"profiles": webnotes.conn.sql("""select p.*, wsp.`read`, wsp.`write`, wsp.`admin` | |||
from `tabProfile` p, `tabWebsite Sitemap Permission` wsp | |||
where wsp.website_sitemap=%s and wsp.profile=p.name""", (group_context.group.name,), as_dict=True) | |||
} | |||
@webnotes.whitelist() | |||
def suggest_user(term, group): | |||
profiles = webnotes.conn.sql("""select pr.name, pr.first_name, pr.last_name, | |||
pr.user_image, pr.fb_location, pr.fb_hometown | |||
from `tabProfile` pr | |||
where (pr.first_name like %(term)s or pr.last_name like %(term)s) | |||
and pr.user_image is not null and pr.enabled=1 | |||
and not exists(select wsp.name from `tabWebsite Sitemap Permission` wsp | |||
where wsp.website_sitemap=%(group)s and wsp.profile=pr.name)""", | |||
{"term": "%{}%".format(term), "group": group}, as_dict=True) | |||
template = webnotes.get_template("templates/includes/profile_display.html") | |||
return [{ | |||
"value": "{} {}".format(pr.first_name, pr.last_name), | |||
"profile_html": template.render({"profile": pr}), | |||
"profile": pr.name | |||
} for pr in profiles] | |||
@webnotes.whitelist() | |||
def add_sitemap_permission(sitemap_page, profile): | |||
if not get_access(sitemap_page).get("admin"): | |||
raise webnotes.PermissionError | |||
permission = webnotes.bean({ | |||
"doctype": "Website Sitemap Permission", | |||
"website_sitemap": sitemap_page, | |||
"profile": profile, | |||
"read": 1 | |||
}) | |||
permission.insert(ignore_permissions=True) | |||
profile = permission.doc.fields | |||
profile.update(webnotes.conn.get_value("Profile", profile.profile, | |||
["name", "first_name", "last_name", "user_image", "fb_location", "fb_hometown"], as_dict=True)) | |||
return webnotes.get_template("templates/includes/sitemap_permission.html").render({ | |||
"profile": profile | |||
}) | |||
@webnotes.whitelist() | |||
def update_permission(sitemap_page, profile, perm, value): | |||
if not get_access(sitemap_page).get("admin"): | |||
raise webnotes.PermissionError | |||
permission = webnotes.bean("Website Sitemap Permission", {"website_sitemap": sitemap_page, "profile": profile}) | |||
permission.doc.fields[perm] = int(value) | |||
permission.save(ignore_permissions=True) | |||
# send email | |||
if perm=="admin" and int(value): | |||
group_title = webnotes.conn.get_value("Website Sitemap", sitemap_page, "page_title") | |||
subject = "You have been made Administrator of Group " + group_title | |||
send(recipients=[profile], | |||
subject= subject, add_unsubscribe_link=False, | |||
message="""<h3>Group Notification<h3>\ | |||
<p>%s</p>\ | |||
<p style="color: #888">This is just for your information.</p>""" % subject) | |||
@webnotes.whitelist() | |||
def update_description(group, description): | |||
if not get_access(group).get("admin"): | |||
raise webnotes.PermissionError | |||
group = webnotes.bean("Website Group", group) | |||
group.doc.group_description = description | |||
group.save(ignore_permissions=True) | |||
@webnotes.whitelist() | |||
def add_website_group(group, new_group, public_read, public_write, group_type="Forum"): | |||
if not get_access(group).get("admin"): | |||
raise webnotes.PermissionError | |||
parent_website_sitemap = webnotes.conn.get_value("Website Sitemap", | |||
{"ref_doctype": "Website Group", "docname": group}) | |||
webnotes.bean({ | |||
"doctype": "Website Group", | |||
"group_name": group + "-" + new_group, | |||
"group_title": new_group, | |||
"parent_website_sitemap": parent_website_sitemap, | |||
"group_type": group_type, | |||
"public_read": int(public_read), | |||
"public_write": int(public_write) | |||
}).insert(ignore_permissions=True) |
@@ -0,0 +1,32 @@ | |||
<div class="small text-muted post-list-help"></div> | |||
<div class="post-list"> | |||
{{ post_list_html }} | |||
</div> | |||
<div class="text-center"> | |||
<button class="btn btn-default btn-more hide">More</button> | |||
</div> | |||
<script> | |||
$(function() { | |||
if($(".post").length===20) { | |||
wn.setup_pagination($(".btn-more"), { | |||
args: { | |||
cmd: "webnotes.templates.website_group.tasks.get_post_list_html", | |||
limit_start: $(".post").length, | |||
limit_length: 20, | |||
group: website.group, | |||
view: website.view, | |||
status: "Closed" ? view=="closed" : undefined | |||
}, | |||
$wrapper: $(".post-list") | |||
}); | |||
} | |||
{%- if view.name == "open" -%} | |||
website.setup_upvote(); | |||
website.toggle_upvote(); | |||
{%- endif -%} | |||
website.toggle_edit(); | |||
}); | |||
</script> |
@@ -0,0 +1,126 @@ | |||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
# MIT License. See license.txt | |||
from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.utils import now_datetime, get_datetime_str | |||
from webnotes.webutils import get_access | |||
from webnotes.templates.website_group.settings import get_settings_context | |||
from webnotes.templates.website_group.post import get_post_context | |||
def get_views(): | |||
return views | |||
def get_context(group_context): | |||
tasks_context = {} | |||
if group_context.view.name in ("open", "closed"): | |||
tasks_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"]) | |||
elif group_context.view.name == "edit": | |||
post = webnotes.doc("Post", webnotes.form_dict.name).fields | |||
tasks_context["session_user"] = webnotes.session.user | |||
tasks_context["post"] = post | |||
if post.assigned_to: | |||
tasks_context["profile"] = webnotes.doc("Profile", post.assigned_to) | |||
elif group_context.view.name == "settings": | |||
tasks_context.update(get_settings_context(group_context)) | |||
elif group_context.view.name == "post": | |||
tasks_context.update(get_post_context(group_context)) | |||
return tasks_context | |||
@webnotes.whitelist(allow_guest=True) | |||
def get_post_list_html(group, view, limit_start=0, limit_length=20, status="Open"): | |||
access = get_access(group) | |||
if isinstance(view, basestring): | |||
view = get_views()[view] | |||
view = webnotes._dict(view) | |||
# verify permission for paging | |||
if webnotes.local.form_dict.cmd == "get_post_list_html": | |||
if not access.get("read"): | |||
return webnotes.PermissionError | |||
if view.name=="open": | |||
now = get_datetime_str(now_datetime()) | |||
order_by = "(p.upvotes + post_reply_count - (timestampdiff(hour, p.creation, \"{}\") / 2)) desc, p.creation desc".format(now) | |||
else: | |||
status = "Closed" | |||
order_by = "p.creation desc" | |||
posts = webnotes.conn.sql("""select p.*, pr.user_image, pr.first_name, pr.last_name, | |||
(select count(pc.name) from `tabPost` pc where pc.parent_post=p.name) as post_reply_count | |||
from `tabPost` p, `tabProfile` pr | |||
where p.website_group = %s and pr.name = p.owner and ifnull(p.parent_post, '')='' | |||
and p.is_task=1 and p.status=%s | |||
order by {order_by} limit %s, %s""".format(order_by=order_by), | |||
(group, status, int(limit_start), int(limit_length)), as_dict=True) | |||
context = {"posts": posts, "limit_start": limit_start, "view": view} | |||
return webnotes.get_template("templates/includes/post_list.html").render(context) | |||
views = { | |||
"open": { | |||
"name": "open", | |||
"template_path": "templates/website_group/tasks.html", | |||
"url": "/{group}", | |||
"label": "Open", | |||
"icon": "icon-inbox", | |||
"default": True, | |||
"upvote": True, | |||
"idx": 1 | |||
}, | |||
"closed": { | |||
"name": "closed", | |||
"template_path": "templates/website_group/tasks.html", | |||
"url": "/{group}?view=closed", | |||
"label": "Closed", | |||
"icon": "icon-smile", | |||
"idx": 2 | |||
}, | |||
"post": { | |||
"name": "post", | |||
"template_path": "templates/website_group/post.html", | |||
"url": "/{group}?view=post&name={post}", | |||
"label": "Post", | |||
"icon": "icon-comments", | |||
"hidden": True, | |||
"no_cache": True, | |||
"upvote": True, | |||
"idx": 3 | |||
}, | |||
"edit": { | |||
"name": "edit", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=edit&name={post}", | |||
"label": "Edit Post", | |||
"icon": "icon-pencil", | |||
"hidden": True, | |||
"no_cache": True, | |||
"idx": 4 | |||
}, | |||
"add": { | |||
"name": "add", | |||
"template_path": "templates/website_group/edit_post.html", | |||
"url": "/{group}?view=add", | |||
"label": "Add Post", | |||
"icon": "icon-plus", | |||
"hidden": True, | |||
"idx": 5 | |||
}, | |||
"settings": { | |||
"name": "settings", | |||
"template_path": "templates/website_group/settings.html", | |||
"url": "/{group}?view=settings", | |||
"label": "Settings", | |||
"icon": "icon-cog", | |||
"hidden": True, | |||
"idx": 6 | |||
} | |||
} |
@@ -1,4 +1,4 @@ | |||
@media (min-width: 992px) { | |||
@media (min-width: 768px) { | |||
.login-wrapper { | |||
border-right: 1px solid #f2f2f2; | |||
} | |||
@@ -163,6 +163,8 @@ img { | |||
.navbar-brand { | |||
padding-right: 30px; | |||
max-width: 80%; | |||
min-height: 20px; | |||
height: auto; | |||
} | |||
@media (min-width: 768px) { | |||
@@ -283,6 +285,7 @@ body { | |||
.post .media-object { | |||
border-radius: 4px; | |||
max-width: 50px; | |||
} | |||
.post .media-heading { | |||
@@ -298,4 +301,12 @@ body { | |||
padding-left: 15px; | |||
background-color: #f8f8f8; | |||
margin-top: 0px; | |||
} | |||
textarea { | |||
resize: vertical; | |||
} | |||
.post-add-textarea { | |||
height: 200px !important; | |||
} |
@@ -9,11 +9,13 @@ import webnotes | |||
from webnotes.utils import get_fullname | |||
from webnotes.utils.email_lib.bulk import send | |||
from webnotes.utils.email_lib import sendmail | |||
from webnotes.utils.file_manager import save_file | |||
from webnotes.webutils import get_access | |||
# TODO move these functions to framework | |||
from aapkamanch.helpers import get_access | |||
from aapkamanch.post import clear_post_cache | |||
from aapkamanch.unit import clear_unit_views | |||
# from aapkamanch.post import clear_post_cache | |||
# from aapkamanch.unit import clear_unit_views | |||
class DocType: | |||
def __init__(self, d, dl): | |||
@@ -42,8 +44,8 @@ class DocType: | |||
self.doc.event_datetime = None | |||
def on_update(self): | |||
clear_unit_views(self.doc.unit) | |||
clear_post_cache(self.doc.parent_post or self.doc.name) | |||
# clear_unit_views(self.doc.website_group) | |||
# clear_post_cache(self.doc.parent_post or self.doc.name) | |||
if self.doc.assigned_to and self.doc.assigned_to != self.assigned_to \ | |||
and webnotes.session.user != self.doc.assigned_to: | |||
@@ -99,4 +101,102 @@ class DocType: | |||
message += "<p><a href='/post/{post_name}'>Click here to view the post</a></p>".format(fullname=owner_fullname, | |||
post_name=post_name) | |||
return message | |||
@webnotes.whitelist(allow_guest=True) | |||
def add_post(group, content, picture, picture_name, title=None, parent_post=None, | |||
assigned_to=None, status=None, event_datetime=None): | |||
access = get_access(group) | |||
if not access.get("write"): | |||
raise webnotes.PermissionError | |||
if parent_post: | |||
if webnotes.conn.get_value("Post", parent_post, "parent_post"): | |||
webnotes.throw("Cannot reply to a reply") | |||
group = webnotes.doc("Website Group", group) | |||
post = webnotes.bean({ | |||
"doctype":"Post", | |||
"title": (title or "").title(), | |||
"content": content, | |||
"website_group": group.name, | |||
"parent_post": parent_post or None | |||
}) | |||
if not parent_post: | |||
if group.group_type == "Tasks": | |||
post.doc.is_task = 1 | |||
post.doc.assigned_to = assigned_to | |||
elif group.group_type == "Events": | |||
post.doc.is_event = 1 | |||
post.doc.event_datetime = event_datetime | |||
post.ignore_permissions = True | |||
post.insert() | |||
if picture_name and picture: | |||
process_picture(post, picture_name, picture) | |||
# send email | |||
if parent_post: | |||
post.run_method("send_email_on_reply") | |||
return post.doc.parent_post or post.doc.name | |||
@webnotes.whitelist(allow_guest=True) | |||
def save_post(post, content, picture=None, picture_name=None, title=None, | |||
assigned_to=None, status=None, event_datetime=None): | |||
post = webnotes.bean("Post", post) | |||
access = get_access(post.doc.website_group) | |||
if not access.get("write"): | |||
raise webnotes.PermissionError | |||
# TODO improve error message | |||
if webnotes.session.user != post.doc.owner: | |||
for fieldname in ("title", "content"): | |||
if post.doc.fields.get(fieldname) != locals().get(fieldname): | |||
webnotes.throw("You cannot change: {}".format(fieldname.title())) | |||
if picture and picture_name: | |||
webnotes.throw("You cannot change: Picture") | |||
post.doc.fields.update({ | |||
"title": (title or "").title(), | |||
"content": content, | |||
"assigned_to": assigned_to, | |||
"status": status, | |||
"event_datetime": event_datetime | |||
}) | |||
post.ignore_permissions = True | |||
post.save() | |||
if picture_name and picture: | |||
process_picture(post, picture_name, picture) | |||
return post.doc.parent_post or post.doc.name | |||
def process_picture(post, picture_name, picture): | |||
file_data = save_file(picture_name, picture, "Post", post.doc.name, decode=True) | |||
post.doc.picture_url = file_data.file_name or file_data.file_url | |||
webnotes.conn.set_value("Post", post.doc.name, "picture_url", post.doc.picture_url) | |||
# clear_unit_views(post.doc.website_group) | |||
@webnotes.whitelist() | |||
def suggest_user(group, term): | |||
"""suggest a user that has read permission in this group tree""" | |||
profiles = webnotes.conn.sql("""select | |||
pr.name, pr.first_name, pr.last_name, | |||
pr.user_image, pr.fb_location, pr.fb_hometown | |||
from `tabProfile` pr | |||
where (pr.first_name like %(term)s or pr.last_name like %(term)s) | |||
and pr.name not in ("Guest", "Administrator") is not null and pr.enabled=1""", | |||
{"term": "%{}%".format(term), "group": group}, as_dict=True) | |||
template = webnotes.get_template("templates/includes/profile_display.html") | |||
return [{ | |||
"value": "{} {}".format(pr.first_name or "", pr.last_name or "").strip(), | |||
"profile_html": template.render({"profile": pr}), | |||
"profile": pr.name | |||
} for pr in profiles] |
@@ -5,6 +5,7 @@ | |||
from __future__ import unicode_literals | |||
import webnotes | |||
from webnotes.webutils import get_access | |||
class DocType: | |||
def __init__(self, d, dl): | |||
@@ -31,3 +32,22 @@ class DocType: | |||
def on_doctype_update(): | |||
webnotes.conn.add_index("User Vote", ["ref_doctype", "ref_name"]) | |||
# don't allow guest to give vote | |||
@webnotes.whitelist() | |||
def set_vote(ref_doctype, ref_name): | |||
website_group = webnotes.conn.get_value(ref_doctype, ref_name, "website_group") | |||
if not get_access(website_group).get("read"): | |||
raise webnotes.PermissionError | |||
try: | |||
user_vote = webnotes.bean({ | |||
"doctype": "User Vote", | |||
"ref_doctype": ref_doctype, | |||
"ref_name": ref_name | |||
}) | |||
user_vote.ignore_permissions = True | |||
user_vote.insert() | |||
return "ok" | |||
except webnotes.DuplicateEntryError: | |||
return "duplicate" |
@@ -27,3 +27,6 @@ class DocType(WebsiteGenerator): | |||
if not self.doc.page_name: | |||
webnotes.throw(_("Page Name is mandatory"), raise_exception=webnotes.MandatoryError) | |||
def get_page_title(self): | |||
return self.doc.group_title |
@@ -2,7 +2,7 @@ | |||
{ | |||
"creation": "2013-04-30 12:58:46", | |||
"docstatus": 0, | |||
"modified": "2013-12-27 16:37:52", | |||
"modified": "2014-02-03 15:25:54", | |||
"modified_by": "Administrator", | |||
"owner": "Administrator" | |||
}, | |||
@@ -25,6 +25,8 @@ | |||
"permlevel": 0 | |||
}, | |||
{ | |||
"cancel": 0, | |||
"delete": 0, | |||
"doctype": "DocPerm", | |||
"name": "__common__", | |||
"parent": "Website Settings", | |||
@@ -48,8 +50,9 @@ | |||
"description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)", | |||
"doctype": "DocField", | |||
"fieldname": "home_page", | |||
"fieldtype": "Data", | |||
"fieldtype": "Link", | |||
"label": "Home Page", | |||
"options": "Website Sitemap", | |||
"reqd": 0 | |||
}, | |||
{ | |||
@@ -241,7 +244,6 @@ | |||
}, | |||
{ | |||
"amend": 0, | |||
"cancel": 0, | |||
"create": 0, | |||
"doctype": "DocPerm", | |||
"permlevel": 1, | |||
@@ -8,7 +8,7 @@ import webnotes | |||
from webnotes.utils.nestedset import DocTypeNestedSet | |||
sitemap_fields = ("page_name", "ref_doctype", "docname", "page_or_generator", | |||
"lastmod", "parent_website_sitemap", "public_read", "public_write") | |||
"lastmod", "parent_website_sitemap", "public_read", "public_write", "page_title") | |||
class DocType(DocTypeNestedSet): | |||
def __init__(self, d, dl): | |||
@@ -10,6 +10,7 @@ class DocType: | |||
def on_update(self): | |||
remove_empty_permissions() | |||
clear_permissions(self.doc.profile) | |||
def remove_empty_permissions(): | |||
permissions_cache_to_be_cleared = webnotes.conn.sql_list("""select distinct profile | |||
@@ -21,20 +22,20 @@ def remove_empty_permissions(): | |||
clear_permissions(permissions_cache_to_be_cleared) | |||
def get_access(website_node, profile=None): | |||
def get_access(sitemap_page, profile=None): | |||
profile = profile or webnotes.session.user | |||
key = "website_sitemap_permissions:{}".format(profile) | |||
cache = webnotes.cache() | |||
permissions = cache.get_value(key) or {} | |||
if not permissions.get(website_node): | |||
permissions[website_node] = _get_access(website_node, profile) | |||
if not permissions.get(sitemap_page): | |||
permissions[sitemap_page] = _get_access(sitemap_page, profile) | |||
cache.set_value(key, permissions) | |||
return permissions.get(website_node) | |||
return permissions.get(sitemap_page) | |||
def _get_access(website_node, profile): | |||
lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", website_node, | |||
def _get_access(sitemap_page, profile): | |||
lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", sitemap_page, | |||
["lft", "rgt", "public_read", "public_write"]) | |||
if not (lft and rgt): | |||
@@ -52,7 +53,7 @@ def _get_access(website_node, profile): | |||
for perm in webnotes.conn.sql("""select wsp.`read`, wsp.`write`, wsp.`admin`, | |||
ws.lft, ws.rgt, ws.name | |||
from `tabWebsite Sitemap Permission` up, `tabWebsite Sitemap` ws | |||
from `tabWebsite Sitemap Permission` wsp, `tabWebsite Sitemap` ws | |||
where wsp.profile = %s and wsp.website_sitemap = ws.name | |||
order by lft asc""", (profile,), as_dict=True): | |||
if perm.lft <= lft and perm.rgt >= rgt: | |||
@@ -0,0 +1,313 @@ | |||
/* | |||
* jQuery canvasResize plugin | |||
* | |||
* Version: 1.2.0 | |||
* Date (d/m/y): 02/10/12 | |||
* Update (d/m/y): 14/05/13 | |||
* Original author: @gokercebeci | |||
* Licensed under the MIT license | |||
* - This plugin working with jquery.exif.js | |||
* (It's under the MPL License http://www.nihilogic.dk/licenses/mpl-license.txt) | |||
* Demo: http://ios6-image-resize.gokercebeci.com/ | |||
* | |||
* - I fixed iOS6 Safari's image file rendering issue for large size image (over mega-pixel) | |||
* using few functions from https://github.com/stomita/ios-imagefile-megapixel | |||
* (detectSubsampling, ) | |||
* And fixed orientation issue by edited http://blog.nihilogic.dk/2008/05/jquery-exif-data-plugin.html | |||
* Thanks, Shinichi Tomita and Jacob Seidelin | |||
*/ | |||
(function($) { | |||
var pluginName = 'canvasResize', | |||
methods = { | |||
newsize: function(w, h, W, H, C) { | |||
var c = C ? 'h' : ''; | |||
if ((W && w > W) || (H && h > H)) { | |||
var r = w / h; | |||
if ((r >= 1 || H === 0) && W && !C) { | |||
w = W; | |||
h = (W / r) >> 0; | |||
} else if (C && r <= (W / H)) { | |||
w = W; | |||
h = (W / r) >> 0; | |||
c = 'w'; | |||
} else { | |||
w = (H * r) >> 0; | |||
h = H; | |||
} | |||
} | |||
return { | |||
'width': w, | |||
'height': h, | |||
'cropped': c | |||
}; | |||
}, | |||
dataURLtoBlob: function(data) { | |||
var mimeString = data.split(',')[0].split(':')[1].split(';')[0]; | |||
var byteString = atob(data.split(',')[1]); | |||
var ab = new ArrayBuffer(byteString.length); | |||
var ia = new Uint8Array(ab); | |||
for (var i = 0; i < byteString.length; i++) { | |||
ia[i] = byteString.charCodeAt(i); | |||
} | |||
var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder); | |||
if (bb) { | |||
// console.log('BlobBuilder'); | |||
bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)(); | |||
bb.append(ab); | |||
return bb.getBlob(mimeString); | |||
} else { | |||
// console.log('Blob'); | |||
bb = new Blob([ab], { | |||
'type': (mimeString) | |||
}); | |||
return bb; | |||
} | |||
}, | |||
/** | |||
* Detect subsampling in loaded image. | |||
* In iOS, larger images than 2M pixels may be subsampled in rendering. | |||
*/ | |||
detectSubsampling: function(img) { | |||
var iw = img.width, ih = img.height; | |||
if (iw * ih > 1048576) { // subsampling may happen over megapixel image | |||
var canvas = document.createElement('canvas'); | |||
canvas.width = canvas.height = 1; | |||
var ctx = canvas.getContext('2d'); | |||
ctx.drawImage(img, -iw + 1, 0); | |||
// subsampled image becomes half smaller in rendering size. | |||
// check alpha channel value to confirm image is covering edge pixel or not. | |||
// if alpha value is 0 image is not covering, hence subsampled. | |||
return ctx.getImageData(0, 0, 1, 1).data[3] === 0; | |||
} else { | |||
return false; | |||
} | |||
}, | |||
/** | |||
* Update the orientation according to the specified rotation angle | |||
*/ | |||
rotate: function(orientation, angle) { | |||
var o = { | |||
// nothing | |||
1: {90: 6, 180: 3, 270: 8}, | |||
// horizontal flip | |||
2: {90: 7, 180: 4, 270: 5}, | |||
// 180 rotate left | |||
3: {90: 8, 180: 1, 270: 6}, | |||
// vertical flip | |||
4: {90: 5, 180: 2, 270: 7}, | |||
// vertical flip + 90 rotate right | |||
5: {90: 2, 180: 7, 270: 4}, | |||
// 90 rotate right | |||
6: {90: 3, 180: 8, 270: 1}, | |||
// horizontal flip + 90 rotate right | |||
7: {90: 4, 180: 5, 270: 2}, | |||
// 90 rotate left | |||
8: {90: 1, 180: 6, 270: 3} | |||
}; | |||
return o[orientation][angle] ? o[orientation][angle] : orientation; | |||
}, | |||
/** | |||
* Transform canvas coordination according to specified frame size and orientation | |||
* Orientation value is from EXIF tag | |||
*/ | |||
transformCoordinate: function(canvas, width, height, orientation) { | |||
//console.log(width, height); | |||
switch (orientation) { | |||
case 5: | |||
case 6: | |||
case 7: | |||
case 8: | |||
canvas.width = height; | |||
canvas.height = width; | |||
break; | |||
default: | |||
canvas.width = width; | |||
canvas.height = height; | |||
} | |||
var ctx = canvas.getContext('2d'); | |||
switch (orientation) { | |||
case 1: | |||
// nothing | |||
break; | |||
case 2: | |||
// horizontal flip | |||
ctx.translate(width, 0); | |||
ctx.scale(-1, 1); | |||
break; | |||
case 3: | |||
// 180 rotate left | |||
ctx.translate(width, height); | |||
ctx.rotate(Math.PI); | |||
break; | |||
case 4: | |||
// vertical flip | |||
ctx.translate(0, height); | |||
ctx.scale(1, -1); | |||
break; | |||
case 5: | |||
// vertical flip + 90 rotate right | |||
ctx.rotate(0.5 * Math.PI); | |||
ctx.scale(1, -1); | |||
break; | |||
case 6: | |||
// 90 rotate right | |||
ctx.rotate(0.5 * Math.PI); | |||
ctx.translate(0, -height); | |||
break; | |||
case 7: | |||
// horizontal flip + 90 rotate right | |||
ctx.rotate(0.5 * Math.PI); | |||
ctx.translate(width, -height); | |||
ctx.scale(-1, 1); | |||
break; | |||
case 8: | |||
// 90 rotate left | |||
ctx.rotate(-0.5 * Math.PI); | |||
ctx.translate(-width, 0); | |||
break; | |||
default: | |||
break; | |||
} | |||
}, | |||
/** | |||
* Detecting vertical squash in loaded image. | |||
* Fixes a bug which squash image vertically while drawing into canvas for some images. | |||
*/ | |||
detectVerticalSquash: function(img, iw, ih) { | |||
var canvas = document.createElement('canvas'); | |||
canvas.width = 1; | |||
canvas.height = ih; | |||
var ctx = canvas.getContext('2d'); | |||
ctx.drawImage(img, 0, 0); | |||
var data = ctx.getImageData(0, 0, 1, ih).data; | |||
// search image edge pixel position in case it is squashed vertically. | |||
var sy = 0; | |||
var ey = ih; | |||
var py = ih; | |||
while (py > sy) { | |||
var alpha = data[(py - 1) * 4 + 3]; | |||
if (alpha === 0) { | |||
ey = py; | |||
} else { | |||
sy = py; | |||
} | |||
py = (ey + sy) >> 1; | |||
} | |||
var ratio = py / ih; | |||
return ratio === 0 ? 1 : ratio; | |||
}, | |||
callback: function(d) { | |||
return d; | |||
} | |||
}, | |||
defaults = { | |||
width: 300, | |||
height: 0, | |||
crop: false, | |||
quality: 80, | |||
'callback': methods.callback | |||
}; | |||
function Plugin(file, options) { | |||
this.file = file; | |||
this.options = $.extend({}, defaults, options); | |||
this._defaults = defaults; | |||
this._name = pluginName; | |||
this.init(); | |||
} | |||
Plugin.prototype = { | |||
init: function() { | |||
//this.options.init(this); | |||
var $this = this; | |||
var file = this.file; | |||
var reader = new FileReader(); | |||
reader.onloadend = function(e) { | |||
var dataURL = e.target.result; | |||
var img = new Image(); | |||
img.onload = function(e) { | |||
// Read Orientation Data in EXIF | |||
$(img).exifLoadFromDataURL(function() { | |||
var orientation = $(img).exif('Orientation')[0] || 1; | |||
orientation = methods.rotate(orientation, $this.options.rotate); | |||
// CW or CCW ? replace width and height | |||
var size = (orientation >= 5 && orientation <= 8) | |||
? methods.newsize(img.height, img.width, $this.options.width, $this.options.height, $this.options.crop) | |||
: methods.newsize(img.width, img.height, $this.options.width, $this.options.height, $this.options.crop); | |||
var iw = img.width, ih = img.height; | |||
var width = size.width, height = size.height; | |||
//console.log(iw, ih, size.width, size.height, orientation); | |||
var canvas = document.createElement("canvas"); | |||
var ctx = canvas.getContext("2d"); | |||
ctx.save(); | |||
methods.transformCoordinate(canvas, width, height, orientation); | |||
// over image size | |||
if (methods.detectSubsampling(img)) { | |||
iw /= 2; | |||
ih /= 2; | |||
} | |||
var d = 1024; // size of tiling canvas | |||
var tmpCanvas = document.createElement('canvas'); | |||
tmpCanvas.width = tmpCanvas.height = d; | |||
var tmpCtx = tmpCanvas.getContext('2d'); | |||
var vertSquashRatio = methods.detectVerticalSquash(img, iw, ih); | |||
var sy = 0; | |||
while (sy < ih) { | |||
var sh = sy + d > ih ? ih - sy : d; | |||
var sx = 0; | |||
while (sx < iw) { | |||
var sw = sx + d > iw ? iw - sx : d; | |||
tmpCtx.clearRect(0, 0, d, d); | |||
tmpCtx.drawImage(img, -sx, -sy); | |||
var dx = Math.floor(sx * width / iw); | |||
var dw = Math.ceil(sw * width / iw); | |||
var dy = Math.floor(sy * height / ih / vertSquashRatio); | |||
var dh = Math.ceil(sh * height / ih / vertSquashRatio); | |||
ctx.drawImage(tmpCanvas, 0, 0, sw, sh, dx, dy, dw, dh); | |||
sx += d; | |||
} | |||
sy += d; | |||
} | |||
ctx.restore(); | |||
tmpCanvas = tmpCtx = null; | |||
// if cropped or rotated width and height data replacing issue | |||
var newcanvas = document.createElement('canvas'); | |||
newcanvas.width = size.cropped === 'h' ? height : width; | |||
newcanvas.height = size.cropped === 'w' ? width : height; | |||
var x = size.cropped === 'h' ? (height - width) * .5 : 0; | |||
var y = size.cropped === 'w' ? (width - height) * .5 : 0; | |||
newctx = newcanvas.getContext('2d'); | |||
newctx.drawImage(canvas, x, y, width, height); | |||
if (file.type === "image/png") { | |||
var data = newcanvas.toDataURL(file.type); | |||
} else { | |||
var data = newcanvas.toDataURL("image/jpeg", ($this.options.quality * .01)); | |||
} | |||
// CALLBACK | |||
$this.options.callback(data, width, height); | |||
}); | |||
}; | |||
img.src = dataURL; | |||
// ===================================================== | |||
}; | |||
reader.readAsDataURL(file); | |||
} | |||
}; | |||
$[pluginName] = function(file, options) { | |||
if (typeof file === 'string') | |||
return methods[file](options); | |||
else | |||
new Plugin(file, options); | |||
}; | |||
})(jQuery); |
@@ -0,0 +1,957 @@ | |||
/* | |||
* Javascript EXIF Reader - jQuery plugin 0.1.3 | |||
* Copyright (c) 2008 Jacob Seidelin, cupboy@gmail.com, http://blog.nihilogic.dk/ | |||
* Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt] | |||
*/ | |||
/* | |||
* I added three functions for read EXIF from dataURL | |||
* - getImageDataFromDataURL | |||
* - getDataFromDataURL | |||
* - jQuery.fn.exifLoadFromDataURL | |||
* | |||
* http://orientation.gokercebeci.com | |||
* @gokercebeci | |||
*/ | |||
(function() { | |||
var BinaryFile = function(strData, iDataOffset, iDataLength) { | |||
var data = strData; | |||
var dataOffset = iDataOffset || 0; | |||
var dataLength = 0; | |||
this.getRawData = function() { | |||
return data; | |||
} | |||
if (typeof strData == "string") { | |||
dataLength = iDataLength || data.length; | |||
this.getByteAt = function(iOffset) { | |||
return data.charCodeAt(iOffset + dataOffset) & 0xFF; | |||
} | |||
} else if (typeof strData == "unknown") { | |||
dataLength = iDataLength || IEBinary_getLength(data); | |||
this.getByteAt = function(iOffset) { | |||
return IEBinary_getByteAt(data, iOffset + dataOffset); | |||
} | |||
} | |||
this.getLength = function() { | |||
return dataLength; | |||
} | |||
this.getSByteAt = function(iOffset) { | |||
var iByte = this.getByteAt(iOffset); | |||
if (iByte > 127) | |||
return iByte - 256; | |||
else | |||
return iByte; | |||
} | |||
this.getShortAt = function(iOffset, bBigEndian) { | |||
var iShort = bBigEndian ? | |||
(this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1) | |||
: (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset) | |||
if (iShort < 0) | |||
iShort += 65536; | |||
return iShort; | |||
} | |||
this.getSShortAt = function(iOffset, bBigEndian) { | |||
var iUShort = this.getShortAt(iOffset, bBigEndian); | |||
if (iUShort > 32767) | |||
return iUShort - 65536; | |||
else | |||
return iUShort; | |||
} | |||
this.getLongAt = function(iOffset, bBigEndian) { | |||
var iByte1 = this.getByteAt(iOffset), | |||
iByte2 = this.getByteAt(iOffset + 1), | |||
iByte3 = this.getByteAt(iOffset + 2), | |||
iByte4 = this.getByteAt(iOffset + 3); | |||
var iLong = bBigEndian ? | |||
(((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4 | |||
: (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1; | |||
if (iLong < 0) | |||
iLong += 4294967296; | |||
return iLong; | |||
} | |||
this.getSLongAt = function(iOffset, bBigEndian) { | |||
var iULong = this.getLongAt(iOffset, bBigEndian); | |||
if (iULong > 2147483647) | |||
return iULong - 4294967296; | |||
else | |||
return iULong; | |||
} | |||
this.getStringAt = function(iOffset, iLength) { | |||
var aStr = []; | |||
for (var i = iOffset, j = 0; i < iOffset + iLength; i++, j++) { | |||
aStr[j] = String.fromCharCode(this.getByteAt(i)); | |||
} | |||
return aStr.join(""); | |||
} | |||
this.getCharAt = function(iOffset) { | |||
return String.fromCharCode(this.getByteAt(iOffset)); | |||
} | |||
this.toBase64 = function() { | |||
return window.btoa(data); | |||
} | |||
this.fromBase64 = function(strBase64) { | |||
data = window.atob(strBase64); | |||
} | |||
} | |||
var BinaryAjax = (function() { | |||
function createRequest() { | |||
var oHTTP = null; | |||
if (window.XMLHttpRequest) { | |||
oHTTP = new XMLHttpRequest(); | |||
} else if (window.ActiveXObject) { | |||
oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); | |||
} | |||
return oHTTP; | |||
} | |||
function getHead(strURL, fncCallback, fncError) { | |||
var oHTTP = createRequest(); | |||
if (oHTTP) { | |||
if (fncCallback) { | |||
if (typeof(oHTTP.onload) != "undefined") { | |||
oHTTP.onload = function() { | |||
if (oHTTP.status == "200") { | |||
fncCallback(this); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
oHTTP = null; | |||
}; | |||
} else { | |||
oHTTP.onreadystatechange = function() { | |||
if (oHTTP.readyState == 4) { | |||
if (oHTTP.status == "200") { | |||
fncCallback(this); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
oHTTP = null; | |||
} | |||
}; | |||
} | |||
} | |||
oHTTP.open("HEAD", strURL, true); | |||
oHTTP.send(null); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
} | |||
function sendRequest(strURL, fncCallback, fncError, aRange, bAcceptRanges, iFileSize) { | |||
var oHTTP = createRequest(); | |||
if (oHTTP) { | |||
var iDataOffset = 0; | |||
if (aRange && !bAcceptRanges) { | |||
iDataOffset = aRange[0]; | |||
} | |||
var iDataLen = 0; | |||
if (aRange) { | |||
iDataLen = aRange[1] - aRange[0] + 1; | |||
} | |||
if (fncCallback) { | |||
if (typeof(oHTTP.onload) != "undefined") { | |||
oHTTP.onload = function() { | |||
if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") { | |||
this.binaryResponse = new BinaryFile(this.responseText, iDataOffset, iDataLen); | |||
this.fileSize = iFileSize || this.getResponseHeader("Content-Length"); | |||
fncCallback(this); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
oHTTP = null; | |||
}; | |||
} else { | |||
oHTTP.onreadystatechange = function() { | |||
if (oHTTP.readyState == 4) { | |||
if (oHTTP.status == "200" || oHTTP.status == "206" || oHTTP.status == "0") { | |||
this.binaryResponse = new BinaryFile(oHTTP.responseBody, iDataOffset, iDataLen); | |||
this.fileSize = iFileSize || this.getResponseHeader("Content-Length"); | |||
fncCallback(this); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
oHTTP = null; | |||
} | |||
}; | |||
} | |||
} | |||
oHTTP.open("GET", strURL, true); | |||
if (oHTTP.overrideMimeType) | |||
oHTTP.overrideMimeType('text/plain; charset=x-user-defined'); | |||
if (aRange && bAcceptRanges) { | |||
oHTTP.setRequestHeader("Range", "bytes=" + aRange[0] + "-" + aRange[1]); | |||
} | |||
oHTTP.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 1970 00:00:00 GMT"); | |||
oHTTP.send(null); | |||
} else { | |||
if (fncError) | |||
fncError(); | |||
} | |||
} | |||
return function(strURL, fncCallback, fncError, aRange) { | |||
if (aRange) { | |||
getHead( | |||
strURL, | |||
function(oHTTP) { | |||
var iLength = parseInt(oHTTP.getResponseHeader("Content-Length"), 10); | |||
var strAcceptRanges = oHTTP.getResponseHeader("Accept-Ranges"); | |||
var iStart, iEnd; | |||
iStart = aRange[0]; | |||
if (aRange[0] < 0) | |||
iStart += iLength; | |||
iEnd = iStart + aRange[1] - 1; | |||
sendRequest(strURL, fncCallback, fncError, [iStart, iEnd], (strAcceptRanges == "bytes"), iLength); | |||
} | |||
); | |||
} else { | |||
sendRequest(strURL, fncCallback, fncError); | |||
} | |||
} | |||
}()); | |||
var script = document.createElement("script"); | |||
script.type = 'text/vbscript'; | |||
script.innerHTML = | |||
"Function IEBinary_getByteAt(strBinary, iOffset)\r\n" | |||
+ " IEBinary_getByteAt = AscB(MidB(strBinary,iOffset+1,1))\r\n" | |||
+ "End Function\r\n" | |||
+ "Function IEBinary_getLength(strBinary)\r\n" | |||
+ " IEBinary_getLength = LenB(strBinary)\r\n" | |||
+ "End Function"; | |||
document.head.appendChild(script); | |||
var EXIF = {}; | |||
(function() { | |||
var bDebug = false; | |||
EXIF.Tags = { | |||
// version tags | |||
0x9000: "ExifVersion", // EXIF version | |||
0xA000: "FlashpixVersion", // Flashpix format version | |||
// colorspace tags | |||
0xA001: "ColorSpace", // Color space information tag | |||
// image configuration | |||
0xA002: "PixelXDimension", // Valid width of meaningful image | |||
0xA003: "PixelYDimension", // Valid height of meaningful image | |||
0x9101: "ComponentsConfiguration", // Information about channels | |||
0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel | |||
// user information | |||
0x927C: "MakerNote", // Any desired information written by the manufacturer | |||
0x9286: "UserComment", // Comments by user | |||
// related file | |||
0xA004: "RelatedSoundFile", // Name of related sound file | |||
// date and time | |||
0x9003: "DateTimeOriginal", // Date and time when the original image was generated | |||
0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally | |||
0x9290: "SubsecTime", // Fractions of seconds for DateTime | |||
0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal | |||
0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized | |||
// picture-taking conditions | |||
0x829A: "ExposureTime", // Exposure time (in seconds) | |||
0x829D: "FNumber", // F number | |||
0x8822: "ExposureProgram", // Exposure program | |||
0x8824: "SpectralSensitivity", // Spectral sensitivity | |||
0x8827: "ISOSpeedRatings", // ISO speed rating | |||
0x8828: "OECF", // Optoelectric conversion factor | |||
0x9201: "ShutterSpeedValue", // Shutter speed | |||
0x9202: "ApertureValue", // Lens aperture | |||
0x9203: "BrightnessValue", // Value of brightness | |||
0x9204: "ExposureBias", // Exposure bias | |||
0x9205: "MaxApertureValue", // Smallest F number of lens | |||
0x9206: "SubjectDistance", // Distance to subject in meters | |||
0x9207: "MeteringMode", // Metering mode | |||
0x9208: "LightSource", // Kind of light source | |||
0x9209: "Flash", // Flash status | |||
0x9214: "SubjectArea", // Location and area of main subject | |||
0x920A: "FocalLength", // Focal length of the lens in mm | |||
0xA20B: "FlashEnergy", // Strobe energy in BCPS | |||
0xA20C: "SpatialFrequencyResponse", // | |||
0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit | |||
0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit | |||
0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution | |||
0xA214: "SubjectLocation", // Location of subject in image | |||
0xA215: "ExposureIndex", // Exposure index selected on camera | |||
0xA217: "SensingMethod", // Image sensor type | |||
0xA300: "FileSource", // Image source (3 == DSC) | |||
0xA301: "SceneType", // Scene type (1 == directly photographed) | |||
0xA302: "CFAPattern", // Color filter array geometric pattern | |||
0xA401: "CustomRendered", // Special processing | |||
0xA402: "ExposureMode", // Exposure mode | |||
0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual | |||
0xA404: "DigitalZoomRation", // Digital zoom ratio | |||
0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm) | |||
0xA406: "SceneCaptureType", // Type of scene | |||
0xA407: "GainControl", // Degree of overall image gain adjustment | |||
0xA408: "Contrast", // Direction of contrast processing applied by camera | |||
0xA409: "Saturation", // Direction of saturation processing applied by camera | |||
0xA40A: "Sharpness", // Direction of sharpness processing applied by camera | |||
0xA40B: "DeviceSettingDescription", // | |||
0xA40C: "SubjectDistanceRange", // Distance to subject | |||
// other tags | |||
0xA005: "InteroperabilityIFDPointer", | |||
0xA420: "ImageUniqueID" // Identifier assigned uniquely to each image | |||
}; | |||
EXIF.TiffTags = { | |||
0x0100: "ImageWidth", | |||
0x0101: "ImageHeight", | |||
0x8769: "ExifIFDPointer", | |||
0x8825: "GPSInfoIFDPointer", | |||
0xA005: "InteroperabilityIFDPointer", | |||
0x0102: "BitsPerSample", | |||
0x0103: "Compression", | |||
0x0106: "PhotometricInterpretation", | |||
0x0112: "Orientation", | |||
0x0115: "SamplesPerPixel", | |||
0x011C: "PlanarConfiguration", | |||
0x0212: "YCbCrSubSampling", | |||
0x0213: "YCbCrPositioning", | |||
0x011A: "XResolution", | |||
0x011B: "YResolution", | |||
0x0128: "ResolutionUnit", | |||
0x0111: "StripOffsets", | |||
0x0116: "RowsPerStrip", | |||
0x0117: "StripByteCounts", | |||
0x0201: "JPEGInterchangeFormat", | |||
0x0202: "JPEGInterchangeFormatLength", | |||
0x012D: "TransferFunction", | |||
0x013E: "WhitePoint", | |||
0x013F: "PrimaryChromaticities", | |||
0x0211: "YCbCrCoefficients", | |||
0x0214: "ReferenceBlackWhite", | |||
0x0132: "DateTime", | |||
0x010E: "ImageDescription", | |||
0x010F: "Make", | |||
0x0110: "Model", | |||
0x0131: "Software", | |||
0x013B: "Artist", | |||
0x8298: "Copyright" | |||
} | |||
EXIF.GPSTags = { | |||
0x0000: "GPSVersionID", | |||
0x0001: "GPSLatitudeRef", | |||
0x0002: "GPSLatitude", | |||
0x0003: "GPSLongitudeRef", | |||
0x0004: "GPSLongitude", | |||
0x0005: "GPSAltitudeRef", | |||
0x0006: "GPSAltitude", | |||
0x0007: "GPSTimeStamp", | |||
0x0008: "GPSSatellites", | |||
0x0009: "GPSStatus", | |||
0x000A: "GPSMeasureMode", | |||
0x000B: "GPSDOP", | |||
0x000C: "GPSSpeedRef", | |||
0x000D: "GPSSpeed", | |||
0x000E: "GPSTrackRef", | |||
0x000F: "GPSTrack", | |||
0x0010: "GPSImgDirectionRef", | |||
0x0011: "GPSImgDirection", | |||
0x0012: "GPSMapDatum", | |||
0x0013: "GPSDestLatitudeRef", | |||
0x0014: "GPSDestLatitude", | |||
0x0015: "GPSDestLongitudeRef", | |||
0x0016: "GPSDestLongitude", | |||
0x0017: "GPSDestBearingRef", | |||
0x0018: "GPSDestBearing", | |||
0x0019: "GPSDestDistanceRef", | |||
0x001A: "GPSDestDistance", | |||
0x001B: "GPSProcessingMethod", | |||
0x001C: "GPSAreaInformation", | |||
0x001D: "GPSDateStamp", | |||
0x001E: "GPSDifferential" | |||
} | |||
EXIF.StringValues = { | |||
ExposureProgram: { | |||
0: "Not defined", | |||
1: "Manual", | |||
2: "Normal program", | |||
3: "Aperture priority", | |||
4: "Shutter priority", | |||
5: "Creative program", | |||
6: "Action program", | |||
7: "Portrait mode", | |||
8: "Landscape mode" | |||
}, | |||
MeteringMode: { | |||
0: "Unknown", | |||
1: "Average", | |||
2: "CenterWeightedAverage", | |||
3: "Spot", | |||
4: "MultiSpot", | |||
5: "Pattern", | |||
6: "Partial", | |||
255: "Other" | |||
}, | |||
LightSource: { | |||
0: "Unknown", | |||
1: "Daylight", | |||
2: "Fluorescent", | |||
3: "Tungsten (incandescent light)", | |||
4: "Flash", | |||
9: "Fine weather", | |||
10: "Cloudy weather", | |||
11: "Shade", | |||
12: "Daylight fluorescent (D 5700 - 7100K)", | |||
13: "Day white fluorescent (N 4600 - 5400K)", | |||
14: "Cool white fluorescent (W 3900 - 4500K)", | |||
15: "White fluorescent (WW 3200 - 3700K)", | |||
17: "Standard light A", | |||
18: "Standard light B", | |||
19: "Standard light C", | |||
20: "D55", | |||
21: "D65", | |||
22: "D75", | |||
23: "D50", | |||
24: "ISO studio tungsten", | |||
255: "Other" | |||
}, | |||
Flash: { | |||
0x0000: "Flash did not fire", | |||
0x0001: "Flash fired", | |||
0x0005: "Strobe return light not detected", | |||
0x0007: "Strobe return light detected", | |||
0x0009: "Flash fired, compulsory flash mode", | |||
0x000D: "Flash fired, compulsory flash mode, return light not detected", | |||
0x000F: "Flash fired, compulsory flash mode, return light detected", | |||
0x0010: "Flash did not fire, compulsory flash mode", | |||
0x0018: "Flash did not fire, auto mode", | |||
0x0019: "Flash fired, auto mode", | |||
0x001D: "Flash fired, auto mode, return light not detected", | |||
0x001F: "Flash fired, auto mode, return light detected", | |||
0x0020: "No flash function", | |||
0x0041: "Flash fired, red-eye reduction mode", | |||
0x0045: "Flash fired, red-eye reduction mode, return light not detected", | |||
0x0047: "Flash fired, red-eye reduction mode, return light detected", | |||
0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode", | |||
0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected", | |||
0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected", | |||
0x0059: "Flash fired, auto mode, red-eye reduction mode", | |||
0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode", | |||
0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode" | |||
}, | |||
SensingMethod: { | |||
1: "Not defined", | |||
2: "One-chip color area sensor", | |||
3: "Two-chip color area sensor", | |||
4: "Three-chip color area sensor", | |||
5: "Color sequential area sensor", | |||
7: "Trilinear sensor", | |||
8: "Color sequential linear sensor" | |||
}, | |||
SceneCaptureType: { | |||
0: "Standard", | |||
1: "Landscape", | |||
2: "Portrait", | |||
3: "Night scene" | |||
}, | |||
SceneType: { | |||
1: "Directly photographed" | |||
}, | |||
CustomRendered: { | |||
0: "Normal process", | |||
1: "Custom process" | |||
}, | |||
WhiteBalance: { | |||
0: "Auto white balance", | |||
1: "Manual white balance" | |||
}, | |||
GainControl: { | |||
0: "None", | |||
1: "Low gain up", | |||
2: "High gain up", | |||
3: "Low gain down", | |||
4: "High gain down" | |||
}, | |||
Contrast: { | |||
0: "Normal", | |||
1: "Soft", | |||
2: "Hard" | |||
}, | |||
Saturation: { | |||
0: "Normal", | |||
1: "Low saturation", | |||
2: "High saturation" | |||
}, | |||
Sharpness: { | |||
0: "Normal", | |||
1: "Soft", | |||
2: "Hard" | |||
}, | |||
SubjectDistanceRange: { | |||
0: "Unknown", | |||
1: "Macro", | |||
2: "Close view", | |||
3: "Distant view" | |||
}, | |||
FileSource: { | |||
3: "DSC" | |||
}, | |||
Components: { | |||
0: "", | |||
1: "Y", | |||
2: "Cb", | |||
3: "Cr", | |||
4: "R", | |||
5: "G", | |||
6: "B" | |||
} | |||
} | |||
function addEvent(oElement, strEvent, fncHandler) | |||
{ | |||
if (oElement.addEventListener) { | |||
oElement.addEventListener(strEvent, fncHandler, false); | |||
} else if (oElement.attachEvent) { | |||
oElement.attachEvent("on" + strEvent, fncHandler); | |||
} | |||
} | |||
function imageHasData(oImg) | |||
{ | |||
return !!(oImg.exifdata); | |||
} | |||
function getImageData(oImg, fncCallback) | |||
{ | |||
BinaryAjax( | |||
oImg.src, | |||
function(oHTTP) { | |||
console.log('BINARY', oHTTP.binaryResponse); | |||
var oEXIF = findEXIFinJPEG(oHTTP.binaryResponse); | |||
oImg.exifdata = oEXIF || {}; | |||
if (fncCallback) | |||
fncCallback(); | |||
} | |||
) | |||
} | |||
function getImageDataFromDataURL(oImg, fncCallback) | |||
{ | |||
var byteString = atob(oImg.src.split(',')[1]); | |||
var f = new BinaryFile(byteString, 0, byteString.length) | |||
var oEXIF = findEXIFinJPEG(f); | |||
oImg.exifdata = oEXIF || {}; | |||
if (fncCallback) | |||
fncCallback(); | |||
} | |||
function findEXIFinJPEG(oFile) { | |||
var aMarkers = []; | |||
if (oFile.getByteAt(0) != 0xFF || oFile.getByteAt(1) != 0xD8) { | |||
return false; // not a valid jpeg | |||
} | |||
var iOffset = 2; | |||
var iLength = oFile.getLength(); | |||
while (iOffset < iLength) { | |||
if (oFile.getByteAt(iOffset) != 0xFF) { | |||
if (bDebug) | |||
console.log("Not a valid marker at offset " + iOffset + ", found: " + oFile.getByteAt(iOffset)); | |||
return false; // not a valid marker, something is wrong | |||
} | |||
var iMarker = oFile.getByteAt(iOffset + 1); | |||
// we could implement handling for other markers here, | |||
// but we're only looking for 0xFFE1 for EXIF data | |||
if (iMarker == 22400) { | |||
if (bDebug) | |||
console.log("Found 0xFFE1 marker"); | |||
return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2); | |||
iOffset += 2 + oFile.getShortAt(iOffset + 2, true); | |||
} else if (iMarker == 225) { | |||
// 0xE1 = Application-specific 1 (for EXIF) | |||
if (bDebug) | |||
console.log("Found 0xFFE1 marker"); | |||
return readEXIFData(oFile, iOffset + 4, oFile.getShortAt(iOffset + 2, true) - 2); | |||
} else { | |||
iOffset += 2 + oFile.getShortAt(iOffset + 2, true); | |||
} | |||
} | |||
} | |||
function readTags(oFile, iTIFFStart, iDirStart, oStrings, bBigEnd) | |||
{ | |||
var iEntries = oFile.getShortAt(iDirStart, bBigEnd); | |||
var oTags = {}; | |||
for (var i = 0; i < iEntries; i++) { | |||
var iEntryOffset = iDirStart + i * 12 + 2; | |||
var strTag = oStrings[oFile.getShortAt(iEntryOffset, bBigEnd)]; | |||
if (!strTag && bDebug) | |||
console.log("Unknown tag: " + oFile.getShortAt(iEntryOffset, bBigEnd)); | |||
oTags[strTag] = readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd); | |||
} | |||
return oTags; | |||
} | |||
function readTagValue(oFile, iEntryOffset, iTIFFStart, iDirStart, bBigEnd) | |||
{ | |||
var iType = oFile.getShortAt(iEntryOffset + 2, bBigEnd); | |||
var iNumValues = oFile.getLongAt(iEntryOffset + 4, bBigEnd); | |||
var iValueOffset = oFile.getLongAt(iEntryOffset + 8, bBigEnd) + iTIFFStart; | |||
switch (iType) { | |||
case 1: // byte, 8-bit unsigned int | |||
case 7: // undefined, 8-bit byte, value depending on field | |||
if (iNumValues == 1) { | |||
return oFile.getByteAt(iEntryOffset + 8, bBigEnd); | |||
} else { | |||
var iValOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8); | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getByteAt(iValOffset + n); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
case 2: // ascii, 8-bit byte | |||
var iStringOffset = iNumValues > 4 ? iValueOffset : (iEntryOffset + 8); | |||
return oFile.getStringAt(iStringOffset, iNumValues - 1); | |||
break; | |||
case 3: // short, 16 bit int | |||
if (iNumValues == 1) { | |||
return oFile.getShortAt(iEntryOffset + 8, bBigEnd); | |||
} else { | |||
var iValOffset = iNumValues > 2 ? iValueOffset : (iEntryOffset + 8); | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getShortAt(iValOffset + 2 * n, bBigEnd); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
case 4: // long, 32 bit int | |||
if (iNumValues == 1) { | |||
return oFile.getLongAt(iEntryOffset + 8, bBigEnd); | |||
} else { | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getLongAt(iValueOffset + 4 * n, bBigEnd); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
case 5: // rational = two long values, first is numerator, second is denominator | |||
if (iNumValues == 1) { | |||
return oFile.getLongAt(iValueOffset, bBigEnd) / oFile.getLongAt(iValueOffset + 4, bBigEnd); | |||
} else { | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getLongAt(iValueOffset + 4 + 8 * n, bBigEnd); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
case 9: // slong, 32 bit signed int | |||
if (iNumValues == 1) { | |||
return oFile.getSLongAt(iEntryOffset + 8, bBigEnd); | |||
} else { | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getSLongAt(iValueOffset + 4 * n, bBigEnd); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
case 10: // signed rational, two slongs, first is numerator, second is denominator | |||
if (iNumValues == 1) { | |||
return oFile.getSLongAt(iValueOffset, bBigEnd) / oFile.getSLongAt(iValueOffset + 4, bBigEnd); | |||
} else { | |||
var aVals = []; | |||
for (var n = 0; n < iNumValues; n++) { | |||
aVals[n] = oFile.getSLongAt(iValueOffset + 8 * n, bBigEnd) / oFile.getSLongAt(iValueOffset + 4 + 8 * n, bBigEnd); | |||
} | |||
return aVals; | |||
} | |||
break; | |||
} | |||
} | |||
function readEXIFData(oFile, iStart, iLength) | |||
{ | |||
if (oFile.getStringAt(iStart, 4) != "Exif") { | |||
if (bDebug) | |||
console.log("Not valid EXIF data! " + oFile.getStringAt(iStart, 4)); | |||
return false; | |||
} | |||
var bBigEnd; | |||
var iTIFFOffset = iStart + 6; | |||
// test for TIFF validity and endianness | |||
if (oFile.getShortAt(iTIFFOffset) == 0x4949) { | |||
bBigEnd = false; | |||
} else if (oFile.getShortAt(iTIFFOffset) == 0x4D4D) { | |||
bBigEnd = true; | |||
} else { | |||
if (bDebug) | |||
console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"); | |||
return false; | |||
} | |||
if (oFile.getShortAt(iTIFFOffset + 2, bBigEnd) != 0x002A) { | |||
if (bDebug) | |||
console.log("Not valid TIFF data! (no 0x002A)"); | |||
return false; | |||
} | |||
if (oFile.getLongAt(iTIFFOffset + 4, bBigEnd) != 0x00000008) { | |||
if (bDebug) | |||
console.log("Not valid TIFF data! (First offset not 8)", oFile.getShortAt(iTIFFOffset + 4, bBigEnd)); | |||
return false; | |||
} | |||
var oTags = readTags(oFile, iTIFFOffset, iTIFFOffset + 8, EXIF.TiffTags, bBigEnd); | |||
if (oTags.ExifIFDPointer) { | |||
var oEXIFTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.ExifIFDPointer, EXIF.Tags, bBigEnd); | |||
for (var strTag in oEXIFTags) { | |||
switch (strTag) { | |||
case "LightSource" : | |||
case "Flash" : | |||
case "MeteringMode" : | |||
case "ExposureProgram" : | |||
case "SensingMethod" : | |||
case "SceneCaptureType" : | |||
case "SceneType" : | |||
case "CustomRendered" : | |||
case "WhiteBalance" : | |||
case "GainControl" : | |||
case "Contrast" : | |||
case "Saturation" : | |||
case "Sharpness" : | |||
case "SubjectDistanceRange" : | |||
case "FileSource" : | |||
oEXIFTags[strTag] = EXIF.StringValues[strTag][oEXIFTags[strTag]]; | |||
break; | |||
case "ExifVersion" : | |||
case "FlashpixVersion" : | |||
oEXIFTags[strTag] = String.fromCharCode(oEXIFTags[strTag][0], oEXIFTags[strTag][1], oEXIFTags[strTag][2], oEXIFTags[strTag][3]); | |||
break; | |||
case "ComponentsConfiguration" : | |||
oEXIFTags[strTag] = | |||
EXIF.StringValues.Components[oEXIFTags[strTag][0]] | |||
+ EXIF.StringValues.Components[oEXIFTags[strTag][1]] | |||
+ EXIF.StringValues.Components[oEXIFTags[strTag][2]] | |||
+ EXIF.StringValues.Components[oEXIFTags[strTag][3]]; | |||
break; | |||
} | |||
oTags[strTag] = oEXIFTags[strTag]; | |||
} | |||
} | |||
if (oTags.GPSInfoIFDPointer) { | |||
var oGPSTags = readTags(oFile, iTIFFOffset, iTIFFOffset + oTags.GPSInfoIFDPointer, EXIF.GPSTags, bBigEnd); | |||
for (var strTag in oGPSTags) { | |||
switch (strTag) { | |||
case "GPSVersionID" : | |||
oGPSTags[strTag] = oGPSTags[strTag][0] | |||
+ "." + oGPSTags[strTag][1] | |||
+ "." + oGPSTags[strTag][2] | |||
+ "." + oGPSTags[strTag][3]; | |||
break; | |||
} | |||
oTags[strTag] = oGPSTags[strTag]; | |||
} | |||
} | |||
return oTags; | |||
} | |||
EXIF.getData = function(oImg, fncCallback) | |||
{ | |||
if (!oImg.complete) | |||
return false; | |||
if (!imageHasData(oImg)) { | |||
getImageData(oImg, fncCallback); | |||
} else { | |||
if (fncCallback) | |||
fncCallback(); | |||
} | |||
return true; | |||
} | |||
EXIF.getDataFromDataURL = function(oImg, fncCallback) | |||
{ | |||
if (!oImg.complete) | |||
return false; | |||
if (!imageHasData(oImg)) { | |||
getImageDataFromDataURL(oImg, fncCallback); | |||
} else { | |||
if (fncCallback) | |||
fncCallback(); | |||
} | |||
return true; | |||
} | |||
EXIF.getTag = function(oImg, strTag) | |||
{ | |||
if (!imageHasData(oImg)) | |||
return; | |||
return oImg.exifdata[strTag]; | |||
} | |||
EXIF.getAllTags = function(oImg) | |||
{ | |||
if (!imageHasData(oImg)) | |||
return {}; | |||
var oData = oImg.exifdata; | |||
var oAllTags = {}; | |||
for (var a in oData) { | |||
if (oData.hasOwnProperty(a)) { | |||
oAllTags[a] = oData[a]; | |||
} | |||
} | |||
return oAllTags; | |||
} | |||
EXIF.pretty = function(oImg) | |||
{ | |||
if (!imageHasData(oImg)) | |||
return ""; | |||
var oData = oImg.exifdata; | |||
var strPretty = ""; | |||
for (var a in oData) { | |||
if (oData.hasOwnProperty(a)) { | |||
if (typeof oData[a] == "object") { | |||
strPretty += a + " : [" + oData[a].length + " values]\r\n"; | |||
} else { | |||
strPretty += a + " : " + oData[a] + "\r\n"; | |||
} | |||
} | |||
} | |||
return strPretty; | |||
} | |||
EXIF.readFromBinaryFile = function(oFile) { | |||
return findEXIFinJPEG(oFile); | |||
} | |||
function loadAllImages() | |||
{ | |||
var aImages = document.getElementsByTagName("img"); | |||
for (var i = 0; i < aImages.length; i++) { | |||
if (aImages[i].getAttribute("exif") == "true") { | |||
if (!aImages[i].complete) { | |||
addEvent(aImages[i], "load", | |||
function() { | |||
EXIF.getData(this); | |||
} | |||
); | |||
} else { | |||
EXIF.getData(aImages[i]); | |||
} | |||
} | |||
} | |||
} | |||
// automatically load exif data for all images with exif=true when doc is ready | |||
jQuery(document).ready(loadAllImages); | |||
// load data for images manually | |||
jQuery.fn.exifLoad = function(fncCallback) { | |||
return this.each(function() { | |||
EXIF.getData(this, fncCallback) | |||
}); | |||
} | |||
// load data for images manually | |||
jQuery.fn.exifLoadFromDataURL = function(fncCallback) { | |||
return this.each(function() { | |||
EXIF.getDataFromDataURL(this, fncCallback) | |||
return true; | |||
}); | |||
} | |||
jQuery.fn.exif = function(strTag) { | |||
var aStrings = []; | |||
this.each(function() { | |||
aStrings.push(EXIF.getTag(this, strTag)); | |||
}); | |||
return aStrings; | |||
} | |||
jQuery.fn.exifAll = function() { | |||
var aStrings = []; | |||
this.each(function() { | |||
aStrings.push(EXIF.getAllTags(this)); | |||
}); | |||
return aStrings; | |||
} | |||
jQuery.fn.exifPretty = function() { | |||
var aStrings = []; | |||
this.each(function() { | |||
aStrings.push(EXIF.pretty(this)); | |||
}); | |||
return aStrings; | |||
} | |||
})(); | |||
})(); |
@@ -1,20 +1,9 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
if(!window.wn) wn = {}; | |||
// MIT License. See license.txt | |||
wn.provide("website"); | |||
$.extend(wn, { | |||
provide: function(namespace) { | |||
var nsl = namespace.split('.'); | |||
var parent = window; | |||
for(var i=0; i<nsl.length; i++) { | |||
var n = nsl[i]; | |||
if(!parent[n]) { | |||
parent[n] = {} | |||
} | |||
parent = parent[n]; | |||
} | |||
return parent; | |||
}, | |||
_assets_loaded: [], | |||
require: function(url) { | |||
if(wn._assets_loaded.indexOf(url)!==-1) return; | |||
@@ -41,17 +30,28 @@ $.extend(wn, { | |||
// opts = {"method": "PYTHON MODULE STRING", "args": {}, "callback": function(r) {}} | |||
wn.prepare_call(opts); | |||
return $.ajax({ | |||
type: "POST", | |||
type: opts.type || "POST", | |||
url: "/", | |||
data: opts.args, | |||
dataType: "json", | |||
success: function(data) { | |||
wn.process_response(opts, data); | |||
}, | |||
error: function(response) { | |||
if(!opts.no_spinner) NProgress.done(); | |||
console.error ? console.error(response) : console.log(response); | |||
statusCode: { | |||
404: function(xhr) { | |||
msgprint("Not Found"); | |||
}, | |||
403: function(xhr) { | |||
msgprint("Not Permitted"); | |||
}, | |||
200: function(data, xhr) { | |||
if(opts.callback) | |||
opts.callback(data); | |||
} | |||
} | |||
}).always(function(data) { | |||
// executed before statusCode functions | |||
if(data.responseText) { | |||
data = JSON.parse(data.responseText); | |||
} | |||
wn.process_response(opts, data); | |||
}); | |||
}, | |||
prepare_call: function(opts) { | |||
@@ -87,10 +87,12 @@ $.extend(wn, { | |||
} | |||
}, | |||
process_response: function(opts, data) { | |||
NProgress.done(); | |||
if(!opts.no_spinner) NProgress.done(); | |||
if(opts.btn) { | |||
$(opts.btn).prop("disabled", false); | |||
} | |||
if(data.exc) { | |||
if(opts.btn) { | |||
$(opts.btn).addClass("btn-danger"); | |||
@@ -114,8 +116,6 @@ $.extend(wn, { | |||
if(opts.msg && data.message) { | |||
$(opts.msg).html(data.message).toggle(true); | |||
} | |||
if(opts.callback) | |||
opts.callback(data); | |||
}, | |||
show_message: function(text, icon) { | |||
if(!icon) icon="icon-refresh icon-spin"; | |||
@@ -175,15 +175,25 @@ $.extend(wn, { | |||
} | |||
} | |||
}); | |||
}, | |||
render_user: function() { | |||
var sid = wn.get_cookie("sid"); | |||
if(sid && sid!=="Guest") { | |||
$(".btn-login-area").toggle(false); | |||
$(".logged-in").toggle(true); | |||
$(".full-name").html(wn.get_cookie("full_name")); | |||
$(".user-picture").attr("src", wn.get_cookie("user_image")); | |||
} | |||
} | |||
}); | |||
// Utility functions | |||
function valid_email(id) { | |||
if(id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1) | |||
return 0; else return 1; } | |||
function valid_email(id) { | |||
return (id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1) ? 0 : 1; | |||
} | |||
var validate_email = valid_email; | |||
@@ -305,11 +315,20 @@ $(document).ready(function() { | |||
<li><a href="app.html"><i class="icon-fixed-width icon-th-large"></i> Switch To App</a></li>'); | |||
} | |||
wn.render_user(); | |||
$(document).trigger("page_change"); | |||
}); | |||
$(document).on("page_change", function() { | |||
$(".page-header").toggleClass("hidden", !!!$(".page-header").text().trim()); | |||
$(".page-footer").toggleClass("hidden", !!!$(".page-footer").text().trim()); | |||
}); | |||
// add prive pages to sidebar | |||
if(website.private_pages && $(".page-sidebar").length) { | |||
$(data.private_pages).prependTo(".page-sidebar"); | |||
} | |||
$(document).trigger("apply_permissions"); | |||
wn.datetime.refresh_when(); | |||
}); |
@@ -0,0 +1,529 @@ | |||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
wn.provide("website"); | |||
$.extend(website, { | |||
toggle_permitted: function() { | |||
if(website.access) { | |||
// hide certain views | |||
$('li[data-view="add"]').toggleClass("hide", !website.access.write); | |||
$('li[data-view="settings"]').toggleClass("hide", !website.access.admin); | |||
$('li[data-view="edit"]').toggleClass("hide", website.view!=="edit"); | |||
// $('li[data-view="settings"]').toggleClass("hide", !website.access.admin); | |||
// show message | |||
$(".post-list-help").html(!website.access.write ? "You do not have permission to post" : ""); | |||
} | |||
}, | |||
setup_pagination: function($btn, opts) { | |||
$btn.removeClass("hide"); | |||
$btn.on("click", function() { | |||
wn.call($.extend({ | |||
btn: $btn, | |||
type: "GET", | |||
callback: function(data) { | |||
if(opts.prepend) { | |||
opts.$wrapper.prepend(data.message); | |||
} else { | |||
opts.$wrapper.append(data.message); | |||
} | |||
$btn.toggleClass("hide", !(data.message && data.message.length===opts.args.limit_length)); | |||
} | |||
}, opts)) | |||
}); | |||
}, | |||
bind_add_post: function() { | |||
$(".btn-post-add").on("click", website.add_post); | |||
$pic_input = $(".control-post-add-picture").on("change", website.add_picture); | |||
$(".btn-post-add-picture").on("click", function() { | |||
$pic_input.click(); | |||
}); | |||
}, | |||
add_post: function() { | |||
if(website.post) { | |||
wn.msgprint("Post already exists. Cannot add again!"); | |||
return; | |||
} | |||
website._update_post(this, "webnotes.website.doctype.post.post.add_post"); | |||
}, | |||
bind_save_post: function() { | |||
$(".btn-post-add").addClass("hide"); | |||
$(".btn-post-save").removeClass("hide").on("click", website.save_post); | |||
$(".post-picture").toggleClass("hide", !$(".post-picture").attr("src")); | |||
}, | |||
save_post: function() { | |||
if(!website.post) { | |||
wn.msgprint("Post does not exist. Please add post!"); | |||
return; | |||
} | |||
website._update_post(this, "webnotes.website.doctype.post.post.save_post"); | |||
}, | |||
_update_post: function(btn, cmd) { | |||
var values = website.get_editor_values(); | |||
if(!values) { | |||
return; | |||
} | |||
wn.call({ | |||
btn: btn, | |||
type: "POST", | |||
args: $.extend({ | |||
cmd: cmd, | |||
group: website.group, | |||
post: website.post || undefined | |||
}, values), | |||
callback: function(data) { | |||
var url = "/" + website.group + "?view=post&name=" + data.message; | |||
window.location.href = url; | |||
// if(history.pushState) { | |||
// app.get_content(url); | |||
// } else { | |||
// window.location.href = url; | |||
// } | |||
} | |||
}); | |||
}, | |||
get_editor_values: function() { | |||
var values = {}; | |||
$.each($('.post-editor [data-fieldname]'), function(i, ele) { | |||
var $ele = $(ele); | |||
values[$ele.attr("data-fieldname")] = $ele.val(); | |||
}); | |||
values.parent_post = $(".post-editor").attr("data-parent-post"); | |||
values.picture_name = $(".control-post-add-picture").val() || null; | |||
var dataurl = $(".post-picture img").attr("src"); | |||
values.picture = dataurl ? dataurl.split(",")[1] : "" | |||
// validations | |||
if(!values.parent_post && !values.title) { | |||
wn.msgprint("Please enter title!"); | |||
return; | |||
} else if(!values.content) { | |||
wn.msgprint("Please enter some text!"); | |||
return; | |||
} else if($('.post-editor [data-fieldname="event_datetime"]').length && !values.event_datetime) { | |||
wn.msgprint("Please enter Event's Date and Time!"); | |||
return; | |||
} | |||
// post process | |||
// convert links in content | |||
values.content = website.process_external_links(values.content); | |||
return values; | |||
}, | |||
process_external_links: function(content) { | |||
return content.replace(/([^\s]*)(http|https|ftp):\/\/[^\s\[\]\(\)]+/g, function(match, p1) { | |||
// mimicing look behind! should not have anything in p1 | |||
// replace(/match/g) | |||
// replace(/(p1)(p2)/g) | |||
// so, when there is a character before http://, it shouldn't be replaced! | |||
if(p1) return match; | |||
return "["+match+"]("+match+")"; | |||
}); | |||
}, | |||
add_picture: function() { | |||
if (this.type === 'file' && this.files && this.files.length > 0) { | |||
$.each(this.files, function (idx, fileobj) { | |||
if (/^image\//.test(fileobj.type)) { | |||
$.canvasResize(fileobj, { | |||
width: 500, | |||
height: 0, | |||
crop: false, | |||
quality: 80, | |||
callback: function(data, width, height) { | |||
$(".post-picture").removeClass("hide").find("img").attr("src", data); | |||
} | |||
}); | |||
} | |||
}); | |||
} | |||
return false; | |||
}, | |||
setup_tasks_editor: function() { | |||
// assign events | |||
var $post_editor = $(".post-editor"); | |||
var $control_assign = $post_editor.find('.control-assign'); | |||
var bind_close = function() { | |||
var close = $post_editor.find("a.close") | |||
if(close.length) { | |||
close.on("click", function() { | |||
// clear assignment | |||
$post_editor.find(".assigned-to").addClass("hide"); | |||
$post_editor.find(".assigned-profile").html(""); | |||
$post_editor.find('[data-fieldname="assigned_to"]').val(null); | |||
$control_assign.val(null); | |||
}); | |||
} | |||
} | |||
if($control_assign.length) { | |||
website.setup_autosuggest({ | |||
$control: $control_assign, | |||
select: function(value, item) { | |||
var $assigned_to = $post_editor.find(".assigned-to").removeClass("hide"); | |||
$assigned_to.find(".assigned-profile").html(item.profile_html); | |||
$post_editor.find('[data-fieldname="assigned_to"]').val(value); | |||
bind_close(); | |||
}, | |||
method: "webnotes.website.doctype.post.post.suggest_user" | |||
}); | |||
bind_close(); | |||
} | |||
}, | |||
setup_event_editor: function() { | |||
var $post_editor = $(".post-editor"); | |||
var $control_event = $post_editor.find('.control-event').empty(); | |||
var $event_field = $post_editor.find('[data-fieldname="event_datetime"]'); | |||
var set_event = function($control) { | |||
var datetime = website.datetimepicker.obj_to_str($control_event.datepicker("getDate")); | |||
if($event_field.val() !== datetime) { | |||
$event_field.val(datetime); | |||
} | |||
}; | |||
website.setup_datepicker({ | |||
$control: $control_event, | |||
onClose: function() { set_event($control_event) } | |||
}); | |||
if($event_field.val()) { | |||
$control_event.val(website.datetimepicker.format_datetime($event_field.val())); | |||
} | |||
}, | |||
format_event_timestamps: function() { | |||
var format = function(datetime) { | |||
if(!datetime) return ""; | |||
var date = datetime.split(" ")[0].split("-"); | |||
var time = datetime.split(" ")[1].split(":"); | |||
var tt = "am"; | |||
if(time[0] >= 12) { | |||
time[0] = parseInt(time[0]) - 12; | |||
tt = "pm"; | |||
} | |||
if(!parseInt(time[0])) { | |||
time[0] = 12; | |||
} | |||
var hhmm = [time[0], time[1]].join(":") | |||
return [date[2], date[1], date[0]].join("-") + " " + hhmm + " " + tt; | |||
} | |||
$(".event-timestamp").each(function() { | |||
$(this).html(format($(this).attr("data-timestamp"))); | |||
}) | |||
}, | |||
toggle_earlier_replies: function() { | |||
var $earlier_replies = $(".child-post").slice(0, $(".child-post").length - 2); | |||
var $btn = $(".btn-earlier-replies").on("click", function() { | |||
if($earlier_replies.hasClass("hide")) { | |||
$earlier_replies.removeClass("hide"); | |||
$(".btn-earlier-label").html("Hide"); | |||
} else { | |||
$earlier_replies.addClass("hide"); | |||
$(".btn-earlier-label").html("Show"); | |||
} | |||
}); | |||
if($earlier_replies.length) { | |||
$btn.toggleClass("hide", false).click(); | |||
} | |||
}, | |||
toggle_edit: function(only_owner) { | |||
if(only_owner) { | |||
var user = wn.get_cookie("user_id"); | |||
$(".edit-post").each(function() { | |||
$(this).toggleClass("hide", !(website.access.write && $(this).attr("data-owner")===user)); | |||
}); | |||
} else { | |||
$(".edit-post").toggleClass("hide", !website.access.write); | |||
} | |||
}, | |||
toggle_upvote: function() { | |||
if(!website.access.read) { | |||
$(".upvote").remove(); | |||
} | |||
}, | |||
toggle_post_editor: function() { | |||
$(".post-editor").toggleClass("hide", !website.access.write); | |||
}, | |||
setup_upvote: function() { | |||
$(".post-list, .parent-post").on("click", ".upvote a", function() { | |||
var sid = wn.get_cookie("sid"); | |||
if(!sid || sid==="Guest") { | |||
wn.msgprint("Please login to Upvote!"); | |||
return; | |||
} | |||
var $post = $(this).parents(".post"); | |||
var post = $post.attr("data-name"); | |||
var $btn = $(this).prop("disabled", true); | |||
$.ajax({ | |||
url: "/", | |||
type: "POST", | |||
data: { | |||
cmd: "webnotes.website.doctype.user_vote.user_vote.set_vote", | |||
ref_doctype: "Post", | |||
ref_name: post | |||
}, | |||
statusCode: { | |||
200: function(data) { | |||
if(data.exc) { | |||
console.log(data.exc); | |||
} else { | |||
var text_class = data.message === "ok" ? "text-success" : "text-danger"; | |||
if(data.message==="ok") { | |||
var count = parseInt($post.find(".upvote-count").text()); | |||
$post.find(".upvote-count").text(count + 1).removeClass("hide"); | |||
} | |||
$btn.addClass(text_class); | |||
setTimeout(function() { $btn.removeClass(text_class); }, 2000); | |||
} | |||
} | |||
} | |||
}).always(function() { | |||
$btn.prop("disabled", false); | |||
}); | |||
}); | |||
}, | |||
setup_autosuggest: function(opts) { | |||
if(opts.$control.hasClass("ui-autocomplete-input")) return; | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.min.js"); | |||
wn.require("/assets/webnotes/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css"); | |||
var $user_suggest = opts.$control.autocomplete({ | |||
source: function(request, response) { | |||
$.ajax({ | |||
url: "/", | |||
data: { | |||
cmd: opts.method, | |||
term: request.term, | |||
group: website.group | |||
}, | |||
success: function(data) { | |||
if(data.exc) { | |||
console.log(data.exc); | |||
} else { | |||
response(data.message); | |||
} | |||
} | |||
}); | |||
}, | |||
select: function(event, ui) { | |||
opts.$control.val(""); | |||
opts.select(ui.item.profile, ui.item); | |||
} | |||
}); | |||
$user_suggest.data( "ui-autocomplete" )._renderItem = function(ul, item) { | |||
return $("<li>").html("<a style='padding: 5px;'>" + item.profile_html + "</a>") | |||
.css("padding", "5px") | |||
.appendTo(ul); | |||
}; | |||
return opts.$control | |||
}, | |||
setup_datepicker: function(opts) { | |||
if(opts.$control.hasClass("hasDatetimepicker")) return; | |||
// libs required for datetime picker | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.min.js"); | |||
wn.require("/assets/webnotes/js/lib/jquery/bootstrap_theme/jquery-ui.selected.css"); | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.slider.min.js"); | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.sliderAccess.js"); | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.css"); | |||
wn.require("/assets/webnotes/js/lib/jquery/jquery.ui.timepicker-addon.js"); | |||
opts.$control.datetimepicker({ | |||
timeFormat: "hh:mm tt", | |||
dateFormat: 'dd-mm-yy', | |||
changeYear: true, | |||
yearRange: "-70Y:+10Y", | |||
stepMinute: 5, | |||
hour: 10, | |||
onClose: opts.onClose | |||
}); | |||
website.setup_datetime_functions(); | |||
return opts.$control; | |||
}, | |||
setup_datetime_functions: function() { | |||
// requires datetime picker | |||
wn.provide("website.datetimepicker"); | |||
website.datetimepicker.str_to_obj = function(datetime_str) { | |||
return $.datepicker.parseDateTime("yy-mm-dd", "HH:mm:ss", datetime_str); | |||
}; | |||
website.datetimepicker.obj_to_str = function(datetime) { | |||
if(!datetime) { | |||
return ""; | |||
} | |||
// requires datepicker | |||
var date_str = $.datepicker.formatDate("yy-mm-dd", datetime) | |||
var time_str = $.datepicker.formatTime("HH:mm:ss", { | |||
hour: datetime.getHours(), | |||
minute: datetime.getMinutes(), | |||
second: datetime.getSeconds() | |||
}) | |||
return date_str + " " + time_str; | |||
}; | |||
website.datetimepicker.format_datetime = function(datetime) { | |||
if (typeof(datetime)==="string") { | |||
datetime = website.datetimepicker.str_to_obj(datetime); | |||
} | |||
var date_str = $.datepicker.formatDate("dd-mm-yy", datetime) | |||
var time_str = $.datepicker.formatTime("hh:mm tt", { | |||
hour: datetime.getHours(), | |||
minute: datetime.getMinutes(), | |||
second: datetime.getSeconds() | |||
}) | |||
return date_str + " " + time_str; | |||
} | |||
}, | |||
setup_settings: function() { | |||
// autosuggest | |||
website.setup_autosuggest({ | |||
$control: $(".add-user-control"), | |||
select: function(value) { | |||
website.add_sitemap_permission(value); | |||
}, | |||
method: "webnotes.templates.website_group.settings.suggest_user" | |||
}); | |||
// trigger for change permission | |||
$(".permission-editor-area").on("click", ".sitemap-permission [type='checkbox']", | |||
website.update_permission); | |||
$(".permission-editor-area").find(".btn-add-group").on("click", website.add_group); | |||
$(".btn-settings").parent().addClass("active"); | |||
// disabled public_write if not public_read | |||
var control_public_read = $(".control-add-group-public_read").click(function() { | |||
if(!$(this).prop("checked")) { | |||
$(".control-add-group-public_write").prop("checked", false).prop("disabled", true); | |||
} else { | |||
$(".control-add-group-public_write").prop("disabled", false); | |||
} | |||
}).trigger("click").trigger("click"); // hack | |||
}, | |||
add_group: function() { | |||
var $control = $(".control-add-group"), | |||
$btn = $(".btn-add-group"); | |||
if($control.val()) { | |||
$btn.prop("disabled", true); | |||
$.ajax({ | |||
url:"/", | |||
type:"POST", | |||
data: { | |||
cmd:"webnotes.templates.website_group.settings.add_website_group", | |||
group: website.group, | |||
new_group: $control.val(), | |||
group_type: $(".control-add-group-type").val(), | |||
public_read: $(".control-add-group-public_read").is(":checked") ? 1 : 0, | |||
public_write: $(".control-add-group-public_write").is(":checked") ? 1 : 0 | |||
}, | |||
statusCode: { | |||
403: function() { | |||
wn.msgprint("Name Not Permitted"); | |||
}, | |||
200: function(data) { | |||
if(data.exc) { | |||
console.log(data.exc); | |||
if(data._server_messages) wn.msgprint(data._server_messages); | |||
} else { | |||
wn.msgprint("Group Added, refreshing..."); | |||
setTimeout(function() { window.location.reload(); }, 1000) | |||
} | |||
} | |||
} | |||
}).always(function() { | |||
$btn.prop("disabled",false); | |||
$control.val(""); | |||
}) | |||
} | |||
}, | |||
update_permission: function() { | |||
var $chk = $(this); | |||
var $tr = $chk.parents("tr:first"); | |||
$chk.prop("disabled", true); | |||
$.ajax({ | |||
url: "/", | |||
type: "POST", | |||
data: { | |||
cmd: "webnotes.templates.website_group.settings.update_permission", | |||
profile: $tr.attr("data-profile"), | |||
perm: $chk.attr("data-perm"), | |||
value: $chk.prop("checked") ? "1" : "0", | |||
sitemap_page: website.group | |||
}, | |||
statusCode: { | |||
403: function() { | |||
wn.msgprint("Not Allowed"); | |||
}, | |||
200: function(data) { | |||
$chk.prop("disabled", false); | |||
if(data.exc) { | |||
$chk.prop("checked", !$chk.prop("checked")); | |||
console.log(data.exc); | |||
} else { | |||
if(!$tr.find(":checked").length) $tr.remove(); | |||
} | |||
} | |||
}, | |||
}); | |||
}, | |||
add_sitemap_permission: function(profile) { | |||
$.ajax({ | |||
url: "/", | |||
type: "POST", | |||
data: { | |||
cmd: "webnotes.templates.website_group.settings.add_sitemap_permission", | |||
profile: profile, | |||
sitemap_page: website.group | |||
}, | |||
success: function(data) { | |||
$(".add-user-control").val(""); | |||
if(data.exc) { | |||
console.log(data.exc); | |||
} else { | |||
$(data.message).prependTo($(".permission-editor tbody")); | |||
} | |||
} | |||
}); | |||
}, | |||
update_group_description: function() { | |||
$(".btn-update-description").prop("disabled", true); | |||
$.ajax({ | |||
url: "/", | |||
type: "POST", | |||
data: { | |||
cmd: "webnotes.templates.website_group.settings.update_description", | |||
description: $(".control-description").val() || "", | |||
group: website.group | |||
}, | |||
success: function(data) { | |||
window.location.reload(); | |||
} | |||
}).always(function() { $(".btn-update-description").prop("disabled", false); }); | |||
} | |||
}); | |||
$(document).on("apply_permissions", function() { | |||
website.toggle_permitted(); | |||
}); |
@@ -13,8 +13,6 @@ from urllib import quote | |||
import mimetypes | |||
from webnotes.website.doctype.website_sitemap.website_sitemap import add_to_sitemap, update_sitemap, remove_sitemap | |||
# for access as webnotes.webutils.fn | |||
from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission \ | |||
import get_access | |||
@@ -73,7 +71,6 @@ def build_json(page_name): | |||
def build_page(page_name): | |||
context = get_context(page_name) | |||
context.update(get_website_settings()) | |||
html = webnotes.get_template(context.base_template_path).render(context) | |||
@@ -149,6 +146,7 @@ def build_context(sitemap_options): | |||
"""get_context method of bean or module is supposed to render content templates and push it into context""" | |||
context = webnotes._dict({ "_": webnotes._ }) | |||
context.update(sitemap_options) | |||
context.update(get_website_settings()) | |||
if sitemap_options.get("controller"): | |||
module = webnotes.get_module(sitemap_options.get("controller")) | |||
@@ -195,7 +193,7 @@ def get_website_settings(): | |||
"utils": webnotes.utils, | |||
"post_login": [ | |||
{"label": "Reset Password", "url": "update-password", "icon": "icon-key"}, | |||
{"label": "Logout", "url": "/?cmd=web_logout", "icon": "icon-signout"} | |||
{"label": "Logout", "url": "?cmd=web_logout", "icon": "icon-signout"} | |||
] | |||
}) | |||
@@ -222,6 +220,10 @@ def get_website_settings(): | |||
context.web_include_js = hooks.web_include_js or [] | |||
context.web_include_css = hooks.web_include_css or [] | |||
# get settings from site config | |||
if webnotes.conf.get("fb_app_id"): | |||
context.fb_app_id = webnotes.conf.fb_app_id | |||
return context | |||
def is_ajax(): | |||
@@ -342,7 +344,9 @@ class WebsiteGenerator(DocListController): | |||
"page_name": page_name, | |||
"link_name": self._website_config.name, | |||
"lastmod": webnotes.utils.get_datetime(self.doc.modified).strftime("%Y-%m-%d"), | |||
"parent_website_sitemap": self.doc.parent_website_sitemap | |||
"parent_website_sitemap": self.doc.parent_website_sitemap, | |||
"page_title": self.get_page_title() \ | |||
if hasattr(self, "get_page_title") else (self.doc.title or self.doc.name) | |||
}) | |||
if self.meta.get_field("public_read"): | |||