@@ -107,7 +107,7 @@ class LoginManager: | |||||
def set_user_info(self): | def set_user_info(self): | ||||
info = webnotes.conn.get_value("Profile", self.user, | 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": | if info.user_type=="Website User": | ||||
webnotes._response.set_cookie("system_user", "no") | webnotes._response.set_cookie("system_user", "no") | ||||
webnotes.response["message"] = "No App" | webnotes.response["message"] = "No App" | ||||
@@ -119,6 +119,7 @@ class LoginManager: | |||||
webnotes.response["full_name"] = full_name | webnotes.response["full_name"] = full_name | ||||
webnotes._response.set_cookie("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_id", self.user) | ||||
webnotes._response.set_cookie("user_image", info.user_image or "") | |||||
def make_session(self, resume=False): | def make_session(self, resume=False): | ||||
# start session | # start session | ||||
@@ -347,6 +347,57 @@ def reset_password(user): | |||||
else: | else: | ||||
return "No such user (%s)" % user | 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): | def profile_query(doctype, txt, searchfield, start, page_len, filters): | ||||
from webnotes.widgets.reportview import get_match_cond | from webnotes.widgets.reportview import get_match_cond | ||||
return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name) | return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name) | ||||
@@ -7,10 +7,17 @@ | |||||
], | ], | ||||
"js/webnotes-web.min.js": [ | "js/webnotes-web.min.js": [ | ||||
"public/js/lib/bootstrap.min.js", | "public/js/lib/bootstrap.min.js", | ||||
"public/js/wn/provide.js", | |||||
"public/js/wn/misc/number_format.js", | "public/js/wn/misc/number_format.js", | ||||
"public/js/lib/nprogress.js", | "public/js/lib/nprogress.js", | ||||
"public/js/wn/translate.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": [ | "js/editor.min.js": [ | ||||
"public/js/lib/jquery/jquery.hotkeys.js", | "public/js/lib/jquery/jquery.hotkeys.js", | ||||
@@ -316,6 +316,11 @@ div#freeze { | |||||
max-height: 15px; | max-height: 15px; | ||||
} | } | ||||
.navbar-brand { | |||||
min-height: 20px; | |||||
height: auto; | |||||
} | |||||
.navbar #spinner { | .navbar #spinner { | ||||
display: block; | display: block; | ||||
float: right; | float: right; | ||||
@@ -11,25 +11,23 @@ | |||||
<ul class="nav nav-tabs view-selector"> | <ul class="nav nav-tabs view-selector"> | ||||
{%- for t in views -%} | {%- for t in views -%} | ||||
<li class="{% if view.name==t.name -%} active {%- endif %} {% if t.hidden -%} hide {%- endif %}" | <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> | <a href="{{ t.url or '' }}"><i class="{{ t.icon }}"></i> | ||||
<span class="nav-label">{{ t.label }}</span></a> | <span class="nav-label">{{ t.label }}</span></a> | ||||
</li> | </li> | ||||
{%- endfor -%} | {%- endfor -%} | ||||
</ul> | </ul> | ||||
{% include view.template_path %} | |||||
{%- if access -%} | |||||
<script> | <script> | ||||
$(function() { | |||||
wn.provide("website"); | |||||
{%- if access -%} | |||||
website.access = {{ access|json }}; | website.access = {{ access|json }}; | ||||
{%- endif -%} | |||||
website.group = "{{ group.name }}"; | website.group = "{{ group.name }}"; | ||||
website.view = "{{ view.name }}"; | website.view = "{{ view.name }}"; | ||||
}) | |||||
</script> | </script> | ||||
{%- endif -%} | |||||
{% include view.template_path %} | |||||
{% endblock %} | {% endblock %} | ||||
{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %} | {% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %} |
@@ -35,7 +35,7 @@ def get_context(context): | |||||
def get_group_context(group, view, bean): | def get_group_context(group, view, bean): | ||||
cache_key = "website_group_context:{}:{}".format(group, view) | cache_key = "website_group_context:{}:{}".format(group, view) | ||||
views = get_views(bean.doc.group_type) | views = get_views(bean.doc.group_type) | ||||
view = views.get(view) | |||||
view = webnotes._dict(views.get(view)) | |||||
if can_cache(view.get("no_cache")): | if can_cache(view.get("no_cache")): | ||||
group_context = webnotes.cache().get_value(cache_key) | group_context = webnotes.cache().get_value(cache_key) | ||||
@@ -86,9 +86,10 @@ def get_handler(group_type): | |||||
return webnotes.get_module(handler[0]) | return webnotes.get_module(handler[0]) | ||||
def get_views(group_type): | def get_views(group_type): | ||||
from copy import deepcopy | |||||
handler = get_handler(group_type) | handler = get_handler(group_type) | ||||
if handler and hasattr(handler, "get_views"): | if handler and hasattr(handler, "get_views"): | ||||
return handler.get_views() | |||||
return deepcopy(handler.get_views() or {}) | |||||
return {} | return {} | ||||
def has_access(group, view): | def has_access(group, view): | ||||
@@ -20,10 +20,10 @@ | |||||
</h4> | </h4> | ||||
{%- endif -%} | {%- endif -%} | ||||
<ul class="list-inline small text-muted post-options"> | <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> | <a><span class="upvote-count {% if not post.upvotes %}hide{% endif %}">{{ post.upvotes }}</span> | ||||
<i class="icon-thumbs-up"></i></a></li>{% endif %} | <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 }}"> | <li><a itemprop="url" href="{{ post_url }}"> | ||||
{% if not post.post_reply_count -%} | {% if not post.post_reply_count -%} | ||||
<i class="icon-reply"></i> Reply | <i class="icon-reply"></i> Reply | ||||
@@ -62,26 +62,7 @@ login.do_login = function(){ | |||||
url: "/", | url: "/", | ||||
data: args, | data: args, | ||||
dataType: "json", | 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(){ | }).always(function(){ | ||||
$("#login-spinner").toggle(false); | $("#login-spinner").toggle(false); | ||||
$('#login_btn').prop("disabled", false); | $('#login_btn').prop("disabled", false); | ||||
@@ -140,4 +121,88 @@ login.set_message = function(message, color) { | |||||
wn.msgprint(message); | wn.msgprint(message); | ||||
return; | return; | ||||
//$('#login_message').html(message).toggle(true); | //$('#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> | </li> | ||||
</ul> | </ul> | ||||
<ul class="btn-login-area nav navbar-nav navbar-right"> | <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> | </ul> | ||||
</div> | </div> | ||||
</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 -%} | {%- if children -%} | ||||
{%- for child in children -%} | {%- for child in children -%} | ||||
<div class="sidebar-item"> | <div class="sidebar-item"> | ||||
<a href="/{{ child.url|lower }}"> | |||||
<a href="/{{ child.name|lower }}"> | |||||
{{ child.page_title }} | {{ child.page_title }} | ||||
{% if not child.public_read %} | {% if not child.public_read %} | ||||
(<i class="icon-fixed-width icon-lock"></i>) | (<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> | <p id="forgot-link"></p> | ||||
</div> | </div> | ||||
<div class="col-sm-6"> | <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> | <p id="switch-view"></p> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
@@ -13,7 +13,8 @@ def get_context(context): | |||||
if hasattr(webnotes.local, "message"): | if hasattr(webnotes.local, "message"): | ||||
message_context["title"] = webnotes.local.message_title | message_context["title"] = webnotes.local.message_title | ||||
message_context["message"] = webnotes.local.message | 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) | message_context.update(context) | ||||
return render_blocks(message_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="small text-muted post-list-help"></div> | ||||
<div class="post-list"> | <div class="post-list"> | ||||
{{ post_list_html }} | {{ post_list_html }} | ||||
@@ -6,17 +5,24 @@ | |||||
<div class="text-center"> | <div class="text-center"> | ||||
<button class="btn btn-default btn-more hide">More</button> | <button class="btn btn-default btn-more hide">More</button> | ||||
</div> | </div> | ||||
{%- endblock -%} | |||||
{%- block group_script -%} | |||||
<script> | <script> | ||||
$(function() { | $(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> | </script> | ||||
{%- endblock -%} |
@@ -5,22 +5,36 @@ from __future__ import unicode_literals | |||||
import webnotes | import webnotes | ||||
from webnotes.utils import now_datetime, get_datetime_str | from webnotes.utils import now_datetime, get_datetime_str | ||||
from webnotes.webutils import get_access | 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(): | def get_views(): | ||||
return views | return views | ||||
def get_context(group_context): | 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) | @webnotes.whitelist(allow_guest=True) | ||||
def get_post_list_html(group, view, limit_start=0, limit_length=20): | def get_post_list_html(group, view, limit_start=0, limit_length=20): | ||||
access = get_access(group) | access = get_access(group) | ||||
if isinstance(view, basestring): | if isinstance(view, basestring): | ||||
view = get_views(group)["view"] | |||||
view = get_views()[view] | |||||
view = webnotes._dict(view) | view = webnotes._dict(view) | ||||
@@ -50,7 +64,7 @@ def get_post_list_html(group, view, limit_start=0, limit_length=20): | |||||
views = { | views = { | ||||
"popular": { | "popular": { | ||||
"name": "popular", | "name": "popular", | ||||
"template_path": "templates/unit_templates/forum_list.html", | |||||
"template_path": "templates/website_group/forum.html", | |||||
"url": "/{group}", | "url": "/{group}", | ||||
"label": "Popular", | "label": "Popular", | ||||
"icon": "icon-heart", | "icon": "icon-heart", | ||||
@@ -60,7 +74,7 @@ views = { | |||||
}, | }, | ||||
"feed": { | "feed": { | ||||
"name": "feed", | "name": "feed", | ||||
"template_path": "templates/unit_templates/forum_list.html", | |||||
"template_path": "templates/website_group/forum.html", | |||||
"url": "/{group}?view=feed", | "url": "/{group}?view=feed", | ||||
"label": "Feed", | "label": "Feed", | ||||
"icon": "icon-rss", | "icon": "icon-rss", | ||||
@@ -69,7 +83,7 @@ views = { | |||||
}, | }, | ||||
"post": { | "post": { | ||||
"name": "post", | "name": "post", | ||||
"template_path": "templates/unit_templates/base_post.html", | |||||
"template_path": "templates/website_group/post.html", | |||||
"url": "/{group}?view=post&name={post}", | "url": "/{group}?view=post&name={post}", | ||||
"label": "Post", | "label": "Post", | ||||
"icon": "icon-comments", | "icon": "icon-comments", | ||||
@@ -80,7 +94,7 @@ views = { | |||||
}, | }, | ||||
"edit": { | "edit": { | ||||
"name": "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}", | "url": "/{group}?view=edit&name={post}", | ||||
"label": "Edit Post", | "label": "Edit Post", | ||||
"icon": "icon-pencil", | "icon": "icon-pencil", | ||||
@@ -90,7 +104,7 @@ views = { | |||||
}, | }, | ||||
"add": { | "add": { | ||||
"name": "add", | "name": "add", | ||||
"template_path": "templates/unit_templates/base_edit.html", | |||||
"template_path": "templates/website_group/edit_post.html", | |||||
"url": "/{group}?view=add", | "url": "/{group}?view=add", | ||||
"label": "Add Post", | "label": "Add Post", | ||||
"icon": "icon-plus", | "icon": "icon-plus", | ||||
@@ -99,8 +113,8 @@ views = { | |||||
}, | }, | ||||
"settings": { | "settings": { | ||||
"name": "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", | "label": "Settings", | ||||
"icon": "icon-cog", | "icon": "icon-cog", | ||||
"hidden": True, | "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 { | .login-wrapper { | ||||
border-right: 1px solid #f2f2f2; | border-right: 1px solid #f2f2f2; | ||||
} | } | ||||
@@ -163,6 +163,8 @@ img { | |||||
.navbar-brand { | .navbar-brand { | ||||
padding-right: 30px; | padding-right: 30px; | ||||
max-width: 80%; | max-width: 80%; | ||||
min-height: 20px; | |||||
height: auto; | |||||
} | } | ||||
@media (min-width: 768px) { | @media (min-width: 768px) { | ||||
@@ -283,6 +285,7 @@ body { | |||||
.post .media-object { | .post .media-object { | ||||
border-radius: 4px; | border-radius: 4px; | ||||
max-width: 50px; | |||||
} | } | ||||
.post .media-heading { | .post .media-heading { | ||||
@@ -298,4 +301,12 @@ body { | |||||
padding-left: 15px; | padding-left: 15px; | ||||
background-color: #f8f8f8; | background-color: #f8f8f8; | ||||
margin-top: 0px; | 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 import get_fullname | ||||
from webnotes.utils.email_lib.bulk import send | from webnotes.utils.email_lib.bulk import send | ||||
from webnotes.utils.email_lib import sendmail | 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 | # 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: | class DocType: | ||||
def __init__(self, d, dl): | def __init__(self, d, dl): | ||||
@@ -42,8 +44,8 @@ class DocType: | |||||
self.doc.event_datetime = None | self.doc.event_datetime = None | ||||
def on_update(self): | 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 \ | if self.doc.assigned_to and self.doc.assigned_to != self.assigned_to \ | ||||
and webnotes.session.user != self.doc.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, | message += "<p><a href='/post/{post_name}'>Click here to view the post</a></p>".format(fullname=owner_fullname, | ||||
post_name=post_name) | post_name=post_name) | ||||
return message | 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 | from __future__ import unicode_literals | ||||
import webnotes | import webnotes | ||||
from webnotes.webutils import get_access | |||||
class DocType: | class DocType: | ||||
def __init__(self, d, dl): | def __init__(self, d, dl): | ||||
@@ -31,3 +32,22 @@ class DocType: | |||||
def on_doctype_update(): | def on_doctype_update(): | ||||
webnotes.conn.add_index("User Vote", ["ref_doctype", "ref_name"]) | 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: | if not self.doc.page_name: | ||||
webnotes.throw(_("Page Name is mandatory"), raise_exception=webnotes.MandatoryError) | 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", | "creation": "2013-04-30 12:58:46", | ||||
"docstatus": 0, | "docstatus": 0, | ||||
"modified": "2013-12-27 16:37:52", | |||||
"modified": "2014-02-03 15:25:54", | |||||
"modified_by": "Administrator", | "modified_by": "Administrator", | ||||
"owner": "Administrator" | "owner": "Administrator" | ||||
}, | }, | ||||
@@ -25,6 +25,8 @@ | |||||
"permlevel": 0 | "permlevel": 0 | ||||
}, | }, | ||||
{ | { | ||||
"cancel": 0, | |||||
"delete": 0, | |||||
"doctype": "DocPerm", | "doctype": "DocPerm", | ||||
"name": "__common__", | "name": "__common__", | ||||
"parent": "Website Settings", | "parent": "Website Settings", | ||||
@@ -48,8 +50,9 @@ | |||||
"description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)", | "description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)", | ||||
"doctype": "DocField", | "doctype": "DocField", | ||||
"fieldname": "home_page", | "fieldname": "home_page", | ||||
"fieldtype": "Data", | |||||
"fieldtype": "Link", | |||||
"label": "Home Page", | "label": "Home Page", | ||||
"options": "Website Sitemap", | |||||
"reqd": 0 | "reqd": 0 | ||||
}, | }, | ||||
{ | { | ||||
@@ -241,7 +244,6 @@ | |||||
}, | }, | ||||
{ | { | ||||
"amend": 0, | "amend": 0, | ||||
"cancel": 0, | |||||
"create": 0, | "create": 0, | ||||
"doctype": "DocPerm", | "doctype": "DocPerm", | ||||
"permlevel": 1, | "permlevel": 1, | ||||
@@ -8,7 +8,7 @@ import webnotes | |||||
from webnotes.utils.nestedset import DocTypeNestedSet | from webnotes.utils.nestedset import DocTypeNestedSet | ||||
sitemap_fields = ("page_name", "ref_doctype", "docname", "page_or_generator", | 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): | class DocType(DocTypeNestedSet): | ||||
def __init__(self, d, dl): | def __init__(self, d, dl): | ||||
@@ -10,6 +10,7 @@ class DocType: | |||||
def on_update(self): | def on_update(self): | ||||
remove_empty_permissions() | remove_empty_permissions() | ||||
clear_permissions(self.doc.profile) | |||||
def remove_empty_permissions(): | def remove_empty_permissions(): | ||||
permissions_cache_to_be_cleared = webnotes.conn.sql_list("""select distinct profile | 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) | 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 | profile = profile or webnotes.session.user | ||||
key = "website_sitemap_permissions:{}".format(profile) | key = "website_sitemap_permissions:{}".format(profile) | ||||
cache = webnotes.cache() | cache = webnotes.cache() | ||||
permissions = cache.get_value(key) or {} | 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) | 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"]) | ["lft", "rgt", "public_read", "public_write"]) | ||||
if not (lft and rgt): | 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`, | for perm in webnotes.conn.sql("""select wsp.`read`, wsp.`write`, wsp.`admin`, | ||||
ws.lft, ws.rgt, ws.name | 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 | where wsp.profile = %s and wsp.website_sitemap = ws.name | ||||
order by lft asc""", (profile,), as_dict=True): | order by lft asc""", (profile,), as_dict=True): | ||||
if perm.lft <= lft and perm.rgt >= rgt: | 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 | // 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, { | $.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: [], | _assets_loaded: [], | ||||
require: function(url) { | require: function(url) { | ||||
if(wn._assets_loaded.indexOf(url)!==-1) return; | if(wn._assets_loaded.indexOf(url)!==-1) return; | ||||
@@ -41,17 +30,28 @@ $.extend(wn, { | |||||
// opts = {"method": "PYTHON MODULE STRING", "args": {}, "callback": function(r) {}} | // opts = {"method": "PYTHON MODULE STRING", "args": {}, "callback": function(r) {}} | ||||
wn.prepare_call(opts); | wn.prepare_call(opts); | ||||
return $.ajax({ | return $.ajax({ | ||||
type: "POST", | |||||
type: opts.type || "POST", | |||||
url: "/", | url: "/", | ||||
data: opts.args, | data: opts.args, | ||||
dataType: "json", | 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) { | prepare_call: function(opts) { | ||||
@@ -87,10 +87,12 @@ $.extend(wn, { | |||||
} | } | ||||
}, | }, | ||||
process_response: function(opts, data) { | process_response: function(opts, data) { | ||||
NProgress.done(); | |||||
if(!opts.no_spinner) NProgress.done(); | |||||
if(opts.btn) { | if(opts.btn) { | ||||
$(opts.btn).prop("disabled", false); | $(opts.btn).prop("disabled", false); | ||||
} | } | ||||
if(data.exc) { | if(data.exc) { | ||||
if(opts.btn) { | if(opts.btn) { | ||||
$(opts.btn).addClass("btn-danger"); | $(opts.btn).addClass("btn-danger"); | ||||
@@ -114,8 +116,6 @@ $.extend(wn, { | |||||
if(opts.msg && data.message) { | if(opts.msg && data.message) { | ||||
$(opts.msg).html(data.message).toggle(true); | $(opts.msg).html(data.message).toggle(true); | ||||
} | } | ||||
if(opts.callback) | |||||
opts.callback(data); | |||||
}, | }, | ||||
show_message: function(text, icon) { | show_message: function(text, icon) { | ||||
if(!icon) icon="icon-refresh icon-spin"; | 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 | // 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; | 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>'); | <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).trigger("page_change"); | ||||
}); | }); | ||||
$(document).on("page_change", function() { | $(document).on("page_change", function() { | ||||
$(".page-header").toggleClass("hidden", !!!$(".page-header").text().trim()); | $(".page-header").toggleClass("hidden", !!!$(".page-header").text().trim()); | ||||
$(".page-footer").toggleClass("hidden", !!!$(".page-footer").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 | import mimetypes | ||||
from webnotes.website.doctype.website_sitemap.website_sitemap import add_to_sitemap, update_sitemap, remove_sitemap | 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 \ | from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission \ | ||||
import get_access | import get_access | ||||
@@ -73,7 +71,6 @@ def build_json(page_name): | |||||
def build_page(page_name): | def build_page(page_name): | ||||
context = get_context(page_name) | context = get_context(page_name) | ||||
context.update(get_website_settings()) | |||||
html = webnotes.get_template(context.base_template_path).render(context) | 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""" | """get_context method of bean or module is supposed to render content templates and push it into context""" | ||||
context = webnotes._dict({ "_": webnotes._ }) | context = webnotes._dict({ "_": webnotes._ }) | ||||
context.update(sitemap_options) | context.update(sitemap_options) | ||||
context.update(get_website_settings()) | |||||
if sitemap_options.get("controller"): | if sitemap_options.get("controller"): | ||||
module = webnotes.get_module(sitemap_options.get("controller")) | module = webnotes.get_module(sitemap_options.get("controller")) | ||||
@@ -195,7 +193,7 @@ def get_website_settings(): | |||||
"utils": webnotes.utils, | "utils": webnotes.utils, | ||||
"post_login": [ | "post_login": [ | ||||
{"label": "Reset Password", "url": "update-password", "icon": "icon-key"}, | {"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_js = hooks.web_include_js or [] | ||||
context.web_include_css = hooks.web_include_css 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 | return context | ||||
def is_ajax(): | def is_ajax(): | ||||
@@ -342,7 +344,9 @@ class WebsiteGenerator(DocListController): | |||||
"page_name": page_name, | "page_name": page_name, | ||||
"link_name": self._website_config.name, | "link_name": self._website_config.name, | ||||
"lastmod": webnotes.utils.get_datetime(self.doc.modified).strftime("%Y-%m-%d"), | "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"): | if self.meta.get_field("public_read"): | ||||