]> git.0d.be Git - panikdb.git/commitdiff
initiate wiki stuff
authorFrédéric Péters <fpeters@0d.be>
Thu, 11 Jun 2020 13:07:33 +0000 (15:07 +0200)
committerFrédéric Péters <fpeters@0d.be>
Thu, 11 Jun 2020 13:07:58 +0000 (15:07 +0200)
panikdb/settings.py
panikdb/static/css/style.scss
panikdb/static/js/combo.wiki.js [new file with mode: 0644]
panikdb/templates/base.html
panikdb/templates/combo/page_template_sidebar.html
panikdb/templates/combo/placeholder.html [new file with mode: 0644]
panikdb/urls.py

index 67b3a6fe920b25a6ea9f1b8117e8508d74e9cee0..16e726a6682d00b227bb58c97eae63f8f137857c 100644 (file)
@@ -113,6 +113,9 @@ TEMPLATES = [
                 'panikdb.context_processors.internal_ip',
                 'panikdb.context_processors.site_settings',
             ],
+            'builtins': [
+                'combo.public.templatetags.combo',
+            ],
         },
     },
 ]
@@ -156,12 +159,16 @@ INSTALLED_APPS = (
     'panikdb.poll',
     'gadjo',
     'combo.data',
+    'combo.profile',
+    'combo.public',
     'combo.manager',
     'combo.apps.assets',
+    'combo.apps.dashboard',
     'combo.apps.gallery',
     'combo.apps.maps',
     'combo.apps.notifications',
     'combo.apps.pwa',
+    'combo.apps.search',
     'sorl.thumbnail',
     'panikombo',
     'django_select2',
@@ -250,6 +257,8 @@ p a:hover { border-bottom: 1px solid black; }
 RAVEN_CONFIG = None
 
 from panikombo.misc import COMBO_PUBLIC_TEMPLATES
+
+COMBO_DASHBOARD_ENABLED = False
 COMBO_DEFAULT_PUBLIC_TEMPLATE = 'standard'
 COMBO_MAP_TILE_URLTEMPLATE = ''
 COMBO_MAP_ATTRIBUTION = ''
index aa663f5a7ad974934faa4e7af53cd761c6d02127..81d679d3ed3ccbdf1e1bc1972a1665b10596530c 100644 (file)
@@ -635,3 +635,127 @@ div#content div.section > h3 {
        background: linear-gradient(to right, #386ede 30%, #00d6eb 100%);
        color: white;
 }
+
+div#content div.wiki-section h3 {
+       margin-bottom: 0;
+}
+
+label#quickedit {
+       position: absolute;
+       right: 2px;
+       input {
+               display: none;
+       }
+
+       span {
+               background: white;
+               border: 1px solid #386ede;
+               padding: 1ex;
+               border-radius: 3px;
+               color: #386ede;
+       }
+
+       input:checked + span {
+               background: #386ede;
+               color: white;
+       }
+}
+
+div[contenteditable=true] {
+       outline: 2px solid #386ede;
+       outline-offset: 5px;
+}
+
+.inline-style-popup,
+.block-style-popup {
+       background: white;
+       box-shadow: 0 0 5px #666;
+       input {
+               display: none;
+               margin: 0;
+               padding: 3px;
+               border: 1px inset #ccc;
+               background: white;
+               width: 0px;
+               transition: width ease 2s;
+               &.shown {
+                       display: inline-block;
+                       width: 400px;
+               }
+       }
+       button {
+               padding: 0 0.5em;
+               height: 2em;
+               text-align: center;
+               font-weight: normal;
+               color: #333;
+               background: #eee;
+               border: 0px;
+               &:hover {
+                       background: #ccc;
+               }
+               &[data-action=createLink] {
+                       color: blue;
+                       text-decoration: underline;
+               }
+               &.on {
+                       background: #444;
+                       color: white;
+               }
+       }
+       &.inline-style-popup button {
+               width: 2em;
+               padding: 0;
+       }
+       &.block-style-popup {
+               &.selected button {
+                       display: none;
+                       &.on {
+                               display: block;
+                       }
+               }
+       }
+}
+
+button.save {
+       position: sticky;
+       bottom: 10px;
+       background: white;
+       border: 1px solid #386ede;
+       color: #386ede;
+       vertical-align: baseline;
+       border-radius: 3px;
+       font-weight: bold;
+       padding: 5px 15px;
+       cursor: pointer;
+       &:hover {
+               color: white;
+               background: #386ede;
+       }
+}
+
+div.wiki-section {
+       position: relative;
+       margin: 0 auto;
+       max-width: 60em;
+       font-size: 16px;
+       pre.code {
+               background: #111;
+               color: white;
+               padding: 2px;
+       }
+       div.note {
+               margin: 1em auto;
+               width: 80%;
+               border: 1px solid #babdb6;
+               background: #F3F3F0 url(https://doc.entrouvert.org/wcs/dev/yelp-note.png) no-repeat 1ex 1ex;
+               padding: 1ex 1ex 1ex 7ex;
+               p:first-child {
+                       margin-top: 0;
+               }
+       }
+}
+
+div#content div.wiki-section h4.intertitle {
+       margin-bottom: 1em;
+}
diff --git a/panikdb/static/js/combo.wiki.js b/panikdb/static/js/combo.wiki.js
new file mode 100644 (file)
index 0000000..b24dc39
--- /dev/null
@@ -0,0 +1,289 @@
+// from django/contrib/admin/static/admin/js/urlify.js
+var LATIN_MAP = {
+    'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE',
+    'Ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I',
+    'Î': 'I', 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O',
+    'Õ': 'O', 'Ö': 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U',
+    'Ü': 'U', 'Ű': 'U', 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à': 'a',
+    'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c',
+    'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i',
+    'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o',
+    'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
+    'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y'
+};
+
+function downcode(string) {
+  return string.toLowerCase().replace(/[^A-Za-z0-9\[\] ]/g,function(a){ return LATIN_MAP[a]||a }).replace(/[^-\w\s]/g, '').replace(/^\s+|\s+$/g, '').replace(/[-\s]+/g, '-');
+};
+
+function remove_auto_anchors() {
+  $('div#main-content .wiki-anchor-auto').each(function(idx, anchor) {
+          $(anchor).parent().removeAttr('id');
+          $(anchor).remove();
+  });
+}
+
+function auto_anchors() {
+  $('div#main-content div.textcell h1, div#main-content div.textcell h2, div#main-content div.textcell h3').each(function(idx, elem) {
+    var $elem = $(elem);
+    if ($elem.attr('id')) return;
+    if ($elem.find('.wiki-anchor').length) return;
+    $elem.attr('id', downcode($elem.text()));
+    $('<a class="wiki-anchor wiki-anchor-auto" href="#' + $elem.attr('id') + '">¶</a>').appendTo($elem);
+  });
+}
+
+(function(window, document, undefined) {
+  var Phylly = {
+    BLOCKS: [
+          {name: 'intertitre', tag: 'h4', klass: 'intertitle'},
+          {name: 'code', tag: 'PRE', klass: 'code'},
+          {name: 'note', tag: 'DIV', subtag: true, klass: 'note'},
+    ],
+    input_event: function(event) {
+      if (event.originalEvent.inputType != "insertParagraph") return true;
+      var sel = document.getSelection();
+      var anchorNode = sel.anchorNode;
+      var prev_p = sel.anchorNode.previousSibling;
+      if (! prev_p) return;
+      if (prev_p.tagName != 'P') {
+        prev_p = $(prev_p).parents('p')[0];
+      }
+      var title_match = prev_p.innerText.match(/^(h[1-6]). /);
+      if (title_match) {
+        var title = document.createElement(title_match[1]);
+        title.innerHTML = prev_p.innerHTML;
+        title.textContent = title.textContent.slice(4);
+        prev_p.replaceWith(title);
+      }
+      return true;
+    },
+
+    init: function() {
+      $(document).on('selectionchange', function(event) {
+        if ($('input[name=link-target].shown').length) {
+          return;
+        }
+        var sel = window.getSelection();
+        if ($(sel.anchorNode).parents('div[contenteditable]').length && sel.toString()) {
+          show_style_popup(sel);
+        } else if (style_popup) {
+          $(style_popup).hide();
+        }
+      });
+    },
+
+    off: function() {
+      $(document).off('selectionchange');
+    },
+
+    bind_events: function(elem) {
+      $(elem).on('input', Phylly.input_event);
+      $(elem).on('keyup click', update_block_style_popup);
+    },
+
+    unbind_events: function(elem) {
+      $(elem).off('input');
+      $(elem).off('keyup click');
+    },
+
+  }
+  window.Phylly = Phylly;
+
+  function get_contenteditable_subnode(node) {
+    if (node === null) return null;
+    if (node.parentNode.contentEditable === 'true') return node;
+    return get_contenteditable_subnode(node.parentNode);
+  }
+  function get_active_block(node) {
+    var main_node = get_contenteditable_subnode(node);
+    if (main_node === null) return null;
+    for (const block of Phylly.BLOCKS) {
+      if (main_node.tagName === block.tag && main_node.classList.contains(block.klass))
+        return block;
+    }
+    return null;
+  }
+
+  var block_style_popup = null;
+  function block_style() {
+    var sel = window.getSelection();
+    var current_anchor = sel.anchorNode;
+    if (this.classList.contains('on')) { // toggle off
+      if (this.action_block.subtag) {
+        // unwrap
+        var main_node = get_contenteditable_subnode(current_anchor);
+        $(current_anchor).detach().insertAfter(main_node);
+      } else {
+        document.execCommand('formatBlock', false, 'p');
+        current_anchor = sel.anchorNode;
+      }
+    } else {
+      action = this.action_block.subtag || this.action_block.tag;
+      if (this.action_block.subtag) {
+        // enclose current tag into a new parent;
+        var new_parent = document.createElement(this.action_block.tag);
+        new_parent.className = this.action_block.klass;
+        $(current_anchor).wrap(new_parent);
+      } else {
+        document.execCommand('formatBlock', false, this.action_block.tag);
+        sel.anchorNode.className = this.action_block.klass;
+        current_anchor = sel.anchorNode;
+      }
+    }
+    var range = document.createRange();
+    range.setStart(current_anchor, 0);
+    sel.removeAllRanges();
+    sel.addRange(range);
+    update_block_style_popup();
+  }
+  function update_block_style_popup() {
+    var sel = window.getSelection();
+    if (! ((sel.anchorNode instanceof Element && (sel.anchorOffset == 0 && sel.isCollapsed)) || get_active_block(sel.anchorNode))) {
+      if (block_style_popup) {
+        $(block_style_popup).hide();
+      }
+      return true;
+    }
+    if (block_style_popup === null) {
+      block_style_popup = $('<div class="block-style-popup"></div>');
+      for (const block of Phylly.BLOCKS) {
+        var button = document.createElement('button');
+        button.action_block = block;
+        button.dataset.action = block.name;
+        button.textContent = block.name;
+        block_style_popup.append(button);
+      }
+      block_style_popup.hide();
+      block_style_popup.insertAfter(document.body);
+      block_style_popup.find('button').on('click', block_style);
+    }
+    block_style_popup.css('position', 'absolute');
+    var block = get_active_block(sel.anchorNode);
+    block_style_popup.find('button').removeClass('on');
+    if (block) {
+      block_style_popup.find('[data-action=' + block.name + ']').addClass('on');
+      block_style_popup.addClass('selected');
+    } else {
+      block_style_popup.removeClass('selected');
+    }
+    var anchor = get_contenteditable_subnode(sel.anchorNode);
+    var pos = $(anchor).offset();
+    block_style_popup.css('top', pos.top - 33);
+    block_style_popup.css('left', pos.left);
+    block_style_popup.show();
+    return true;
+  }
+
+  var style_popup = null;
+  function update_style() {
+    var action = $(this).data('action');
+    var param = null;
+    if (action == 'code') {
+      action = 'insertHTML';
+      param = $('<code></code>', {text: window.getSelection().toString()})[0].outerHTML;
+    }
+    if (action == 'createLink') {
+      var sel = window.getSelection();
+      var $input = $('input[name=link-target]');
+      $input[0]._range = sel.getRangeAt(0);
+      if (sel.anchorNode instanceof Element) {
+        var elem = sel.anchorNode.childNodes[sel.anchorOffset];
+        if (elem.tagName == 'A') {
+          $input.val(elem.href);
+        }
+      }
+      $input.addClass('shown');
+      $input.focus();
+      return;
+    }
+    document.execCommand(action, false, param);
+  }
+  function validate_link(ev) {
+    var charCode = typeof ev.which == "number" ? ev.which : ev.keyCode;
+    if (ev.key == "Enter") {
+      var $input = $(this);
+      var range = this._range;
+      var url = $input.val();
+      $input.removeClass('shown');
+      var sel = window.getSelection();
+      sel.addRange(this._range);
+      if (url) {
+        document.execCommand('createLink', false, url);
+      } else {
+        document.execCommand('unlink', false, null);
+      }
+      sel.empty();
+      $input.val('');
+    }
+  }
+  function focusout_link(ev) {
+    var $input = $(this);
+    $input.removeClass('shown');
+    var range = this._range;
+    var sel = window.getSelection();
+    sel.addRange(this._range);
+  }
+
+  function show_style_popup(sel) {
+    if (style_popup === null) {
+      style_popup = $('<div class="inline-style-popup">' +
+                      '<button data-action="italic"><i>i</i></button>' +
+                      '<button data-action="bold"><b>b</b></button>' +
+                      '<button data-action="code">&lt;&gt;</button>' +
+                      '<button data-action="removeFormat">×</button>' +
+                      '<button data-action="createLink">a</button>' +
+                      '<input name="link-target"/>' +
+                      '</div>');
+      style_popup.hide();
+      style_popup.insertAfter(document.body);
+      style_popup.find('button').on('click', update_style);
+      style_popup.find('[name=link-target]').on('keypress', validate_link).on('focusout', focusout_link);
+    }
+    style_popup.css('position', 'absolute');
+    var pos = sel.getRangeAt(0).getClientRects()[0];
+    style_popup.css('top', pos.top + window.scrollY - 33);
+    style_popup.css('left', pos.left + window.scrollX);
+    style_popup.show();
+  };
+}(window, document));
+
+$(function() {
+  $('#quickedit input').on('change', function() {
+    var enable = $(this).is(':checked');
+    if (enable) {
+      remove_auto_anchors();
+      $('div[data-edit-url] > div').each(function(i, elem) {
+        $(elem).attr('contenteditable', 'true');
+        var $button = $('<button class="save">Enregistrer</button>');
+        $button[0].div_zone = elem;
+        elem.edit_url = $(elem).parents('[data-edit-url]').data('edit-url');
+        $button.insertAfter(elem);
+      });
+      Phylly.init(),
+      $('div[data-edit-url] > div').each(function(i, elem) {
+        Phylly.bind_events(elem);
+      });
+      $('.save').on('click', function() {
+        var csrf = $('[name=csrfmiddlewaretoken]').val();
+        attr = this.div_zone.edit_url.replace(/^.*(data_textcell.*)\//, 'c$1-text');
+        var params = {};
+        params[attr] = this.div_zone.innerHTML;
+        params['csrfmiddlewaretoken'] = csrf;
+        $.post(this.div_zone.edit_url, params).fail(function() {
+          $(this).css('background', 'red');
+        });
+        return false;
+      });
+    } else {
+      auto_anchors();
+      Phylly.off(),
+      $('button.save').remove();
+      $('div[data-edit-url] > div').each(function(i, elem) {
+        $(elem).attr('contenteditable', 'false');
+        Phylly.unbind_events(elem);
+      });
+    }
+  });
+  $('#quickedit input').trigger('change');
+});
index 6505c640af779e533b8a6515fdde85d6b5d2f4e2..5cffbac4174921135eeaa46cfed8ee4e77e58834 100644 (file)
     {% endblock %}
 
     {% block user-links %}
+    {% if request.user.is_authenticated %}
             <a class="icon-user-space" href="{% url 'profile-view' %}">{{ user.first_name }} {{ user.last_name }} ({{ user.username }})</a>
             <a class="icon-signout" href="{% url 'logout' %}">Déconnexion</a>
-            </span>
+    {% endif %}
     {% endblock %}
 
     {% block page-end %}
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8c6acb284ce6bbc1736c155184dd00bdd30876a4 100644 (file)
@@ -0,0 +1,19 @@
+{% extends "base.html" %}
+{% load i18n %}
+
+{% block gadjo-js %}
+{{ block.super }}
+<script src="{{ STATIC_URL }}js/combo.wiki.js"></script>
+{% endblock %}
+
+{% block content %}
+<div class="section wiki-section">
+<h3>{{ page.title }} {% if request.user.is_authenticated %}<label id="quickedit"><input type="checkbox"><span>Mode édition</span></label>{% endif %}</h3>
+{% csrf_token %}
+
+<div>
+{% placeholder "content" %}
+</div>
+
+</div>
+{% endblock %}
diff --git a/panikdb/templates/combo/placeholder.html b/panikdb/templates/combo/placeholder.html
new file mode 100644 (file)
index 0000000..14bfef8
--- /dev/null
@@ -0,0 +1,9 @@
+{% for cell in cells %}
+<div class="cell {{ cell.css_class_names }} {% if cell.slug %}{{cell.slug}}{% endif %}"
+     {% if cell.slug %}id="{{cell.slug}}"{% endif %}
+     {% if cell.get_cell_type_str == "data_textcell" and request.user.is_authenticated %}data-edit-url="{% url 'combo-manager-page-edit-cell' page_pk=page.id cell_reference=cell.get_reference %}"{% endif %}
+     {% if cell.ajax_refresh %}
+     data-ajax-cell-url="{% url 'combo-public-ajax-page-cell' page_pk=page.id cell_reference=cell.get_reference %}"
+     data-ajax-cell-refresh="{{ cell.ajax_refresh }}"
+     {% endif %}><div>{% render_cell cell %}</div></div>
+{% endfor %}
index ecfb65acc649850a9aef0d569bb48679e5cbfed7..5ef2167e569e0bb99bc4b09d55068ec4821a4242 100644 (file)
@@ -26,6 +26,7 @@ from .aa import views as aa_views
 from . import views
 
 from combo.manager.urls import urlpatterns as combo_manager_urls
+import combo.public.views
 
 from .urls_utils import decorated_includes, cms_permission_required
 
@@ -81,6 +82,10 @@ urlpatterns = [
     url(r'^vote/$', poll_views.vote, name='vote'),
     url(r'^vote/results/$', poll_views.vote_results, name='vote-results'),
 
+    url(r'^wiki/', combo.public.views.page),
+    url(r'^ajax/cell/(?P<page_pk>\d+)/(?P<cell_reference>[\w_]+-\d+)/$',
+        combo.public.views.ajax_page_cell, name='combo-public-ajax-page-cell'),
+
     url(r'^admin/', include(admin.site.urls)),
 ]