diff --git a/CHANGES.rst b/CHANGES.rst index aa6b471..49294b8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,38 @@ Changelog for Isso - Nothing changed yet. +0.9.5 (unreleased) +------------------ + +- Nothing changed yet. + + +0.9.4 (2014-07-09) +------------------ + +- fixed a regression when using Isso and Gevent + + +0.9.3 (2014-07-09) +------------------ + +- remove scrollIntoView while expanding further comments if a fragment is used + (e.g. #isso-thread brought you back to the top, unexpectedly) + +- implement a custom Markdown renderer to support multi-line code listings. The + extension "fenced_code" is now enabled by default and generates HTML + compatible with Highlight.js__. + +- escape HTML entities when editing a comment with raw HTML + +- fix CSS for input + +- remove isso.css from binary distribution to avoid confusion (it's still there + from the very first release, but modifications do not work) + +.. __: http://highlightjs.org/ + + 0.9 (2014-05-29) ---------------- diff --git a/MANIFEST.in b/MANIFEST.in index b877f8c..8f0481a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,5 +9,4 @@ include isso/js/embed.dev.js include isso/js/count.min.js include isso/js/count.dev.js -include isso/css/isso.css include isso/demo/index.html diff --git a/docs/_static/css/site.scss b/docs/_static/css/site.scss index 9c64a10..28e25ca 100644 --- a/docs/_static/css/site.scss +++ b/docs/_static/css/site.scss @@ -209,6 +209,21 @@ main { h4 { margin-bottom: 0.5em; } + + blockquote { + margin-top: 10px; + margin-bottom: 10px; + padding-left: 15px; + border-left: 3px solid #ccc; + } + + pre { + background: #eee; + border: 1px solid #ddd; + padding: 10px 15px; + color: #4d4d4c; + overflow: auto; + } } .sidebar { diff --git a/docs/conf.py b/docs/conf.py index 0437457..7e8c833 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,14 +12,27 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import pkg_resources -dist = pkg_resources.get_distribution("isso") - import sys +import io +import re +import pkg_resources from os.path import join, dirname sys.path.insert(0, join(dirname(__file__), "_isso/")) +try: + dist = pkg_resources.get_distribution("isso") +except pkg_resources.DistributionNotFound: + dist = type("I'm a Version", (object, ), {}) + with io.open(join(dirname(__file__), "../setup.py")) as fp: + for line in fp: + m = re.match("\s*version='([^']+)'\s*", line) + if m: + dist.version = m.group(1) + break + else: + dist.version = "" + # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. diff --git a/docs/docs/install.rst b/docs/docs/install.rst index e1edb3c..2b7a11b 100644 --- a/docs/docs/install.rst +++ b/docs/docs/install.rst @@ -47,8 +47,8 @@ but not recommended): .. code-block:: sh - ~> virtualenv /home/user/python/isso - ~> source /home/user/python/isso/activate + ~> virtualenv /path/to/isso + ~> source /path/to/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 @@ -69,11 +69,29 @@ machines or a shared host (e.g. Uberspace.de). Install from PyPi ----------------- -Requirements: +Requirements +^^^^^^^^^^^^ -- Python 2.7, 3.3 or 3.4 (+ devel headers) -- SQLite 3.3.8 or later -- a working C compiler +- Python 2.7, 3.3 or 3.4+ (+ devel headers) +- SQLite 3.3.8 or later +- a working C compiler + +For Debian/Ubuntu just `copy and paste +`_ to your terminal: + +.. code-block:: sh + + ~> sudo apt-get install python-dev sqlite3 build-essential + +Similar for Fedora (and derivates): + +.. code-block:: sh + + ~> sudo yum install python-devel sqlite + ~> sudo yum groupinstall “Development Tools” + +Installation +^^^^^^^^^^^^ Install Isso with `pip `_: @@ -88,16 +106,20 @@ Install Isso with `pip `_: ~> easy_install isso # cross your fingers For easier execution, you can symlink the executable to a location in your -PATH: +:envvar:`PATH`. .. code-block:: sh ~> ln -s /path/to/isso-venv/bin/isso /usr/local/bin/isso +Upgrade +^^^^^^^ + To upgrade Isso, activate your virtual environment again, and run .. code-block:: sh + ~> source /path/to/isso/bin/activate # optional ~> pip install --upgrade isso .. _prebuilt-package: @@ -108,16 +130,14 @@ Prebuilt Packages * Debian: https://packages.crapouillou.net/ – built from PyPi. Includes startup scripts and vhost configurations for Lighttpd, Apache and Nginx [`source `__]. - - `#729218 `_ is a - ITP for Debian. To be officially packages by Debian, `#51 - `_ needs to be done (contributions - are welcome). + `#729218 `_ is an + ITP for Debian. * Gentoo: http://eroen.eu/cgit/cgit.cgi/eroen-overlay/tree/www-apps/isso?h=isso – not yet available in Portage, but you can use the ebuild to build Isso. -* Arch Linux: https://aur.archlinux.org/packages/isso/ – built from PyPi. +* Arch Linux: https://aur.archlinux.org/packages/isso/ + – install with `yaourt isso`. Install from Source ------------------- diff --git a/isso/css/isso.css b/isso/css/isso.css index 380fd90..eef6b81 100644 --- a/isso/css/isso.css +++ b/isso/css/isso.css @@ -178,19 +178,12 @@ .isso-postbox > .form-wrapper > .auth-section .input-wrapper input { padding: .3em 10px; max-width: 100%; + border-radius: 3px; background-color: #fff; line-height: 1.4em; border: 1px solid rgba(0, 0, 0, 0.2); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } -.isso-postbox > .form-wrapper > .auth-section .input-wrapper:first-child input { - border-radius: 3px 0 0 3px; - border-right: 0; -} -.isso-postbox > .form-wrapper > .auth-section .input-wrapper:nth-last-child(2) input { - border-radius: 0 3px 3px 0; - border-left: 0; -} .isso-postbox > .form-wrapper > .auth-section .post-action { display: inline-block; float: right; @@ -220,7 +213,6 @@ } .isso-postbox > .form-wrapper > .auth-section .input-wrapper input { width: 100%; - border-radius: 3px; } .isso-postbox > .form-wrapper > .auth-section .post-action { display: block; diff --git a/isso/dispatch.py b/isso/dispatch.py index def980e..9429507 100644 --- a/isso/dispatch.py +++ b/isso/dispatch.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- +from __future__ import unicode_literals + import sys import os import logging diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index b00e1cc..4312d45 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -89,10 +89,6 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", if(rv.hidden_replies > 0) { insert_loader(rv, lastcreated); } - - if (window.location.hash.length > 0) { - $(window.location.hash).scrollIntoView(); - } }, function(err) { console.log(err); diff --git a/isso/js/app/utils.js b/isso/js/app/utils.js index d49e60b..f5f4992 100644 --- a/isso/js/app/utils.js +++ b/isso/js/app/utils.js @@ -38,6 +38,21 @@ define(["app/i18n"], function(i18n) { i18n.pluralize("date-year", Math.ceil(days / 365.25)); }; + var HTMLEntity = { + "&": "&", + "<": "<", + ">": ">", + '"': '"', + "'": ''', + "/": '/' + }; + + var escape = function(html) { + return String(html).replace(/[&<>"'\/]/g, function (s) { + return HTMLEntity[s]; + }); + }; + var text = function(html) { var _ = document.createElement("div"); _.innerHTML = html.replace(/

<\/div>/gi, '
') @@ -47,8 +62,8 @@ define(["app/i18n"], function(i18n) { }; var detext = function(text) { - return text.replace(/\n\n/gi, '

') - .replace(/\n/gi, '
'); + return escape(text.replace(/\n\n/gi, '

') + .replace(/\n/gi, '
')); }; return { diff --git a/isso/run.py b/isso/run.py index 8b5fdc3..905e553 100644 --- a/isso/run.py +++ b/isso/run.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- +from __future__ import unicode_literals + import os from isso import make_app diff --git a/isso/tests/test_html.py b/isso/tests/test_html.py index 7cba522..9cb3dc2 100644 --- a/isso/tests/test_html.py +++ b/isso/tests/test_html.py @@ -1,5 +1,6 @@ # -*- encoding: utf-8 -*- +import textwrap import unittest from isso.utils import html @@ -26,6 +27,37 @@ class TestHTML(unittest.TestCase): for (input, expected) in examples: self.assertEqual(convert(input), expected) + def test_github_flavoured_markdown(self): + convert = html.Markdown(extensions=("fenced_code", )) + + # without lang + _in = textwrap.dedent("""\ + Hello, World + + ``` + #!/usr/bin/env python + print("Hello, World")""") + _out = textwrap.dedent("""\ +

Hello, World

+
#!/usr/bin/env python
+            print("Hello, World")
+            
""") + + self.assertEqual(convert(_in), _out) + + # w/ lang + _in = textwrap.dedent("""\ + Hello, World + + ```python + #!/usr/bin/env python + print("Hello, World")""") + _out = textwrap.dedent("""\ +

Hello, World

+
#!/usr/bin/env python
+            print("Hello, World")
+            
""") + @unittest.skipIf(html.html5lib_version == "0.95", "backport") def test_sanitizer(self): sanitizer = html.Sanitizer(elements=[], attributes=[]) diff --git a/isso/utils/__init__.py b/isso/utils/__init__.py index dfc361c..75a650b 100644 --- a/isso/utils/__init__.py +++ b/isso/utils/__init__.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -from __future__ import division +from __future__ import division, unicode_literals import pkg_resources werkzeug = pkg_resources.get_distribution("werkzeug") diff --git a/isso/utils/html.py b/isso/utils/html.py index 4d540de..088d558 100644 --- a/isso/utils/html.py +++ b/isso/utils/html.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- +from __future__ import unicode_literals + import pkg_resources import operator @@ -59,16 +61,30 @@ def Markdown(extensions=("strikethrough", "superscript", "autolink")): flags = reduce(operator.xor, map( lambda ext: getattr(misaka, 'EXT_' + ext.upper()), extensions), 0) + md = misaka.Markdown(Unofficial(), extensions=flags) def inner(text): - rv = misaka.html(text, extensions=flags).rstrip("\n") - if not rv.endswith("

") and not rv.endswith("

"): - return "

" + rv + "

" - return rv + rv = md.render(text).rstrip("\n") + if rv.startswith("

") or rv.endswith("

"): + return rv + return "

" + rv + "

" return inner +class Unofficial(misaka.HtmlRenderer): + """A few modifications to process "common" Markdown. + + For instance, fenced code blocks (~~~ or ```) are just wrapped in + which does not preserve line breaks. If a language is given, it is added + to , compatible with Highlight.js. + """ + + def block_code(self, text, lang): + lang = ' class="{0}"'.format(lang) if lang else '' + return "
{0}
\n".format(text, lang) + + class Markup(object): """Text to HTML conversion using Markdown (+ configurable extensions) and an HTML sanitizer to remove malicious elements. diff --git a/isso/utils/parse.py b/isso/utils/parse.py index 216da66..85c931b 100644 --- a/isso/utils/parse.py +++ b/isso/utils/parse.py @@ -1,5 +1,5 @@ -from __future__ import print_function +from __future__ import print_function, unicode_literals from itertools import chain diff --git a/isso/wsgi.py b/isso/wsgi.py index b3abcd4..ffb9fb9 100644 --- a/isso/wsgi.py +++ b/isso/wsgi.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- +from __future__ import unicode_literals + import socket try: @@ -140,7 +142,7 @@ class CORSMiddleware(object): return start_response(status, headers.to_list(), exc_info) if environ.get("REQUEST_METHOD") == "OPTIONS": - add_cors_headers("200 Ok", [("Content-Type", "text/plain")]) + add_cors_headers(b"200 Ok", [("Content-Type", "text/plain")]) return [b'200 Ok'] return self.app(environ, add_cors_headers) diff --git a/share/isso.conf b/share/isso.conf index 4797be3..a333b81 100644 --- a/share/isso.conf +++ b/share/isso.conf @@ -134,7 +134,7 @@ reply-to-self = false # Misaka-specific Markdown extensions, all flags starting with EXT_ can be used # there, separated by comma. -options = strikethrough, superscript, autolink +options = strikethrough, autolink, fenced_code, no_intra_emphasis # Additional HTML tags to allow in the generated output, comma-separated. By # default, only a, blockquote, br, code, del, em, h1, h2, h3, h4, h5, h6, hr,