浏览代码

Website Group rendering (#421)

version-14
Anand Doshi 11 年前
父节点
当前提交
1e6f58daf2
共有 38 个文件被更改,包括 2895 次插入117 次删除
  1. +2
    -1
      webnotes/auth.py
  2. +51
    -0
      webnotes/core/doctype/profile/profile.py
  3. +8
    -1
      webnotes/public/build.json
  4. +5
    -0
      webnotes/public/css/common.css
  5. +6
    -8
      webnotes/templates/generators/website_group.html
  6. +3
    -2
      webnotes/templates/generators/website_group.py
  7. +2
    -2
      webnotes/templates/includes/inline_post.html
  8. +86
    -21
      webnotes/templates/includes/login.js
  9. +1
    -7
      webnotes/templates/includes/navbar.html
  10. +66
    -0
      webnotes/templates/includes/post_editor.html
  11. +9
    -0
      webnotes/templates/includes/profile_display.html
  12. +1
    -1
      webnotes/templates/includes/sidebar.html
  13. +8
    -0
      webnotes/templates/includes/sitemap_permission.html
  14. +5
    -0
      webnotes/templates/pages/login.html
  15. +2
    -1
      webnotes/templates/pages/message.py
  16. +19
    -0
      webnotes/templates/website_group/edit_post.html
  17. +29
    -0
      webnotes/templates/website_group/events.html
  18. +125
    -0
      webnotes/templates/website_group/events.py
  19. +16
    -10
      webnotes/templates/website_group/forum.html
  20. +26
    -12
      webnotes/templates/website_group/forum.py
  21. +34
    -0
      webnotes/templates/website_group/post.html
  22. +41
    -0
      webnotes/templates/website_group/post.py
  23. +77
    -0
      webnotes/templates/website_group/settings.html
  24. +105
    -0
      webnotes/templates/website_group/settings.py
  25. +32
    -0
      webnotes/templates/website_group/tasks.html
  26. +126
    -0
      webnotes/templates/website_group/tasks.py
  27. +12
    -1
      webnotes/website/css/website.css
  28. +106
    -6
      webnotes/website/doctype/post/post.py
  29. +20
    -0
      webnotes/website/doctype/user_vote/user_vote.py
  30. +3
    -0
      webnotes/website/doctype/website_group/website_group.py
  31. +5
    -3
      webnotes/website/doctype/website_settings/website_settings.txt
  32. +1
    -1
      webnotes/website/doctype/website_sitemap/website_sitemap.py
  33. +8
    -7
      webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py
  34. +313
    -0
      webnotes/website/js/jquery.canvasResize.js
  35. +957
    -0
      webnotes/website/js/jquery.exif.js
  36. +47
    -28
      webnotes/website/js/website.js
  37. +529
    -0
      webnotes/website/js/website_group.js
  38. +9
    -5
      webnotes/webutils.py

+ 2
- 1
webnotes/auth.py 查看文件

@@ -107,7 +107,7 @@ class LoginManager:
def set_user_info(self):
info = webnotes.conn.get_value("Profile", self.user,
["user_type", "first_name", "last_name"], as_dict=1)
["user_type", "first_name", "last_name", "user_image"], as_dict=1)
if info.user_type=="Website User":
webnotes._response.set_cookie("system_user", "no")
webnotes.response["message"] = "No App"
@@ -119,6 +119,7 @@ class LoginManager:
webnotes.response["full_name"] = full_name
webnotes._response.set_cookie("full_name", full_name)
webnotes._response.set_cookie("user_id", self.user)
webnotes._response.set_cookie("user_image", info.user_image or "")
def make_session(self, resume=False):
# start session


+ 51
- 0
webnotes/core/doctype/profile/profile.py 查看文件

@@ -347,6 +347,57 @@ def reset_password(user):
else:
return "No such user (%s)" % user

@webnotes.whitelist(allow_guest=True)
def facebook_login(data):
data = json.loads(data)
if not (data.get("id") and data.get("fb_access_token")):
raise webnotes.ValidationError

user = data["email"]
if not get_fb_userid(data.get("fb_access_token")):
# garbage
raise webnotes.ValidationError
if not webnotes.conn.exists("Profile", user):
if data.get("birthday"):
b = data.get("birthday").split("/")
data["birthday"] = b[2] + "-" + b[0] + "-" + b[1]
profile = webnotes.bean({
"doctype":"Profile",
"first_name": data["first_name"],
"last_name": data["last_name"],
"email": data["email"],
"enabled": 1,
"new_password": webnotes.generate_hash(data["email"]),
"fb_username": data["username"],
"fb_userid": data["id"],
"fb_location": data.get("location", {}).get("name"),
"fb_hometown": data.get("hometown", {}).get("name"),
"fb_age_range": data.get("age_range") and "{min}-{max}".format(**data.get("age_range")),
"birth_date": data.get("birthday"),
"fb_bio": data.get("bio"),
"fb_education": data.get("education") and data.get("education")[-1].get("type"),
"user_type": "Website User"
})
profile.ignore_permissions = True
profile.get_controller().no_welcome_mail = True
profile.insert()
webnotes.local.login_manager.user = user
webnotes.local.login_manager.post_login()
def get_fb_userid(fb_access_token):
import requests
response = requests.get("https://graph.facebook.com/me?access_token=" + fb_access_token)
if response.status_code==200:
print response.json()
return response.json().get("id")
else:
return webnotes.AuthenticationError
def profile_query(doctype, txt, searchfield, start, page_len, filters):
from webnotes.widgets.reportview import get_match_cond
return webnotes.conn.sql("""select name, concat_ws(' ', first_name, middle_name, last_name)


+ 8
- 1
webnotes/public/build.json 查看文件

@@ -7,10 +7,17 @@
],
"js/webnotes-web.min.js": [
"public/js/lib/bootstrap.min.js",
"public/js/wn/provide.js",
"public/js/wn/misc/number_format.js",
"public/js/lib/nprogress.js",
"public/js/wn/translate.js",
"website/js/website.js"
"public/js/wn/misc/pretty_date.js",
"website/js/website.js",
"website/js/website_group.js"
],
"js/canvasResize.min.js": [
"website/js/jquery.exif.js",
"website/js/jquery.canvasResize.js"
],
"js/editor.min.js": [
"public/js/lib/jquery/jquery.hotkeys.js",


+ 5
- 0
webnotes/public/css/common.css 查看文件

@@ -316,6 +316,11 @@ div#freeze {
max-height: 15px;
}

.navbar-brand {
min-height: 20px;
height: auto;
}

.navbar #spinner {
display: block;
float: right;


+ 6
- 8
webnotes/templates/generators/website_group.html 查看文件

@@ -11,25 +11,23 @@
<ul class="nav nav-tabs view-selector">
{%- for t in views -%}
<li class="{% if view.name==t.name -%} active {%- endif %} {% if t.hidden -%} hide {%- endif %}"
data-view="{{ t.view }}">
data-view="{{ t.name }}">
<a href="{{ t.url or '' }}"><i class="{{ t.icon }}"></i>
<span class="nav-label">{{ t.label }}</span></a>
</li>
{%- endfor -%}
</ul>

{% include view.template_path %}

{%- if access -%}
<script>
$(function() {
wn.provide("website");
{%- if access -%}
website.access = {{ access|json }};
{%- endif -%}
website.group = "{{ group.name }}";
website.view = "{{ view.name }}";
})
</script>
{%- endif -%}

{% include view.template_path %}

{% endblock %}

{% block sidebar %}{% include "templates/includes/sidebar.html" %}{% endblock %}

+ 3
- 2
webnotes/templates/generators/website_group.py 查看文件

@@ -35,7 +35,7 @@ def get_context(context):
def get_group_context(group, view, bean):
cache_key = "website_group_context:{}:{}".format(group, view)
views = get_views(bean.doc.group_type)
view = views.get(view)
view = webnotes._dict(views.get(view))
if can_cache(view.get("no_cache")):
group_context = webnotes.cache().get_value(cache_key)
@@ -86,9 +86,10 @@ def get_handler(group_type):
return webnotes.get_module(handler[0])
def get_views(group_type):
from copy import deepcopy
handler = get_handler(group_type)
if handler and hasattr(handler, "get_views"):
return handler.get_views()
return deepcopy(handler.get_views() or {})
return {}
def has_access(group, view):


+ 2
- 2
webnotes/templates/includes/inline_post.html 查看文件

@@ -20,10 +20,10 @@
</h4>
{%- endif -%}
<ul class="list-inline small text-muted post-options">
{% if view_options and view_options.upvote %}<li class="upvote">
{% if view and view.upvote %}<li class="upvote">
<a><span class="upvote-count {% if not post.upvotes %}hide{% endif %}">{{ post.upvotes }}</span>
<i class="icon-thumbs-up"></i></a></li>{% endif %}
{%- if not post.parent_post and view != "post" -%}
{%- if not post.parent_post and view.name != "post" -%}
<li><a itemprop="url" href="{{ post_url }}">
{% if not post.post_reply_count -%}
<i class="icon-reply"></i> Reply


+ 86
- 21
webnotes/templates/includes/login.js 查看文件

@@ -62,26 +62,7 @@ login.do_login = function(){
url: "/",
data: args,
dataType: "json",
statusCode: {
200: function(data) {
if(data.message=="Logged In") {
window.location.href = "app";
} else if(data.message=="No App") {
if(localStorage) {
var last_visited = localStorage.getItem("last_visited") || "/index";
localStorage.removeItem("last_visited");
window.location.href = last_visited;
} else {
window.location.href = "/index";
}
} else if(window.is_sign_up) {
wn.msgprint(data.message);
}
},
401: function(xhr, data) {
login.set_message("Invalid Login");
}
}
statusCode: login.login_handlers
}).always(function(){
$("#login-spinner").toggle(false);
$('#login_btn').prop("disabled", false);
@@ -140,4 +121,88 @@ login.set_message = function(message, color) {
wn.msgprint(message);
return;
//$('#login_message').html(message).toggle(true);
}
}

login.login_handlers = {
200: function(data) {
if(data.message=="Logged In") {
window.location.href = "app";
} else if(data.message=="No App") {
if(localStorage) {
var last_visited = localStorage.getItem("last_visited") || "/index";
localStorage.removeItem("last_visited");
window.location.href = last_visited;
} else {
window.location.href = "/index";
}
} else if(window.is_sign_up) {
wn.msgprint(data.message);
}
},
401: function(xhr, data) {
login.set_message("Invalid Login");
}
}


{% if fb_app_id is defined -%}
// facebook login
$(document).ready(function() {
$.ajaxSetup({ cache: true });
var user_id = wn.get_cookie("user_id");
var sid = wn.get_cookie("sid");
// logged in?
if(!sid || sid==="Guest") {
// fallback on facebook login -- no login again
$(".btn-login").html("Login via Facebook").removeAttr("disabled");
} else {
// get private stuff (if access)
// app.setup_user({"user": user_id});
}
});

$(function() {
$login = $(".btn-login").prop("disabled", true);
$.getScript('//connect.facebook.net/en_UK/all.js', function() {
$login.prop("disabled", false);
FB.init({
appId: '{{ fb_app_id }}',
});
$login.click(function() {
$login.prop("disabled", true).html("Logging In...");
login.via_facebook();
});
});
});

login.via_facebook = function() {
// not logged in to facebook either
FB.login(function(response) {
if (response.authResponse) {
// yes logged in via facebook
console.log('Welcome! Fetching your information.... ');
var fb_access_token = response.authResponse.accessToken;

// get user graph
FB.api('/me', function(response) {
response.fb_access_token = fb_access_token || "[none]";
$.ajax({
url:"/",
type: "POST",
data: {
cmd:"webnotes.core.doctype.profile.profile.facebook_login",
data: JSON.stringify(response)
},
statusCode: login.login_handlers
})
});
} else {
wn.msgprint("You have denied access to this application via Facebook. \
Please change your privacy settings in Facebook and try again. \
If you do not want to use Facebook login, <a href='/login'>sign-up</a> here");
}
},{scope:"email"});
}
{%- endif %}

+ 1
- 7
webnotes/templates/includes/navbar.html 查看文件

@@ -67,13 +67,7 @@
</li>
</ul>
<ul class="btn-login-area nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Login <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="#" class="btn-login" disabled=disabled>Login via Facebook</a></li>
<li><a href="/login">Sign Up / Login</a></li>
</ul>
</li>
<li><a href="/login">Sign Up / Login</a></li>
</ul>
</div>
</div>


+ 66
- 0
webnotes/templates/includes/post_editor.html 查看文件

@@ -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">&times;</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>

+ 9
- 0
webnotes/templates/includes/profile_display.html 查看文件

@@ -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
- 1
webnotes/templates/includes/sidebar.html 查看文件

@@ -1,7 +1,7 @@
{%- if children -%}
{%- for child in children -%}
<div class="sidebar-item">
<a href="/{{ child.url|lower }}">
<a href="/{{ child.name|lower }}">
{{ child.page_title }}
{% if not child.public_read %}
(<i class="icon-fixed-width icon-lock"></i>)


+ 8
- 0
webnotes/templates/includes/sitemap_permission.html 查看文件

@@ -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>

+ 5
- 0
webnotes/templates/pages/login.html 查看文件

@@ -33,6 +33,11 @@
<p id="forgot-link"></p>
</div>
<div class="col-sm-6">
{%- if fb_app_id is defined -%}
<div id="fb-root"></div>
<p><button type="button" class="btn btn-default btn-login">
Login via Facebook</button></p>
{%- endif -%}
<p id="switch-view"></p>
</div>
</div>


+ 2
- 1
webnotes/templates/pages/message.py 查看文件

@@ -13,7 +13,8 @@ def get_context(context):
if hasattr(webnotes.local, "message"):
message_context["title"] = webnotes.local.message_title
message_context["message"] = webnotes.local.message
message_context["success"] = webnotes.local.message_success
if hasattr(webnotes.local, "message_success"):
message_context["success"] = webnotes.local.message_success
message_context.update(context)
return render_blocks(message_context)

+ 19
- 0
webnotes/templates/website_group/edit_post.html 查看文件

@@ -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>

+ 29
- 0
webnotes/templates/website_group/events.html 查看文件

@@ -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>

+ 125
- 0
webnotes/templates/website_group/events.py 查看文件

@@ -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
}
}

+ 16
- 10
webnotes/templates/website_group/forum.html 查看文件

@@ -1,4 +1,3 @@
{%- block view -%}
<div class="small text-muted post-list-help"></div>
<div class="post-list">
{{ post_list_html }}
@@ -6,17 +5,24 @@
<div class="text-center">
<button class="btn btn-default btn-more hide">More</button>
</div>
{%- endblock -%}

{%- block group_script -%}
<script>
$(function() {
app.setup_more_btn({
cmd: "webnotes.templates.website_group.forum.get_post_list_html"
});
app.toggle_edit(true);
app.setup_upvote();
app.toggle_upvote();
if($(".post").length===20) {
wn.setup_pagination($(".btn-more"), {
args: {
cmd: "webnotes.templates.website_group.forum.get_post_list_html",
limit_start: $(".post").length,
limit_length: 20,
group: website.group,
view: website.view
},
$wrapper: $(".post-list")
});
}
website.toggle_edit(true);
website.setup_upvote();
website.toggle_upvote();
});
</script>
{%- endblock -%}

+ 26
- 12
webnotes/templates/website_group/forum.py 查看文件

@@ -5,22 +5,36 @@ from __future__ import unicode_literals
import webnotes
from webnotes.utils import now_datetime, get_datetime_str
from webnotes.webutils import get_access
from webnotes.templates.generators.website_group import get_views
from webnotes.templates.website_group.settings import get_settings_context
from webnotes.templates.website_group.post import get_post_context

def get_views():
return views
def get_context(group_context):
return {
"post_list_html": get_post_list_html(group_context["group"]["name"], group_context["view"])
}
forum_context = {}
if group_context.view.name in ("popular", "feed"):
forum_context["post_list_html"] = get_post_list_html(group_context["group"]["name"], group_context["view"])
elif group_context.view.name == "edit":
forum_context["session_user"] = webnotes.session.user
forum_context["post"] = webnotes.doc("Post", webnotes.form_dict.name).fields

elif group_context.view.name == "settings":
forum_context.update(get_settings_context(group_context))
elif group_context.view.name == "post":
forum_context.update(get_post_context(group_context))
return forum_context

@webnotes.whitelist(allow_guest=True)
def get_post_list_html(group, view, limit_start=0, limit_length=20):
access = get_access(group)
if isinstance(view, basestring):
view = get_views(group)["view"]
view = get_views()[view]
view = webnotes._dict(view)
@@ -50,7 +64,7 @@ def get_post_list_html(group, view, limit_start=0, limit_length=20):
views = {
"popular": {
"name": "popular",
"template_path": "templates/unit_templates/forum_list.html",
"template_path": "templates/website_group/forum.html",
"url": "/{group}",
"label": "Popular",
"icon": "icon-heart",
@@ -60,7 +74,7 @@ views = {
},
"feed": {
"name": "feed",
"template_path": "templates/unit_templates/forum_list.html",
"template_path": "templates/website_group/forum.html",
"url": "/{group}?view=feed",
"label": "Feed",
"icon": "icon-rss",
@@ -69,7 +83,7 @@ views = {
},
"post": {
"name": "post",
"template_path": "templates/unit_templates/base_post.html",
"template_path": "templates/website_group/post.html",
"url": "/{group}?view=post&name={post}",
"label": "Post",
"icon": "icon-comments",
@@ -80,7 +94,7 @@ views = {
},
"edit": {
"name": "edit",
"template_path": "templates/unit_templates/base_edit.html",
"template_path": "templates/website_group/edit_post.html",
"url": "/{group}?view=edit&name={post}",
"label": "Edit Post",
"icon": "icon-pencil",
@@ -90,7 +104,7 @@ views = {
},
"add": {
"name": "add",
"template_path": "templates/unit_templates/base_edit.html",
"template_path": "templates/website_group/edit_post.html",
"url": "/{group}?view=add",
"label": "Add Post",
"icon": "icon-plus",
@@ -99,8 +113,8 @@ views = {
},
"settings": {
"name": "settings",
"template_path": "templates/unit_templates/base_settings.html",
"url": "/{group}&view=settings",
"template_path": "templates/website_group/settings.html",
"url": "/{group}?view=settings",
"label": "Settings",
"icon": "icon-cog",
"hidden": True,


+ 34
- 0
webnotes/templates/website_group/post.html 查看文件

@@ -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>

+ 41
- 0
webnotes/templates/website_group/post.py 查看文件

@@ -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
})

+ 77
- 0
webnotes/templates/website_group/settings.html 查看文件

@@ -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>

+ 105
- 0
webnotes/templates/website_group/settings.py 查看文件

@@ -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)

+ 32
- 0
webnotes/templates/website_group/tasks.html 查看文件

@@ -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>

+ 126
- 0
webnotes/templates/website_group/tasks.py 查看文件

@@ -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
}
}

+ 12
- 1
webnotes/website/css/website.css 查看文件

@@ -1,4 +1,4 @@
@media (min-width: 992px) {
@media (min-width: 768px) {
.login-wrapper {
border-right: 1px solid #f2f2f2;
}
@@ -163,6 +163,8 @@ img {
.navbar-brand {
padding-right: 30px;
max-width: 80%;
min-height: 20px;
height: auto;
}

@media (min-width: 768px) {
@@ -283,6 +285,7 @@ body {

.post .media-object {
border-radius: 4px;
max-width: 50px;
}

.post .media-heading {
@@ -298,4 +301,12 @@ body {
padding-left: 15px;
background-color: #f8f8f8;
margin-top: 0px;
}

textarea {
resize: vertical;
}

.post-add-textarea {
height: 200px !important;
}

+ 106
- 6
webnotes/website/doctype/post/post.py 查看文件

@@ -9,11 +9,13 @@ import webnotes
from webnotes.utils import get_fullname
from webnotes.utils.email_lib.bulk import send
from webnotes.utils.email_lib import sendmail
from webnotes.utils.file_manager import save_file

from webnotes.webutils import get_access

# TODO move these functions to framework
from aapkamanch.helpers import get_access
from aapkamanch.post import clear_post_cache
from aapkamanch.unit import clear_unit_views
# from aapkamanch.post import clear_post_cache
# from aapkamanch.unit import clear_unit_views

class DocType:
def __init__(self, d, dl):
@@ -42,8 +44,8 @@ class DocType:
self.doc.event_datetime = None
def on_update(self):
clear_unit_views(self.doc.unit)
clear_post_cache(self.doc.parent_post or self.doc.name)
# clear_unit_views(self.doc.website_group)
# clear_post_cache(self.doc.parent_post or self.doc.name)

if self.doc.assigned_to and self.doc.assigned_to != self.assigned_to \
and webnotes.session.user != self.doc.assigned_to:
@@ -99,4 +101,102 @@ class DocType:
message += "<p><a href='/post/{post_name}'>Click here to view the post</a></p>".format(fullname=owner_fullname,
post_name=post_name)
return message
@webnotes.whitelist(allow_guest=True)
def add_post(group, content, picture, picture_name, title=None, parent_post=None,
assigned_to=None, status=None, event_datetime=None):
access = get_access(group)
if not access.get("write"):
raise webnotes.PermissionError

if parent_post:
if webnotes.conn.get_value("Post", parent_post, "parent_post"):
webnotes.throw("Cannot reply to a reply")
group = webnotes.doc("Website Group", group)
post = webnotes.bean({
"doctype":"Post",
"title": (title or "").title(),
"content": content,
"website_group": group.name,
"parent_post": parent_post or None
})
if not parent_post:
if group.group_type == "Tasks":
post.doc.is_task = 1
post.doc.assigned_to = assigned_to
elif group.group_type == "Events":
post.doc.is_event = 1
post.doc.event_datetime = event_datetime
post.ignore_permissions = True
post.insert()

if picture_name and picture:
process_picture(post, picture_name, picture)
# send email
if parent_post:
post.run_method("send_email_on_reply")
return post.doc.parent_post or post.doc.name
@webnotes.whitelist(allow_guest=True)
def save_post(post, content, picture=None, picture_name=None, title=None,
assigned_to=None, status=None, event_datetime=None):
post = webnotes.bean("Post", post)

access = get_access(post.doc.website_group)
if not access.get("write"):
raise webnotes.PermissionError
# TODO improve error message
if webnotes.session.user != post.doc.owner:
for fieldname in ("title", "content"):
if post.doc.fields.get(fieldname) != locals().get(fieldname):
webnotes.throw("You cannot change: {}".format(fieldname.title()))
if picture and picture_name:
webnotes.throw("You cannot change: Picture")
post.doc.fields.update({
"title": (title or "").title(),
"content": content,
"assigned_to": assigned_to,
"status": status,
"event_datetime": event_datetime
})
post.ignore_permissions = True
post.save()
if picture_name and picture:
process_picture(post, picture_name, picture)
return post.doc.parent_post or post.doc.name
def process_picture(post, picture_name, picture):
file_data = save_file(picture_name, picture, "Post", post.doc.name, decode=True)
post.doc.picture_url = file_data.file_name or file_data.file_url
webnotes.conn.set_value("Post", post.doc.name, "picture_url", post.doc.picture_url)
# clear_unit_views(post.doc.website_group)
@webnotes.whitelist()
def suggest_user(group, term):
"""suggest a user that has read permission in this group tree"""
profiles = webnotes.conn.sql("""select
pr.name, pr.first_name, pr.last_name,
pr.user_image, pr.fb_location, pr.fb_hometown
from `tabProfile` pr
where (pr.first_name like %(term)s or pr.last_name like %(term)s)
and pr.name not in ("Guest", "Administrator") is not null and pr.enabled=1""",
{"term": "%{}%".format(term), "group": group}, as_dict=True)
template = webnotes.get_template("templates/includes/profile_display.html")
return [{
"value": "{} {}".format(pr.first_name or "", pr.last_name or "").strip(),
"profile_html": template.render({"profile": pr}),
"profile": pr.name
} for pr in profiles]

+ 20
- 0
webnotes/website/doctype/user_vote/user_vote.py 查看文件

@@ -5,6 +5,7 @@

from __future__ import unicode_literals
import webnotes
from webnotes.webutils import get_access

class DocType:
def __init__(self, d, dl):
@@ -31,3 +32,22 @@ class DocType:
def on_doctype_update():
webnotes.conn.add_index("User Vote", ["ref_doctype", "ref_name"])

# don't allow guest to give vote
@webnotes.whitelist()
def set_vote(ref_doctype, ref_name):
website_group = webnotes.conn.get_value(ref_doctype, ref_name, "website_group")
if not get_access(website_group).get("read"):
raise webnotes.PermissionError
try:
user_vote = webnotes.bean({
"doctype": "User Vote",
"ref_doctype": ref_doctype,
"ref_name": ref_name
})
user_vote.ignore_permissions = True
user_vote.insert()
return "ok"
except webnotes.DuplicateEntryError:
return "duplicate"

+ 3
- 0
webnotes/website/doctype/website_group/website_group.py 查看文件

@@ -27,3 +27,6 @@ class DocType(WebsiteGenerator):
if not self.doc.page_name:
webnotes.throw(_("Page Name is mandatory"), raise_exception=webnotes.MandatoryError)
def get_page_title(self):
return self.doc.group_title

+ 5
- 3
webnotes/website/doctype/website_settings/website_settings.txt 查看文件

@@ -2,7 +2,7 @@
{
"creation": "2013-04-30 12:58:46",
"docstatus": 0,
"modified": "2013-12-27 16:37:52",
"modified": "2014-02-03 15:25:54",
"modified_by": "Administrator",
"owner": "Administrator"
},
@@ -25,6 +25,8 @@
"permlevel": 0
},
{
"cancel": 0,
"delete": 0,
"doctype": "DocPerm",
"name": "__common__",
"parent": "Website Settings",
@@ -48,8 +50,9 @@
"description": "Link that is the website home page. Standard Links (index, login, products, blog, about, contact)",
"doctype": "DocField",
"fieldname": "home_page",
"fieldtype": "Data",
"fieldtype": "Link",
"label": "Home Page",
"options": "Website Sitemap",
"reqd": 0
},
{
@@ -241,7 +244,6 @@
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"doctype": "DocPerm",
"permlevel": 1,


+ 1
- 1
webnotes/website/doctype/website_sitemap/website_sitemap.py 查看文件

@@ -8,7 +8,7 @@ import webnotes
from webnotes.utils.nestedset import DocTypeNestedSet

sitemap_fields = ("page_name", "ref_doctype", "docname", "page_or_generator",
"lastmod", "parent_website_sitemap", "public_read", "public_write")
"lastmod", "parent_website_sitemap", "public_read", "public_write", "page_title")

class DocType(DocTypeNestedSet):
def __init__(self, d, dl):


+ 8
- 7
webnotes/website/doctype/website_sitemap_permission/website_sitemap_permission.py 查看文件

@@ -10,6 +10,7 @@ class DocType:
def on_update(self):
remove_empty_permissions()
clear_permissions(self.doc.profile)
def remove_empty_permissions():
permissions_cache_to_be_cleared = webnotes.conn.sql_list("""select distinct profile
@@ -21,20 +22,20 @@ def remove_empty_permissions():
clear_permissions(permissions_cache_to_be_cleared)

def get_access(website_node, profile=None):
def get_access(sitemap_page, profile=None):
profile = profile or webnotes.session.user
key = "website_sitemap_permissions:{}".format(profile)
cache = webnotes.cache()
permissions = cache.get_value(key) or {}
if not permissions.get(website_node):
permissions[website_node] = _get_access(website_node, profile)
if not permissions.get(sitemap_page):
permissions[sitemap_page] = _get_access(sitemap_page, profile)
cache.set_value(key, permissions)
return permissions.get(website_node)
return permissions.get(sitemap_page)
def _get_access(website_node, profile):
lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", website_node,
def _get_access(sitemap_page, profile):
lft, rgt, public_read, public_write = webnotes.conn.get_value("Website Sitemap", sitemap_page,
["lft", "rgt", "public_read", "public_write"])

if not (lft and rgt):
@@ -52,7 +53,7 @@ def _get_access(website_node, profile):

for perm in webnotes.conn.sql("""select wsp.`read`, wsp.`write`, wsp.`admin`,
ws.lft, ws.rgt, ws.name
from `tabWebsite Sitemap Permission` up, `tabWebsite Sitemap` ws
from `tabWebsite Sitemap Permission` wsp, `tabWebsite Sitemap` ws
where wsp.profile = %s and wsp.website_sitemap = ws.name
order by lft asc""", (profile,), as_dict=True):
if perm.lft <= lft and perm.rgt >= rgt:


+ 313
- 0
webnotes/website/js/jquery.canvasResize.js 查看文件

@@ -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);

+ 957
- 0
webnotes/website/js/jquery.exif.js 查看文件

@@ -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;
}
})();
})();

+ 47
- 28
webnotes/website/js/website.js 查看文件

@@ -1,20 +1,9 @@
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
if(!window.wn) wn = {};
// MIT License. See license.txt

wn.provide("website");

$.extend(wn, {
provide: function(namespace) {
var nsl = namespace.split('.');
var parent = window;
for(var i=0; i<nsl.length; i++) {
var n = nsl[i];
if(!parent[n]) {
parent[n] = {}
}
parent = parent[n];
}
return parent;
},
_assets_loaded: [],
require: function(url) {
if(wn._assets_loaded.indexOf(url)!==-1) return;
@@ -41,17 +30,28 @@ $.extend(wn, {
// opts = {"method": "PYTHON MODULE STRING", "args": {}, "callback": function(r) {}}
wn.prepare_call(opts);
return $.ajax({
type: "POST",
type: opts.type || "POST",
url: "/",
data: opts.args,
dataType: "json",
success: function(data) {
wn.process_response(opts, data);
},
error: function(response) {
if(!opts.no_spinner) NProgress.done();
console.error ? console.error(response) : console.log(response);
statusCode: {
404: function(xhr) {
msgprint("Not Found");
},
403: function(xhr) {
msgprint("Not Permitted");
},
200: function(data, xhr) {
if(opts.callback)
opts.callback(data);
}
}
}).always(function(data) {
// executed before statusCode functions
if(data.responseText) {
data = JSON.parse(data.responseText);
}
wn.process_response(opts, data);
});
},
prepare_call: function(opts) {
@@ -87,10 +87,12 @@ $.extend(wn, {
}
},
process_response: function(opts, data) {
NProgress.done();
if(!opts.no_spinner) NProgress.done();
if(opts.btn) {
$(opts.btn).prop("disabled", false);
}
if(data.exc) {
if(opts.btn) {
$(opts.btn).addClass("btn-danger");
@@ -114,8 +116,6 @@ $.extend(wn, {
if(opts.msg && data.message) {
$(opts.msg).html(data.message).toggle(true);
}
if(opts.callback)
opts.callback(data);
},
show_message: function(text, icon) {
if(!icon) icon="icon-refresh icon-spin";
@@ -175,15 +175,25 @@ $.extend(wn, {
}
}
});
},
render_user: function() {
var sid = wn.get_cookie("sid");
if(sid && sid!=="Guest") {
$(".btn-login-area").toggle(false);
$(".logged-in").toggle(true);
$(".full-name").html(wn.get_cookie("full_name"));
$(".user-picture").attr("src", wn.get_cookie("user_image"));
}
}
});


// Utility functions

function valid_email(id) {
if(id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1)
return 0; else return 1; }
function valid_email(id) {
return (id.toLowerCase().search("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")==-1) ? 0 : 1;

}

var validate_email = valid_email;

@@ -305,11 +315,20 @@ $(document).ready(function() {
<li><a href="app.html"><i class="icon-fixed-width icon-th-large"></i> Switch To App</a></li>');
}
wn.render_user();
$(document).trigger("page_change");
});

$(document).on("page_change", function() {
$(".page-header").toggleClass("hidden", !!!$(".page-header").text().trim());
$(".page-footer").toggleClass("hidden", !!!$(".page-footer").text().trim());
});

// add prive pages to sidebar
if(website.private_pages && $(".page-sidebar").length) {
$(data.private_pages).prependTo(".page-sidebar");
}
$(document).trigger("apply_permissions");
wn.datetime.refresh_when();
});

+ 529
- 0
webnotes/website/js/website_group.js 查看文件

@@ -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();
});

+ 9
- 5
webnotes/webutils.py 查看文件

@@ -13,8 +13,6 @@ from urllib import quote

import mimetypes
from webnotes.website.doctype.website_sitemap.website_sitemap import add_to_sitemap, update_sitemap, remove_sitemap

# for access as webnotes.webutils.fn
from webnotes.website.doctype.website_sitemap_permission.website_sitemap_permission \
import get_access

@@ -73,7 +71,6 @@ def build_json(page_name):
def build_page(page_name):
context = get_context(page_name)
context.update(get_website_settings())
html = webnotes.get_template(context.base_template_path).render(context)
@@ -149,6 +146,7 @@ def build_context(sitemap_options):
"""get_context method of bean or module is supposed to render content templates and push it into context"""
context = webnotes._dict({ "_": webnotes._ })
context.update(sitemap_options)
context.update(get_website_settings())
if sitemap_options.get("controller"):
module = webnotes.get_module(sitemap_options.get("controller"))
@@ -195,7 +193,7 @@ def get_website_settings():
"utils": webnotes.utils,
"post_login": [
{"label": "Reset Password", "url": "update-password", "icon": "icon-key"},
{"label": "Logout", "url": "/?cmd=web_logout", "icon": "icon-signout"}
{"label": "Logout", "url": "?cmd=web_logout", "icon": "icon-signout"}
]
})
@@ -222,6 +220,10 @@ def get_website_settings():
context.web_include_js = hooks.web_include_js or []
context.web_include_css = hooks.web_include_css or []
# get settings from site config
if webnotes.conf.get("fb_app_id"):
context.fb_app_id = webnotes.conf.fb_app_id
return context
def is_ajax():
@@ -342,7 +344,9 @@ class WebsiteGenerator(DocListController):
"page_name": page_name,
"link_name": self._website_config.name,
"lastmod": webnotes.utils.get_datetime(self.doc.modified).strftime("%Y-%m-%d"),
"parent_website_sitemap": self.doc.parent_website_sitemap
"parent_website_sitemap": self.doc.parent_website_sitemap,
"page_title": self.get_page_title() \
if hasattr(self, "get_page_title") else (self.doc.title or self.doc.name)
})
if self.meta.get_field("public_read"):


正在加载...
取消
保存