Merge branch 'legacy/0.9'
Conflicts: isso/migrate.py
This commit is contained in:
commit
d70eb160b9
8
Makefile
8
Makefile
@ -7,7 +7,7 @@ ISSO_JS_DST := isso/js/embed.min.js isso/js/embed.dev.js \
|
||||
|
||||
ISSO_CSS := isso/css/isso.css
|
||||
|
||||
ISSO_PY_SRC := $(shell git ls-files | grep .py)
|
||||
ISSO_PY_SRC := $(shell git ls-files | grep -E "^isso/.+.py$$")
|
||||
|
||||
DOCS_RST_SRC := $(shell find docs/ -type f -name '*.rst') \
|
||||
$(wildcard docs/_isso/*) \
|
||||
@ -30,6 +30,12 @@ all: man js site
|
||||
init:
|
||||
(cd isso/js; bower install almond requirejs requirejs-text jade)
|
||||
|
||||
check:
|
||||
@echo "Python 2.x"
|
||||
-@python2 -m pyflakes $(ISSO_PY_SRC)
|
||||
@echo "Python 3.x"
|
||||
-@python3 -m pyflakes $(ISSO_PY_SRC)
|
||||
|
||||
isso/js/%.min.js: $(ISSO_JS_SRC) $(ISSO_CSS)
|
||||
r.js -o isso/js/build.$*.js out=$@
|
||||
|
||||
|
@ -81,7 +81,8 @@ notify
|
||||
Available backends:
|
||||
|
||||
stdout
|
||||
Log to standard output. Default, if none selected.
|
||||
Log to standard output. Default, if none selected. Note, this
|
||||
functionality is broken since a few releases.
|
||||
|
||||
smtp
|
||||
Send notifications via SMTP on new comments with activation (if
|
||||
|
@ -23,7 +23,6 @@ Isso:
|
||||
"mode": 1,
|
||||
"hash": "4505c1eeda98",
|
||||
"author": null,
|
||||
"email": null,
|
||||
"website": null
|
||||
"created": 1387321261.572392,
|
||||
"modified": null,
|
||||
|
@ -49,8 +49,8 @@ but not recommended):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> virtualenv /path/to/isso
|
||||
~> source /path/to/isso/bin/activate
|
||||
~> virtualenv /opt/isso
|
||||
~> source /opt/isso/bin/activate
|
||||
|
||||
After calling `source`, you can now install packages from PyPi locally into this
|
||||
virtual environment. If you don't like Isso anymore, you just `rm -rf` the
|
||||
@ -58,6 +58,11 @@ folder. Inside this virtual environment, you may also execute the example
|
||||
commands from above to upgrade your Python Package Manager (although it barely
|
||||
makes sense), it is completely independent from your global system.
|
||||
|
||||
To use Isso installed in a virtual environment outside of the virtual
|
||||
environment, you just need to add */opt/isso/bin* to your :envvar:`PATH` or
|
||||
execute */opt/isso/bin/isso* directly. It will launch Isso from within the
|
||||
virtual environment.
|
||||
|
||||
With a virtualenv active, you may now continue to :ref:`install-from-pypi`!
|
||||
Of course you may not need a virtualenv when you are running dedicated virtual
|
||||
machines or a shared host (e.g. Uberspace.de).
|
||||
@ -112,7 +117,7 @@ For easier execution, you can symlink the executable to a location in your
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> ln -s /path/to/isso-venv/bin/isso /usr/local/bin/isso
|
||||
~> ln -s /opt/isso/bin/isso /usr/local/bin/isso
|
||||
|
||||
Upgrade
|
||||
^^^^^^^
|
||||
@ -121,7 +126,7 @@ To upgrade Isso, activate your virtual environment again, and run
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> source /path/to/isso/bin/activate # optional
|
||||
~> source /opt/isso/bin/activate # optional
|
||||
~> pip install --upgrade isso
|
||||
|
||||
.. _prebuilt-package:
|
||||
@ -205,7 +210,54 @@ Init scripts to run Isso as a service (check your distribution's documentation
|
||||
for your init-system; e.g. Debian uses SysVinit, Fedora uses SystemD) if you
|
||||
don't use FastCGi or uWSGI:
|
||||
|
||||
- SystemD: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.service
|
||||
- SysVinit: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.init
|
||||
- SystemD (Isso + Gunicorn): https://github.com/jgraichen/debian-isso/blob/master/debian/isso.service
|
||||
- SysVinit (Isso + Gunicorn): https://github.com/jgraichen/debian-isso/blob/master/debian/isso.init
|
||||
- OpenBSD: https://gist.github.com/noqqe/7397719
|
||||
- Supervisor: https://github.com/posativ/isso/issues/47
|
||||
|
||||
If you're writing your own init script, you can utilize ``start-stop-daemon``
|
||||
to run Isso in the background (Isso runs in the foreground usually). Below you
|
||||
find a very basic SysVinit script which you can use for inspiration:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: isso
|
||||
# Required-Start: $local_fs $network
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Description: lightweight Disqus alternative
|
||||
### END INIT INFO
|
||||
|
||||
EXEC=/opt/isso/bin/isso
|
||||
EXEC_OPTS="-c /etc/isso.cfg"
|
||||
|
||||
RUNAS=isso
|
||||
PIDFILE=/var/run/isso.pid
|
||||
|
||||
start() {
|
||||
echo 'Starting service…' >&2
|
||||
start-stop-daemon --start --user "$RUNAS" --background --make-pidfile --pidfile $PIDFILE \
|
||||
--exec $EXEC -- $EXEC_OPTS
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo 'Stopping service…' >&2
|
||||
start-stop-daemon --stop --user "$RUNAS" --pidfile $PIDFILE --exec $EXEC
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
retart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
|
@ -12,6 +12,21 @@ Install Isso in a virtual environment as described in
|
||||
:ref:`install-interludium`. Alternatively, you can use `pip install --user`
|
||||
to install Isso into the user's home.
|
||||
|
||||
UnicodeDecodeError: 'ascii' codec can't decode byte 0xff
|
||||
--------------------------------------------------------
|
||||
|
||||
Likely an issue with your environment, check you set your preferred file
|
||||
encoding either in :envvar:`LANG`, :envvar:`LANGUAGE`, :envvar:`LC_ALL` or
|
||||
:envvar:`LC_CTYPE`:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
$ env LANG=C.UTF-8 isso [-h] [--version] ...
|
||||
|
||||
If none of the mentioned variables is set, the interaction with Isso will
|
||||
likely fail (unable to print non-ascii characters to stdout/err, unable to
|
||||
parse configuration file with non-ascii characters and to forth).
|
||||
|
||||
The web console shows 404 Not Found responses
|
||||
---------------------------------------------
|
||||
|
||||
|
@ -211,6 +211,8 @@ def main():
|
||||
help="perform a trial run with no changes made")
|
||||
imprt.add_argument("-t", "--type", dest="type", default=None,
|
||||
choices=["disqus", "wordpress"], help="export type")
|
||||
imprt.add_argument("--empty-id", dest="empty_id", action="store_true",
|
||||
help="workaround for weird Disqus XML exports, #135")
|
||||
|
||||
serve = subparser.add_parser("run", help="run server")
|
||||
|
||||
@ -227,7 +229,7 @@ def main():
|
||||
dbpath = conf.get("general", "dbpath")
|
||||
|
||||
mydb = db.SQLite3(dbpath, conf)
|
||||
migrate.dispatch(args.type, mydb, args.dump)
|
||||
migrate.dispatch(args.type, mydb, args.dump, args.empty_id)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
@ -9,6 +9,8 @@ from collections import defaultdict
|
||||
|
||||
logger = logging.getLogger("isso")
|
||||
|
||||
from isso.compat import buffer
|
||||
|
||||
from isso.db.comments import Comments
|
||||
from isso.db.threads import Threads
|
||||
from isso.db.spam import Guard
|
||||
|
27
isso/js/app/i18n/bg.js
Normal file
27
isso/js/app/i18n/bg.js
Normal file
@ -0,0 +1,27 @@
|
||||
define({
|
||||
"postbox-text": "Въведете коментара си тук (поне 3 знака)",
|
||||
"postbox-author": "Име/псевдоним (незадължително)",
|
||||
"postbox-email": "Ел. поща (незадължително)",
|
||||
"postbox-website": "Уебсайт (незадължително)",
|
||||
"postbox-submit": "Публикуване",
|
||||
"num-comments": "1 коментар\n{{ n }} коментара",
|
||||
"no-comments": "Все още няма коментари",
|
||||
"comment-reply": "Отговор",
|
||||
"comment-edit": "Редактиране",
|
||||
"comment-save": "Запис",
|
||||
"comment-delete": "Изтриване",
|
||||
"comment-confirm": "Потвърждение",
|
||||
"comment-close": "Затваряне",
|
||||
"comment-cancel": "Отказ",
|
||||
"comment-deleted": "Коментарът е изтрит.",
|
||||
"comment-queued": "Коментарът чака на опашката за модериране.",
|
||||
"comment-anonymous": "анонимен",
|
||||
"comment-hidden": "{{ n }} скрити",
|
||||
"date-now": "сега",
|
||||
"date-minute": "преди 1 минута\nпреди {{ n }} минути",
|
||||
"date-hour": "преди 1 час\nпреди {{ n }} часа",
|
||||
"date-day": "вчера\nпреди {{ n }} дни",
|
||||
"date-week": "миналата седмица\nпреди {{ n }} седмици",
|
||||
"date-month": "миналия месец\nпреди {{ n }} месеца",
|
||||
"date-year": "миналата година\nпреди {{ n }} години"
|
||||
});
|
@ -2,6 +2,7 @@ define({
|
||||
"postbox-text": "Scrivi un commento qui (minimo 3 caratteri)",
|
||||
"postbox-author": "Nome (opzionale)",
|
||||
"postbox-email": "E-mail (opzionale)",
|
||||
"postbox-website": "Sito web (opzionale)",
|
||||
"postbox-submit": "Invia",
|
||||
"num-comments": "Un Commento\n{{ n }} Commenti",
|
||||
"no-comments": "Ancora Nessun Commento",
|
||||
@ -15,6 +16,7 @@ define({
|
||||
"comment-deleted": "Commento eliminato.",
|
||||
"comment-queued": "Commento in coda per moderazione.",
|
||||
"comment-anonymous": "Anonimo",
|
||||
"comment-hidden": "{{ n }} Nascosto",
|
||||
"date-now": "poco fa",
|
||||
"date-minute": "un minuto fa\n{{ n }} minuti fa",
|
||||
"date-hour": "un ora fa\n{{ n }} ore fa",
|
||||
|
@ -7,7 +7,8 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n",
|
||||
|
||||
var Postbox = function(parent) {
|
||||
|
||||
var el = $.htmlify(jade.render("postbox", {
|
||||
var localStorage = utils.localStorageImpl,
|
||||
el = $.htmlify(jade.render("postbox", {
|
||||
"author": JSON.parse(localStorage.getItem("author")),
|
||||
"email": JSON.parse(localStorage.getItem("email")),
|
||||
"website": JSON.parse(localStorage.getItem("website"))
|
||||
@ -184,7 +185,7 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n",
|
||||
$("a.edit", footer).toggle("click",
|
||||
function(toggler) {
|
||||
var edit = $("a.edit", footer);
|
||||
var avatar = $(".avatar", el, false)[0];
|
||||
var avatar = config["avatar"] ? $(".avatar", el, false)[0] : null;
|
||||
|
||||
edit.textContent = i18n.translate("comment-save");
|
||||
edit.insertAfter($.new("a.cancel", i18n.translate("comment-cancel"))).on("click", function() {
|
||||
@ -212,7 +213,7 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n",
|
||||
},
|
||||
function(toggler) {
|
||||
var textarea = $(".textarea", text);
|
||||
var avatar = $(".avatar", el, false)[0];
|
||||
var avatar = config["avatar"] ? $(".avatar", el, false)[0] : null;
|
||||
|
||||
if (! toggler.canceled && textarea !== null) {
|
||||
if (utils.text(textarea.innerHTML).length < 3) {
|
||||
|
@ -68,11 +68,34 @@ define(["app/i18n"], function(i18n) {
|
||||
.replace(/\n/gi, '<br>');
|
||||
};
|
||||
|
||||
// Safari private browsing mode supports localStorage, but throws QUOTA_EXCEEDED_ERR
|
||||
var localStorageImpl;
|
||||
try {
|
||||
localStorage.setItem("x", "y");
|
||||
localStorage.removeItem("x");
|
||||
localStorageImpl = localStorage;
|
||||
} catch (ex) {
|
||||
localStorageImpl = (function(storage) {
|
||||
return {
|
||||
setItem: function(key, val) {
|
||||
storage[key] = val;
|
||||
},
|
||||
getItem: function(key) {
|
||||
return typeof(storage[key]) !== 'undefined' ? storage[key] : null;
|
||||
},
|
||||
removeItem: function(key) {
|
||||
delete storage[key];
|
||||
}
|
||||
};
|
||||
})({});
|
||||
}
|
||||
|
||||
return {
|
||||
cookie: cookie,
|
||||
pad: pad,
|
||||
ago: ago,
|
||||
text: text,
|
||||
detext: detext
|
||||
detext: detext,
|
||||
localStorageImpl: localStorageImpl
|
||||
};
|
||||
});
|
||||
|
@ -8,6 +8,7 @@ import io
|
||||
import re
|
||||
import logging
|
||||
import textwrap
|
||||
import functools
|
||||
|
||||
from time import mktime, strptime, time
|
||||
from collections import defaultdict
|
||||
@ -67,12 +68,13 @@ class Disqus(object):
|
||||
ns = '{http://disqus.com}'
|
||||
internals = '{http://disqus.com/disqus-internals}'
|
||||
|
||||
def __init__(self, db, xmlfile):
|
||||
def __init__(self, db, xmlfile, empty_id=False):
|
||||
self.threads = set([])
|
||||
self.comments = set([])
|
||||
|
||||
self.db = db
|
||||
self.xmlfile = xmlfile
|
||||
self.empty_id = empty_id
|
||||
|
||||
def insert(self, thread, posts):
|
||||
|
||||
@ -119,7 +121,7 @@ class Disqus(object):
|
||||
progress.update(i, thread.find(Disqus.ns + 'id').text)
|
||||
|
||||
# skip (possibly?) duplicate, but empty thread elements
|
||||
if thread.find(Disqus.ns + 'id').text is None:
|
||||
if thread.find(Disqus.ns + 'id').text is None and not self.empty_id:
|
||||
continue
|
||||
|
||||
id = thread.attrib.get(Disqus.internals + 'id')
|
||||
@ -134,7 +136,9 @@ class Disqus(object):
|
||||
len(self.threads), len(self.comments)))
|
||||
|
||||
orphans = set(map(lambda e: e.attrib.get(Disqus.internals + "id"), tree.findall(Disqus.ns + "post"))) - self.comments
|
||||
if orphans:
|
||||
if orphans and not self.threads:
|
||||
print("Isso couldn't import any thread, try again with --empty-id")
|
||||
elif orphans:
|
||||
print("Found %i orphans:" % len(orphans))
|
||||
for post in tree.findall(Disqus.ns + "post"):
|
||||
if post.attrib.get(Disqus.internals + "id") not in orphans:
|
||||
@ -158,7 +162,7 @@ class WordPress(object):
|
||||
self.xmlfile = xmlfile
|
||||
self.count = 0
|
||||
|
||||
for line in io.open(xmlfile):
|
||||
for line in io.open(xmlfile, encoding="utf-8"):
|
||||
m = WordPress.detect(line)
|
||||
if m:
|
||||
self.ns = WordPress.ns.replace("1.0", m.group(1))
|
||||
@ -253,7 +257,7 @@ def autodetect(peek):
|
||||
return None
|
||||
|
||||
|
||||
def dispatch(type, db, dump):
|
||||
def dispatch(type, db, dump, empty_id=False):
|
||||
if db.execute("SELECT * FROM comments").fetchone():
|
||||
if input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"):
|
||||
raise SystemExit("Abort.")
|
||||
@ -263,10 +267,13 @@ def dispatch(type, db, dump):
|
||||
elif type == "wordpress":
|
||||
cls = WordPress
|
||||
else:
|
||||
with io.open(dump) as fp:
|
||||
with io.open(dump, encoding="utf-8") as fp:
|
||||
cls = autodetect(fp.read(io.DEFAULT_BUFFER_SIZE))
|
||||
|
||||
if cls is None:
|
||||
raise SystemExit("Unknown format, abort.")
|
||||
|
||||
if cls is Disqus:
|
||||
cls = functools.partial(cls, empty_id=empty_id)
|
||||
|
||||
cls(db, dump).migrate()
|
||||
|
Loading…
Reference in New Issue
Block a user