Commits

Anonymous committed f2db530

implemented page renaming.

  • Participants
  • Parent commits 946d02e

Comments (0)

Files changed (13)

File couchit/_design/page/alias/map.js

+function(doc) {
+    if (doc.itemType == 'aliaspage')
+        emit([doc.site, doc.title.toLowerCase().replace(/ /g, "_"), doc);
+}

File couchit/contrib/markdown.py

     module_name = '.'.join([_import_path, ext_module, ext_name])
     extension_module_name = '_'.join(['mdx', ext_name])
 
-    module = __import__(module_name, {}, {}, [ext_module])
     try:
         module = __import__(module_name, {}, {}, [ext_module])
     except ImportError:

File couchit/contrib/markdown_extensions/wikilinks.py

+#!/usr/bin/env python
+
+'''
+WikiLinks Extension for Python-Markdown
+======================================
+
+Converts [[WikiLinks]] to relative links.  Requires Python-Markdown 2.0+
+
+Basic usage:
+
+    >>> import markdown
+    >>> text = "Some text with a [[WikiLink]]."
+    >>> html = markdown.markdown(text, ['wikilinks'])
+    >>> html
+    u'<p>Some text with a <a href="/WikiLink/" class="wikilink">WikiLink</a>.</p>'
+
+Whitespace behavior:
+
+    >>> markdown.markdown('[[ foo bar_baz ]]', ['wikilinks'])
+    u'<p><a class="wikilink" href="/foo_bar_baz/">foo bar_baz</a></p>'
+    >>> markdown.markdown('foo [[ ]] bar', ['wikilinks'])
+    u'<p>foo  bar</p>'
+
+To define custom settings the simple way:
+
+    >>> markdown.markdown(text, 
+    ...     ['wikilinks(base_url=/wiki/,end_url=.html,html_class=foo)']
+    ... )
+    u'<p>Some text with a <a href="/wiki/WikiLink.html" class="foo">WikiLink</a>.</p>'
+    
+Custom settings the complex way:
+
+    >>> md = markdown.Markdown(
+    ...     extensions = ['wikilinks'], 
+    ...     extension_configs = {'wikilinks': [
+    ...                                 ('base_url', 'http://example.com/'), 
+    ...                                 ('end_url', '.html'),
+    ...                                 ('html_class', '') ]},
+    ...     safe_mode = True)
+    >>> md.convert(text)
+    u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>'
+
+Use MetaData with mdx_meta.py (Note the blank html_class in MetaData):
+
+    >>> text = """wiki_base_url: http://example.com/
+    ... wiki_end_url:   .html
+    ... wiki_html_class:
+    ...
+    ... Some text with a WikiLink."""
+    >>> md = markdown.Markdown(extensions=['meta', 'wikilinks'])
+    >>> md.convert(text)
+    u'<p>Some text with a <a href="http://example.com/WikiLink.html">WikiLink</a>.</p>'
+
+MetaData should not carry over to next document:
+
+    >>> md.convert("No [[MetaData]] here.")
+    u'<p>No <a href="/MetaData/" class="wikilink">MetaData</a> here.</p>'
+
+From the command line:
+
+    python markdown.py -x wikilinks(base_url=http://example.com/,end_url=.html,html_class=foo) src.txt
+
+By [Waylan Limberg](http://achinghead.com/).
+
+License: [BSD](http://www.opensource.org/licenses/bsd-license.php) 
+
+Dependencies:
+* [Python 2.3+](http://python.org)
+* [Markdown 2.0+](http://www.freewisdom.org/projects/python-markdown/)
+'''
+
+from couchit.contrib import markdown
+
+class WikiLinkExtension (markdown.Extension) :
+    def __init__(self, configs):
+        # set extension defaults
+        self.config = {
+                        'base_url' : ['/', 'String to append to beginning or URL.'],
+                        'end_url' : ['/', 'String to append to end of URL.'],
+                        'html_class' : ['wikilink', 'CSS hook. Leave blank for none.']
+        }
+        
+        # Override defaults with user settings
+        for key, value in configs :
+            self.setConfig(key, value)
+        
+    def extendMarkdown(self, md, md_globals):
+        self.md = md
+    
+        # append to end of inline patterns
+        WIKILINK_RE = r'\[\[([^\]]+)\]\]'
+        #r'''(?P<escape>\\|\b)(?P<camelcase>([A-Z]+[a-z-_]+){2,})\b'''
+        WIKILINK_PATTERN = WikiLinks(WIKILINK_RE, self.config)
+        WIKILINK_PATTERN.md = md
+        md.inlinePatterns.append(WIKILINK_PATTERN)  
+        
+
+class WikiLinks (markdown.BasePattern) :
+    def __init__(self, pattern, config):
+        markdown.BasePattern.__init__(self, pattern)
+        self.config = config
+  
+    def handleMatch(self, m):
+        if m.group(2).strip():
+            base_url, end_url, html_class = self._getMeta()
+            label = m.group(2).strip()
+            url = '%s%s%s'% (base_url, label.replace(' ', '_'), end_url)
+            a = markdown.etree.Element('a')
+            a.text = markdown.AtomicString(label)
+            a.set('href', url)
+            if html_class:
+                a.set('class', html_class)
+        else:
+            a = ''
+        return a
+
+    def _getMeta(self):
+        """ Return meta data or config data. """
+        base_url = self.config['base_url'][0]
+        end_url = self.config['end_url'][0]
+        html_class = self.config['html_class'][0]
+        if hasattr(self.md, 'Meta'):
+            if self.md.Meta.has_key('wiki_base_url'):
+                base_url = self.md.Meta['wiki_base_url'][0]
+            if self.md.Meta.has_key('wiki_end_url'):
+                end_url = self.md.Meta['wiki_end_url'][0]
+            if self.md.Meta.has_key('wiki_html_class'):
+                html_class = self.md.Meta['wiki_html_class'][0]
+        return base_url, end_url, html_class
+    
+
+def makeExtension(configs=None) :
+    return WikiLinkExtension(configs=configs)
+
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
+

File couchit/models.py

 from couchit.utils import make_hash
 from couchit.utils.diff import unified_diff, diff_blocks
 
-__all__ = ['Page', 'Site', 'PasswordToken']
+__all__ = ['Page', 'Site', 'PasswordToken', 'PageExist',
+'AliasExist', 'AliasPage' ]
 
+class PageExist(Exception):
+    """ raised when a page exist """
+    
+class AliasExist(Exception):
+    """ raised when an alias exist for this pagename """
 
 class ArrayField(Field):
     _to_python = list
             db[self.id] = self._data
         
         return self
-                        
+
+class AliasPage(Document):
+    title = TextField()
+    site = TextField()
+    page = TextField()
+    
+    itemType = TextField(default='alias_page')  
+    
+    def get_alias(cls, db, siteid, pagename):
+        rows = cls.view(db, '_view/page/alias', key=[siteid, pagename.lower()])
+        lrows = list(iter(rows))
+        if lrows:
+            return lrows[0]
+        return None
+    get_alias = classmethod(get_alias)   
+    
+    def is_exist(cls, db, siteid, pagename):
+        if cls.get_alias(db, siteid, pagename) is not None:
+            return True
+        return False
+    is_exist = classmethod(is_exist)
+        
+                      
 class Page(Document):
     site = TextField()
     title = TextField()
         if rows:
             return rows[0]
         return None
+    
+    def get_pagename(cls, db, siteid, pagename):
+        rows = cls.view(db, '_view/page/by_slug', key=[siteid, pagename.lower()])
+        lrows = list(iter(rows))
+        if lrows:
+            return lrows[0]
+        return None
+    get_pagename = classmethod(get_pagename)
+        
+    def is_exist(cls, db, siteid, pagename):
+        page = cls.get_pagename(db, siteid, pagename)
+        if page is not None:
+            return True
+        return False
+    is_exist=classmethod(is_exist)
+        
+    def rename(self, db, new_title):
+        if not self.id:
+            return False
+        pagename =  new_title.replace(" ", "_")
+        if self.is_exist(db, self.site, pagename):
+            raise PageExist
+            
+        alias = AliasPage.get_alias(db, self.site, pagename)
+        if alias is not None: # if an alias exist, we remove it.
+            del db[alias.id]
+            
+        new_alias = AliasPage(
+            title=self.title,
+            site=self.site,
+            page=self.id
+        )
+        new_alias.store(db)
+        
+        self.title = new_title
+        self.store(db)
+        return self
+

File couchit/template.py

             extension_configs = {'wikilinks': [
                                         ('base_url', base_url),
                                         ('html_class', ''),
-                                        ('end_url', '') ]},
-            safe_mode = 'escape')
+                                        ('end_url', '') ]}
+    )
     
     return md.convert(value)
 template_env.filters['markdown'] = convert_markdown

File couchit/views.py

 from werkzeug.routing import NotFound
 from werkzeug.utils import url_unquote, generate_etag
 from couchit import settings
-from couchit.models import Site, Page, PasswordToken
+from couchit.models import *
 from couchit.api import *
 from couchit.http import BCResponse
 from couchit.template import render_response, url_for, render_template, send_json
     if pagename is None:
         pagename ='home'
         
+    redirect_from = request.values.get('redirect_from', '')
+        
     page = get_page(local.db, request.site.id, pagename)
+    if not page or page.id is None:
+        alias = AliasPage.get_alias(local.db, request.site.id, pagename)
+        if alias is not None:
+            page = Page.load(local.db, alias.page)
+            return redirect(url_for('show_page', 
+            pagename=page.title.replace(' ', '_'),
+            redirect_from=pagename))
+    
     if not page or page.id is None or not re_page.match(pagename):
         if pagename.lower() in FORBIDDEN_PAGES:
             redirect_url = "%s?error=%s" % (
     
    
     return render_response('page/show.html', page=page, pages=pages, 
-        lexers=LEXERS_CHOICE)
+        lexers=LEXERS_CHOICE, redirect_from=redirect_from)
 
 @can_edit
 def edit_page(request, pagename=None):
             title=pagename.replace("_", " ")
         )
         
+    if request.is_xhr and request.method=="POST":
+        error = ""
+        data = json.loads(request.data)
+        new_title = data.get('new_title')
+        if new_title and new_title is not None:
+            try:
+                page.rename(local.db, new_title)
+                redirect_url = url_for('show_page', pagename=new_title.replace(' ', '_'))
+                return send_json({"ok": True, "redirect_url": redirect_url})
+            except PageExist:
+                error = "A page already exist with this name"
+            else:
+                error = "An unexpected error happened, please contact administrator."
+                
+            
+        else:
+            error = u"New title is empty"
+        return send_json({
+            "ok": False,
+            "error": error
+        })
+            
     if request.method == "POST":
-        page.content = request.form.get('content', '')
-        page.store(local.db)
-        redirect_url = url_for('show_page', pagename=pagename)
-        return redirect(redirect_url)
+        if 'new_title' in request.form:
+            new_title = request.form['new_title']
+            try:
+                page.rename(local.db, new_title)
+                redirect_url = url_for('show_page', pagename=new_title.replace(' ', '_'))
+                return redirect(redirect_url)
+            except PageExist:
+                error = "A page already exist with this name"
+            else:
+                error = "An unexpected error happened, please contact administrator."
+        else:
+            page.content = request.form.get('content', '')
+            page.store(local.db)
+            redirect_url = url_for('show_page', pagename=pagename)
+            return redirect(redirect_url)
     
-    return redirect(url_for('show_page'))
+    return redirect(url_for('show_page', pagename=pagename))
 
 @can_edit  
 def delete_page(request, pagename):
     diff = ''
     rev1 = rev2 = page
     revisions = request.values.getlist('r')
-    if revisions:
+    if revisions and len(revisions) >=2:
         diff, rev1, rev2 = get_diff(local.db, page, revisions[0], revisions[1])
     
     if request.is_xhr:

File static/css/screen.css

 header h2 {padding-bottom:10px;}
 header h1 a {text-decoration:none;}
 #page article,#pedit #pedit_wrapper {display:block;padding:10px;border-right:4px solid;border-left:4px solid;border-bottom:4px solid;-moz-border-radius-bottomleft:5px;-moz-border-radius-bottomright:5px;-khtml-border-bottom-left-radius:5px;-webkit-border-bottom-left-radius:5px;-khtml-border-bottom-right-radius:5px;-webkit-border-bottom-right-radius:5px;border-bottom-left-radius:5px;border-bottom-right-radius:5px;min-height:350px;}
+span.rename {font-size:12px;line-height:18px;vertical-align:middle;}
 #phistory, #pdiff,#pdesign,#psettings,#pgeneric {display:block;padding:10px;border:4px solid;-moz-border-radius:5px;-khtml-border-radius:5px;-webkit-radius:5px;border-radius:5px;min-height:350px;}
 #pgeneric {text-align:left;margin-top:10px;}
 #pdiff h4 {border-bottom:1px solid;}

File static/css/src/layout.css

     
 }
 
-span.rename {
-    font-size: 12px;
-    line-height: 18px;
-    vertical-align: middle;
+.rename {
+    display: inline;
+    width: 200px;
+    margin-left: 10px;
+    opacity: 0.6;
+    filter: 0.6;
 }
 
+
 #phistory, 
 #pdiff,
 #pdesign,

File static/css/src/typography.css

     font-weight: bold;
     text-decoration: none;
 }
+
+.rename {
+    font-size: 12px;
+    line-height: 18px;
+    vertical-align: middle;
+}

File static/img/favicon.ico

Binary file added.

File static/img/favicon.png

Added
New image

File static/js/src/page.js

                 }
                 document.location.href=this.href;
             }, false);
+            
+        if (!Page.created && !Page.home) {
+            
+            this.page_title = $('page_title');
+            this.page_title.setStyle({
+                'cursor': 'pointer'
+            });
+            
+            this.createRenameForm();
+            //this.createRenameHelp();
+
+            this.page_title.observe('click', function(e) {
+                self.handleRename();
+            }, false);
+        }
+            
     },
     
     init: function() {
         if (active_container)
             this.update_tabs(active_container);
         
-        
-        
         /* init size of textarea */
         var new_height = document.viewport.getHeight() - 250;
         $('content').setStyle({'height': new_height + 'px'});
                         $('si').checked = true;
                 }, false);
             });
-            
-            
-            
+  
             
           },{  
               id: 'markdown_snippet_button'  
           },{  
               id: 'markdown_help_button'  
           });
+    },
+    
+    createRenameForm: function() {
+        var url_action = $('fedit').action;
+        this._form = new Element('form', {
+            'id': 'frename',
+            'method': 'post',
+            'action': url_action
+        });
+        var input = new Element('input', {
+            'type': 'text',
+            'id': 'new_title',
+            'name': 'new_title',
+            'value': $('page_title').innerHTML
+        });
+        var old_title = new Element('input', {
+            'type': 'hidden',
+            'id': 'old_title',
+            'name': 'old_title',
+            'value': $('page_title').innerHTML
+        });
+        
+        var cancel = new Element('a', {
+            'id': 'rcancel',
+            'class': 'cancel',
+            'href':'#'
+        }).update('Cancel');
+        this._form.insert(old_title);
+        this._form.insert(input);
+        this._form.insert(cancel);
+        this._form.onsubmit = this.renamePage.bind(this);
+        
+    },
+    
+    renamePage: function(e) {
+        Event.stop(e);
+        var new_title=$('new_title').getValue();
+        var old_title=$('old_title').getValue();
+        if (!new_title)
+            alert("Page title can't be empty.");
+        else if (old_title == new_title) {
+            this._form.remove();
+            this.createRenameForm();
+            //this.createRenameHelp();
+            this.page_title.show();
+        } else {
+            new Ajax.Request(this._form.action, {
+              method: 'post',
+              contentType: 'application/json', 
+              requestHeaders: {Accept: 'application/json'},
+              postBody: Object.toJSON(this._form.serialize(true)),
+              onSuccess: function(response) {
+                  data = response.responseText.evalJSON(true);
+                  if (data['ok']) {
+                      document.location.href = data['redirect_url'];
+                  } else {
+                      alert (data['error']);
+                  }
+
+              },
+              onFailure: function() {
+                  alert("mmm... error while trying rename :(, Please contact administrator")
+              }
+             });
+        } 
+        
+    },
+    
+    createRenameHelp: function() {
+        var self = this;
+        this._help = new Element('div', {
+            'class': 'rename hidden',
+        }).update('&#x21E4; Click to rename');
+        this.page_title.insert(this._help);
+        
+        this.page_title.observe('mouseover', function(e) {
+            self._help.removeClassName('hidden');
+        }, false);
+        
+        this.page_title.observe('mouseout', function(e) {
+            self._help.addClassName('hidden');
+        }, false);
+    },
+    
+    
+    handleRename: function() {
+        var self = this;
+        this.page_title.hide();
+        //this._help.remove();
+        this.page_title.parentNode.insertBefore(this._form, $('page_title'));
+        this._form.select('.cancel').each(function(el) {
+            el.observe("click", function(e) {
+                Event.stop(e);
+                self._form.remove();
+                self.createRenameForm();
+                //self.createRenameHelp();
+                self.page_title.show();
+                return false;
+            }, false);
+        });
+        
+        
     }
-})
+});

File templates/page/show.html

 </section>
 {% endif %}
 
+{% if redirect_from %}
+<section id="redirect">
+    <p>This page was redirected from &quot;<strong>{{ redirect_from.replace("_", "") }}</strong>&quot;.</p>
+</section>
+{% endif %}
+
 <ul id="tabs_wiki" class="subsection_tabs">  
      <li class="tab tab-view"><a href="#pview">VIEW</a></li>
      {% if can_edit %}