1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-30 12:56:46 +00:00

Merge branch 'personal-wiki'

This commit is contained in:
zhengxie 2013-04-08 10:23:11 +08:00
commit d76e6b0566
12 changed files with 640 additions and 1 deletions

View File

@ -821,6 +821,20 @@ textarea:-moz-placeholder {/* for FF */
/* for separate pages */
/*myhome*/
.personal-nav {
border-bottom:1px solid #ddd;
margin-bottom:16px;
margin-top:-12px;
text-align:right;
}
.personal-nav .item {
float:left;
font-size:15px;
margin:8px 16px 0 0;
}
.personal-nav a {
font-weight:normal;
}
.home-profile .pic {
margin-left:9px;
}

View File

@ -131,6 +131,7 @@ INSTALLED_APPS = (
'seahub.base',
'seahub.contacts',
'seahub.wiki',
'seahub.group',
'seahub.notifications',
# 'seahub.organizations',

View File

@ -5,6 +5,19 @@
{% block sub_title %}{% trans "My Home" %} - {% endblock %}
{% block nav_myhome_class %}class="cur"{% endblock %}
{% block title_panel %}
<div class="tabnav">
<ul class="tabnav-tabs">
<li class="tabnav-tab tabnav-tab-cur">{% trans "Libraries" %}</li>
<li class="tabnav-tab"><a href="{% url 'personal_wiki' %}">{% trans "Personal Wiki" %}</a></li>
</ul>
</div>
{% endblock %}
{% block left_panel %}
<div class="info-item">
<h3 class="info-item-top">{% trans "Account" %}</h3>

View File

@ -1,6 +1,5 @@
{% load seahub_tags i18n %}
{% load url from future %}
<h3>{% trans "Libraries" %}</h3>
<div id="tabs">
<div class="ovhd">
<ul class="fleft" id="tabs-nav">

View File

@ -0,0 +1,146 @@
{% extends "myhome_base.html" %}
{% load seahub_tags avatar_tags i18n %}
{% load url from future %}
{% block sub_title %}{% trans "Personal Wiki" %}{% endblock %}
{% block nav_myhome_class %}class="cur"{% endblock %}
{% block title_panel %}
<div class="tabnav">
<ul class="tabnav-tabs">
<li class="tabnav-tab"><a href="{% url 'myhome' %}">{% trans "Libraries" %}</a></li>
<li class="tabnav-tab tabnav-tab-cur">{% trans "Personal Wiki" %}</li>
</ul>
</div>
{% endblock %}
{% block main_panel %}
{% if not wiki_exists %}
<div class="empty-tips">
<h2 class="center-contents">{% trans "You do not have personal wiki" %}</h2>
<p>{% trans "Seafile Wiki enables you to take notes in a simple way. The contents of wiki is stored in a normal library with pre-defined file/folder structure. This enables you to edit your wiki in your desktop and then sync back to the server." %}</p>
<a id="wiki-create" href="#">{% trans "Create Wiki Now" %}</a>
</div>
<form id="wiki-create-form" action="" method="post" class="hide">
<h3>{% trans "Create Wiki" %}</h3>
<label>{% trans "Name" %}</label><br/>
<input id="repo-name" type="text" name="repo_name" value="personal-wiki" maxlength="{{max_file_name}}"/><br />
<label>{% trans "Description" %}</label><br/>
<textarea id="repo-desc" name="repo_desc">{% trans "Personal Wiki Pages" %}</textarea><br/>
<p class="error hide"></p>
<input type="submit" id="wiki-create-submit" value="{% trans "Submit"%}" class="submit" />
</form>
{% else %}
<div class="wiki-top w100 ovhd">
<ul class="wiki-nav fleft">
<li class="item"><a href="{% url 'personal_wiki' %}" class="link">{% trans "Home" %}</a></li>
<li class="item"><a href="{% url 'personal_wiki_pages' %}" class="link">{% trans "Pages" %}</a></li>
{% if group.view_perm != "pub" %}
<li class="item"><a href="{% url 'repo_history' repo_id %}" target="_blank" class="link">{% trans "Wiki History" %}</a></li>
{% endif %}
</ul>
<button id="page-create" class="op-btn">{% trans "New Page" %}</button>
<button id="page-delete" class="op-btn" data-url="{% url 'personal_wiki_page_delete' page %}">{% trans "Delete Page" %}</button>
<button id="page-edit" class="op-btn">{% trans "Edit Page" %}</button>
<button id="page-history" class="op-btn">{% trans "Page History" %}</button>
</div>
<div id="wiki-area" class="article">
<h1>{{ page|capfirst }}</h1>
<div id="wiki-content"></div><!--content will be offered by js-->
<p id="wiki-last-modified">{% blocktrans with modifier=latest_contributor|email2nickname modify_time=last_modified|translate_seahub_time %}Last modified by {{modifier}}, {{modify_time}}{% endblocktrans %}</p>
</div>
<form id="page-create-form" action="{% url 'personal_wiki_page_new' %}" method="post" class="simple-input-popup hide">
<h3>{% trans "New Page"%}</h3>
<label>{% trans "Name"%}</label><br/>
<input id="page-name" type="text" name="page_name" value="" maxlength="{{max_file_name}}" class="long-input" /><br />
<p class="error hide"></p>
<input type="submit" id="page-create-submit" value="{% trans "Submit"%}" class="submit"/>
</form>
{% endif %}
{% endblock main_panel %}
{% block extra_script %}
<script type="text/javascript" src="{{MEDIA_URL}}js/Markdown.Converter.js"></script>
<script type="text/javascript" src="{{MEDIA_URL}}js/Markdown.Extra.js"></script>
<script type="text/javascript">
{% if wiki_exists %}
var converter = new Markdown.Converter();
Markdown.Extra.init(converter, {extensions: ["fenced_code_gfm"]});
$('#wiki-content').html(converter.makeHtml('{{ content|escapejs }}')).children(':first').css('margin-top', '0');
$('#page-create').click(function() {
$('#page-create-form').modal({appendTo: '#main', autoResize: true});
})
$('#page-edit').click(function() {
location.href = ("{% url 'personal_wiki_page_edit' page %}");
})
$('#page-list').click(function () {
location.href = ("{% url 'personal_wiki_pages' %}");
});
$('#page-history').click(function () {
window.open("{% url 'file_revisions' repo_id %}" + "?p=" + "{{path|urlencode}}");
});
addConfirmTo($('#page-delete'), {
'title': 'Delete Page',
'con': 'Are you sure you want to delete this page?'
});
$('a.wiki-page-missing').each(function(){
$(this).click(function() {
$('#page-name').val($(this).text());
$('#page-create-form').modal({appendTo: '#main', autoResize: true});
return false;
});
});
{% else %}
$('#wiki-create').click(function() {
$('#wiki-create-form').modal({appendTo: '#main', autoResize: true});
return false;
});
$('#wiki-create-submit').click(function () {
if (!$.trim($('#repo-name').val()) || !$.trim($('#repo-desc').val())) {
apply_form_error('wiki-create-form', '{% trans 'Name and description can not be blank.' %}');
return false;
}
var submit_btn = $(this);
disable(submit_btn);
$.ajax({
url: '{% url 'personal_wiki_create' %}',
type: 'POST',
dataType: 'json',
cache: false,
beforeSend: prepareCSRFToken,
data: {
'repo_name': $('#repo-name').val(),
'repo_desc': $('#repo-desc').val()
},
success: function(data) {
location.href = data['href'];
},
error: function(data, textStatus, jqXHR) {
var errors = $.parseJSON(data.responseText);
$.each(errors, function(index, value) {
apply_form_error('wiki-create-form', value);
});
enable(submit_btn);
}
});
return false;
});
{% endif %} // END wiki_exists
</script>
{% endblock %}

View File

@ -0,0 +1,53 @@
{% extends "myhome_base.html" %}
{% load seahub_tags avatar_tags group_avatar_tags i18n %}
{% load url from future %}
{% block sub_title %}{% trans "Personal Wiki" %}{% endblock %}
{% block nav_myhome_class %}class="cur"{% endblock %}
{% block title_panel %}
<div class="tabnav">
<ul class="tabnav-tabs">
<li class="tabnav-tab"><a href="{% url 'myhome' %}">{% trans "Libraries" %}</a></li>
<li class="tabnav-tab tabnav-tab-cur">{% trans "Personal Wiki" %}</li>
</ul>
</div>
{% endblock %}
{% block main_panel %}
<div class="wiki-top w100 ovhd">
<ul class="wiki-nav fleft">
<li class="item"><a href="{% url 'personal_wiki' %}" class="link">{% trans "Home" %}</a></li>
<li class="item"><a href="{% url 'personal_wiki_pages' %}" class="link">{% trans "Pages" %}</a></li>
<li class="item"><a href="{% url 'repo_history' repo_id %}" target="_blank" class="link">{% trans "Wiki History" %}</a></li>
</ul>
<button id="page-create" class="op-btn">{% trans "New Page" %}</button>
</div>
<ul id="wiki-pages">
{% for page_slug, page in pages.items %}
<li><a href="{% url 'personal_wiki' page_slug %}">{{ page }}</a></li>
{% endfor %}
</ul>
<form id="page-create-form" action="{% url 'personal_wiki_page_new' %}" method="post" class="hide">
<h3>{% trans "New Page"%}</h3>
<label>{% trans "Name"%}</label><br/>
<input id="page-name" type="text" name="page_name" value="" maxlength="{{max_file_name}}"/><br />
<p class="error hide"></p>
<input type="submit" id="page-create-submit" value="{% trans "Submit"%}" class="submit" />
</form>
{% endblock main_panel %}
{% block extra_script %}
<script type="text/javascript">
$('#page-create').click(function() {
$('#page-create-form').modal({appendTo: '#main', autoResize: true});
})
</script>
{% endblock %}

11
urls.py
View File

@ -9,6 +9,9 @@ from seahub.views.repo import RepoView, RepoHistoryView
from seahub.views.search import search
from notifications.views import notification_list
from group.views import group_list
from seahub.views.wiki import personal_wiki, personal_wiki_pages, \
personal_wiki_create, personal_wiki_page_new, personal_wiki_page_edit, \
personal_wiki_page_delete
# Uncomment the next two lines to enable the admin:
#from django.contrib import admin
@ -30,6 +33,14 @@ urlpatterns = patterns('',
(r'^$', root),
#url(r'^home/$', direct_to_template, { 'template': 'home.html' } ),
url(r'^home/my/$', myhome, name='myhome'),
url(r'^home/wiki/$', personal_wiki, name='personal_wiki'),
url(r'^home/wiki/(?P<page_name>[^/]+)/$', personal_wiki, name='personal_wiki'),
url(r'^home/wiki_pages/$', personal_wiki_pages, name='personal_wiki_pages'),
url(r'^home/wiki_create/$', personal_wiki_create, name='personal_wiki_create'),
url(r'^home/wiki_page_new/$', personal_wiki_page_new, name='personal_wiki_page_new'),
url(r'^home/wiki_page_edit/(?P<page_name>[^/]+)$', personal_wiki_page_edit, name='personal_wiki_page_edit'),
url(r'^home/wiki_page_delete/(?P<page_name>[^/]+)$', personal_wiki_page_delete, name='personal_wiki_page_delete'),
url(r'^home/public/reply/(?P<msg_id>[\d]+)/$', innerpub_msg_reply, name='innerpub_msg_reply'),
url(r'^home/owner/(?P<owner_name>[^/]+)/$', ownerhome, name='ownerhome'),

190
views/wiki.py Normal file
View File

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
"""
File related views, including view_file, edit_file, view_history_file,
view_trash_file, view_snapshot_file
"""
import os
import simplejson as json
import stat
import tempfile
import urllib
import urllib2
import chardet
from django.contrib.sites.models import Site, RequestSite
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseBadRequest, Http404, \
HttpResponseRedirect
from django.shortcuts import render_to_response, redirect
from django.template import Context, loader, RequestContext
from django.template.loader import render_to_string
from django.utils.hashcompat import md5_constructor
from django.utils.http import urlquote
from django.utils.translation import ugettext as _
from auth.decorators import login_required
import seaserv
from pysearpc import SearpcError
from seahub.wiki.models import PersonalWiki, WikiDoesNotExist, WikiPageMissing
from seahub.wiki import get_personal_wiki_page, get_personal_wiki_repo, \
convert_wiki_link, get_wiki_pages
from seahub.wiki.forms import WikiCreateForm, WikiNewPageForm
from seahub.utils import get_file_contributors, render_error
@login_required
def personal_wiki(request, page_name="home"):
username = request.user.username
wiki_exists = True
try:
content, repo, dirent = get_personal_wiki_page(username, page_name)
except WikiDoesNotExist:
wiki_exists = False
return render_to_response("wiki/personal_wiki.html", {
"wiki_exists": wiki_exists,
}, context_instance=RequestContext(request))
except WikiPageMissing:
repo = get_personal_wiki_repo(username)
filename = clean_page_name(page_name) + '.md'
if not post_empty_file(repo.id, "/", filename, username):
return render_error(request, _("Failed to create wiki page. Please retry later."))
return HttpResponseRedirect(reverse('personal_wiki', args=[page_name]))
else:
url_prefix = reverse('personal_wiki', args=[])
content = convert_wiki_link(content, url_prefix, repo.id, username)
# fetch file latest contributor and last modified
path = '/' + dirent.obj_name
file_path_hash = md5_constructor(urllib2.quote(path.encode('utf-8'))).hexdigest()[:12]
contributors, last_modified, last_commit_id = get_file_contributors(\
repo.id, path.encode('utf-8'), file_path_hash, dirent.obj_id)
latest_contributor = contributors[0] if contributors else None
return render_to_response("wiki/personal_wiki.html", {
"wiki_exists": wiki_exists,
"content": content,
"page": os.path.splitext(dirent.obj_name)[0],
"last_modified": last_modified,
"latest_contributor": latest_contributor,
"path": path,
"repo_id": repo.id,
}, context_instance=RequestContext(request))
@login_required
def personal_wiki_pages(request):
"""
List personal wiki pages.
"""
try:
repo = get_personal_wiki_repo(request.user.username)
pages = get_wiki_pages(repo)
except SearpcError:
return render_error(request, _('Internal Server Error'))
except WikiDoesNotExist:
return render_error(request, _('Wiki does not exists.'))
return render_to_response("wiki/personal_wiki_pages.html", {
"pages": pages,
"repo_id": repo.id
}, context_instance=RequestContext(request))
@login_required
def personal_wiki_create(request):
if request.method != 'POST':
raise Http404
content_type = 'application/json; charset=utf-8'
def json_error(err_msg, status=400):
result = {'error': err_msg}
return HttpResponse(json.dumps(result), status=status,
content_type=content_type)
form = WikiCreateForm(request.POST)
if not form.is_valid():
return json_error(str(form.errors.values()[0]))
# create group repo in user context
repo_name = form.cleaned_data['repo_name']
repo_desc = form.cleaned_data['repo_desc']
username = request.user.username
passwd = None
permission = "rw"
repo_id = seaserv.create_repo(repo_name, repo_desc, username, passwd)
if not repo_id:
return json_error(_(u'Failed to create'), 500)
PersonalWiki.objects.save_personal_wiki(username=username, repo_id=repo_id)
# create home page
page_name = "home.md"
if not seaserv.post_empty_file(repo_id, "/", page_name, username):
return json_error(_(u'Failed to create home page. Please retry later'), 500)
next = reverse('personal_wiki', args=[])
return HttpResponse(json.dumps({'href': next}), content_type=content_type)
@login_required
def personal_wiki_page_new(request, page_name="home"):
if request.method == 'POST':
page_name = request.POST.get('page_name', '')
if not page_name:
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
page_name = clean_page_name(page_name)
repo = find_wiki_repo(request, group)
if not repo:
return render_error(request, _('Wiki is not found.'))
filename = page_name + ".md"
filepath = "/" + page_name + ".md"
# check whether file exists
if get_file_id_by_path(repo.id, filepath):
return render_error(request, _('Page "%s" already exists.') % filename)
if not post_empty_file(repo.id, "/", filename, request.user.username):
return render_error(request, _('Failed to create wiki page. Please retry later.'))
url = "%s?p=%s&from=wiki_page_new&gid=%s" % (
reverse('file_edit', args=[repo.id]),
urllib2.quote(filepath.encode('utf-8')), group.id)
return HttpResponseRedirect(url)
@login_required
def personal_wiki_page_edit(request, page_name="home"):
try:
repo = get_personal_wiki_repo(request.user.username)
except WikiDoesNotExist:
return render_error(request, _('Wiki is not found.'))
filepath = "/" + page_name + ".md"
url = "%s?p=%s&from=personal_wiki_page_edit" % (
reverse('file_edit', args=[repo.id]),
urllib2.quote(filepath.encode('utf-8')))
return HttpResponseRedirect(url)
@login_required
def personal_wiki_page_delete(request, page_name):
try:
repo = get_personal_wiki_repo(request.user.username)
except WikiDoesNotExist:
return render_error(request, _('Wiki is not found.'))
file_name = page_name + '.md'
username = request.user.username
if del_file(repo.id, '/', file_name, username):
messages.success(request, 'Successfully deleted "%s".' % page_name)
else:
messages.error(request, 'Failed to delete "%s". Please retry later.' % page_name)
return HttpResponseRedirect(reverse('personal_wiki', args=[]))

4
wiki/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from utils import get_personal_wiki_page, get_personal_wiki_repo, \
convert_wiki_link, get_wiki_pages

37
wiki/forms.py Normal file
View File

@ -0,0 +1,37 @@
# encoding: utf-8
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from seaserv import is_valid_filename
from utils import clean_page_name
class WikiCreateForm(forms.Form):
"""
A form used to create wiki.
"""
repo_name = forms.CharField(max_length=settings.MAX_FILE_NAME,
error_messages={
'required': _(u'Name can\'t be empty'),
'max_length': _(u'Name is too long (maximum is 255 characters)')
})
repo_desc = forms.CharField(max_length=100, error_messages={
'required': _(u'Description can\'t be empty'),
'max_length': _(u'Description is too long (maximum is 100 characters)')
})
def clean_repo_name(self):
repo_name = self.cleaned_data['repo_name']
if not is_valid_filename(repo_name):
error_msg = _(u'"%s" is not a valid name') % repo_name
raise forms.ValidationError(error_msg)
else:
return repo_name
class WikiNewPageForm(forms.Form):
page_name = forms.CharField(max_length=500)
def clean_page_name(self):
page_name = self.cleaned_data['page_name']

25
wiki/models.py Normal file
View File

@ -0,0 +1,25 @@
from django.db import models
class WikiDoesNotExist(Exception):
pass
class WikiPageMissing(Exception):
pass
class PersonalWikiManager(models.Manager):
def save_personal_wiki(self, username, repo_id):
"""
Create or update group wiki.
"""
try:
wiki = self.get(username=username)
wiki.repo_id = repo_id
except self.model.DoesNotExist:
wiki = self.model(username=username, repo_id=repo_id)
wiki.save(using=self._db)
return wiki
class PersonalWiki(models.Model):
username = models.CharField(max_length=256, unique=True)
repo_id = models.CharField(max_length=36)
objects = PersonalWikiManager()

146
wiki/utils.py Normal file
View File

@ -0,0 +1,146 @@
# -*- coding: utf-8 -*-
import os
import stat
import urllib2
import seaserv
from pysearpc import SearpcError
from seahub.utils import EMPTY_SHA1
from seahub.utils.repo import list_dir_by_path
from seahub.utils.slugify import slugify
from seahub.utils import render_error, render_permission_error, string2list, \
gen_file_get_url, get_file_type_and_ext, \
get_file_contributors
from models import WikiPageMissing, WikiDoesNotExist, \
PersonalWiki
__all__ = ["get_wiki_dirent"]
SLUG_OK = "!@#$%^&()_+-,.;'"
def normalize_page_name(page_name):
# Remove special characters. Lower page name and replace spaces with '-'.
return slugify(page_name, ok=SLUG_OK)
def clean_page_name(page_name):
# Remove special characters. Do not lower page name and spaces are allowed.
return slugify(page_name, ok=SLUG_OK, lower=False, spaces=True)
def get_wiki_dirent(repo_id, page_name):
file_name = page_name + ".md"
repo = seaserv.get_repo(repo_id)
if not repo:
raise WikiDoesNotExist
cmmt = seaserv.get_commits(repo.id, 0, 1)[0]
if cmmt is None:
raise WikiPageMissing
dirs = list_dir_by_path(cmmt, "/")
if not dirs:
raise WikiPageMissing
else:
for e in dirs:
if stat.S_ISDIR(e.mode):
continue # skip directories
if normalize_page_name(file_name) == normalize_page_name(e.obj_name):
return e
raise WikiPageMissing
def get_file_url(repo, obj_id, file_name):
repo_id = repo.id
access_token = seaserv.seafserv_rpc.web_get_access_token(repo_id, obj_id,
'view', '')
url = gen_file_get_url(access_token, file_name)
return url
def get_wiki_page(request, page_name):
repo = find_wiki_repo(request, group)
dirent = get_wiki_dirent(repo.id, page_name)
if not dirent:
raise WikiPageMissing
url = get_file_url(repo, dirent.obj_id, dirent.obj_name)
file_response = urllib2.urlopen(url)
content = file_response.read()
return content, repo.id, dirent
def get_personal_wiki_repo(username):
try:
wiki = PersonalWiki.objects.get(username=username)
except PersonalWiki.DoesNotExist:
raise WikiDoesNotExist
repo = seaserv.get_repo(wiki.repo_id)
if not repo:
raise WikiDoesNotExist
return repo
def get_personal_wiki_page(username, page_name):
repo = get_personal_wiki_repo(username)
dirent = get_wiki_dirent(repo.id, page_name)
url = get_file_url(repo, dirent.obj_id, dirent.obj_name)
file_response = urllib2.urlopen(url)
content = file_response.read()
return content, repo, dirent
def get_wiki_pages(repo):
"""
return pages in hashtable {normalized_name: page_name}
"""
dir_id = seaserv.seafserv_threaded_rpc.get_dir_id_by_path(repo.id, '/')
dirs = seaserv.seafserv_threaded_rpc.list_dir(dir_id)
pages = {}
for e in dirs:
if stat.S_ISDIR(e.mode):
continue # skip directories
name, ext = os.path.splitext(e.obj_name)
if ext == '.md':
key = normalize_page_name(name)
pages[key] = name
return pages
def convert_wiki_link(content, url_prefix, repo_id, username):
import re
def repl(matchobj):
if matchobj.group(2): # return origin string in backquotes
return matchobj.group(2)
page_name = matchobj.group(1).strip()
filetype, fileext = get_file_type_and_ext(page_name)
if fileext == '':
# convert page_name that extension is missing to a markdown page
dirent = get_wiki_dirent(repo_id, page_name)
if dirent is not None:
a_tag = "<a href='%s'>%s</a>"
return a_tag % (url_prefix + '/' + normalize_page_name(page_name), page_name)
else:
a_tag = '''<a class="wiki-page-missing" href='%s'>%s</a>'''
return a_tag % (url_prefix + '/' + page_name.replace('/', '-'), page_name)
elif filetype == IMAGE:
# load image to wiki page
path = "/" + page_name
filename = os.path.basename(path)
obj_id = get_file_id_by_path(repo_id, path)
if not obj_id:
# Replace '/' in page_name to '-', since wiki name can not
# contain '/'.
return '''<a class="wiki-page-missing" href='%s'>%s</a>''' % \
(url_prefix + '/' + page_name.replace('/', '-'), page_name)
token = seaserv.web_get_access_token(repo_id, obj_id, 'view', username)
return '<img src="%s" alt="%s" />' % (gen_file_get_url(token, filename), filename)
else:
from base.templatetags.seahub_tags import file_icon_filter
# convert other types of filelinks to clickable links
path = "/" + page_name
icon = file_icon_filter(page_name)
s = reverse('repo_view_file', args=[repo_id]) + \
'?p=' + urllib2.quote(smart_str(path))
a_tag = '''<img src="%simg/file/%s" alt="%s" class="vam" /> <a href='%s' target='_blank' class="vam">%s</a>'''
return a_tag % (MEDIA_URL, icon, icon, s, page_name)
return re.sub(r'\[\[(.+)\]\]|(`.+`)', repl, content)