Compare commits
5 Commits
master
...
feature/cl
Author | SHA1 | Date | |
---|---|---|---|
|
b359d077b9 | ||
|
67dcf43ac6 | ||
|
d81a6f6b5c | ||
|
1641f7f9c9 | ||
|
770dbf48af |
47
.drone.yml
47
.drone.yml
@ -1,47 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
steps:
|
||||
- name: publish
|
||||
pull: default
|
||||
image: plugins/docker:18.09
|
||||
settings:
|
||||
registry: https://registry.nixaid.com
|
||||
repo: "registry.nixaid.com/${DRONE_REPO_NAMESPACE}/${DRONE_REPO_NAME}"
|
||||
tags:
|
||||
- latest
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
# storage_path: /drone/docker
|
||||
# storage_driver: aufs
|
||||
# ipv6: false
|
||||
# debug: true
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
- name: notify
|
||||
pull: default
|
||||
image: drillster/drone-email:latest
|
||||
settings:
|
||||
from: "Drone CI <noreply@nixaid.com>"
|
||||
host: mx.nixaid.com
|
||||
port: 587
|
||||
subject: "NIXAID Drone Pipeline {{#success build.status}}SUCCESS{{else}}FAILURE{{/success}} Notification"
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
status:
|
||||
- success
|
||||
- failure
|
@ -1,85 +0,0 @@
|
||||
# workspace:
|
||||
# base: /workspace
|
||||
# path: src/git.nixaid.com/arno/myapp/
|
||||
#
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
pipeline:
|
||||
restore_cache:
|
||||
image: drillster/drone-volume-cache:latest
|
||||
restore: true
|
||||
mount:
|
||||
- /drone/docker
|
||||
# Set the ``DRONE_VOLUME=/tmp/drone-cache:/cache`` drone-server variable,
|
||||
# so you can benefit from the caching.
|
||||
# Otherwise you will have to make this repository trusted in Drone and use
|
||||
# the volumes as follows.
|
||||
# volumes:
|
||||
# - /tmp/drone-cache:/cache
|
||||
|
||||
# drone repo add arno/isso
|
||||
# drone secret add/update --name docker_username --value arno --event push --event tag --event deployment arno/isso
|
||||
# drone secret add/update --name docker_password --value "$(pass show vps/registry.nixaid.com | head -1)" --event push --event tag --event deployment arno/isso
|
||||
publish:
|
||||
image: plugins/docker:17.12
|
||||
# repo: andrey01/${DRONE_REPO_NAME}
|
||||
registry: registry.nixaid.com
|
||||
repo: registry.nixaid.com/arno/${DRONE_REPO_NAME}
|
||||
tags:
|
||||
- latest
|
||||
# - ${DRONE_COMMIT_SHA:0:7}
|
||||
# group: docker
|
||||
# dockerfile: Dockerfile
|
||||
secrets: [docker_username, docker_password]
|
||||
# Since we restore the docker image cache to /drone/docker
|
||||
storage_path: /drone/docker
|
||||
use_cache: true
|
||||
when:
|
||||
event: [push, tag]
|
||||
branch: master
|
||||
|
||||
rebuild_cache:
|
||||
image: drillster/drone-volume-cache:latest
|
||||
rebuild: true
|
||||
mount:
|
||||
- /drone/docker
|
||||
# Set the ``DRONE_VOLUME=/tmp/drone-cache:/cache`` drone-server variable,
|
||||
# so you can benefit from the caching.
|
||||
# Otherwise you will have to make this repository trusted in Drone and use
|
||||
# the volumes as follows.
|
||||
# volumes:
|
||||
# - /tmp/drone-cache:/cache
|
||||
|
||||
# # ca_cert comes from /srv/data/registry/certs/ca.crt
|
||||
# claircheck:
|
||||
# # image: jmccann/drone-clair:1
|
||||
# image: andrey01/drone-clair
|
||||
# url: http://clair:6060
|
||||
# secrets: [ docker_username, docker_password ]
|
||||
# # ignore errors for now. This will work only in drone 0.9 https://github.com/drone/drone-runtime/commit/3e8bd99f60f4032226523320cd2b2321f9525159
|
||||
# err_ignore: true
|
||||
# scan_image: registry.nixaid.com/arno/${DRONE_REPO_NAME}:latest
|
||||
# ca_cert: |
|
||||
# -----BEGIN CERTIFICATE-----
|
||||
# MIIBOjCB4KADAgECAgkAzhpbLWXa4H0wCgYIKoZIzj0EAwIwEDEOMAwGA1UEAwwF
|
||||
# bXktQ0EwHhcNMTgwNzA5MjIzMTAzWhcNMjgwNzA2MjIzMTAzWjAQMQ4wDAYDVQQD
|
||||
# DAVteS1DQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABFIE8bTfQ76U5qG/Xgjw
|
||||
# BbQU0oRJLYlRxBIWF9MTNSJr2LoaoyrU8jrcWQGRrfKPoVuwUJWp2tp5SJy0AHH7
|
||||
# 4fijIzAhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgKkMAoGCCqGSM49
|
||||
# BAMCA0kAMEYCIQCYbTbxRD2yX4LzGjh84fKPWPQM9ps8RE2nfwZjqdRUGgIhAOHb
|
||||
# USigh6FzqEPk2jiaV3t1wNtChRWRfupTKG6CD345
|
||||
# -----END CERTIFICATE-----
|
||||
|
||||
notify:
|
||||
image: drillster/drone-email:latest
|
||||
from: Drone CI <noreply@nixaid.com>
|
||||
subject: NIXAID Drone Pipeline {{#success build.status}}SUCCESS{{else}}FAILURE{{/success}} Notification
|
||||
host: mx.nixaid.com
|
||||
port: 587
|
||||
# username: arno
|
||||
# secrets: [ email_username, email_password ]
|
||||
# recipients: [ andrey.arapov@nixaid.com ]
|
||||
when:
|
||||
status: [success, failure] # changed
|
||||
event: [push, tag]
|
99
.gitignore
vendored
99
.gitignore
vendored
@ -3,18 +3,15 @@
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
*~
|
||||
# *~
|
||||
|
||||
*.pyc
|
||||
.Python
|
||||
.sass-cache
|
||||
.vagrant
|
||||
|
||||
/bin
|
||||
/include
|
||||
/lib
|
||||
/lib64
|
||||
/man
|
||||
/share
|
||||
/isso.egg-info/
|
||||
/isso/js/components
|
||||
@ -25,97 +22,3 @@
|
||||
|
||||
/docs/_build
|
||||
/docs/_static/css/site.css
|
||||
|
||||
/pip-selfcheck.json
|
||||
|
||||
# github/gitignore
|
||||
Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# IPython Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
29
.travis.yml
29
.travis.yml
@ -1,25 +1,12 @@
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.5
|
||||
env: TOX_ENV=py35
|
||||
- python: 3.6
|
||||
env: TOX_ENV=py36
|
||||
- python: 3.7
|
||||
dist: xenial
|
||||
env: TOX_ENV=py37
|
||||
- python: 3.8
|
||||
dist: xenial
|
||||
env: TOX_ENV=py38
|
||||
python: 2.7
|
||||
env:
|
||||
- TOX_ENV=py26
|
||||
- TOX_ENV=py27
|
||||
- TOX_ENV=py33
|
||||
- TOX_ENV=backport
|
||||
install:
|
||||
- pip install -U pip
|
||||
- pip install flake8 tox
|
||||
- pip install tox
|
||||
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
- make flakes
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#isso"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
@ -1,6 +0,0 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
[isso.js]
|
||||
file_filter = .tx/js/<lang>
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
439
CHANGES.rst
439
CHANGES.rst
@ -1,445 +1,10 @@
|
||||
Changelog for Isso
|
||||
==================
|
||||
|
||||
0.12.3 (UNRELEASED)
|
||||
-------------------
|
||||
|
||||
- New "flags" option in the [markdown] section to customize Misaka's Markdown
|
||||
HTML rendering. By default, no flags are set.
|
||||
|
||||
[markup]
|
||||
flags = skip-html, escape, hard-wrap
|
||||
|
||||
Check docs/configuration/server.rst for more details.
|
||||
|
||||
0.12.2 (2019-01-21)
|
||||
-------------------
|
||||
|
||||
- Revert use of labels instead of placeholders, since it breaks
|
||||
mail notifications. #524
|
||||
|
||||
0.12.1 (2019-01-19)
|
||||
-------------------
|
||||
|
||||
- Revert fix for duplicate slashes, as it prevents isso from
|
||||
starting in some cases. #523
|
||||
|
||||
0.12.0 (2019-01-18)
|
||||
-------------------
|
||||
|
||||
- Fix compatibility with new XML API.
|
||||
- Don't enable admin interface with default password by default. #491
|
||||
- Add support and documentation for "generic" imports.
|
||||
- Remove potential duplicate slashes in URLs from
|
||||
email links. #420
|
||||
- Add data-isso-reply-notifications to attributes in configuration.
|
||||
- Use default IP in imports if none is found. Fixes imports of some comments.
|
||||
- embed: fix feed link creation on older browsers.
|
||||
- Properly handle to field in mail notifications when using uWSGI spooler
|
||||
- css: fix vertical alignment of notification checkbox
|
||||
|
||||
0.11.1 (2018-11-03)
|
||||
-------------------
|
||||
|
||||
- Include pre-built minified JavaScript and CSS.
|
||||
|
||||
0.11.0 (2018-11-03)
|
||||
-------------------
|
||||
|
||||
Bugs & features:
|
||||
|
||||
- Fix link in moderation mails if isso is setup on a sub-url (e.g. domain.tld/comments/)
|
||||
- Add reply notifications
|
||||
- Add admin interface
|
||||
- Add links highlighting in comments
|
||||
- Add apidoc
|
||||
- Add rc.d script for FreeBSD
|
||||
- Add the possibility to set CORS Origin through ISSO_CORS_ORIGIN environ variable
|
||||
- Add preview button
|
||||
- Add Atom feed at /feed?uri={thread-id}
|
||||
- Add optionnal gravatar support
|
||||
- Add nofollow noopener on links inside comments
|
||||
- Add Dockerfile
|
||||
- Upgraded to Misaka 2
|
||||
- Some tests/travis/documentation improvements and fixes + pep8
|
||||
|
||||
Translations:
|
||||
|
||||
- Fix Chinese translation & typo in CJK
|
||||
- Add Danish translation
|
||||
- Add Hungarian translation
|
||||
- Add Persian translation
|
||||
- Improvement on german translation
|
||||
|
||||
0.10.6 (2016-09-22)
|
||||
-------------------
|
||||
|
||||
- fix missing configuration field
|
||||
|
||||
|
||||
0.10.5 (2016-09-20)
|
||||
-------------------
|
||||
|
||||
- add support for different vote levels, #260
|
||||
|
||||
List of vote levels used to customize comment appearance based on score.
|
||||
Provide a comma-separated values (eg. `"0,5,10,25,100"`) or a JSON array (eg.
|
||||
`"[-5,5,15]"`).
|
||||
|
||||
For example, the value `"-5,5"` will cause each `isso-comment` to be given
|
||||
one of these 3 classes:
|
||||
|
||||
- `isso-vote-level-0` for scores lower than `-5`
|
||||
- `isso-vote-level-1` for scores between `-5` and `4`
|
||||
- `isso-vote-level-2` for scores of `5` and greater
|
||||
|
||||
These classes can then be used to customize the appearance of comments (eg.
|
||||
put a star on popular comments).
|
||||
|
||||
- add new post preview API endpoint, #254
|
||||
|
||||
- add an option for mandatory author, #257
|
||||
|
||||
- clients can now use `data-title` to get the HTML title for a new page, #252
|
||||
|
||||
- add finish translation and other minor bugfixes
|
||||
|
||||
|
||||
0.10.4 (2016-04-12)
|
||||
-------------------
|
||||
|
||||
- fix wrapper attribute when using data-isso-require-mail="true", #238
|
||||
- fix reponse for OPTIONS response on Python 3, #242
|
||||
|
||||
|
||||
0.10.3 (2016-02-24)
|
||||
-------------------
|
||||
|
||||
- follow redirects, #193
|
||||
|
||||
|
||||
0.10.2 (2016-02-21)
|
||||
-------------------
|
||||
|
||||
- fix getAttribute return value
|
||||
|
||||
|
||||
0.10.1 (2016-02-06)
|
||||
-------------------
|
||||
|
||||
- fix empty author, email and website values when writing a comment
|
||||
|
||||
|
||||
0.10 (2016-02-06)
|
||||
-----------------
|
||||
|
||||
- add new configuration section for hash handling.
|
||||
|
||||
[hash]
|
||||
salt = Eech7co8Ohloopo9Ol6baimi
|
||||
algorithm = pbkdf2
|
||||
|
||||
You can customize the salt, choose different hash functions and tweak the
|
||||
parameters for PBKDF2.
|
||||
|
||||
- Python 3.4+ validate TLS connections against the system's CA. Previously no
|
||||
validation was in place, see PEP-446__ for details.
|
||||
|
||||
- add `fenced_code` and `no_intra_emphasis` to default configuration.
|
||||
|
||||
Fenced code allows to write code without indentation using `~~~` delimiters
|
||||
(optionally with language identifier).
|
||||
|
||||
Intra emphasis would compile `foo_bar_baz` to foo<em>bar</em>baz. This
|
||||
behavior is very confusing for users not knowing the Markdown spec in detail.
|
||||
|
||||
- new configuration to require an email when submitting comments, #199. Set
|
||||
|
||||
[guard]
|
||||
require-email = true
|
||||
|
||||
and use `data-isso-require-email="true"` to enable this feature. Disabled by
|
||||
default.
|
||||
|
||||
- new Bulgarian translation by sahwar, new Swedish translation by Gustav
|
||||
Näslund – #143, new Vietnamese translation by Đinh Xuân Sâm, new Croatian
|
||||
translation by streger, new Czech translation by Jan Chren
|
||||
|
||||
- fix SMTP setup without credentials, #174
|
||||
|
||||
- version pin Misaka to 1.x, html5lib to 0.9999999
|
||||
|
||||
.. __: https://www.python.org/dev/peps/pep-0466/
|
||||
|
||||
|
||||
0.9.10 (2015-04-11)
|
||||
-------------------
|
||||
|
||||
- fix regression in SMTP authentication, #174
|
||||
|
||||
|
||||
0.9.9 (2015-03-04)
|
||||
------------------
|
||||
|
||||
- several Python 3.x related bugfixes
|
||||
|
||||
- don't lose comment form if the server rejected the POST request, #144
|
||||
|
||||
- add localStorage fallback if QUOTA_EXCEEDED_ERR is thrown (e.g. Safari
|
||||
private browsing)
|
||||
|
||||
- add '--empty-id' flag to Disqus import, because Disqus' export sucks
|
||||
|
||||
- (re)gain compatibility with Werkzeug 0.8 and really old html5lib versions
|
||||
available in Debian Squeeze, #170 & #168
|
||||
|
||||
- add User-Agent when Isso requests the URL, an alternate way to #151 (add
|
||||
'X-Isso' when requesting).
|
||||
|
||||
0.9.8 (2014-10-08)
|
||||
------------------
|
||||
|
||||
- add compatibility with configparser==3.5.0b1, #128
|
||||
|
||||
|
||||
0.9.7 (2014-09-25)
|
||||
------------------
|
||||
|
||||
- fix SMTP authentication using CRAM-MD5 (incorrect usage of
|
||||
`smtplib`), #126
|
||||
|
||||
|
||||
0.9.6 (2014-08-18)
|
||||
------------------
|
||||
|
||||
- remember name, email and website in localStorage, #119
|
||||
|
||||
- add option to hide voting feature, #115
|
||||
|
||||
data-isso-vote="true|false"
|
||||
|
||||
- remove email field from JSON responses
|
||||
|
||||
This is a quite serious issue. For the identicon, an expensive hash is used
|
||||
to avoid the leakage of personal information like a real email address. A
|
||||
`git blame` reveals, the email has been unintenionally exposed since the very
|
||||
first release of Isso :-/
|
||||
|
||||
The testsuite now contains a dedicated test to prevent this error in the
|
||||
future.
|
||||
|
||||
|
||||
0.9.5 (2014-08-10)
|
||||
------------------
|
||||
|
||||
- prevent no-break space ( ) insertion to enable manual line breaks using
|
||||
two trailing spaces (as per Markdown convention), #112
|
||||
|
||||
- limit request size to 256 kb, #107
|
||||
|
||||
Previously unlimited or limited by proxy server). 256 kb is a rough
|
||||
approximation of the next database schema with comments limited to 65535
|
||||
characters and additional fields.
|
||||
|
||||
- add support for logging to file, #103
|
||||
|
||||
[general]
|
||||
log-file =
|
||||
|
||||
- show timestamp when hovering <time>, #104
|
||||
|
||||
- fix a regression when editing comments with multiple paragraphs introduced
|
||||
in 0.9.3 which would HTML escape manually inserted linebreaks.
|
||||
|
||||
|
||||
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)
|
||||
0.7 (unreleased)
|
||||
----------------
|
||||
|
||||
- comment pagination by Srijan Choudhary, #15
|
||||
|
||||
Isso can now limit the amount of comments shown by default and add link to
|
||||
show more. By default, all top-level comments are shown but only 5 nested
|
||||
comments (per reply). You can override the settings:
|
||||
|
||||
isso-data-max-comments-top="N"
|
||||
isso-data-max-comments-nested="N"
|
||||
|
||||
Where N is a number from 0 to infinity ("inf"). If you limit the amount of
|
||||
shown top level comments, the overall comment count may be incorrect and a
|
||||
known issue.
|
||||
|
||||
You can also configure the amount of comments shown per click (5 by default):
|
||||
|
||||
isso-data-reveal-on-click="N"
|
||||
|
||||
This feature also required a change in the comment structure. Previously, all
|
||||
comments are stored tree-like but shown linearly. To ease the implementation
|
||||
of pagination, the comment tree is now limited to a maximum depth of one.
|
||||
Jeff Atwood explains, why `discussions are flat by design`__.
|
||||
|
||||
.. __: http://blog.codinghorror.com/web-discussions-flat-by-design/
|
||||
|
||||
When you upgrade, Isso will automatically normalize the tree and some
|
||||
information gets lost. All new replies to a comment are now automatically a
|
||||
direct child of the top-level comment.
|
||||
|
||||
- style improvements by William Dorffer, #39, #84 #90 and #91
|
||||
|
||||
Isso now longer uses a fat SCSS library, but plain CSS instead. The design is
|
||||
now responsive and no longer sets global CSS rules.
|
||||
|
||||
- experimental WordPress import, #75
|
||||
|
||||
Isso should be able to import WXR 1.0-1.2 exports. The import code is based
|
||||
on two WXR dumps I found (and created) and may not work for you. Please
|
||||
report any failure.
|
||||
|
||||
- avatar changes, #49
|
||||
|
||||
You can now configure the client to not show avatars:
|
||||
|
||||
data-isso-avatar="false"
|
||||
|
||||
Also there is no longer an avatar shown next to the comment box. This is due
|
||||
to the new CSS and removes two runtime dependencies.
|
||||
|
||||
- you may now set a full From header, #87
|
||||
|
||||
[smtp]
|
||||
from = Foo Bar <spam@local>
|
||||
|
||||
- SMTP (all caps) is now recognized for notifications, #95
|
||||
|
||||
- Isso now ships a small demo site at /demo, #44
|
||||
|
||||
- a few bugfixes: Disqus import now anonymizes IP addresses, uWSGI spooling for
|
||||
Python 3, HTTP-Referer fallback for HTTP-Origin
|
||||
|
||||
- remove Django's PBKDF2 implementation in favour of the PBKDF2 function
|
||||
available in werkzeug 0.9 or higher. If you're still using werkzeug 0.8, Isso
|
||||
imports passlib__ as fallback (if available).
|
||||
|
||||
|
||||
This release also features a new templating engine Jade__ which replaces
|
||||
Markup.js__. Jade can compile directly to JavaScript with a tiny runtime module
|
||||
on the client. Along with the removal of sha1.js and pbkdf2.js and a few build
|
||||
optimizations, the JS client now weighs only 40kb (12kb gzipped) – 52kb resp.
|
||||
17kb before.
|
||||
|
||||
.. __: https://pypi.python.org/pypi/passlib
|
||||
.. __: http://jade-lang.com/
|
||||
.. __: https://github.com/adammark/Markup.js
|
||||
|
||||
|
||||
0.8 (2014-03-28)
|
||||
----------------
|
||||
|
||||
- replace ``<textarea>`` with ``<div contentedtiable="true">`` to remove the
|
||||
sluggish auto-resize on input feature. If you use a custom CSS, replace
|
||||
``textarea`` with ``.textarea`` and also set ``white-space: pre``.
|
||||
|
||||
- remove superscript extension from Markdown defaults as it may lead to
|
||||
unexpected behavior for certain smileys such as "^^". To enable the extension,
|
||||
add
|
||||
|
||||
[markup]
|
||||
options = superscript
|
||||
allowed-elements = sup
|
||||
|
||||
to your configuration.
|
||||
|
||||
- comment count requests are now bundled into a single POST request, but the old
|
||||
API is still there (deprecated though).
|
||||
|
||||
- store *session-key* in database (once generated on database creation). That
|
||||
means links to activate, edit or delete comments are now always valid even
|
||||
when you restart Isso.
|
||||
|
||||
Currently statically set session keys in ``[general]`` are automatically
|
||||
migrated into the database on startup and you will get a notice that you can
|
||||
remove this option.
|
||||
|
||||
- fix undefined timestamp when client time differs for more than 1 second.
|
||||
The human-readable "time ago" deltas have been refined to match `Moment.js`_
|
||||
behavior.
|
||||
|
||||
- avatar colors and background can now be customized:
|
||||
|
||||
* ``data-isso-avatar-bg="#f0f0f0"`` sets the background color
|
||||
* ``data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."`` sets possible
|
||||
avatar colors (up to 8 colors are possible).
|
||||
|
||||
- new [markup] section to customize Misaka's Markdown generation (strikethrough,
|
||||
superscript and autolink enabled by default). Furthermore, you can now allow
|
||||
certain HTML elemenets and attributes in the generated output, e.g. to enable
|
||||
images, set
|
||||
|
||||
[markup]
|
||||
allowed-elements = img
|
||||
allowed-attributes = src
|
||||
|
||||
Check docs/configuration/server.rst for more details.
|
||||
|
||||
- replace requirejs-domready with a (self-made) HTML5 idiom, #51
|
||||
|
||||
.. _Moment.js: http://momentjs.com/docs/#/displaying/fromnow/
|
||||
|
||||
|
||||
0.7 (2014-01-29)
|
||||
----------------
|
||||
|
||||
- fix malicious HTML injection (due to wrong API usage). All unknown/unsafe
|
||||
HTML tags are now removed from the output (`html5lib` 0.99(9) or later) or
|
||||
properly escaped (older `html5lib` versions).
|
||||
|
||||
See 36d702c and 3a1f92b for more details.
|
||||
|
||||
- remove kriskowal/q JS library (promises implementation) in favour of a
|
||||
self-made 50 LoC implementation to ease packaging (for Debian), #51
|
||||
|
||||
- add documentation to display a comment counter, #56 and #57
|
||||
|
||||
- SMTP notifications now support STARTTLS and use this transport security
|
||||
by default, #48 and #58. This also changes the configuration option from
|
||||
`ssl = [yes|no]` to `security = [none|starttls|ssl]`.
|
||||
|
||||
- translation can now be made (and updated) with Transifex_. If you want to
|
||||
take ownership for a language, contact me on IRC.
|
||||
|
||||
- fix french pluralform
|
||||
|
||||
- the (by default random) session-key is now shown on application startup
|
||||
to make different keys per startup more visible
|
||||
|
||||
- use `threading.lock` by default for systems without semaphore support
|
||||
|
||||
.. _Transifex: https://www.transifex.com/projects/p/isso/
|
||||
- Nothing changed yet.
|
||||
|
||||
|
||||
0.6 (2013-12-16)
|
||||
|
121
CONTRIBUTORS.txt
121
CONTRIBUTORS.txt
@ -1,121 +0,0 @@
|
||||
# Contributions to Isso
|
||||
|
||||
## Creator & Maintainer
|
||||
|
||||
* Martin Zimmermann <info@posativ.org>
|
||||
|
||||
## Co-Maintainers
|
||||
|
||||
* Benoît Latinier @blatinier <benoit@latinier.fr>
|
||||
|
||||
* Jelmer Vernooij <jelmer@jelmer.uk>
|
||||
|
||||
## Contributors
|
||||
|
||||
In chronological order:
|
||||
|
||||
* Florian Baumann <flo@noqqe.de>
|
||||
* Documentation fixes in the early days
|
||||
|
||||
* Federico Ceratto <federico.ceratto@gmail.com>
|
||||
* Support for ipaddr
|
||||
* Added a sample configuration file
|
||||
|
||||
* Sploinga <xavier@sploing.be>
|
||||
* French translation
|
||||
|
||||
* Laurent Arnoud <laurent@spkdev.net>
|
||||
* Make Isso not accept blank comments
|
||||
|
||||
* Chimo <chimo@chromic.org>
|
||||
* STARTTLS for SMTP notifications
|
||||
|
||||
* Jocelyn Delande <jocelyn at delalande dot fr>
|
||||
* Documentation for comment counter and API
|
||||
* Default SMTP security to STARTTLS
|
||||
|
||||
* Srijan Choudhary <srijan4@gmail.com>
|
||||
* Correct nginx examples in documentation
|
||||
* Added comment pagination feature
|
||||
* Provide a demo site
|
||||
|
||||
* William Dorffer <schoewilliam@gmail.com>
|
||||
* Convert SCSS to plain CSS
|
||||
* Refresh style, make it responsive
|
||||
|
||||
* Baptiste Darthenay
|
||||
* Esperanto translation
|
||||
|
||||
* Matías Ducasa <https://github.com/matiasducasa>
|
||||
* Spanish translation
|
||||
|
||||
* Daniel Gräber <https://github.com/albohlabs>
|
||||
* Added ansible for provisioning
|
||||
|
||||
* Nick Hu <https://github.com/NickHu>
|
||||
* Added configuration to require email addresses (no validation)
|
||||
* Fix Vagrantfile
|
||||
|
||||
* Benoît Latinier @blatinier <benoit@latinier.fr>
|
||||
* Fix thread discovery
|
||||
* Added mandatory author
|
||||
* Added admin interface
|
||||
|
||||
* Ivan Pantic <ivanpantic82@gmail.com>
|
||||
* Added vote levels
|
||||
|
||||
* Martin Schenck @schemar
|
||||
* Improvement in the german translation
|
||||
|
||||
* @cclauss
|
||||
* Pep8 and drop of legacy supports (old python & debian version tested in travis)
|
||||
* Make travis use pyflakes
|
||||
|
||||
* Lucas Cimon @Lucas-C
|
||||
* Added the possibility to define CORS origins through ISSO_CORS_ORIGIN environment variable
|
||||
* Fix a bug with <a> in <svg>
|
||||
* Fixing likes counter of replies not being displayed
|
||||
* Adding contrib/dump_comments.py
|
||||
* Adding a [server] proxy-fix-enable-x-prefix configuration option
|
||||
* Using .access_route instead of .remote_addr to take into account HTTP_X_FORWARDED_FOR header
|
||||
|
||||
* Yuchen Pei @ycpei
|
||||
* Fix link in moderation emails when isso is installed in a sub URL
|
||||
|
||||
* @Rocket1184
|
||||
* Fix typo in CJK translations
|
||||
|
||||
* @vincentbernat
|
||||
* Added documentation about data-isso-id attribute (overriding the standard isso-thread-id)
|
||||
* Added multi-staged Dockerfile
|
||||
* Added atom feed
|
||||
* Added a nofollow/noopener on links inside comments to protect against bots
|
||||
* Added a preview using the existing preview endpoint
|
||||
|
||||
* @p-vitt & @M4a1x
|
||||
* Documentation on troubleshooting for uberspace users
|
||||
|
||||
* Facundo Batista <facundo@taniquetil.com.ar>
|
||||
* Added a generic way to migrate from a json file
|
||||
|
||||
* @benjhess
|
||||
* Optionnal gravatar support
|
||||
|
||||
* Steffen Prince @sprin
|
||||
* Upgrade to Misaka 2
|
||||
|
||||
* Rocka <i@rocka.me>
|
||||
* Implementation and documentation about async comment loading
|
||||
|
||||
* Pelle Nilsson @pellenilsson
|
||||
* Reply notifications
|
||||
|
||||
* Craig P Hicks @craigphicks
|
||||
* Fix sub urls configurations on admin interface
|
||||
|
||||
* Chris Warrick @Kwpolska
|
||||
* Update Polish translation
|
||||
* Redirect to comment after moderation
|
||||
|
||||
* [Your name or handle] <[email or website]>
|
||||
* [Brief summary of your changes]
|
34
Dockerfile
34
Dockerfile
@ -1,34 +0,0 @@
|
||||
# First, compile JS stuff
|
||||
FROM node:dubnium-buster
|
||||
WORKDIR /src/
|
||||
COPY . .
|
||||
RUN npm install -g requirejs uglify-js jade bower \
|
||||
&& make init js
|
||||
|
||||
# Second, create virtualenv
|
||||
FROM python:3.8-buster
|
||||
WORKDIR /src/
|
||||
COPY --from=0 /src .
|
||||
RUN python3 -m venv /isso \
|
||||
&& . /isso/bin/activate \
|
||||
&& pip3 install --no-cache-dir --upgrade pip \
|
||||
&& pip3 install --no-cache-dir gunicorn cffi flask \
|
||||
&& python setup.py install \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Third, create final repository
|
||||
FROM python:3.8-slim-buster
|
||||
WORKDIR /isso/
|
||||
COPY --from=1 /isso .
|
||||
|
||||
# Configuration
|
||||
VOLUME /db /config
|
||||
EXPOSE 8080
|
||||
ENV ISSO_SETTINGS /config/isso.cfg
|
||||
CMD ["/isso/bin/gunicorn", "-b", "0.0.0.0:8080", "-w", "4", "--preload", "isso.run", "--worker-tmp-dir", "/dev/shm"]
|
||||
|
||||
# Example of use:
|
||||
#
|
||||
# docker build -t isso .
|
||||
# docker run -it --rm -v /opt/isso:/config -v /opt/isso:/db -v $PWD:$PWD isso /isso/bin/isso -c \$ISSO_SETTINGS import disqus.xml
|
||||
# docker run -d --rm --name isso -p 8080:8080 -v /opt/isso:/config -v /opt/isso:/db isso
|
13
MANIFEST.in
13
MANIFEST.in
@ -1,19 +1,6 @@
|
||||
include man/man1/isso.1
|
||||
include man/man5/isso.conf.5
|
||||
|
||||
include share/isso.conf
|
||||
|
||||
include isso/js/embed.min.js
|
||||
include isso/js/embed.dev.js
|
||||
include isso/js/count.min.js
|
||||
include isso/js/count.dev.js
|
||||
include isso/js/admin.js
|
||||
|
||||
include isso/defaults.ini
|
||||
|
||||
include isso/templates/admin.html
|
||||
include isso/templates/disabled.html
|
||||
include isso/templates/login.html
|
||||
include isso/css/admin.css
|
||||
include isso/css/isso.css
|
||||
include isso/img/isso.svg
|
||||
|
85
Makefile
85
Makefile
@ -1,75 +1,18 @@
|
||||
# INSTALLATION: pip install sphinx && npm install --global node-sass
|
||||
all: css js
|
||||
|
||||
ISSO_JS_SRC := $(shell find isso/js/app -type f) \
|
||||
$(shell ls isso/js/*.js | grep -vE "(min|dev)") \
|
||||
isso/js/lib/requirejs-jade/jade.js
|
||||
css:
|
||||
scss isso/css/isso.scss isso/css/isso.css
|
||||
|
||||
ISSO_JS_DST := isso/js/embed.min.js isso/js/embed.dev.js \
|
||||
isso/js/count.min.js isso/js/count.dev.js
|
||||
js:
|
||||
r.js -o isso/js/build.embed.js
|
||||
r.js -o isso/js/build.embed.js optimize="none" out="isso/js/embed.dev.js"
|
||||
r.js -o isso/js/build.count.js
|
||||
r.js -o isso/js/build.count.js optimize="none" out="isso/js/count.dev.js"
|
||||
site:
|
||||
cd docs/ && sphinx-build -E -b dirhtml -a . _build
|
||||
scss docs/_static/css/site.scss docs/_build/_static/css/site.css
|
||||
|
||||
ISSO_CSS := isso/css/isso.css
|
||||
|
||||
ISSO_PY_SRC := $(shell git ls-files | grep -E "^isso/.+.py$$")
|
||||
|
||||
DOCS_RST_SRC := $(shell find docs/ -type f -name '*.rst') \
|
||||
$(wildcard docs/_isso/*) \
|
||||
docs/index.html docs/conf.py docs/docutils.conf \
|
||||
share/isso.conf
|
||||
|
||||
DOCS_CSS_SRC := docs/_static/css/site.scss
|
||||
|
||||
DOCS_CSS_DEP := $(shell find docs/_static/css/neat -type f) \
|
||||
$(shell find docs/_static/css/bourbon -type f)
|
||||
|
||||
DOCS_CSS_DST := docs/_static/css/site.css
|
||||
|
||||
DOCS_MAN_DST := man/man1/isso.1 man/man5/isso.conf.5
|
||||
|
||||
DOCS_HTML_DST := docs/_build/html
|
||||
|
||||
RJS = r.js
|
||||
|
||||
SASS = node-sass
|
||||
|
||||
all: man js site
|
||||
|
||||
init:
|
||||
(cd isso/js; bower --allow-root install almond requirejs requirejs-text jade)
|
||||
|
||||
flakes:
|
||||
flake8 . --count --max-line-length=127 --show-source --statistics
|
||||
|
||||
isso/js/%.min.js: $(ISSO_JS_SRC) $(ISSO_CSS)
|
||||
$(RJS) -o isso/js/build.$*.js out=$@
|
||||
|
||||
isso/js/%.dev.js: $(ISSO_JS_SRC) $(ISSO_CSS)
|
||||
$(RJS) -o isso/js/build.$*.js optimize="none" out=$@
|
||||
|
||||
js: $(ISSO_JS_DST)
|
||||
|
||||
man: $(DOCS_RST_SRC)
|
||||
sphinx-build -b man docs/ man/
|
||||
mkdir -p man/man1/ man/man5
|
||||
mv man/isso.1 man/man1/isso.1
|
||||
mv man/isso.conf.5 man/man5/isso.conf.5
|
||||
|
||||
${DOCS_CSS_DST}: $(DOCS_CSS_SRC) $(DOCS_CSS_DEP)
|
||||
$(SASS) --no-cache $(DOCS_CSS_SRC) $@
|
||||
|
||||
${DOCS_HTML_DST}: $(DOCS_RST_SRC) $(DOCS_CSS_DST)
|
||||
sphinx-build -b dirhtml docs/ $@
|
||||
|
||||
site: $(DOCS_HTML_DST)
|
||||
|
||||
coverage: $(ISSO_PY_SRC)
|
||||
nosetests --with-doctest --with-coverage --cover-package=isso --cover-html isso/
|
||||
|
||||
test: $($ISSO_PY_SRC)
|
||||
python3 setup.py nosetests
|
||||
|
||||
clean:
|
||||
rm -f $(DOCS_MAN_DST) $(DOCS_CSS_DST) $(ISSO_JS_DST)
|
||||
rm -rf $(DOCS_HTML_DST)
|
||||
|
||||
.PHONY: clean site man init js coverage test
|
||||
coverage:
|
||||
nosetests --with-doctest --with-doctest-ignore-unicode --with-coverage \
|
||||
--cover-package=isso --cover-html isso/ specs/
|
||||
|
||||
|
43
README.md
43
README.md
@ -1,12 +1,39 @@
|
||||
[![Build Status](https://drone.nixaid.com/api/badges/arno/isso/status.svg)](https://drone.nixaid.com/arno/isso)
|
||||
Isso – Ich schrei sonst
|
||||
=======================
|
||||
|
||||
Isso – a commenting server similar to Disqus
|
||||
============================================
|
||||
[![Build Status](https://travis-ci.org/posativ/isso.png?branch=master)](https://travis-ci.org/posativ/isso)
|
||||
|
||||
Isso – *Ich schrei sonst* – is a lightweight commenting server written in
|
||||
Python and JavaScript. It aims to be a drop-in replacement for
|
||||
[Disqus](http://disqus.com).
|
||||
You love static blog generators (especially [Acrylamid][1] *cough*) and the
|
||||
only option to interact with your community is [Disqus][2]. There's nothing
|
||||
wrong with it, but if you care about the privacy of your audience you are
|
||||
better off with a comment system that is under your control. This is, where
|
||||
Isso comes into play.
|
||||
|
||||
![Isso in Action](http://posativ.org/~tmp/isso-sample.png)
|
||||
[1]: https://github.com/posativ/acrylamid
|
||||
[2]: https://disqus.com/
|
||||
|
||||
See [posativ.org/isso](http://posativ.org/isso/) for more details.
|
||||
**[Try Yourself!](http://posativ.org/isso/)**
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) comments written in Markdown
|
||||
* SQLite backend, Disqus import
|
||||
* client-side JS (currently 54kb minified, 18kb gzipped)
|
||||
* I18N, available in english, french, russian and german
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Please refer to the official documentation: <http://posativ.org/isso/docs>.
|
||||
|
||||
|
||||
Alternatives
|
||||
------------
|
||||
|
||||
- [talkatv](https://github.com/talkatv/talkatv) – Python
|
||||
- [Juvia](https://github.com/phusion/juvia) – Ruby on Rails
|
||||
- [Tildehash.com](http://www.tildehash.com/?article=why-im-reinventing-disqus) – PHP
|
||||
- [SO: Unobtrusive, self-hosted comments](http://stackoverflow.com/q/2053217)
|
||||
|
65
Vagrantfile
vendored
65
Vagrantfile
vendored
@ -1,65 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# This is the Vagrant config file for setting up an environment for Isso development.
|
||||
# It requires:
|
||||
#
|
||||
# * Vagrant (https://vagrantup.com)
|
||||
# * A VM engine, like VirtualBox (https://virtualbox.org)
|
||||
# * The Vagrant-Hostmanager plugin (https://github.com/smdahlen/vagrant-hostmanager)
|
||||
# * Ansible (https://www.ansible.com)
|
||||
#
|
||||
# With them installed, cd into this directory and do 'vagrant up'. It's possible Vagrant will
|
||||
# ask for your root password so it can update your /etc/hosts file. Once it's happily churning out
|
||||
# console output, go get a coffee :)
|
||||
#
|
||||
# The resulting VM should be accessible at http://isso-dev.local/ so you can try the demo page out.
|
||||
# Edit files in your checkout as usual. If you need to look at log files and stuff, 'vagrant ssh'
|
||||
# to get into the VM. Useful info about it:
|
||||
#
|
||||
# * Running Ubuntu 14.04
|
||||
# * Isso is running on uWSGI
|
||||
# * Actual webserver is Nginx to talk to uWSGI over a unix socket
|
||||
# * uWSGI log file is /var/log/uwsgi/apps/isso.log
|
||||
# * Isso DB file is /var/isso/comments.db
|
||||
# * Isso log file is /var/log/isso.log
|
||||
#
|
||||
# When the VM is getting rebooted vagrant mounts the shared folder after uWSGI is getting startet. To fix this issue for
|
||||
# the moment you need to 'vagrant ssh' into the VM and execute 'sudo service uwsgi restart'.
|
||||
#
|
||||
# For debugging with _pudb_ stop uWSGI service and start it manually
|
||||
# 'sudo uwsgi --ini /etc/uwsgi/apps-available/isso.ini'.
|
||||
#
|
||||
# Enjoy!
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
config.vm.box = "ubuntu/trusty32"
|
||||
|
||||
config.vm.hostname = 'isso-dev.local'
|
||||
config.vm.network "private_network", type: "dhcp"
|
||||
|
||||
config.hostmanager.enabled = true
|
||||
config.hostmanager.manage_host = true
|
||||
config.hostmanager.ignore_private_ip = false
|
||||
config.hostmanager.include_offline = true
|
||||
config.hostmanager.ip_resolver = proc do |machine|
|
||||
result = ""
|
||||
machine.communicate.execute("ifconfig eth1") do |type, data|
|
||||
result << data if type == :stdout
|
||||
end
|
||||
(ip = /inet addr:(\d+\.\d+\.\d+\.\d)/.match(result)) && ip[1]
|
||||
end
|
||||
|
||||
config.vm.provision "ansible" do |ansible|
|
||||
ansible.playbook = "ansible/site.yml"
|
||||
ansible.limit = "all"
|
||||
ansible.verbose = "v"
|
||||
end
|
||||
|
||||
config.vm.post_up_message = "Browse to http://isso-dev.local/demo/index.html to start."
|
||||
end
|
@ -1,39 +0,0 @@
|
||||
user root;
|
||||
worker_processes 4;
|
||||
worker_rlimit_nofile 8192;
|
||||
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 2014;
|
||||
multi_accept on;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
log_format timed '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||
'$request_time $upstream_response_time $upstream_addr '
|
||||
' $upstream_status $upstream_cache_status $pipe';
|
||||
|
||||
access_log /var/log/nginx/access.log timed;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
|
||||
keepalive_timeout 30;
|
||||
|
||||
gzip on;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
server {
|
||||
client_max_body_size 20M;
|
||||
listen 80 default_server;
|
||||
server_name isso-dev.local;
|
||||
|
||||
root /vagrant/isso/demo;
|
||||
|
||||
location / {
|
||||
# uwsgi_pass unix:///run/uwsgi/app/isso/socket;
|
||||
uwsgi_pass 127.0.0.1:8080;
|
||||
include uwsgi_params;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
[uwsgi]
|
||||
plugins = python
|
||||
|
||||
chdir = /vagrant
|
||||
|
||||
uid = root
|
||||
gid = root
|
||||
|
||||
socket = :8080
|
||||
|
||||
master = true
|
||||
processes = 4
|
||||
cache2 = name=hash,items=10240,blocksize=32
|
||||
spooler = /var/isso/spool
|
||||
module = isso.run
|
||||
env = ISSO_SETTINGS=/vagrant/share/isso-dev.conf
|
||||
env = PYTHON_EGG_CACHE=/tmp
|
||||
|
||||
# uncomment for debugging
|
||||
# daemonize = /var/log/uwsgi/uwsgi.log
|
||||
py-autoreload = 1
|
||||
|
||||
# prevent uWSGI from remapping stdin to /dev/null
|
||||
honour-stdin = true
|
@ -1,85 +0,0 @@
|
||||
---
|
||||
|
||||
- name: Provision development server
|
||||
hosts: all
|
||||
sudo: true
|
||||
tasks:
|
||||
|
||||
- name: Apt | Add nodesource keys
|
||||
apt_key: url=https://deb.nodesource.com/gpgkey/nodesource.gpg.key state=present
|
||||
|
||||
- name: Apt | Add nodesource sources list deb
|
||||
apt_repository: repo='deb https://deb.nodesource.com/node {{ ansible_distribution_release }} main' state=present
|
||||
|
||||
- name: Apt | Add nodesource sources list deb src
|
||||
apt_repository: repo='deb-src https://deb.nodesource.com/node {{ ansible_distribution_release }} main' state=present
|
||||
|
||||
- name: Apt | Install packages
|
||||
apt: pkg={{ item }} state=latest update_cache=true
|
||||
with_items:
|
||||
- build-essential
|
||||
- curl
|
||||
- htop
|
||||
- vim
|
||||
- git
|
||||
- python-dev
|
||||
- python-software-properties
|
||||
- python-setuptools
|
||||
- python-pip
|
||||
- nginx
|
||||
- uwsgi
|
||||
- uwsgi-plugin-python
|
||||
- supervisor
|
||||
- sqlite3
|
||||
- nodejs
|
||||
- libffi-dev
|
||||
|
||||
- name: NPM | Install packages
|
||||
npm: name={{ item }} global=yes
|
||||
with_items:
|
||||
- bower
|
||||
- requirejs
|
||||
- uglify-js
|
||||
- jade
|
||||
|
||||
- name: Python | Install egg
|
||||
shell: cd /vagrant; python setup.py develop
|
||||
|
||||
- name: Make
|
||||
shell: cd /vagrant; {{ item }}
|
||||
with_items:
|
||||
- make init
|
||||
- make js
|
||||
|
||||
- name: Spool | Create directory
|
||||
file: path=/var/isso/spool state=directory mode=0777
|
||||
|
||||
- name: uwsgi | Deploy configuration
|
||||
copy: src=files/uwsgi.ini dest=/etc/uwsgi/apps-available/isso.ini
|
||||
|
||||
- name: uwsgi | Enable app
|
||||
file: src=/etc/uwsgi/apps-available/isso.ini dest=/etc/uwsgi/apps-enabled/isso.ini state=link
|
||||
|
||||
- name: uwsgi | Restart service daemon
|
||||
service: name=uwsgi state=restarted enabled=yes
|
||||
|
||||
- name: uwsgi | Chmod logfile
|
||||
file: path=/var/log/uwsgi/uwsgi.log state=touch mode="a+r"
|
||||
|
||||
- name: nginx | Deploy nginx.conf
|
||||
copy: src=files/nginx.conf dest=/etc/nginx/nginx.conf
|
||||
|
||||
- name: nginx | Delete default vhost
|
||||
action: file path=/etc/nginx/sites-enabled/default state=absent
|
||||
|
||||
- name: nginx | Deploy vhost config
|
||||
copy: src=files/nginx.vhost.conf dest=/etc/nginx/sites-available/isso.conf
|
||||
|
||||
- name: nginx | Enable vhost
|
||||
file: src=/etc/nginx/sites-available/isso.conf dest=/etc/nginx/sites-enabled/000-isso state=link
|
||||
|
||||
- name: nginx | Chmod logfile
|
||||
file: path=/var/log/nginx mode="a+rx" state=directory recurse=true
|
||||
|
||||
- name: nginx | Restart service daemon
|
||||
service: name=nginx state=restarted enabled=yes
|
11
apidoc.json
11
apidoc.json
@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "isso",
|
||||
"description": "a Disqus alternative",
|
||||
"title": "isso API",
|
||||
"order": ["Thread", "Comment"],
|
||||
"template": {
|
||||
"withCompare": false
|
||||
}
|
||||
|
||||
}
|
||||
|
13
bin/tx-pull
13
bin/tx-pull
@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
set -g TRANSIFEX ".tx/js/"
|
||||
set -g JS "isso/js/app/i18n/%s.js"
|
||||
|
||||
# fetch latest translations to .tx/<ressource>/<lang>
|
||||
tx pull -a
|
||||
|
||||
for lang in (ls $TRANSIFEX)
|
||||
printf "define(" > (printf $JS $lang)
|
||||
cat .tx/js/$lang >> (printf $JS $lang)
|
||||
printf ");\n" >> (printf $JS $lang)
|
||||
end
|
34
bin/tx-push
34
bin/tx-push
@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env fish
|
||||
|
||||
set TRANSIFEX "https://www.transifex.com/api/2"
|
||||
|
||||
if [ (count $argv) -ne 1 ]
|
||||
echo "tx-push FILE"
|
||||
exit 2
|
||||
end
|
||||
|
||||
if [ ! -f ~/.transifexrc ]
|
||||
echo "no ~/.transifexrc found"
|
||||
exit 1
|
||||
end
|
||||
|
||||
set user (cat ~/.transifexrc | grep -E "^username" | awk -F " ?= ?" '{ print $2 }')
|
||||
set pass (cat ~/.transifexrc | grep -E "^password" | awk -F " ?= ?" '{ print $2 }')
|
||||
|
||||
set lang (echo $argv | cut -d / -f 5 | cut -d . -f 1)
|
||||
set trans (mktemp -t tx.XXX)
|
||||
|
||||
if [ $lang = "en" ]
|
||||
set url "$TRANSIFEX/project/isso/resource/js/content/"
|
||||
else
|
||||
set url "$TRANSIFEX/project/isso/resource/js/translation/$lang/"
|
||||
end
|
||||
|
||||
printf '{"content":' > $trans
|
||||
cat $argv \
|
||||
| sed "s,^define(,,g;\$ s,);,,g" \
|
||||
| python -c 'import json,sys; print json.dumps(sys.stdin.read())' \
|
||||
>> $trans
|
||||
printf '}' >> $trans
|
||||
|
||||
curl -L -u $user:$pass -XPUT $url -H "Content-Type: application/json" -d @$trans
|
@ -1,128 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2020 Lucas Cimon.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
"""Dump isso comments as text
|
||||
|
||||
The script can be run like this:
|
||||
|
||||
contrib/dump_comments.py .../path/to/isso.db --sort-by-last-reply
|
||||
|
||||
To get a list of all available options:
|
||||
|
||||
contrib/dump_comments.py --help
|
||||
|
||||
By installing the optional colorama dependency, you'll get a colored output.
|
||||
An example of output can be found at https://github.com/posativ/isso/issues/634
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sqlite3
|
||||
from collections import defaultdict, namedtuple
|
||||
from datetime import date
|
||||
from textwrap import indent
|
||||
|
||||
|
||||
class ColorFallback():
|
||||
__getattr__ = lambda self, name: '' # noqa: E731
|
||||
|
||||
|
||||
try:
|
||||
from colorama import Fore, Style, init
|
||||
init() # needed for Windows
|
||||
except ImportError: # fallback so that the imported classes always exist
|
||||
Fore = Style = ColorFallback()
|
||||
|
||||
|
||||
Comment = namedtuple('Comment', ('uri', 'id', 'parent', 'created', 'text', 'author', 'email', 'website', 'likes', 'dislikes', 'replies'))
|
||||
|
||||
INDENT = ' '
|
||||
QUERY = 'SELECT uri, comments.id, parent, created, text, author, email, website, likes, dislikes FROM comments INNER JOIN threads on comments.tid = threads.id'
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
if not args.colors:
|
||||
global Fore, Style
|
||||
Fore = Style = ColorFallback()
|
||||
db = sqlite3.connect(args.db_path)
|
||||
comments_per_uri = defaultdict(list)
|
||||
for result in db.execute(QUERY).fetchall():
|
||||
comment = Comment(*result, replies=[])
|
||||
comments_per_uri[comment.uri].append(comment)
|
||||
root_comments_per_sort_date = {}
|
||||
for comments in comments_per_uri.values():
|
||||
comments_per_id = {comment.id: comment for comment in comments}
|
||||
root_comments, sort_date = [], None
|
||||
for comment in comments:
|
||||
if comment.parent: # == this is a "reply" comment
|
||||
comments_per_id[comment.parent].replies.append(comment)
|
||||
if args.sort_by_last_reply and (sort_date is None or comment.created > sort_date):
|
||||
sort_date = comment.created
|
||||
else:
|
||||
root_comments.append(comment)
|
||||
if sort_date is None or comment.created > sort_date:
|
||||
sort_date = comment.created
|
||||
root_comments_per_sort_date[sort_date] = root_comments
|
||||
for _, root_comments in sorted(root_comments_per_sort_date.items(), key=lambda pair: pair[0]):
|
||||
print(Fore.MAGENTA + args.url_prefix + root_comments[0].uri + Fore.RESET)
|
||||
for comment in root_comments:
|
||||
print_comment(INDENT, comment)
|
||||
for comment in comment.replies:
|
||||
print_comment(INDENT * 2, comment)
|
||||
print()
|
||||
|
||||
|
||||
def print_comment(prefix, comment):
|
||||
author = comment.author or 'Anonymous'
|
||||
email = comment.email or ''
|
||||
website = comment.website or ''
|
||||
when = date.fromtimestamp(comment.created)
|
||||
popularity = ''
|
||||
if comment.likes:
|
||||
popularity = '+{.likes}'.format(comment)
|
||||
if comment.dislikes:
|
||||
if popularity:
|
||||
popularity += '/'
|
||||
popularity = '-{.dislikes}'.format(comment)
|
||||
print(prefix + '{Style.BRIGHT}{author}{Style.RESET_ALL} {Style.DIM}- {email} {website}{Style.RESET_ALL} {when} {Style.DIM}{popularity}{Style.RESET_ALL}'.format(Style=Style, **locals()))
|
||||
print(indent(comment.text, prefix))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Dump all Isso comments in chronological order, grouped by replies',
|
||||
formatter_class=ArgparseHelpFormatter)
|
||||
parser.add_argument('db_path', help='File path to Isso Sqlite DB')
|
||||
parser.add_argument('--sort-by-last-reply', action='store_true', help='By default comments are sorted by "parent" comment date, this sort comments based on the last replies')
|
||||
parser.add_argument('--url-prefix', default='', help='Optional domain name to prefix to pages URLs')
|
||||
parser.add_argument('--no-colors', action='store_false', dest='colors', default=True, help='Disabled colored output')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
class ArgparseHelpFormatter(argparse.RawTextHelpFormatter, argparse.ArgumentDefaultsHelpFormatter):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
"""Comment importer from Blogger
|
||||
|
||||
This python script can convert comments posted to a Blogger-powered blog to a
|
||||
JSON file with can then be imported into Isso (by following the procedure
|
||||
explained in docs/docs/extras/advanced-migration.rst.
|
||||
|
||||
The script can be run like this:
|
||||
|
||||
python import_blogger.py -p 'http://myblog.com/' blogger.xml out.json
|
||||
|
||||
where `blogger.xml` is a dump of the blog produced by the Blogger platform, and
|
||||
the URL following the `-p` option is a prefix that will be applied to all post
|
||||
URLs: the original host will be stripped and the path will be appended to the
|
||||
string you specify here (this can be useful in the case that your blog moved to
|
||||
a different domain, subdomain, or just into a new directory).
|
||||
The `out.json` file is the file which will be generated by this tool, and which
|
||||
can then be fed into isso:
|
||||
|
||||
isso -c /path/to/isso.cfg import -t generic out.json
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
import feedparser
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class Post:
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
self.title = None
|
||||
self.comments = []
|
||||
|
||||
def add_comment(self, comment):
|
||||
comment['id'] = len(self.comments) + 1
|
||||
self.comments.append(comment)
|
||||
|
||||
|
||||
def encode_post(post):
|
||||
ret = {}
|
||||
ret['id'] = post.url
|
||||
ret['title'] = post.title
|
||||
ret['comments'] = post.comments
|
||||
return ret
|
||||
|
||||
|
||||
class ImportBlogger:
|
||||
TYPE_COMMENT = 'http://schemas.google.com/blogger/2008/kind#comment'
|
||||
TYPE_POST = 'http://schemas.google.com/blogger/2008/kind#post'
|
||||
|
||||
def __init__(self, filename_in, filename_out, prefix):
|
||||
self.channel = feedparser.parse(filename_in)
|
||||
self.filename_out = filename_out
|
||||
self.prefix = prefix
|
||||
|
||||
def run(self):
|
||||
self.posts = {}
|
||||
for item in self.channel.entries:
|
||||
terms = [tag.term for tag in item.tags]
|
||||
if not terms:
|
||||
continue
|
||||
if terms[0] == self.TYPE_COMMENT:
|
||||
post = self.ensure_post(item)
|
||||
post.add_comment(self.process_comment(item))
|
||||
elif terms[0] == self.TYPE_POST:
|
||||
self.process_post(item)
|
||||
|
||||
data = [encode_post(p) for p in self.posts.values() if p.comments]
|
||||
with open(self.filename_out, 'w') as fp:
|
||||
json.dump(data, fp, indent=2)
|
||||
|
||||
def process_post(self, item):
|
||||
pid = self.post_id(item)
|
||||
if pid in self.posts:
|
||||
post = self.posts[pid]
|
||||
else:
|
||||
post = Post(pid)
|
||||
self.posts[pid] = post
|
||||
post.title = item.title
|
||||
|
||||
def ensure_post(self, item):
|
||||
pid = self.post_id(item)
|
||||
post = self.posts.get(pid, None)
|
||||
if not post:
|
||||
post = Post(pid)
|
||||
self.posts[pid] = post
|
||||
return post
|
||||
|
||||
def process_comment(self, item):
|
||||
comment = {}
|
||||
comment['author'] = item.author_detail.name
|
||||
comment['email'] = item.author_detail.email
|
||||
comment['website'] = item.author_detail.get('href', '')
|
||||
t = time.strftime('%Y-%m-%d %H:%M:%S', item.published_parsed)
|
||||
comment['created'] = t
|
||||
comment['text'] = item.content[0].value
|
||||
comment['remote_addr'] = '127.0.0.1'
|
||||
return comment
|
||||
|
||||
def post_id(self, item):
|
||||
u = urlparse(item.link)
|
||||
return self.prefix + u.path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert comments from blogger.com')
|
||||
parser.add_argument('input', help='input file')
|
||||
parser.add_argument('output', help='output file')
|
||||
parser.add_argument('-p', dest='prefix',
|
||||
help='prefix to be added to paths (ID)',
|
||||
type=str, default='')
|
||||
args = parser.parse_args()
|
||||
|
||||
importer = ImportBlogger(args.input, args.output, args.prefix)
|
||||
importer.run()
|
60
docs/DEVELOPMENT.md
Normal file
60
docs/DEVELOPMENT.md
Normal file
@ -0,0 +1,60 @@
|
||||
Development
|
||||
===========
|
||||
|
||||
If you want to hack on Isso or track down issues, there's an alternate
|
||||
way to set up Isso. It requires a lot more dependencies and effort.
|
||||
|
||||
Requirements:
|
||||
|
||||
- Python 2.6, 2.7 or 3.3
|
||||
- Ruby 1.8 or higher
|
||||
- Node.js, [NPM](https://npmjs.org/) and [Bower](http://bower.io/)
|
||||
|
||||
On Debian/Ubuntu install the following packages
|
||||
|
||||
~> sudo aptitude install python-setuptools python-dev npm ruby
|
||||
~> ln -s /usr/bin/nodejs /usr/bin/node
|
||||
|
||||
Get the repository:
|
||||
|
||||
~> git clone https://github.com/posativ/isso.git
|
||||
~> cd isso/
|
||||
|
||||
Install `virtualenv` and create a new environment for Isso (recommended):
|
||||
|
||||
~> pip install virtualenv
|
||||
~> virtualenv .
|
||||
~> source ./bin/activate
|
||||
|
||||
Install Isso dependencies:
|
||||
|
||||
~> python setup.py develop
|
||||
~> isso run
|
||||
|
||||
Compile SCSS to CSS:
|
||||
|
||||
~> gem install sass
|
||||
~> scss --watch isso/css/isso.scss
|
||||
|
||||
Install JS components:
|
||||
|
||||
~> cd isso/js
|
||||
~> bower install almond q requirejs requirejs-domready requirejs-text
|
||||
|
||||
|
||||
Integration
|
||||
-----------
|
||||
|
||||
```html
|
||||
<script src="/isso/js/config.js"></script>
|
||||
<script data-main="/isso/js/embed" src="/isso/js/components/requirejs/require.js"></script>
|
||||
```
|
||||
|
||||
|
||||
Optimization
|
||||
------------
|
||||
|
||||
~> npm install -g requirejs uglifyjs
|
||||
~> cd isso/js
|
||||
~> r.js -o build.embed.js
|
||||
~> r.js -o build.count.js
|
1298
docs/_isso/html5.py
Normal file
1298
docs/_isso/html5.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -10,8 +10,7 @@
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }},
|
||||
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
|
@ -1,11 +0,0 @@
|
||||
|
||||
from docutils import nodes
|
||||
from sphinx.writers.html import HTMLTranslator
|
||||
|
||||
|
||||
class IssoTranslator(HTMLTranslator):
|
||||
|
||||
def visit_title(self, node):
|
||||
if self.section_level == 1:
|
||||
raise nodes.SkipNode
|
||||
HTMLTranslator.visit_title(self, node)
|
@ -13,20 +13,14 @@
|
||||
{{ doc("docs/quickstart", "Quickstart") }}
|
||||
{{ doc("docs/troubleshooting", "Troubleshooting") }}
|
||||
</ul>
|
||||
<strong>Advanced Setup</strong>
|
||||
<ul>
|
||||
{{ doc("docs/setup/sub-uri", "Sub URI") }}
|
||||
{{ doc("docs/setup/multiple-sites", "Multiple Sites") }}
|
||||
</ul>
|
||||
<strong>Configuration</strong>
|
||||
<ul>
|
||||
{{ doc("docs/configuration/setup", "Setup") }}
|
||||
{{ doc("docs/configuration/server", "Server") }}
|
||||
{{ doc("docs/configuration/client", "Client") }}
|
||||
</ul>
|
||||
<strong>Extras</strong>
|
||||
<strong>Advanced</strong>
|
||||
<ul>
|
||||
{{ doc("docs/extras/deployment", "Deployment") }}
|
||||
{{ doc("docs/extras/advanced-integration", "Advanced Integration") }}
|
||||
{{ doc("docs/extras/uwsgi", "uWSGI") }}
|
||||
{{ doc("docs/extras/api", "API") }}
|
||||
{{ doc("docs/extras/contribs", "Community tools") }}
|
||||
</ul>
|
||||
|
45
docs/_static/css/site.scss
vendored
45
docs/_static/css/site.scss
vendored
@ -180,14 +180,6 @@ main {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
p + p:last-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
@ -209,21 +201,6 @@ 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 {
|
||||
@ -298,7 +275,7 @@ main {
|
||||
pre, code {
|
||||
color: rgb(77, 77, 76);
|
||||
font-size: 12px;
|
||||
font-family: Monaco, Menlo, Consolas, monospace;
|
||||
font-family: Monaco, Menlo, Consolas, monospaced;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
@ -313,10 +290,6 @@ main {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.headerlink {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
p + p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
@ -364,20 +337,4 @@ main {
|
||||
dd {
|
||||
margin-left: 1.2em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
.admonition {
|
||||
|
||||
p + p {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
p:not(:first-child) {
|
||||
margin-left: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
docs/_static/flattr.png
vendored
BIN
docs/_static/flattr.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB |
58
docs/conf.py
58
docs/conf.py
@ -13,26 +13,11 @@
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import io
|
||||
import re
|
||||
import pkg_resources
|
||||
import os
|
||||
|
||||
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.
|
||||
@ -64,19 +49,16 @@ master_doc = 'docs/index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Isso'
|
||||
copyright = u'2016, Martin Zimmermann'
|
||||
copyright = u'2013, Martin Zimmermann'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = dist.version
|
||||
# The short X.Y version.
|
||||
version = "{0}.{1}".format(*LooseVersion(dist.version).version)
|
||||
version = '0.5'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.5.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
@ -122,7 +104,7 @@ pygments_style = 'trac'
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = '_isso'
|
||||
html_translator_class = "remove_heading.IssoTranslator"
|
||||
html_translator_class = "html5.Isso"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@ -200,22 +182,22 @@ htmlhelp_basename = 'Issodoc'
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'Isso.tex', u'Isso Documentation',
|
||||
u'Martin Zimmermann', 'manual'),
|
||||
('index', 'Isso.tex', u'Isso Documentation',
|
||||
u'Martin Zimmermann', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@ -244,10 +226,8 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('docs/man/index', 'isso', u'a Disqus alternative',
|
||||
[u'Martin Zimmermann'], 1),
|
||||
('docs/configuration/server', 'isso.conf', u'server configuration',
|
||||
[u'Martin Zimmermann'], 5)
|
||||
('index', 'isso', u'Isso Documentation',
|
||||
[u'Martin Zimmermann'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@ -260,9 +240,9 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Isso', u'Isso Documentation',
|
||||
u'Martin Zimmermann', 'Isso', 'a commenting server similar to Disqus',
|
||||
'Miscellaneous'),
|
||||
('index', 'Isso', u'Isso Documentation',
|
||||
u'Martin Zimmermann', 'Isso', 'a commenting server similar to Disqus',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
@ -30,20 +30,6 @@ Report issues
|
||||
- **Isso-related issues** – Copy and paste traceback into a ticket and provide
|
||||
some details of your usage.
|
||||
|
||||
Translations
|
||||
------------
|
||||
|
||||
Isso supports multiple languages and it is fairly easy to add new translations.
|
||||
You can either use the `english translation file`__ or use Transifex_. Contact
|
||||
me on IRC (@posativ) if you want to be the main contributor for a language.
|
||||
|
||||
You may notice some "weird" newlines in translations -- that's the separator
|
||||
for pluralforms_ in the templating engine.
|
||||
|
||||
.. __: https://github.com/posativ/isso/blob/master/isso/js/app/i18n/en.js
|
||||
.. _Transifex: https://www.transifex.com/projects/p/isso/
|
||||
.. _pluralforms: http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms
|
||||
|
||||
Where I need help.
|
||||
------------------
|
||||
|
||||
@ -59,3 +45,14 @@ definitely need help:
|
||||
- delete or activate comments matching a filter (e.g. name, email, ip address)
|
||||
|
||||
- close threads and remove threads completely
|
||||
|
||||
- edit comments
|
||||
|
||||
* `Pagination <https://github.com/posativ/isso/issues/14>`_ – while Isso is
|
||||
generally a lot faster than Disqus, after approx. 50 comments you will
|
||||
notice roughly 1 second rendering time. It would be nice if the client
|
||||
fetches only N comments and continues when the user scrolls down (or click
|
||||
on a button to fetch more).
|
||||
|
||||
* CSS improvements. For some websites, the Isso integration just looks ugly.
|
||||
If you can improve it, please do it :)
|
||||
|
0
docs/docs/cli/comments.rst
Normal file
0
docs/docs/cli/comments.rst
Normal file
@ -7,39 +7,17 @@ preferably in the script tag which embeds the JS:
|
||||
.. code-block:: html
|
||||
|
||||
<script data-isso="/prefix/"
|
||||
data-isso-id="thread-id"
|
||||
data-isso-css="true"
|
||||
data-isso-lang="ru"
|
||||
data-isso-reply-to-self="false"
|
||||
data-isso-require-author="false"
|
||||
data-isso-require-email="false"
|
||||
data-isso-reply-notifications="false"
|
||||
data-isso-max-comments-top="10"
|
||||
data-isso-max-comments-nested="5"
|
||||
data-isso-reveal-on-click="5"
|
||||
data-isso-avatar="true"
|
||||
data-isso-avatar-bg="#f0f0f0"
|
||||
data-isso-avatar-fg="#9abf88 #5698c4 #e279a3 #9163b6 ..."
|
||||
data-isso-vote="true"
|
||||
data-isso-vote-levels=""
|
||||
data-isso-feed="false"
|
||||
data-isso-reply-toself="false"
|
||||
src="/prefix/js/embed.js"></script>
|
||||
|
||||
Furthermore you can override the automatic title detection inside
|
||||
the embed tag, as well as the thread ID, e.g.:
|
||||
the embed tag, e.g.:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<section id="isso-thread" data-title="Foo!" data-isso-id="/path/to/resource"></section>
|
||||
|
||||
Additionally, you can override any translation string for any language by adding
|
||||
a ``data-isso-`` attribute that is equal to the translation key (found `here`__) with
|
||||
``-text-[lang]`` appended to it. So, for example, if you want to override the
|
||||
english translation of the ``postbox-notification`` message, you could add:
|
||||
|
||||
``data-isso-postbox-notification-text-en="Select to be notified of replies to your comment"``
|
||||
|
||||
.. __: https://github.com/posativ/isso/blob/master/isso/js/app/i18n/en.js
|
||||
<section id="isso-thread" data-title="Foo!"></section>
|
||||
|
||||
data-isso
|
||||
---------
|
||||
@ -65,94 +43,20 @@ Defaults to `true`.
|
||||
data-isso-lang
|
||||
--------------
|
||||
|
||||
Override useragent's preferred language. Isso has been translated in over 12
|
||||
languages. The language is configured by its `ISO 639-1
|
||||
<https://en.wikipedia.org/wiki/ISO_639-1>`_ (two letter) code.
|
||||
|
||||
You find a list of all supported languages on `GitHub
|
||||
<https://github.com/posativ/isso/tree/master/isso/js/app/i18n>`_.
|
||||
Override useragent's preferred language. Currently available: german (de),
|
||||
english (en) and french (fr).
|
||||
|
||||
data-isso-reply-to-self
|
||||
-----------------------
|
||||
|
||||
Set to `true` when spam guard is configured with `reply-to-self = true`.
|
||||
|
||||
data-isso-require-author
|
||||
------------------------
|
||||
data-isso-id
|
||||
------------
|
||||
|
||||
Set to `true` when spam guard is configured with `require-author = true`.
|
||||
Set a custom thread id, defaults to current URI. If you a comment counter, add
|
||||
this attribute to the link tag, too.
|
||||
|
||||
data-isso-require-email
|
||||
-----------------------
|
||||
.. code-block:: html
|
||||
|
||||
Set to `true` when spam guard is configured with `require-email = true`.
|
||||
|
||||
data-isso-reply-notifications
|
||||
-----------------------------
|
||||
|
||||
Set to `true` when reply notifications is configured with `reply-notifications = true`.
|
||||
|
||||
data-isso-max-comments-top and data-isso-max-comments-nested
|
||||
------------------------------------------------------------
|
||||
|
||||
Number of top level (or nested) comments to show by default. If some
|
||||
comments are not shown, an "X Hidden" link is shown.
|
||||
|
||||
Set to `"inf"` to show all, or `"0"` to hide all.
|
||||
|
||||
data-isso-reveal-on-click
|
||||
-------------------------
|
||||
|
||||
Number of comments to reveal on clicking the "X Hidden" link.
|
||||
|
||||
data-isso-avatar
|
||||
----------------
|
||||
|
||||
Enable or disable avatar generation.
|
||||
|
||||
data-isso-avatar-bg
|
||||
-------------------
|
||||
|
||||
Set avatar background color. Any valid CSS color will do.
|
||||
|
||||
data-isso-avatar-fg
|
||||
-------------------
|
||||
|
||||
Set avatar foreground color. Up to 8 colors are possible. The default color
|
||||
scheme is based in `this color palette <http://colrd.com/palette/19308/>`_.
|
||||
Multiple colors must be separated by space. If you use less than eight colors
|
||||
and not a multiple of 2, the color distribution is not even.
|
||||
|
||||
data-isso-gravatar
|
||||
------------------
|
||||
|
||||
Uses gravatar images instead of generating svg images. You have to set
|
||||
"data-isso-avatar" to **false** when you want to use this. Otherwise
|
||||
both the gravatar and avatar svg image will show up. Please also set
|
||||
option "gravatar" to **true** in the server configuration...
|
||||
|
||||
data-isso-vote
|
||||
--------------
|
||||
|
||||
Enable or disable voting feature on the client side.
|
||||
|
||||
data-isso-vote-levels
|
||||
---------------------
|
||||
|
||||
List of vote levels used to customize comment appearance based on score.
|
||||
Provide a comma-separated values (eg. `"0,5,10,25,100"`) or a JSON array (eg. `"[-5,5,15]"`).
|
||||
|
||||
For example, the value `"-5,5"` will cause each `isso-comment` to be given one of these 3 classes:
|
||||
|
||||
- `isso-vote-level-0` for scores lower than `-5`
|
||||
- `isso-vote-level-1` for scores between `-5` and `4`
|
||||
- `isso-vote-level-2` for scores of `5` and greater
|
||||
|
||||
These classes can then be used to customize the appearance of comments (eg. put a star on popular comments)
|
||||
|
||||
data-isso-feed
|
||||
--------------
|
||||
|
||||
Enable or disable the addition of a link to the feed for the comment
|
||||
thread. The link will only be valid if the appropriate setting, in
|
||||
``[rss]`` section, is also enabled server-side.
|
||||
<section data-isso-id="test.abc" id="isso-thread"></section>
|
||||
|
@ -2,9 +2,7 @@ Server Configuration
|
||||
====================
|
||||
|
||||
The Isso configuration file is an `INI-style`__ textfile. It reads integers,
|
||||
booleans, strings and lists. Here's the default isso configuration:
|
||||
`isso.conf <https://github.com/posativ/isso/blob/master/share/isso.conf>`. A
|
||||
basic configuration from scratch looks like this:
|
||||
booleans and strings. Below is a basic example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
@ -12,7 +10,7 @@ basic configuration from scratch looks like this:
|
||||
dbpath = /var/lib/isso/comments.db
|
||||
host = https://example.tld/
|
||||
[server]
|
||||
listen = http://localhost:1234/
|
||||
port = 1234
|
||||
|
||||
To use your configuration file with Isso, append ``-c /path/to/cfg`` to the
|
||||
executable or run Isso with an environment variable:
|
||||
@ -41,10 +39,10 @@ session key and hostname. Here are the default values for this section:
|
||||
[general]
|
||||
dbpath = /tmp/isso.db
|
||||
name =
|
||||
host =
|
||||
host = http://localhost:8080/
|
||||
max-age = 15m
|
||||
notify = stdout
|
||||
log-file =
|
||||
session-key = ... ; python: binascii.b2a_hex(os.urandom(24))
|
||||
notify =
|
||||
|
||||
dbpath
|
||||
file location to the SQLite3 database, highly recommended to change this
|
||||
@ -55,68 +53,34 @@ name
|
||||
not used otherwise.
|
||||
|
||||
host
|
||||
Your website(s). If Isso is unable to connect to at least one site, you'll
|
||||
get a warning during startup and comments are most likely non-functional.
|
||||
URL to your website. When you start Isso, it will probe your website with
|
||||
a simple ``GET /`` request to see if it can reach the webserver. If this
|
||||
fails, Isso may not be able check if a web page exists, thus fails to
|
||||
accept new comments.
|
||||
|
||||
You'll need at least one host/website to run Isso. This is due to security
|
||||
reasons: Isso uses CORS_ to embed comments and to restrict comments only to
|
||||
your website, you have to "whitelist" your website(s).
|
||||
|
||||
I recommend the first value to be a non-SSL website that is used as fallback
|
||||
if Firefox users (and only those) supress their HTTP referer completely.
|
||||
You can supply more than one host:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[general]
|
||||
host =
|
||||
http://example.tld/
|
||||
https://example.tld/
|
||||
http://localhost/
|
||||
https://localhost/
|
||||
|
||||
This is useful, when your website is available on HTTP and HTTPS.
|
||||
|
||||
session-key
|
||||
private session key to validate client cookies. If you restart the
|
||||
application several times per hour for whatever reason, use a fixed
|
||||
key.
|
||||
|
||||
max-age
|
||||
time range that allows users to edit/remove their own comments. See
|
||||
:ref:`Appendum: Timedelta <appendum-timedelta>` for valid values.
|
||||
|
||||
notify
|
||||
Select notification backend(s) for new comments, separated by comma.
|
||||
Available backends:
|
||||
|
||||
stdout
|
||||
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
|
||||
moderated) and deletion links.
|
||||
|
||||
reply-notifications
|
||||
Allow users to request E-mail notifications for replies to their post.
|
||||
|
||||
It is highly recommended to also turn on moderation when enabling this
|
||||
setting, as Isso can otherwise be easily exploited for sending spam.
|
||||
|
||||
Do not forget to configure the client accordingly.
|
||||
|
||||
log-file
|
||||
Log console messages to file instead of standard out.
|
||||
|
||||
gravatar
|
||||
When set to ``true`` this will add the property "gravatar_image"
|
||||
containing the link to a gravatar image to every comment. If a comment
|
||||
does not contain an email address, gravatar will render a random icon.
|
||||
This is only true when using the default value for "gravatar-url"
|
||||
which contains the query string param ``d=identicon`` ...
|
||||
|
||||
gravatar-url
|
||||
Url for gravatar images. The "{}" is where the email hash will be placed.
|
||||
Defaults to "https://www.gravatar.com/avatar/{}?d=identicon"
|
||||
|
||||
latest-enabled
|
||||
If True it will enable the ``/latest`` endpoint. Optional, defaults
|
||||
to False.
|
||||
|
||||
|
||||
|
||||
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
|
||||
Select notification backend for new comments. Currently, only SMTP
|
||||
is available.
|
||||
|
||||
|
||||
Moderation
|
||||
@ -128,21 +92,13 @@ Enable moderation queue and handling of comments still in moderation queue
|
||||
|
||||
[moderation]
|
||||
enabled = false
|
||||
approve-if-email-previously-approved = false
|
||||
purge-after = 30d
|
||||
|
||||
enabled
|
||||
enable comment moderation queue. This option only affects new comments.
|
||||
Comments in moderation queue are not visible to other users until you
|
||||
Comments in modertion queue are not visible to other users until you
|
||||
activate them.
|
||||
|
||||
approve-if-email-previously-approved
|
||||
automatically approve comments by an email address if that address has
|
||||
had a comment approved within the last 6 months. No ownership verification
|
||||
is done on the entered email address. This means that if someone is able
|
||||
to guess correctly the email address used by a previously approved author,
|
||||
they will be able to have their new comment auto-approved.
|
||||
|
||||
purge-after
|
||||
remove unprocessed comments in moderation queue after given time.
|
||||
|
||||
@ -167,7 +123,7 @@ listen
|
||||
; UNIX domain socket
|
||||
listen = unix:///tmp/isso.sock
|
||||
; TCP/IP
|
||||
listen = http://localhost:1234/
|
||||
listen = http:///localhost:1234/
|
||||
|
||||
When ``gevent`` is available, it is automatically used for `http://`
|
||||
Currently, gevent can not handle http requests on unix domain socket
|
||||
@ -176,27 +132,15 @@ listen
|
||||
|
||||
Does not apply for `uWSGI`.
|
||||
|
||||
public-endpoint
|
||||
public URL that Isso is accessed from by end users. Should always be
|
||||
a http:// or https:// absolute address. If left blank, automatic
|
||||
detection is attempted. Normally only needs to be specified if
|
||||
different than the `listen` setting.
|
||||
|
||||
reload
|
||||
reload application, when the source code has changed. Useful for
|
||||
development. Only works with the internal webserver.
|
||||
development (don't forget to use a fixed `session-key`). Only works
|
||||
when ``gevent`` and ``uwsgi`` are *not* available.
|
||||
|
||||
profile
|
||||
show 10 most time consuming function in Isso after each request. Do
|
||||
not use in production.
|
||||
|
||||
trusted-proxies
|
||||
an optional list of reverse proxies IPs behind which you have deployed
|
||||
your Isso web service (e.g. `127.0.0.1`).
|
||||
This allow for proper remote address resolution based on a
|
||||
`X-Forwarded-For` HTTP header, which is important for the mechanism
|
||||
forbiding several comment votes coming from the same subnet.
|
||||
|
||||
.. _configure-smtp:
|
||||
|
||||
SMTP
|
||||
@ -212,11 +156,10 @@ also can moderate (=activate or delete) comments. Don't forget to configure
|
||||
username =
|
||||
password =
|
||||
host = localhost
|
||||
port = 587
|
||||
security = starttls
|
||||
port = 465
|
||||
ssl = on
|
||||
to =
|
||||
from =
|
||||
timeout = 10
|
||||
|
||||
username
|
||||
self-explanatory, optional
|
||||
@ -231,22 +174,16 @@ host
|
||||
port
|
||||
SMTP port
|
||||
|
||||
security
|
||||
use a secure connection to the server, possible values: *none*, *starttls*
|
||||
or *ssl*. Note, that there is no easy way for Python 2.7 and 3.3 to
|
||||
implement certification validation and thus the connection is vulnerable to
|
||||
Man-in-the-Middle attacks. You should definitely use a dedicated SMTP
|
||||
account for Isso in that case.
|
||||
ssl
|
||||
use SSL to connect to the server. Python probably does not validate the
|
||||
certificate. Needs research, though. But you should use a dedicated
|
||||
email account anyways.
|
||||
|
||||
to
|
||||
recipient address, e.g. your email address
|
||||
|
||||
from
|
||||
sender address, e.g. `"Foo Bar" <isso@example.tld>`
|
||||
|
||||
timeout
|
||||
specify a timeout in seconds for blocking operations like the
|
||||
connection attempt.
|
||||
sender address, e.g. isso@example.tld
|
||||
|
||||
|
||||
Guard
|
||||
@ -262,8 +199,6 @@ for IPv4, ``/48`` for IPv6).
|
||||
ratelimit = 2
|
||||
direct-reply = 3
|
||||
reply-to-self = false
|
||||
require-author = false
|
||||
require-email = false
|
||||
|
||||
enabled
|
||||
enable guard, recommended in production. Not useful for debugging
|
||||
@ -281,124 +216,8 @@ reply-to-self
|
||||
the comment. After the editing timeframe is gone, commenters can reply to
|
||||
their own comments anyways.
|
||||
|
||||
Do not forget to configure the `client <client>`_ accordingly
|
||||
Do not forget to configure the client.
|
||||
|
||||
require-author
|
||||
force commenters to enter a value into the author field. No validation is
|
||||
performed on the provided value.
|
||||
|
||||
Do not forget to configure the `client <client>`_ accordingly.
|
||||
|
||||
require-email
|
||||
force commenters to enter a value into the email field. No validation is
|
||||
performed on the provided value.
|
||||
|
||||
Do not forget to configure the `client <client>`_ accordingly.
|
||||
|
||||
Markup
|
||||
------
|
||||
|
||||
Customize markup and sanitized HTML. Currently, only Markdown (via Misaka) is
|
||||
supported, but new languages are relatively easy to add.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[markup]
|
||||
options = strikethrough, superscript, autolink
|
||||
flags = skip-html, escape, hard-wrap
|
||||
allowed-elements =
|
||||
allowed-attributes =
|
||||
|
||||
options
|
||||
`Misaka-specific Markdown extensions <https://misaka.61924.nl/#api>`_, all
|
||||
extension flags can be used there, separated by comma, either by their name
|
||||
or as `EXT_`_.
|
||||
|
||||
flags
|
||||
`Misaka-specific HTML rendering flags
|
||||
<https://misaka.61924.nl/#html-render-flags>`_, all html rendering flags
|
||||
can be used here, separated by comma, either by their name or as `HTML_`_.
|
||||
Per Misaka's defaults, no flags are set.
|
||||
|
||||
allowed-elements
|
||||
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*, *ins*, *li*, *ol*, *p*, *pre*, *strong*,
|
||||
*table*, *tbody*, *td*, *th*, *thead* and *ul* are allowed.
|
||||
|
||||
allowed-attributes
|
||||
Additional HTML attributes (independent from elements) to allow in the
|
||||
generated output, comma-separated. By default, only *align* and *href* are
|
||||
allowed.
|
||||
|
||||
To allow images in comments, you just need to add ``allowed-elements = img`` and
|
||||
``allowed-attributes = src``.
|
||||
|
||||
Hash
|
||||
----
|
||||
|
||||
Customize used hash functions to hide the actual email addresses from
|
||||
commenters but still be able to generate an identicon.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[hash]
|
||||
salt = Eech7co8Ohloopo9Ol6baimi
|
||||
algorithm = pbkdf2
|
||||
|
||||
salt
|
||||
A salt is used to protect against rainbow tables. Isso does not make use of
|
||||
pepper (yet). The default value has been in use since the release of Isso
|
||||
and generates the same identicons for same addresses across installations.
|
||||
|
||||
algorithm
|
||||
Hash algorithm to use -- either from Python's `hashlib` or PBKDF2 (a
|
||||
computational expensive hash function).
|
||||
|
||||
The actual identifier for PBKDF2 is `pbkdf2:1000:6:sha1`, which means 1000
|
||||
iterations, 6 bytes to generate and SHA1 as pseudo-random family used for
|
||||
key strengthening.
|
||||
Arguments have to be in that order, but can be reduced to `pbkdf2:4096`
|
||||
for example to override the iterations only.
|
||||
|
||||
.. _configure-rss:
|
||||
|
||||
RSS
|
||||
---
|
||||
|
||||
Isso can provide an Atom feed for each comment thread. Users can use
|
||||
them to subscribe to comments and be notified of changes. Atom feeds
|
||||
are enabled as soon as there is a base URL defined in this section.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[rss]
|
||||
base =
|
||||
limit = 100
|
||||
|
||||
base
|
||||
base URL to use to build complete URI to pages (by appending the URI from Isso)
|
||||
|
||||
limit
|
||||
number of most recent comments to return for a thread
|
||||
|
||||
Admin
|
||||
-----
|
||||
|
||||
Isso has an optional web administration interface that can be used to moderate
|
||||
comments. The interface is available under ``/admin`` on your isso URL.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[admin]
|
||||
enabled = true
|
||||
password = secret
|
||||
|
||||
enabled
|
||||
whether to enable the admin interface
|
||||
|
||||
password
|
||||
the plain text password to use for logging into the administration interface
|
||||
|
||||
Appendum
|
||||
--------
|
||||
@ -412,18 +231,3 @@ Timedelta
|
||||
|
||||
You can add different types: `1m30s` equals to 90 seconds, `3h45m12s`
|
||||
equals to 3 hours, 45 minutes and 12 seconds (12512 seconds).
|
||||
|
||||
Environment variables
|
||||
---------------------
|
||||
|
||||
.. _environment-variables:
|
||||
|
||||
Isso also support configuration through some environment variables:
|
||||
|
||||
ISSO_CORS_ORIGIN
|
||||
By default, `isso` will use the `Host` or else the `Referrer` HTTP header
|
||||
of the request to defines a CORS `Access-Control-Allow-Origin` HTTP header
|
||||
in the response.
|
||||
This environent variable allows you to define a broader fixed value,
|
||||
in order for example to share a single Isso instance among serveral of your
|
||||
subdomains : `ISSO_CORS_ORIGIN=*.example.test`
|
||||
|
@ -1,3 +1,34 @@
|
||||
Setup
|
||||
=====
|
||||
|
||||
Sub-URI
|
||||
-------
|
||||
|
||||
You can run Isso on the same domain as your website, which circumvents issues
|
||||
originating from CORS_. Also, privacy-protecting browser addons such as
|
||||
`Request Policy`_ wont block comments.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
server {
|
||||
listen [::]:80;
|
||||
listen [::]:443 ssl;
|
||||
server_name example.tld;
|
||||
root /var/www/example.tld;
|
||||
|
||||
location /isso {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Script-Name /isso;
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
}
|
||||
|
||||
Now, the website integration is just as described in :doc:`../quickstart` but
|
||||
with a different location.
|
||||
|
||||
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
|
||||
.. _Request Policy: https://www.requestpolicy.com/
|
||||
|
||||
|
||||
.. _configure-multiple-sites:
|
||||
|
||||
@ -5,8 +36,8 @@ Multiple Sites
|
||||
--------------
|
||||
|
||||
Isso is designed to serve comments for a single website and therefore stores
|
||||
comments for a relative URL. This is done to support HTTP, HTTPS and even domain transfers
|
||||
without manual intervention. You can chain Isso to support multiple
|
||||
comments for a relative URL to support HTTP, HTTPS and even domain transfers
|
||||
without manual intervention. But you can chain Isso to support multiple
|
||||
websites on different domains.
|
||||
|
||||
The following example uses `gunicorn <http://gunicorn.org/>`_ as WSGI server (
|
@ -1,56 +0,0 @@
|
||||
Advanced integration
|
||||
====================
|
||||
|
||||
Comment counter
|
||||
---------------
|
||||
|
||||
If you want to display a comment counter for a given thread, simply
|
||||
put a link to that comments thread anchor:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<a href="/my-uri.html#isso-thread">Comments</a>
|
||||
|
||||
The *isso js client* willl replace the content of this tag with a human readable
|
||||
counter like *"5 comments"*.
|
||||
|
||||
Alternatively, if guessing from `href` is not relevant, you could use a
|
||||
`data-isso-id` attribute on the `<a>` to indicate which thread to count for.
|
||||
|
||||
Now, either include `count.min.js` if you want to show only the comment count
|
||||
(e.g. on an index page) or `embed.min.js` for the full comment client (see
|
||||
:doc:`../quickstart`); do not mix both.
|
||||
|
||||
You can have as many comments counters as you want in a page, and they will be
|
||||
merged into a single `GET` request.
|
||||
|
||||
Asynchronous comments loading
|
||||
-----------------------------
|
||||
|
||||
Isso will automatically fetch comments after `DOMContentLoaded` event. However
|
||||
in the case where your website is creating content dynamically (eg. via ajax),
|
||||
you need to re-fetch comment thread manually. Here is how you can re-fetch the
|
||||
comment thread:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
window.Isso.fetchComments()
|
||||
|
||||
It will delete all comments under the thread but not the PostBox, fetch
|
||||
comments with `data-isso-id` attribute of the element `section#isso-thread` (if
|
||||
that attribute does not exist, fallback to `window.location.pathname`), then
|
||||
fill comments into the thread. In other words, you should change `data-isso-id`
|
||||
attribute of the element `section#isso-thread` (or modify the pathname with
|
||||
`location.pushState`) before you can get new comments. And the thread element
|
||||
itself should *NOT* be touched or removed.
|
||||
|
||||
If you removed the `section#isso-thread` element, just create another element
|
||||
with same TagName and ID in which you wish comments to be placed, then call the
|
||||
`init` method of `Isso`:
|
||||
|
||||
.. code-block:: js
|
||||
|
||||
window.Isso.init()
|
||||
|
||||
Then Isso will initialize the comment section and fetch comments, as if the page
|
||||
was loaded.
|
@ -1,70 +0,0 @@
|
||||
Advanced Migration
|
||||
==================
|
||||
|
||||
In quickstart we saw you can import comments from Disqus or WordPress. But there
|
||||
are a many other comments system and you could be using one of them.
|
||||
|
||||
Isso provides a way to import such comments, however it's up to you to to:
|
||||
|
||||
- dump comments
|
||||
- fit the data to the following JSON format::
|
||||
|
||||
A list of threads, each item being a dict with the following data:
|
||||
|
||||
- id: a text representing the unique thread id (note: by default isso
|
||||
associates this ID to the article URL, but it can be changed on
|
||||
client side with "data-isso-id" - see :doc:`client configuration <../configuration/client>` )
|
||||
- title: the title of the thread
|
||||
- comments: the list of comments
|
||||
|
||||
Each item in that list of comments is a dict with the following data:
|
||||
|
||||
- id: an integer with the unique id of the comment inside the thread
|
||||
(it can be repeated among different threads); this will be used to
|
||||
order the comment inside the thread
|
||||
- author: the author's name
|
||||
- email: the author's email
|
||||
- website: the author's website
|
||||
- remote_addr: the author's IP
|
||||
- created: a timestamp, in the format "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
[
|
||||
{
|
||||
"id": "/blog/article1",
|
||||
"title": "First article!",
|
||||
"comments": [
|
||||
{
|
||||
"author": "James",
|
||||
"created": "2018-11-28 17:24:23",
|
||||
"email": "email@mail.com",
|
||||
"id": "1",
|
||||
"remote_addr": "127.0.0.1",
|
||||
"text": "Great article!",
|
||||
"website": "http://fefzfzef.frzr"
|
||||
},
|
||||
{
|
||||
"author": "Harold",
|
||||
"created": "2018-11-28 17:58:03",
|
||||
"email": "email2@mail.com",
|
||||
"id": "2",
|
||||
"remote_addr": "",
|
||||
"text": "I hated it...",
|
||||
"website": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Keep in mind that isso expects to have an array, so keep the opening and ending square bracket even if you have only one article thread!
|
||||
|
||||
Next you can import you json dump:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> isso -c /path/to/isso.cfg import -t generic comment-dump.json
|
||||
[100%] 53 threads, 192 comments
|
||||
|
@ -23,11 +23,12 @@ Isso:
|
||||
"mode": 1,
|
||||
"hash": "4505c1eeda98",
|
||||
"author": null,
|
||||
"website": null,
|
||||
"email": null,
|
||||
"website": null
|
||||
"created": 1387321261.572392,
|
||||
"modified": null,
|
||||
"likes": 3,
|
||||
"dislikes": 0
|
||||
"dislikes": 0,
|
||||
}
|
||||
|
||||
id :
|
||||
@ -70,7 +71,7 @@ modified :
|
||||
List comments
|
||||
-------------
|
||||
|
||||
List all publicly visible comments for thread `uri`:
|
||||
List all publicely visible comments for thread `uri`:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
@ -83,19 +84,6 @@ plain :
|
||||
pass plain=1 to get the raw comment text, defaults to 0.
|
||||
|
||||
|
||||
Get the latest N comments for all threads:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
GET /latest?limit=N
|
||||
|
||||
The N parameter limits how many of the latest comments to retrieve; it's
|
||||
mandatory, and must be an integer greater than 0.
|
||||
|
||||
This endpoint needs to be enabled in the configuration (see the
|
||||
``latest-enabled`` option in the ``general`` section).
|
||||
|
||||
|
||||
Create comment
|
||||
--------------
|
||||
|
||||
@ -186,28 +174,4 @@ Up- and downvote comments
|
||||
Get comment count
|
||||
-----------------
|
||||
|
||||
Counts all publicly visible comments for thread `uri`:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
GET /count?uri=%2Fhello-world%2F
|
||||
2
|
||||
|
||||
uri :
|
||||
URI to count comments for, required.
|
||||
|
||||
returns an integer
|
||||
|
||||
Get Atom feed
|
||||
-------------
|
||||
|
||||
Get an Atom feed of comments for thread `uri`:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
GET /feed?uri=%2Fhello-world%2F
|
||||
|
||||
uri :
|
||||
URI to get comments for, required.
|
||||
|
||||
Returns an XML document as the Atom feed.
|
||||
...
|
||||
|
@ -1,20 +0,0 @@
|
||||
Community tools
|
||||
===============
|
||||
|
||||
Utility scripts
|
||||
---------------
|
||||
|
||||
Some utility scripts have been developed by isso users.
|
||||
They are stored in the `GitHub contrib/ directory
|
||||
<https://github.com/posativ/isso/tree/master/contrib>`_ :
|
||||
|
||||
* `dump_comments.py` : dump isso comments as text, optionally with color
|
||||
* `import_blogger.py` : comment importer from Blogger
|
||||
|
||||
|
||||
Related projects
|
||||
----------------
|
||||
|
||||
* `wonderfall/isso Docker image <https://github.com/Wonderfall/docker-isso>`
|
||||
* `a grav plugin to integrate isso comments <https://github.com/Sommerregen/grav-plugin-jscomments>`
|
||||
* `a Pelican theme supporting isso comments <https://github.com/Lucas-C/pelican-mg>`
|
@ -1,273 +0,0 @@
|
||||
Deployment
|
||||
----------
|
||||
|
||||
Isso ships with a built-in web server, which is useful for the initial setup
|
||||
and may be used in production for low-traffic sites (up to 20 requests per
|
||||
second). Running a "real" WSGI server supports nice things such as UNIX domain
|
||||
sockets, daemonization and solid HTTP handler. WSGI servers are more stable, secure
|
||||
and web-scale than the built-in web server.
|
||||
|
||||
* gevent_, coroutine-based network library
|
||||
* uWSGI_, full-featured uWSGI server
|
||||
* gunicorn_, Python WSGI HTTP Server for UNIX
|
||||
* mod_wsgi_, Apache interface to WSGI
|
||||
* mod_fastcgi_, Apache interface to FastCGI
|
||||
* uberspace.de, `try this guide (in german) <http://blog.posativ.org/2014/isso-und-uberspace-de/>`_
|
||||
* Openshift, Isso has a one click installer
|
||||
|
||||
|
||||
`gevent <http://www.gevent.org/>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Probably the easiest deployment method. Install with PIP (requires libevent):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install gevent
|
||||
|
||||
Then, just use the ``isso`` executable as usual. Gevent monkey-patches Python's
|
||||
standard library to work with greenlets.
|
||||
|
||||
To execute Isso, just use the commandline interface:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ isso -c my.cfg run
|
||||
|
||||
Unfortunately, gevent 0.13.2 does not support UNIX domain sockets (see `#295
|
||||
<https://github.com/surfly/gevent/issues/295>`_ and `#299
|
||||
<https://github.com/surfly/gevent/issues/299>`_ for details).
|
||||
|
||||
|
||||
`uWSGI <http://uwsgi-docs.readthedocs.org/en/latest/>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Isso has special support for uWSGI, namely fast IPC caching, job spooling and
|
||||
delayed jobs. It is the author's choice, but not the only one. You need
|
||||
uWSGI 1.9 or higher, fortunately you can install it from PyPi:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> apt-get install build-essential python-dev
|
||||
~> pip install uwsgi
|
||||
|
||||
For convenience, I recommend a INI-style configuration (you can also
|
||||
supply everything as command-line arguments):
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[uwsgi]
|
||||
http = :8080
|
||||
master = true
|
||||
; set to `nproc`
|
||||
processes = 4
|
||||
cache2 = name=hash,items=1024,blocksize=32
|
||||
; you may change this
|
||||
spooler = /tmp/isso/mail
|
||||
module = isso.run
|
||||
; uncomment if you use a virtual environment
|
||||
; virtualenv = /path/to/isso
|
||||
env = ISSO_SETTINGS=/path/to/isso.cfg
|
||||
|
||||
Then, create the spooling directory and start Isso via uWSGI:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> mkdir /tmp/isso/mail
|
||||
~> uwsgi /path/to/uwsgi.ini
|
||||
|
||||
|
||||
`gunicorn <http://gunicorn.org>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX with a pre-fork
|
||||
worker ported from Ruby's Unicorn project. Install gunicorn_ via PIP:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install gunicorn
|
||||
|
||||
To execute Isso, use a command similar to:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ export ISSO_SETTINGS="/path/to/isso.cfg"
|
||||
$ gunicorn -b localhost:8080 -w 4 --preload isso.run
|
||||
|
||||
|
||||
`mod_wsgi <https://code.google.com/p/modwsgi/>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
First, create a startup script, called `isso.wsgi`. If Isso is in your system module
|
||||
search path, then the script is quite simple. This script is included in the
|
||||
isso distribution as `run.py`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from isso import make_app
|
||||
from isso import dist, config
|
||||
|
||||
application = make_app(
|
||||
config.load(
|
||||
os.path.join(dist.location, dist.project_name, "defaults.ini"),
|
||||
"/path/to/isso.cfg"),
|
||||
multiprocessing=True)
|
||||
|
||||
If you have installed Isso in a virtual environment, then you will have to add the path
|
||||
of the virtualenv to the site-specific paths of Python:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import site
|
||||
site.addsitedir("/path/to/isso_virtualenv")
|
||||
|
||||
import os
|
||||
|
||||
from isso import make_app
|
||||
from isso import dist, config
|
||||
|
||||
application = make_app(
|
||||
config.load(
|
||||
os.path.join(dist.location, dist.project_name, "defaults.ini"),
|
||||
"/path/to/isso.cfg",
|
||||
multiprocessing=True)
|
||||
|
||||
Using the aforementioned script will load system modules when available and modules
|
||||
from the virtualenv otherwise. Should you want the opposite behavior, where modules from
|
||||
the virtualenv have priority over system modules, the following script does the trick:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import site
|
||||
import sys
|
||||
|
||||
# Remember original sys.path.
|
||||
prev_sys_path = list(sys.path)
|
||||
|
||||
# Add the new site-packages directory.
|
||||
site.addsitedir("/path/to/isso_virtualenv")
|
||||
|
||||
# Reorder sys.path so new directories at the front.
|
||||
new_sys_path = []
|
||||
for item in list(sys.path):
|
||||
if item not in prev_sys_path:
|
||||
new_sys_path.append(item)
|
||||
sys.path.remove(item)
|
||||
sys.path[:0] = new_sys_path
|
||||
|
||||
from isso import make_app
|
||||
from isso import dist, config
|
||||
|
||||
application = make_app(
|
||||
config.load(
|
||||
os.path.join(dist.location, dist.project_name, "defaults.ini"),
|
||||
"/path/to/isso.cfg",
|
||||
multiprocessing=True)
|
||||
|
||||
The last two scripts are based on those given by
|
||||
`mod_wsgi documentation <https://code.google.com/p/modwsgi/wiki/VirtualEnvironments>`_.
|
||||
|
||||
The Apache configuration will then be similar to the following:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.org
|
||||
|
||||
WSGIDaemonProcess isso user=www-data group=www-data threads=5
|
||||
WSGIScriptAlias /mounted_isso_path /path/to/isso.wsgi
|
||||
</VirtualHost>
|
||||
|
||||
You will need to adjust the user and group according to your Apache installation and
|
||||
security policy. Be aware that the directory containing the comments database must
|
||||
be writable by the user or group running the WSGI daemon process: having a writable
|
||||
database only is not enough, since SQLite will need to create a lock file in the same
|
||||
directory.
|
||||
|
||||
`mod_fastcgi <http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can use this method if your hosting provider doesn't allow you to have long
|
||||
running processes. If FastCGI has not yet been configured in your server,
|
||||
please follow these steps:
|
||||
|
||||
.. note:: This information may be incorrect, if you have more knowledge on how
|
||||
to deploy Python via `mod_fastcgi`, consider extending/correcting this section.
|
||||
|
||||
For more information, see `Flask: Configuring Apache
|
||||
<http://flask.pocoo.org/docs/deploying/fastcgi/#configuring-apache>`_.
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
LoadModule fastcgi_module /usr/lib64/httpd/modules/mod_fastcgi.so
|
||||
|
||||
FastCgiServer /var/www/html/yourapplication/app.fcgi -idle-timeout 300 -processes 5
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.org
|
||||
|
||||
AddHandler fastcgi-script fcgi
|
||||
ScriptAlias / /var/www/isso.fcgi
|
||||
|
||||
<Location />
|
||||
SetHandler fastcgi-script
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
|
||||
Next, to run isso as a FastCGI script you'll need to install ``flup`` with
|
||||
PIP:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install flup
|
||||
|
||||
Finally, copy'n'paste to `/var/www/isso.fcgi` (or whatever location you prefer):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python
|
||||
#: uncomment if you're using a virtualenv
|
||||
# import sys
|
||||
# sys.path.insert(0, '<your_local_path>/lib/python2.7/site-packages')
|
||||
|
||||
from isso import make_app, dist, config
|
||||
import os
|
||||
|
||||
from flup.server.fcgi import WSGIServer
|
||||
|
||||
application = make_app(config.load(
|
||||
os.path.join(dist.location, dist.project_name, "defaults.ini"),
|
||||
"/path/to/isso.cfg"))
|
||||
WSGIServer(application).run()
|
||||
|
||||
`Openshift <http://openshift.com>`__
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
With `Isso Openshift Deployment Kit`_, Isso can be installed on Open
|
||||
Shift with just one click. Make sure you already have installed ``rhc``
|
||||
(`instructions`_) and completed the setup.
|
||||
|
||||
1. Run the following, you will get an Open Shift instance installed with
|
||||
Isso:
|
||||
|
||||
::
|
||||
|
||||
rhc create-app appname python-2.7 --from-code https://github.com/avinassh/isso-openshift.git
|
||||
|
||||
2. Above step also clones Git repository of your Open Shift instance, in
|
||||
current directory. Make changes to the configuration file and push
|
||||
back to Openshift, it will be redeployed with new settings.
|
||||
|
||||
3. Visit ``http://<yourappname>-<openshift-namespace>.com/info`` to
|
||||
verify Isso is deployed properly and is working.
|
||||
|
||||
.. _Isso Openshift Deployment Kit: https://github.com/avinassh/isso-openshift
|
||||
.. _instructions: https://developers.openshift.com/en/managing-client-tools.html
|
57
docs/docs/extras/uwsgi.rst
Normal file
57
docs/docs/extras/uwsgi.rst
Normal file
@ -0,0 +1,57 @@
|
||||
uWSGI
|
||||
=====
|
||||
|
||||
In short: `uWSGI <http://uwsgi-docs.readthedocs.org/>`_ is awesome. Isso
|
||||
has builtin support for it (and simple fallback if uWSGI is not
|
||||
available). Use uWSGI if you think that the builtin WSGI server is a bad
|
||||
choice or slow (hint: it's both).
|
||||
|
||||
With uWSGI, you have roughly 100% performance improvements for just
|
||||
using it. Instead of one thread per request, you can use multiple
|
||||
processes, hence it is more "web scale". Other side effects: spooling,
|
||||
fast inter-process caching.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
You need uWSGI 1.9 or higher, fortunately you can install it with
|
||||
Python:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> apt-get install build-essential python-dev
|
||||
~> pip install uwsgi
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
For convenience, I recommend a INI-style configuration (you can also
|
||||
supply everything as command-line arguments):
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[uwsgi]
|
||||
http = :8080
|
||||
master = true
|
||||
processes = 4
|
||||
cache2 = name=hash,items=1024,blocksize=32
|
||||
spooler = %d/mail
|
||||
module = isso.run
|
||||
virtualenv = %d
|
||||
env = ISSO_SETTINGS=%d/sample.cfg
|
||||
|
||||
You shoud adjust ``processes`` to your CPU count. Then, save this file
|
||||
to a directory if choice. Next to this file, create an empty directory
|
||||
called ``mail``:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> mkdir mail/
|
||||
~> ls
|
||||
uwsgi.ini mail/
|
||||
|
||||
Now start Isso:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> uwsgi /path/to/uwsgi.ini
|
@ -1,25 +1,16 @@
|
||||
Overview
|
||||
========
|
||||
|
||||
Welcome to Isso's documentation. This documentation will help you get started
|
||||
fast. If you run into any problems when using Isso, you can find the answer in
|
||||
troubleshooting guide or you can ask me on IRC or GitHub.
|
||||
|
||||
Documentation overview:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
install
|
||||
quickstart
|
||||
troubleshooting
|
||||
Welcome to the Isso's documentation. This documentation will help you get
|
||||
started fast. If you get any problems when using Isso, you can find the answer
|
||||
in troubleshooting or you can ask me on IRC or GitHub.
|
||||
|
||||
What's Isso?
|
||||
------------
|
||||
|
||||
Isso is a lightweight commenting server similar to Disqus. It allows anonymous
|
||||
comments, maintains identity and is simple to administrate. It uses JavaScript
|
||||
and cross-origin ressource sharing for easy integration into (static) websites.
|
||||
and cross-origin ressource sharing for easy integration into static websites.
|
||||
|
||||
No, I meant "Isso"
|
||||
------------------
|
||||
|
@ -1,274 +1,22 @@
|
||||
Installation
|
||||
============
|
||||
------------
|
||||
|
||||
Isso is a web application written in Python. If pip and virtualenv mean anything
|
||||
to you, continue with :ref:`install-from-pypi`. If you are running
|
||||
Debian/Ubuntu, Gentoo, Archlinux or Fedora, you can use
|
||||
:ref:`prebuilt-package`. If not, read the next section carefully.
|
||||
Requirements:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
- Python 2.6, 2.7 or 3.3
|
||||
- a working C compiler
|
||||
|
||||
.. _install-interludium:
|
||||
|
||||
Interludium: Python is not PHP
|
||||
------------------------------
|
||||
|
||||
If you think hosting a web application written in Python is as easy as one
|
||||
written in PHP, you are wrong. Unlike for PHP, many Linux distribution use
|
||||
Python for internal tools. Your package manager already ships several python
|
||||
libraries, but most likely not all required by Isso (or in an up-to-date
|
||||
version – looking at you, Debian!).
|
||||
|
||||
That's why most Python developers use the `Python Package Index`_ to get their
|
||||
dependencies. The most important rule to follow is to never install *anything* from PyPi
|
||||
as root. Not because of malicious software, but because you *will* break your
|
||||
system.
|
||||
``easy_install`` is one tool to mess up your system. Another package manager is
|
||||
``pip``. If you ever searched for an issue with Python/pip and Stackoverflow is
|
||||
suggesting you ``easy_install pip`` or ``pip install --upgrade pip`` (as root
|
||||
of course!), you are doing it wrong. `Why you should not use Python's
|
||||
easy_install carelessly on Debian`_ is worth the read.
|
||||
|
||||
Fortunately, Python has a way to install packages (both as root and as user)
|
||||
without interfering with your globally installed packages: `virtualenv`. Use
|
||||
this *always* if you are installing software unavailable in your favourite
|
||||
package manager.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# for Debian/Ubuntu
|
||||
~> sudo apt-get install python-setuptools python-virtualenv python-dev
|
||||
|
||||
# Fedora/Red Hat
|
||||
~> sudo yum install python-setuptools python-virtualenv python-devel
|
||||
|
||||
The next steps should be done as regular user, not as root (although possible
|
||||
but not recommended):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> 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
|
||||
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).
|
||||
|
||||
.. _Python Package Index: https://pypi.python.org/pypi
|
||||
.. _Why you should not use Python's easy_install carelessly on Debian:
|
||||
https://workaround.org/easy-install-debian
|
||||
|
||||
.. _install-from-pypi:
|
||||
|
||||
Install from PyPi
|
||||
-----------------
|
||||
|
||||
Requirements
|
||||
^^^^^^^^^^^^
|
||||
|
||||
- Python 2.7 or 3.4+ (+ devel headers)
|
||||
- SQLite 3.3.8 or later
|
||||
- a working C compiler
|
||||
|
||||
For Debian/Ubuntu just `copy and paste
|
||||
<http://thejh.net/misc/website-terminal-copy-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 <http://www.pip-installer.org/en/latest/>`_:
|
||||
Install Isso with PIP_:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> pip install isso
|
||||
|
||||
`Don't have pip? <https://twitter.com/gardaud/status/357638468572151808>`_
|
||||
.. _PIP: http://www.pip-installer.org/en/latest/
|
||||
|
||||
.. code-block:: sh
|
||||
Init scripts:
|
||||
|
||||
~> easy_install isso # cross your fingers
|
||||
|
||||
For easier execution, you can symlink the executable to a location in your
|
||||
:envvar:`PATH`.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> ln -s /opt/isso/bin/isso /usr/local/bin/isso
|
||||
|
||||
Upgrade
|
||||
^^^^^^^
|
||||
|
||||
To upgrade Isso, activate your virtual environment again, and run
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> source /opt/isso/bin/activate # optional
|
||||
~> pip install --upgrade isso
|
||||
|
||||
.. _prebuilt-package:
|
||||
|
||||
Prebuilt Packages
|
||||
-----------------
|
||||
|
||||
* Debian (since Buster): https://packages.debian.org/search?keywords=isso
|
||||
|
||||
* 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/
|
||||
– install with `yaourt isso`.
|
||||
|
||||
* Fedora: https://copr.fedoraproject.org/coprs/jujens/isso/ — copr
|
||||
repository. Built from Pypi, includes a systemctl unit script.
|
||||
|
||||
Build a Docker image
|
||||
--------------------
|
||||
|
||||
You can get a Docker image by running ``docker build . -t
|
||||
isso``. Assuming you have your configuration in ``/opt/isso``, you can
|
||||
use the following command to spawn the Docker container:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> docker run -d --rm --name isso -p 127.0.0.1:8080:8080 -v /opt/isso:/config -v /opt/isso:/db isso
|
||||
|
||||
Then, you can use a reverse proxy to expose port 8080.
|
||||
|
||||
Install from Source
|
||||
-------------------
|
||||
|
||||
If you want to hack on Isso or track down issues, there's an alternate
|
||||
way to set up Isso. It requires a lot more dependencies and effort:
|
||||
|
||||
- Python 2.7 or 3.4+ (+ devel headers)
|
||||
- Virtualenv
|
||||
- SQLite 3.3.8 or later
|
||||
- a working C compiler
|
||||
- Node.js, `NPM <https://npmjs.org/>`__ and `Bower <http://bower.io/>`__
|
||||
|
||||
Get a fresh copy of Isso:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> git clone https://github.com/posativ/isso.git
|
||||
~> cd isso/
|
||||
|
||||
To create a virtual environment (recommended), run:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> virtualenv .
|
||||
~> source ./bin/activate
|
||||
|
||||
Install Isso and its dependencies:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> python setup.py develop # or `install`
|
||||
~> isso run
|
||||
|
||||
Install JavaScript modules:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> make init
|
||||
|
||||
Integration without optimization:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<script src="/js/config.js"></script>
|
||||
<script data-main="/js/embed" src="/js/components/requirejs/require.js"></script>
|
||||
|
||||
Optimization:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> npm install -g requirejs uglify-js jade
|
||||
~> make js
|
||||
|
||||
.. _init-scripts:
|
||||
|
||||
Init scripts
|
||||
------------
|
||||
|
||||
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 (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
|
||||
- SystemD: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.service
|
||||
- SysVinit: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.init
|
||||
- OpenBSD: https://gist.github.com/noqqe/7397719
|
||||
- FreeBSD: https://gist.github.com/ckoepp/52f6f0262de04cee1b88ef4a441e276d
|
||||
- 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
|
||||
will 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 run"
|
||||
|
||||
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
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
|
@ -1,38 +0,0 @@
|
||||
Isso
|
||||
====
|
||||
|
||||
What's Isso?
|
||||
------------
|
||||
|
||||
Isso is a lightweight commenting server similar to Disqus. It allows anonymous
|
||||
comments, maintains identity and is simple to administrate. It uses JavaScript
|
||||
and cross-origin resource sharing for easy integration into static websites.
|
||||
|
||||
No, I meant "Isso"
|
||||
------------------
|
||||
|
||||
Isso is an informal, german abbreviation for "Ich schrei sonst!", which can
|
||||
roughly be translated to "I'm yelling otherwise". It usually ends the
|
||||
discussion without any further arguments.
|
||||
|
||||
In germany, Isso `is also pokémon N° 360`__.
|
||||
|
||||
.. __: http://bulbapedia.bulbagarden.net/wiki/Wynaut_(Pok%C3%A9mon)
|
||||
|
||||
What's wrong with Disqus?
|
||||
-------------------------
|
||||
|
||||
No anonymous comments (IP address, email and name recorded), hosted in the USA,
|
||||
third-party. Just like IntenseDebate, livefrye etc. When you embed Disqus, they
|
||||
can do anything with your readers (and probably mine Bitcoins, see the loading
|
||||
times).
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
../quickstart
|
||||
../troubleshooting
|
||||
|
@ -2,24 +2,20 @@ Quickstart
|
||||
==========
|
||||
|
||||
Assuming you have successfully :doc:`installed <install>` Isso, here's
|
||||
a quickstart guide that covers the most common setup. Sections covered:
|
||||
a quickstart quide that covers common setups.
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 1
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
You must provide a custom configuration to set `dbpath` (your database
|
||||
location) and `host` (a list of websites for CORS_). All other options have
|
||||
sane defaults.
|
||||
You must provide a custom configuration. Most default parameters are useful for
|
||||
development, not persistence. Two most important options are `dbpath` to set
|
||||
the location of your database and `host` which is your website:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[general]
|
||||
; database location, check permissions, automatically created if it
|
||||
does not exist
|
||||
; database location, check permissions
|
||||
dbpath = /var/lib/isso/comments.db
|
||||
; your website or blog (not the location of Isso!)
|
||||
host = http://example.tld/
|
||||
@ -32,12 +28,12 @@ sane defaults.
|
||||
https://example.tld/
|
||||
|
||||
Note, that multiple, *different* websites are **not** supported in a single
|
||||
configuration. To serve comments for different websites, refer to
|
||||
configuration. To serve comments for diffent websites, refer to
|
||||
:ref:`Multiple Sites <configure-multiple-sites>`.
|
||||
|
||||
The moderation is done with signed URLs sent by email or logged to stdout.
|
||||
By default, comments are accepted and immediately shown to other users. To
|
||||
enable moderation queue, add:
|
||||
You moderate Isso through signed URLs sent by email or logged. By default,
|
||||
comments are accepted and immediately shown to other users. To enable
|
||||
moderation queue, add:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
@ -45,7 +41,7 @@ enable moderation queue, add:
|
||||
enabled = true
|
||||
|
||||
To moderate comments, either use the activation or deletion URL in the logs or
|
||||
:ref:`use SMTP <configure-smtp>` to get notified of new comments, including the
|
||||
:ref:`use SMTP <configure-smtp>` to get notified on new comments including the
|
||||
URLs for activation and deletion:
|
||||
|
||||
.. code-block:: ini
|
||||
@ -55,33 +51,24 @@ URLs for activation and deletion:
|
||||
[smtp]
|
||||
; SMTP settings
|
||||
|
||||
For more options, see :doc:`server <configuration/server>` and :doc:`client
|
||||
<configuration/client>` configuration.
|
||||
|
||||
Migration
|
||||
---------
|
||||
|
||||
Isso provides a tool for importing comments from Disqus_ or WordPress_.
|
||||
You can also import comments from any other comment system, but this topic is more
|
||||
complex and is covered in :doc:`advanced migration <extras/advanced-migration>`.
|
||||
You can migrate your existing comments from Disqus_. Log into Disqus, go to
|
||||
your website, click on *Discussions* and select the *Export* tab. You'll
|
||||
receive an email with your comments. Unfortunately, Disqus does not export
|
||||
up- and downvotes.
|
||||
|
||||
To export your comments from Disqus, log into Disqus, go to your website, click
|
||||
on *Discussions* and select the *Export* tab. You'll receive an email with your
|
||||
comments. Unfortunately, Disqus does not export up- and downvotes.
|
||||
|
||||
To export comments from your previous WordPress installation, go to *Tools*,
|
||||
export your data. It has been reported that WordPress may generate broken XML.
|
||||
Try to repair the file using ``xmllint`` before you continue with the import.
|
||||
|
||||
Now import the XML dump:
|
||||
To import existing comments, run Isso with your new configuration file:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
~> isso -c /path/to/isso.cfg import -t [disqus|wordpress] disqus-or-wordpress.xml
|
||||
~> isso -c /path/to/isso.cfg import user-2013-09-02T11_39_22.971478-all.xml
|
||||
[100%] 53 threads, 192 comments
|
||||
|
||||
.. _Disqus: https://disqus.com/
|
||||
.. _WordPress: https://wordpress.org/
|
||||
.. _Disqus: <https://disqus.com/>
|
||||
|
||||
|
||||
Running Isso
|
||||
------------
|
||||
@ -95,7 +82,8 @@ To run Isso, simply execute:
|
||||
|
||||
Next, we configure Nginx_ to proxy Isso. Do not run Isso on a public interface!
|
||||
A popular but often error-prone (because of CORS_) setup to host Isso uses a
|
||||
dedicated domain such as ``comments.example.tld``.
|
||||
dedicated domain such as ``comments.example.tld``; see
|
||||
:doc:`configuration/setup` for alternate ways.
|
||||
|
||||
Assuming both, your website and Isso are on the same server, the nginx
|
||||
configuration looks like this:
|
||||
@ -116,19 +104,15 @@ configuration looks like this:
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
|
||||
Integration
|
||||
-----------
|
||||
|
||||
Now, you embed Isso to your website:
|
||||
|
||||
.. code-block:: html
|
||||
|
||||
<script data-isso="//comments.example.tld/"
|
||||
src="//comments.example.tld/js/embed.min.js"></script>
|
||||
<script data-isso="http://comments.example.tld/"
|
||||
src="http://comments.example.tld/js/embed.min.js"></script>
|
||||
|
||||
<section id="isso-thread"></section>
|
||||
|
||||
@ -138,17 +122,102 @@ Note, that `data-isso` is optional, but when a website includes a script using
|
||||
That's it. When you open your website, you should see a commenting form. Leave
|
||||
a comment to see if the setup works. If not, see :doc:`troubleshooting`.
|
||||
|
||||
Going Further
|
||||
-------------
|
||||
|
||||
There are several server and client configuration options not covered in this
|
||||
quickstart, check out :doc:`configuration/server` and
|
||||
:doc:`configuration/client` for more information. For further website
|
||||
integration, see :doc:`extras/advanced-integration`.
|
||||
|
||||
To launch Isso automatically, check the :ref:`init-scripts` section from the
|
||||
installation guide. A different approach to deploy a web application is
|
||||
written here: :doc:`Deployment of Isso <extras/deployment>`.
|
||||
|
||||
.. _Nginx: http://nginx.org/
|
||||
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
|
||||
|
||||
|
||||
Deployment
|
||||
----------
|
||||
|
||||
Isso ships with a built-in web server, which is useful for the initial setup.
|
||||
But it is not recommended to use the built-in web server for production. A few
|
||||
WSGI servers are supported out-of-the-box:
|
||||
|
||||
* gevent_, coroutine-based network library
|
||||
* uWSGI_, full-featured uWSGI server
|
||||
* gunicorn_, Python WSGI HTTP Server for UNIX
|
||||
|
||||
.. _gevent: http://www.gevent.org/
|
||||
.. _uWSGI: http://uwsgi-docs.readthedocs.org/en/latest/
|
||||
.. _gunicorn: http://gunicorn.org/
|
||||
|
||||
gevent
|
||||
^^^^^^
|
||||
|
||||
Probably the easiest deployment method. Install with PIP (requires libevent):
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install gevent
|
||||
|
||||
Then, just use the ``isso`` executable as usual. Gevent monkey-patches Python's
|
||||
standard library to work with greenlets.
|
||||
|
||||
To execute Isso, just use the commandline interface:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ isso -c my.cfg run
|
||||
|
||||
Unfortunately, gevent 0.13.2 does not support UNIX domain sockets (see `#295
|
||||
<https://github.com/surfly/gevent/issues/295>`_ and `#299
|
||||
<https://github.com/surfly/gevent/issues/299>`_ for details).
|
||||
|
||||
uWSGI
|
||||
^^^^^
|
||||
|
||||
The author's favourite WSGI server. Due the complexity of uWSGI, there is a
|
||||
:doc:`separate document <extras/uwsgi>` on how to setup uWSGI for use
|
||||
with Isso.
|
||||
|
||||
gunicorn
|
||||
^^^^^^^^
|
||||
|
||||
Install gunicorn_ via PIP:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ pip install gunicorn
|
||||
|
||||
To execute Isso, use a command similar to:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ export ISSO_SETTINGS="/path/to/isso.cfg"
|
||||
$ gunicorn -b localhost:8080 -w 4 --preload isso.run
|
||||
|
||||
mod_wsgi
|
||||
^^^^^^^^
|
||||
|
||||
I have no experience at all with `mod_wsgi`, most things are taken from
|
||||
`Flask: Configuring Apache <http://flask.pocoo.org/docs/deploying/mod_wsgi/#configuring-apache>`_:
|
||||
|
||||
.. code-block:: apache
|
||||
|
||||
<VirtualHost *>
|
||||
ServerName example.org
|
||||
|
||||
WSGIDaemonProcess isso user=... group=... threads=5
|
||||
WSGIScriptAlias / /var/www/isso.wsgi
|
||||
</VirtualHost>
|
||||
|
||||
Now, you need to create a new `isso.wsgi` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import os
|
||||
|
||||
from isso import make_app
|
||||
from isso.core import Config
|
||||
|
||||
application = make_app(Config.load("/path/to/isso.cfg"))
|
||||
|
||||
Unless you know how to preload the application, add a static session key to
|
||||
your `isso.cfg`:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[general]
|
||||
; cat /dev/urandom | strings | grep -o '[[:alnum:]]' | head -n 30 | tr -d '\n'
|
||||
session-key = superrandomkey1
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
Sub-URI
|
||||
=======
|
||||
|
||||
You can run Isso on the same domain as your website, which circumvents issues
|
||||
originating from CORS_. Also, privacy-protecting browser addons such as
|
||||
`Request Policy`_ wont block comments.
|
||||
|
||||
.. code-block:: nginx
|
||||
|
||||
server {
|
||||
listen [::]:80;
|
||||
listen [::]:443 ssl;
|
||||
server_name example.tld;
|
||||
root /var/www/example.tld;
|
||||
|
||||
location /isso {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Script-Name /isso;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
}
|
||||
|
||||
Now, the website integration is just as described in :doc:`../quickstart` but
|
||||
with a different location.
|
||||
|
||||
.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS
|
||||
.. _Request Policy: https://www.requestpolicy.com/
|
@ -1,39 +1,4 @@
|
||||
Troubleshooting
|
||||
===============
|
||||
|
||||
For uberspace users
|
||||
-------------------
|
||||
Some uberspace users experienced problems with isso and they solved their
|
||||
issues by adding `DirectoryIndex disabled` as the first line in the `.htaccess`
|
||||
file for the domain the isso server is running on.
|
||||
|
||||
pkg_ressources.DistributionNotFound
|
||||
-----------------------------------
|
||||
|
||||
This is usually caused by messing up the system's Python with newer packages
|
||||
from PyPi (e.g. by executing `easy_install --upgrade pip` as root) and is not
|
||||
related to Isso at all.
|
||||
|
||||
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 are 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 so forth).
|
||||
|
||||
The web console shows 404 Not Found responses
|
||||
---------------------------------------------
|
||||
|
||||
That's fine. Isso returns "404 Not Found" to indicate "No comments".
|
||||
To be written.
|
||||
|
2
docs/docs/usage.rst
Normal file
2
docs/docs/usage.rst
Normal file
@ -0,0 +1,2 @@
|
||||
Usage
|
||||
=====
|
@ -1,2 +0,0 @@
|
||||
[html4css1 writer]
|
||||
initial_header_level: 2
|
112
docs/example.conf
Normal file
112
docs/example.conf
Normal file
@ -0,0 +1,112 @@
|
||||
# Isso configuration file
|
||||
# vim: set filetype=ini
|
||||
|
||||
[general]
|
||||
|
||||
# file location to the SQLite3 database, highly recommended to change this
|
||||
# location to a non-temporary location
|
||||
dbpath = /tmp/comments.db
|
||||
|
||||
# required to dispatch multiple websites, not used otherwise.
|
||||
name =
|
||||
|
||||
# URL to your website. When you start Isso, it will probe your website with a
|
||||
# simple GET / request to see if it can reach the webserver. If this fails, Isso
|
||||
# may not be able check if a web page exists, thus fails to accept new comments.
|
||||
# You can supply more than one host:
|
||||
# host =
|
||||
# http://localhost/
|
||||
# https://localhost/
|
||||
host = http://localhost/
|
||||
|
||||
# time range that allows users to edit/remove their own comments.
|
||||
# It supports years, weeks, days, hours, minutes, seconds.
|
||||
# 3h45m12s equals to 3 hours, 45 minutes and 12 seconds.
|
||||
max-age = 15m
|
||||
|
||||
# private session key to validate client cookies. If you restart the application
|
||||
# several times per hour for whatever reason, use a fixed key.
|
||||
session-key = ... ; python: binascii.b2a_hex(os.urandom(24))
|
||||
|
||||
# Select notification backend for new comments. Currently, only SMTP is
|
||||
# available.
|
||||
notify =
|
||||
|
||||
|
||||
[moderation]
|
||||
# enable comment moderation queue. This option only affects new comments.
|
||||
# Comments in modertion queue are not visible to other users until you activate
|
||||
# them.
|
||||
enabled = false
|
||||
|
||||
# remove unprocessed comments in moderation queue after given time.
|
||||
purge-after = 30d
|
||||
|
||||
|
||||
[server]
|
||||
# interface to listen on. Isso supports TCP/IP and unix domain sockets: UNIX
|
||||
# domain socket listen = unix:///tmp/isso.sock TCP/IP listen =
|
||||
# http:///localhost:1234/
|
||||
#
|
||||
# When gevent is available, it is automatically used for http:// Currently,
|
||||
# gevent can not handle http requests on unix domain socket (see #295 and #299
|
||||
# for details). Does not apply for uWSGI.
|
||||
listen = http://localhost:8080
|
||||
|
||||
# reload application, when the source code has changed. Useful for development
|
||||
# (don't forget to use a fixed session-key). Only works when gevent and uwsgi
|
||||
# are not available.
|
||||
reload = off
|
||||
|
||||
# show 10 most time consuming function in Isso after each request. Do not use in
|
||||
# production.
|
||||
profile = off
|
||||
|
||||
|
||||
[smtp]
|
||||
# Isso can notify you on new comments via SMTP. In the email notification, you
|
||||
# also can moderate (=activate or delete) comments.
|
||||
|
||||
# self-explanatory, optional
|
||||
username =
|
||||
|
||||
# self-explanatory (yes, plain text, create a dedicated account for
|
||||
# notifications), optional.
|
||||
password =
|
||||
|
||||
# SMTP server
|
||||
host = localhost
|
||||
|
||||
# SMTP port
|
||||
port = 465
|
||||
|
||||
# use SSL to connect to the server. Python probably does not validate the
|
||||
# certificate. Needs research, though. But you should use a dedicated email
|
||||
# account anyways.
|
||||
ssl = on
|
||||
|
||||
# recipient address, e.g. your email address
|
||||
to =
|
||||
|
||||
# sender address, e.g. isso@example.tld
|
||||
from =
|
||||
|
||||
|
||||
[guard]
|
||||
# Enable basic spam protection features, e.g. rate-limit per IP address (/24 for
|
||||
# IPv4, /48 for IPv6).
|
||||
|
||||
# enable guard, recommended in production. Not useful for debugging purposes.
|
||||
enabled = true
|
||||
|
||||
# limit to N new comments per minute.
|
||||
ratelimit = 2
|
||||
|
||||
# how many comments directly to the thread (prevent a simple while true; do curl
|
||||
# ...; done.
|
||||
direct-reply = 3
|
||||
|
||||
# allow commenters to reply to their own comments when they could still edit the
|
||||
# comment. After the editing timeframe is gone, commenters can reply to their
|
||||
# own comments anyways. Do not forget to configure the client.
|
||||
reply-to-self = false
|
25
docs/faq.rst
25
docs/faq.rst
@ -1,17 +1,32 @@
|
||||
Frequently asked question
|
||||
=========================
|
||||
|
||||
Why not use Gravatar/Libravatar/... ?
|
||||
-------------------------------------
|
||||
|
||||
Various people asked or complained about the generated icons next to their
|
||||
comments. First, it is not an avatar, it is an identicon used to
|
||||
*identify* an author of multiple comments without leaking personal
|
||||
informations (unlike Gravatar).
|
||||
|
||||
If you are in need of Gravatar_, then use Disqus. If you run your own
|
||||
Libravatar_ server, you can work on a patch for Isso which adds *optional*
|
||||
support for avatars.
|
||||
|
||||
.. _Gravatar: https://secure.gravatar.com/
|
||||
.. _Libravatar: http://libravatar.org/
|
||||
|
||||
Why SQLite3?
|
||||
------------
|
||||
|
||||
Although partially answered on the index page, here is a more complete answer: If
|
||||
Although partially answered on the index page, here a more complete answer: If
|
||||
you manage massive amounts of comments, Isso is a really bad choice. Isso is
|
||||
designed to be simple and easy to setup, it is not optimized for high-traffic
|
||||
designed to be simple and easy to setup, not optimizied for high-traffic
|
||||
websites (use a `dedicated Disqus`_ instance then).
|
||||
|
||||
Comments are not big data.
|
||||
comments are not big data
|
||||
|
||||
For example, if you have 209 threads and 778 comments in total this only needs 620 kilobytes
|
||||
of memory. This is an excellent use case for SQLite.
|
||||
For example, 209 threads and 778 comments in total only need 620K (kilobyte)
|
||||
memory. Excellent use case for SQLite.
|
||||
|
||||
.. _dedicated Disqus:
|
||||
|
@ -14,10 +14,10 @@
|
||||
</figure>
|
||||
<ul>
|
||||
<li>
|
||||
<p><strong>Comments written in Markdown</strong></p>
|
||||
<p>Users can edit or delete own comments (within 15 minutes by
|
||||
default).</p>
|
||||
<p>Comments in moderation queue are not publicly visible before
|
||||
<p><strong>Create, Edit and Remove Comments</strong></p>
|
||||
<p>Commenters can edit or delete their own comments (within
|
||||
15 minutes by default).</p>
|
||||
<p>Comments in moderation queue are not publicly visible until
|
||||
activation.</p>
|
||||
</li>
|
||||
<li>
|
||||
@ -25,13 +25,13 @@
|
||||
<p>Because comments are not Big Data.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Disqus & WordPress Import</strong></p>
|
||||
<p>You can migrate your Disqus/WordPress comments without any hassle.</p>
|
||||
<p><strong>Disqus Import</strong></p>
|
||||
<p>You can migrate your Disqus comments without any hassle.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p><strong>Configurable JS client</strong></p>
|
||||
<p>Embed a single JS file, 40kb (12kb gzipped) and you are
|
||||
done.</p>
|
||||
<p><strong>client-side JavaScript</strong></p>
|
||||
<p>Embed a single JS file, 54kb (18kb gzipped) and you are
|
||||
done.</p>
|
||||
<p>Supports Firefox, Safari, Chrome and IE10.</p>
|
||||
</li>
|
||||
</ul>
|
||||
@ -50,18 +50,6 @@
|
||||
<code>#isso</code></a> on <a href="http://freenode.net/">Freenode</a>
|
||||
or open an issue on <a href="https://github.com/posativ/isso/issues">GitHub</a>.
|
||||
</p>
|
||||
<p>
|
||||
Or join the <a href="http://librelist.com/browser/isso/">mailing list</a>,
|
||||
just send an email to <a href="mailto:isso@librelist.com">isso@librelist.com</a>.
|
||||
</p>
|
||||
<h2>Contribute</h2>
|
||||
<p>
|
||||
<a href="{{ pathto('contribute') }}">Write Code</a>,
|
||||
<a href="https://www.transifex.com/projects/p/isso/">Translate</a> or
|
||||
<a href="https://flattr.com/thing/2059355/posativisso-on-GitHub">Flattr
|
||||
<img src="{{ pathto('_static/flattr.png', 1) }}" alt="Flattr icon"/></a>.
|
||||
</p>
|
||||
|
||||
{% include "searchbox.html" %}
|
||||
</div>
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
Releasing steps
|
||||
===============
|
||||
|
||||
* Run ``python3 setup.py nosetests``, ``python2 setup.py nosetests``
|
||||
* Update version number in ``setup.py`` and ``CHANGES.rst``
|
||||
* ``git commit -m "Preparing ${VERSION}" setup.py CHANGES.rst``
|
||||
* ``git tag -as ${VERSION}``
|
||||
* ``make init all``
|
||||
* ``python3 setup.py sdist``
|
||||
* ``twine upload --sign dist/isso-${VERSION}.tar.gz``
|
144
isso/__init__.py
144
isso/__init__.py
@ -3,7 +3,7 @@
|
||||
#
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2012-2014 Martin Zimmermann.
|
||||
# Copyright (c) 2012-2013 Martin Zimmermann.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@ -25,7 +25,7 @@
|
||||
#
|
||||
# Isso – a lightweight Disqus alternative
|
||||
|
||||
from __future__ import print_function, unicode_literals
|
||||
from __future__ import print_function
|
||||
|
||||
import pkg_resources
|
||||
dist = pkg_resources.get_distribution("isso")
|
||||
@ -35,8 +35,7 @@ import sys
|
||||
|
||||
if sys.argv[0].startswith("isso"):
|
||||
try:
|
||||
import gevent.monkey
|
||||
gevent.monkey.patch_all()
|
||||
import gevent.monkey; gevent.monkey.patch_all()
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -49,32 +48,28 @@ from os.path import dirname, join
|
||||
from argparse import ArgumentParser
|
||||
from functools import partial, reduce
|
||||
|
||||
import pkg_resources
|
||||
werkzeug = pkg_resources.get_distribution("werkzeug")
|
||||
|
||||
from itsdangerous import URLSafeTimedSerializer
|
||||
|
||||
from werkzeug.routing import Map
|
||||
from werkzeug.exceptions import HTTPException, InternalServerError
|
||||
|
||||
from werkzeug.middleware.shared_data import SharedDataMiddleware
|
||||
from werkzeug.wsgi import SharedDataMiddleware
|
||||
from werkzeug.local import Local, LocalManager
|
||||
from werkzeug.serving import run_simple
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
from werkzeug.contrib.fixers import ProxyFix
|
||||
from werkzeug.contrib.profiler import ProfilerMiddleware
|
||||
|
||||
local = Local()
|
||||
local_manager = LocalManager([local])
|
||||
|
||||
from isso import config, db, migrate, wsgi, ext, views
|
||||
from isso.core import ThreadedMixin, ProcessMixin, uWSGIMixin
|
||||
from isso.wsgi import origin, urlsplit
|
||||
from isso.utils import http, JSONRequest, html, hash
|
||||
from isso import db, wsgi, ext, views
|
||||
from isso.core import ThreadedMixin, ProcessMixin, uWSGIMixin, Config
|
||||
from isso.utils import parse, http, JSONRequest, origin
|
||||
from isso.views import comments
|
||||
|
||||
from isso.ext.notifications import Stdout, SMTP
|
||||
|
||||
logging.getLogger('werkzeug').setLevel(logging.WARN)
|
||||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s: %(message)s")
|
||||
@ -82,37 +77,22 @@ logging.basicConfig(
|
||||
logger = logging.getLogger("isso")
|
||||
|
||||
|
||||
class ProxyFixCustom(ProxyFix):
|
||||
def __init__(self, app):
|
||||
# This is needed for werkzeug.wsgi.get_current_url called in isso/views/comments.py
|
||||
# to work properly when isso is hosted under a sub-path
|
||||
# cf. https://werkzeug.palletsprojects.com/en/1.0.x/middleware/proxy_fix/
|
||||
super().__init__(app, x_prefix=1)
|
||||
|
||||
|
||||
class Isso(object):
|
||||
|
||||
salt = b"Eech7co8Ohloopo9Ol6baimi"
|
||||
|
||||
def __init__(self, conf):
|
||||
|
||||
self.conf = conf
|
||||
self.db = db.SQLite3(conf.get('general', 'dbpath'), conf)
|
||||
self.signer = URLSafeTimedSerializer(
|
||||
self.db.preferences.get("session-key"))
|
||||
self.markup = html.Markup(conf.section('markup'))
|
||||
self.hasher = hash.new(conf.section("hash"))
|
||||
self.signer = URLSafeTimedSerializer(conf.get('general', 'session-key'))
|
||||
|
||||
super(Isso, self).__init__(conf)
|
||||
|
||||
subscribers = []
|
||||
smtp_backend = False
|
||||
for backend in conf.getlist("general", "notify"):
|
||||
if backend == "stdout":
|
||||
subscribers.append(Stdout(None))
|
||||
elif backend in ("smtp", "SMTP"):
|
||||
smtp_backend = True
|
||||
else:
|
||||
logger.warn("unknown notification backend '%s'", backend)
|
||||
if smtp_backend or conf.getboolean("general", "reply-notifications"):
|
||||
subscribers.append(Stdout(None))
|
||||
|
||||
if conf.get("general", "notify") == "smtp":
|
||||
subscribers.append(SMTP(self))
|
||||
|
||||
self.signal = ext.Signal(*subscribers)
|
||||
@ -120,10 +100,7 @@ class Isso(object):
|
||||
self.urls = Map()
|
||||
|
||||
views.Info(self)
|
||||
comments.API(self, self.hasher)
|
||||
|
||||
def render(self, text):
|
||||
return self.markup.render(text)
|
||||
comments.API(self)
|
||||
|
||||
def sign(self, obj):
|
||||
return self.signer.dumps(obj)
|
||||
@ -135,8 +112,7 @@ class Isso(object):
|
||||
local.request = request
|
||||
|
||||
local.host = wsgi.host(request.environ)
|
||||
local.origin = origin(self.conf.getiter(
|
||||
"general", "host"))(request.environ)
|
||||
local.origin = origin(self.conf.getiter("general", "host"))(request.environ)
|
||||
|
||||
adapter = self.urls.bind_to_environ(request.environ)
|
||||
|
||||
@ -150,8 +126,7 @@ class Isso(object):
|
||||
except HTTPException as e:
|
||||
return e
|
||||
except Exception:
|
||||
logger.exception("%s %s", request.method,
|
||||
request.environ["PATH_INFO"])
|
||||
logger.exception("%s %s", request.method, request.environ["PATH_INFO"])
|
||||
return InternalServerError()
|
||||
else:
|
||||
return response
|
||||
@ -166,21 +141,23 @@ class Isso(object):
|
||||
|
||||
def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):
|
||||
|
||||
if not any((threading, multiprocessing, uwsgi)):
|
||||
raise RuntimeError("either set threading, multiprocessing or uwsgi")
|
||||
|
||||
if threading:
|
||||
class App(Isso, ThreadedMixin):
|
||||
pass
|
||||
elif multiprocessing:
|
||||
|
||||
if multiprocessing:
|
||||
class App(Isso, ProcessMixin):
|
||||
pass
|
||||
else:
|
||||
|
||||
if uwsgi:
|
||||
class App(Isso, uWSGIMixin):
|
||||
pass
|
||||
|
||||
isso = App(conf)
|
||||
|
||||
# show session-key (to see that it changes randomely if unset)
|
||||
logger.info("session-key = %s", isso.conf.get("general", "session-key"))
|
||||
|
||||
# check HTTP server connection
|
||||
for host in conf.getiter("general", "host"):
|
||||
with http.curl('HEAD', host, '/', 5) as resp:
|
||||
@ -188,32 +165,22 @@ def make_app(conf=None, threading=True, multiprocessing=False, uwsgi=False):
|
||||
logger.info("connected to %s", host)
|
||||
break
|
||||
else:
|
||||
logger.warn("unable to connect to your website, Isso will probably not "
|
||||
"work correctly. Please make sure, Isso can reach your "
|
||||
"website via HTTP(S).")
|
||||
logger.warn("unable to connect to %s", ", ".join(conf.getiter("general", "host")))
|
||||
|
||||
wrapper = [local_manager.make_middleware]
|
||||
|
||||
if isso.conf.getboolean("server", "profile"):
|
||||
wrapper.append(partial(ProfilerMiddleware,
|
||||
sort_by=("cumulative", ), restrictions=("isso/(?!lib)", 10)))
|
||||
sort_by=("cumtime", ), restrictions=("isso/(?!lib)", 10)))
|
||||
|
||||
wrapper.append(partial(SharedDataMiddleware, exports={
|
||||
'/js': join(dirname(__file__), 'js/'),
|
||||
'/css': join(dirname(__file__), 'css/'),
|
||||
'/img': join(dirname(__file__), 'img/'),
|
||||
'/demo': join(dirname(__file__), 'demo/')
|
||||
}))
|
||||
'/css': join(dirname(__file__), 'css/')}))
|
||||
|
||||
wrapper.append(partial(wsgi.CORSMiddleware,
|
||||
origin=origin(isso.conf.getiter("general", "host")),
|
||||
allowed=("Origin", "Referer", "Content-Type"),
|
||||
exposed=("X-Set-Cookie", "Date")))
|
||||
origin=origin(isso.conf.getiter("general", "host"))))
|
||||
|
||||
wrapper.extend([wsgi.SubURI, ProxyFixCustom])
|
||||
|
||||
if werkzeug.version.startswith("0.8"):
|
||||
wrapper.append(wsgi.LegacyWerkzeugMiddleware)
|
||||
wrapper.extend([wsgi.SubURI, ProxyFix])
|
||||
|
||||
return reduce(lambda x, f: f(x), wrapper, isso)
|
||||
|
||||
@ -223,56 +190,17 @@ def main():
|
||||
parser = ArgumentParser(description="a blog comment hosting service")
|
||||
subparser = parser.add_subparsers(help="commands", dest="command")
|
||||
|
||||
parser.add_argument('--version', action='version',
|
||||
version='%(prog)s ' + dist.version)
|
||||
parser.add_argument('--version', action='version', version='%(prog)s ' + dist.version)
|
||||
parser.add_argument("-c", dest="conf", default="/etc/isso.conf",
|
||||
metavar="/etc/isso.conf", help="set configuration file")
|
||||
metavar="/etc/isso.conf", help="set configuration file")
|
||||
|
||||
imprt = subparser.add_parser('import', help="import Disqus XML export")
|
||||
imprt.add_argument("dump", metavar="FILE")
|
||||
imprt.add_argument("-n", "--dry-run", dest="dryrun", action="store_true",
|
||||
help="perform a trial run with no changes made")
|
||||
imprt.add_argument("-t", "--type", dest="type", default=None,
|
||||
choices=["disqus", "wordpress", "generic"], help="export type")
|
||||
imprt.add_argument("--empty-id", dest="empty_id", action="store_true",
|
||||
help="workaround for weird Disqus XML exports, #135")
|
||||
|
||||
# run Isso as stand-alone server
|
||||
subparser.add_parser("run", help="run server")
|
||||
serve = subparser.add_parser("run", help="run server")
|
||||
|
||||
args = parser.parse_args()
|
||||
conf = config.load(
|
||||
join(dist.location, dist.project_name, "defaults.ini"), args.conf)
|
||||
|
||||
if args.command == "import":
|
||||
conf.set("guard", "enabled", "off")
|
||||
|
||||
if args.dryrun:
|
||||
xxx = tempfile.NamedTemporaryFile()
|
||||
dbpath = xxx.name
|
||||
else:
|
||||
dbpath = conf.get("general", "dbpath")
|
||||
|
||||
mydb = db.SQLite3(dbpath, conf)
|
||||
migrate.dispatch(args.type, mydb, args.dump, args.empty_id)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if conf.get("general", "log-file"):
|
||||
handler = logging.FileHandler(conf.get("general", "log-file"))
|
||||
|
||||
logger.addHandler(handler)
|
||||
logging.getLogger("werkzeug").addHandler(handler)
|
||||
|
||||
logger.propagate = False
|
||||
logging.getLogger("werkzeug").propagate = False
|
||||
|
||||
if not any(conf.getiter("general", "host")):
|
||||
logger.error("No website(s) configured, Isso won't work.")
|
||||
sys.exit(1)
|
||||
conf = Config.load(args.conf)
|
||||
|
||||
if conf.get("server", "listen").startswith("http://"):
|
||||
host, port, _ = urlsplit(conf.get("server", "listen"))
|
||||
host, port, _ = parse.host(conf.get("server", "listen"))
|
||||
try:
|
||||
from gevent.pywsgi import WSGIServer
|
||||
WSGIServer((host, port), make_app(conf)).serve_forever()
|
||||
|
@ -1,26 +1,22 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
try:
|
||||
text_type = unicode # Python 2
|
||||
string_types = (str, unicode)
|
||||
PY2K = True
|
||||
except NameError: # Python 3
|
||||
PY2K = False
|
||||
import sys
|
||||
PY2K = sys.version_info[0] == 2
|
||||
|
||||
if not PY2K:
|
||||
|
||||
map, zip, filter = map, zip, filter
|
||||
|
||||
text_type = str
|
||||
string_types = (str, )
|
||||
|
||||
if not PY2K:
|
||||
buffer = memoryview
|
||||
filter, map, zip = filter, map, zip
|
||||
|
||||
def iteritems(dikt):
|
||||
return iter(dikt.items()) # noqa: E731
|
||||
from functools import reduce
|
||||
else:
|
||||
buffer = buffer
|
||||
from itertools import ifilter, imap, izip
|
||||
filter, map, zip = ifilter, imap, izip
|
||||
|
||||
def iteritems(dikt):
|
||||
return dikt.iteritems() # noqa: E731
|
||||
reduce = reduce
|
||||
from itertools import imap, izip, ifilter
|
||||
map, zip, filter = imap, izip, ifilter
|
||||
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
|
||||
buffer = buffer
|
||||
|
152
isso/config.py
152
isso/config.py
@ -1,152 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from email.utils import parseaddr, formataddr
|
||||
try:
|
||||
from backports.configparser import ConfigParser
|
||||
except ImportError:
|
||||
from configparser import ConfigParser
|
||||
|
||||
from isso.compat import text_type as str
|
||||
|
||||
logger = logging.getLogger("isso")
|
||||
|
||||
|
||||
def timedelta(string):
|
||||
"""
|
||||
Parse :param string: into :class:`datetime.timedelta`, you can use any
|
||||
(logical) combination of Nw, Nd, Nh and Nm, e.g. `1h30m` for 1 hour, 30
|
||||
minutes or `3w` for 3 weeks.
|
||||
|
||||
Raises a ValueError if the input is invalid/unparseable.
|
||||
|
||||
>>> print(timedelta("3w"))
|
||||
21 days, 0:00:00
|
||||
>>> print(timedelta("3w 12h 57m"))
|
||||
21 days, 12:57:00
|
||||
>>> print(timedelta("1h30m37s"))
|
||||
1:30:37
|
||||
>>> print(timedelta("1asdf3w"))
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: invalid human-readable timedelta
|
||||
"""
|
||||
|
||||
keys = ["weeks", "days", "hours", "minutes", "seconds"]
|
||||
regex = "".join(["((?P<%s>\\d+)%s ?)?" % (k, k[0]) for k in keys])
|
||||
kwargs = {}
|
||||
for k, v in re.match(regex, string).groupdict(default="0").items():
|
||||
kwargs[k] = int(v)
|
||||
|
||||
rv = datetime.timedelta(**kwargs)
|
||||
if rv == datetime.timedelta():
|
||||
raise ValueError("invalid human-readable timedelta")
|
||||
|
||||
return datetime.timedelta(**kwargs)
|
||||
|
||||
|
||||
class Section(object):
|
||||
"""A wrapper around :class:`IssoParser` that returns a partial configuration
|
||||
section object.
|
||||
|
||||
>>> conf = new({"foo": {"bar": "spam"}})
|
||||
>>> section = conf.section("foo")
|
||||
>>> conf.get("foo", "bar") == section.get("bar")
|
||||
True
|
||||
"""
|
||||
|
||||
def __init__(self, conf, section):
|
||||
self.conf = conf
|
||||
self.section = section
|
||||
|
||||
def get(self, key):
|
||||
return self.conf.get(self.section, key)
|
||||
|
||||
def getint(self, key):
|
||||
return self.conf.getint(self.section, key)
|
||||
|
||||
def getlist(self, key):
|
||||
return self.conf.getlist(self.section, key)
|
||||
|
||||
def getiter(self, key):
|
||||
return self.conf.getiter(self.section, key)
|
||||
|
||||
def getboolean(self, key):
|
||||
return self.conf.getboolean(self.section, key)
|
||||
|
||||
|
||||
class IssoParser(ConfigParser):
|
||||
"""Parse INI-style configuration with some modifications for Isso.
|
||||
|
||||
* parse human-readable timedelta such as "15m" as "15 minutes"
|
||||
* handle indented lines as "lists"
|
||||
"""
|
||||
|
||||
def getint(self, section, key):
|
||||
try:
|
||||
delta = timedelta(self.get(section, key))
|
||||
except ValueError:
|
||||
return super(IssoParser, self).getint(section, key)
|
||||
else:
|
||||
try:
|
||||
return int(delta.total_seconds())
|
||||
except AttributeError:
|
||||
return int(delta.total_seconds())
|
||||
|
||||
def getlist(self, section, key):
|
||||
return list(map(str.strip, self.get(section, key).split(',')))
|
||||
|
||||
def getiter(self, section, key):
|
||||
for item in map(str.strip, self.get(section, key).split('\n')):
|
||||
if item:
|
||||
yield item
|
||||
|
||||
def section(self, section):
|
||||
return Section(self, section)
|
||||
|
||||
|
||||
def new(options=None):
|
||||
|
||||
cp = IssoParser(allow_no_value=True)
|
||||
|
||||
if options:
|
||||
cp.read_dict(options)
|
||||
|
||||
return cp
|
||||
|
||||
|
||||
def load(default, user=None):
|
||||
|
||||
# return set of (section, option)
|
||||
def setify(cp):
|
||||
return set((section, option) for section in cp.sections()
|
||||
for option in cp.options(section))
|
||||
|
||||
parser = new()
|
||||
parser.read(default)
|
||||
|
||||
a = setify(parser)
|
||||
|
||||
if user:
|
||||
parser.read(user)
|
||||
|
||||
for item in setify(parser).difference(a):
|
||||
logger.warn("no such option: [%s] %s", *item)
|
||||
if item in (("server", "host"), ("server", "port")):
|
||||
logger.warn("use `listen = http://$host:$port` instead")
|
||||
if item == ("smtp", "ssl"):
|
||||
logger.warn("use `security = none | starttls | ssl` instead")
|
||||
if item == ("general", "session-key"):
|
||||
logger.info("Your `session-key` has been stored in the "
|
||||
"database itself, this option is now unused")
|
||||
|
||||
if not parseaddr(parser.get("smtp", "from"))[0]:
|
||||
parser.set("smtp", "from",
|
||||
formataddr(("Ich schrei sonst!", parser.get("smtp", "from"))))
|
||||
|
||||
return parser
|
140
isso/core.py
140
isso/core.py
@ -2,11 +2,16 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import binascii
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
from configparser import ConfigParser
|
||||
|
||||
try:
|
||||
import uwsgi
|
||||
except ImportError:
|
||||
@ -19,12 +24,139 @@ if PY2K:
|
||||
else:
|
||||
import _thread as thread
|
||||
|
||||
from flask_caching.backends.null import NullCache
|
||||
from flask_caching.backends.simple import SimpleCache
|
||||
from isso.utils import parse
|
||||
from isso.compat import text_type as str
|
||||
|
||||
from werkzeug.contrib.cache import NullCache, SimpleCache
|
||||
|
||||
logger = logging.getLogger("isso")
|
||||
|
||||
|
||||
class Section:
|
||||
|
||||
def __init__(self, conf, section):
|
||||
self.conf = conf
|
||||
self.section = section
|
||||
|
||||
def get(self, key):
|
||||
return self.conf.get(self.section, key)
|
||||
|
||||
def getint(self, key):
|
||||
return self.conf.getint(self.section, key)
|
||||
|
||||
def getiter(self, key):
|
||||
return self.conf.getiter(self.section, key)
|
||||
|
||||
def getboolean(self, key):
|
||||
return self.conf.getboolean(self.section, key)
|
||||
|
||||
|
||||
class IssoParser(ConfigParser):
|
||||
"""
|
||||
Extended :class:`ConfigParser` to parse human-readable timedeltas
|
||||
into seconds and handles multiple values per key.
|
||||
|
||||
>>> import io
|
||||
>>> parser = IssoParser(allow_no_value=True)
|
||||
>>> parser.read_file(io.StringIO(u'''
|
||||
... [foo]
|
||||
... bar = 1h
|
||||
... baz = 12
|
||||
... bla =
|
||||
... spam
|
||||
... ham
|
||||
... asd = fgh
|
||||
... '''))
|
||||
>>> parser.getint("foo", "bar")
|
||||
3600
|
||||
>>> parser.getint("foo", "baz")
|
||||
12
|
||||
>>> list(parser.getiter("foo", "bla")) # doctest: +IGNORE_UNICODE
|
||||
['spam', 'ham']
|
||||
>>> list(parser.getiter("foo", "asd")) # doctest: +IGNORE_UNICODE
|
||||
['fgh']
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _total_seconds(cls, td):
|
||||
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
|
||||
|
||||
def getint(self, section, key):
|
||||
try:
|
||||
delta = parse.timedelta(self.get(section, key))
|
||||
except ValueError:
|
||||
return super(IssoParser, self).getint(section, key)
|
||||
else:
|
||||
try:
|
||||
return int(delta.total_seconds())
|
||||
except AttributeError:
|
||||
return int(IssoParser._total_seconds(delta))
|
||||
|
||||
def getiter(self, section, key):
|
||||
for item in map(str.strip, self.get(section, key).split('\n')):
|
||||
if item:
|
||||
yield item
|
||||
|
||||
def section(self, section):
|
||||
return Section(self, section)
|
||||
|
||||
|
||||
class Config:
|
||||
|
||||
default = [
|
||||
"[general]",
|
||||
"name = ",
|
||||
"dbpath = /tmp/isso.db", "session-key = %s" % binascii.b2a_hex(os.urandom(16)),
|
||||
"host = http://localhost:8080/", "max-age = 15m",
|
||||
"notify = ",
|
||||
"[moderation]",
|
||||
"enabled = false",
|
||||
"purge-after = 30d",
|
||||
"[server]",
|
||||
"listen = http://localhost:8080/",
|
||||
"reload = off", "profile = off",
|
||||
"[smtp]",
|
||||
"username = ", "password = ",
|
||||
"host = localhost", "port = 465", "ssl = on",
|
||||
"to = ", "from = ",
|
||||
"[guard]",
|
||||
"enabled = true",
|
||||
"ratelimit = 2",
|
||||
"direct-reply = 3",
|
||||
"reply-to-self = false"
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def load(cls, configfile):
|
||||
|
||||
# return set of (section, option)
|
||||
setify = lambda cp: set((section, option) for section in cp.sections()
|
||||
for option in cp.options(section))
|
||||
|
||||
rv = IssoParser(allow_no_value=True)
|
||||
rv.read_file(io.StringIO(u'\n'.join(Config.default)))
|
||||
|
||||
a = setify(rv)
|
||||
|
||||
if configfile:
|
||||
rv.read(configfile)
|
||||
|
||||
diff = setify(rv).difference(a)
|
||||
|
||||
if diff:
|
||||
for item in diff:
|
||||
logger.warn("no such option: [%s] %s", *item)
|
||||
if item in (("server", "host"), ("server", "port")):
|
||||
logger.warn("use `listen = http://$host:$port` instead")
|
||||
|
||||
if rv.get("smtp", "username") and not rv.get("general", "notify"):
|
||||
logger.warn(("SMTP is no longer enabled by default, add "
|
||||
"`notify = smtp` to the general section to "
|
||||
"enable SMTP nofications."))
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
class Cache:
|
||||
"""Wrapper around werkzeug's cache class, to make it compatible to
|
||||
uWSGI's cache framework.
|
||||
@ -123,9 +255,7 @@ class uWSGIMixin(Mixin):
|
||||
self.cache = uWSGICache
|
||||
|
||||
timedelta = conf.getint("moderation", "purge-after")
|
||||
|
||||
def purge(signum):
|
||||
return self.db.comments.purge(timedelta)
|
||||
purge = lambda signum: self.db.comments.purge(timedelta)
|
||||
uwsgi.register_signal(1, "", purge)
|
||||
uwsgi.add_timer(1, timedelta)
|
||||
|
||||
|
@ -1,134 +0,0 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
input {
|
||||
text-align: center;
|
||||
}
|
||||
.header::before, .header::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.header::after {
|
||||
clear: both;
|
||||
}
|
||||
.header::before, .header::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.header {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 68em;
|
||||
padding-bottom: 1em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
.header header {
|
||||
display: block;
|
||||
float: left;
|
||||
font-weight: normal;
|
||||
margin-right: 16.0363%;
|
||||
width: 41.9818%;
|
||||
}
|
||||
.header header .logo {
|
||||
float: left;
|
||||
max-height: 60px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.header header h1 {
|
||||
font-size: 1.55em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
.header header h2 {
|
||||
font-size: 1.05em;
|
||||
}
|
||||
.header a, .header a:visited {
|
||||
color: #4d4c4c;
|
||||
text-decoration: none;
|
||||
}
|
||||
.outer {
|
||||
background-color: #eeeeee;
|
||||
box-shadow: 0 0 0.5em #c0c0c0 inset;
|
||||
}
|
||||
.outer .filters::before, .outer .filters::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.outer .filters::after {
|
||||
clear: both;
|
||||
}
|
||||
.outer .filters::before, .outer .filters::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
.outer .filters {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 68em;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #4d4c4c;
|
||||
}
|
||||
.label {
|
||||
background-color: #ddd;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
line-height: 1.4em;
|
||||
outline: 0 none;
|
||||
padding: calc(0.6em - 1px);
|
||||
}
|
||||
.active {
|
||||
box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.6) inset;
|
||||
}
|
||||
.label-valid {
|
||||
background-color: #cfc;
|
||||
border-color: #cfc;
|
||||
}
|
||||
.label-pending {
|
||||
background-color: #ffc;
|
||||
border-color: #ffc;
|
||||
}
|
||||
.mode {
|
||||
float: left;
|
||||
}
|
||||
.pagination {
|
||||
float: right;
|
||||
}
|
||||
.note .label {
|
||||
margin: 9px;
|
||||
padding: 3px;
|
||||
}
|
||||
#login {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.isso-comment-footer a {
|
||||
cursor: pointer;
|
||||
}
|
||||
.thread-title {
|
||||
margin-left: 3em;
|
||||
}
|
||||
.group {
|
||||
float: left;
|
||||
margin-left: 2em;
|
||||
}
|
||||
.editable {
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 5px;
|
||||
margin: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
13
isso/css/bourbon/_bourbon-deprecated-upcoming.scss
vendored
Normal file
13
isso/css/bourbon/_bourbon-deprecated-upcoming.scss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
//************************************************************************//
|
||||
// These mixins/functions are deprecated
|
||||
// They will be removed in the next MAJOR version release
|
||||
//************************************************************************//
|
||||
@mixin box-shadow ($shadows...) {
|
||||
@include prefixer(box-shadow, $shadows, spec);
|
||||
@warn "box-shadow is deprecated and will be removed in the next major version release";
|
||||
}
|
||||
|
||||
@mixin background-size ($lengths...) {
|
||||
@include prefixer(background-size, $lengths, spec);
|
||||
@warn "background-size is deprecated and will be removed in the next major version release";
|
||||
}
|
59
isso/css/bourbon/_bourbon.scss
vendored
Normal file
59
isso/css/bourbon/_bourbon.scss
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
// Custom Helpers
|
||||
@import "helpers/deprecated-webkit-gradient";
|
||||
@import "helpers/gradient-positions-parser";
|
||||
@import "helpers/linear-positions-parser";
|
||||
@import "helpers/radial-arg-parser";
|
||||
@import "helpers/radial-positions-parser";
|
||||
@import "helpers/render-gradients";
|
||||
@import "helpers/shape-size-stripper";
|
||||
|
||||
// Custom Functions
|
||||
@import "functions/compact";
|
||||
@import "functions/flex-grid";
|
||||
@import "functions/grid-width";
|
||||
@import "functions/linear-gradient";
|
||||
@import "functions/modular-scale";
|
||||
@import "functions/px-to-em";
|
||||
@import "functions/radial-gradient";
|
||||
@import "functions/tint-shade";
|
||||
@import "functions/transition-property-name";
|
||||
|
||||
// CSS3 Mixins
|
||||
@import "css3/animation";
|
||||
@import "css3/appearance";
|
||||
@import "css3/backface-visibility";
|
||||
@import "css3/background";
|
||||
@import "css3/background-image";
|
||||
@import "css3/border-image";
|
||||
@import "css3/border-radius";
|
||||
@import "css3/box-sizing";
|
||||
@import "css3/columns";
|
||||
@import "css3/flex-box";
|
||||
@import "css3/font-face";
|
||||
@import "css3/hidpi-media-query";
|
||||
@import "css3/image-rendering";
|
||||
@import "css3/inline-block";
|
||||
@import "css3/keyframes";
|
||||
@import "css3/linear-gradient";
|
||||
@import "css3/perspective";
|
||||
@import "css3/radial-gradient";
|
||||
@import "css3/transform";
|
||||
@import "css3/transition";
|
||||
@import "css3/user-select";
|
||||
@import "css3/placeholder";
|
||||
|
||||
// Addons & other mixins
|
||||
@import "addons/button";
|
||||
@import "addons/clearfix";
|
||||
@import "addons/font-family";
|
||||
@import "addons/hide-text";
|
||||
@import "addons/html5-input-types";
|
||||
@import "addons/position";
|
||||
@import "addons/prefixer";
|
||||
@import "addons/retina-image";
|
||||
@import "addons/size";
|
||||
@import "addons/timing-functions";
|
||||
@import "addons/triangle";
|
||||
|
||||
// Soon to be deprecated Mixins
|
||||
@import "bourbon-deprecated-upcoming";
|
273
isso/css/bourbon/addons/_button.scss
vendored
Normal file
273
isso/css/bourbon/addons/_button.scss
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
@mixin button ($style: simple, $base-color: #4294f0) {
|
||||
|
||||
@if type-of($style) == color {
|
||||
$base-color: $style;
|
||||
$style: simple;
|
||||
}
|
||||
|
||||
// Grayscale button
|
||||
@if $base-color == grayscale($base-color) {
|
||||
@if $style == simple {
|
||||
@include simple($base-color, $grayscale: true);
|
||||
}
|
||||
|
||||
@else if $style == shiny {
|
||||
@include shiny($base-color, $grayscale: true);
|
||||
}
|
||||
|
||||
@else if $style == pill {
|
||||
@include pill($base-color, $grayscale: true);
|
||||
}
|
||||
}
|
||||
|
||||
// Colored button
|
||||
@else {
|
||||
@if $style == simple {
|
||||
@include simple($base-color);
|
||||
}
|
||||
|
||||
@else if $style == shiny {
|
||||
@include shiny($base-color);
|
||||
}
|
||||
|
||||
@else if $style == pill {
|
||||
@include pill($base-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Simple Button
|
||||
//************************************************************************//
|
||||
@mixin simple($base-color, $grayscale: false) {
|
||||
$color: hsl(0, 0, 100%);
|
||||
$border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
|
||||
$inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
|
||||
$stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
|
||||
$text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
|
||||
|
||||
@if lightness($base-color) > 70% {
|
||||
$color: hsl(0, 0, 20%);
|
||||
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
|
||||
}
|
||||
|
||||
@if $grayscale == true {
|
||||
$border: grayscale($border);
|
||||
$inset-shadow: grayscale($inset-shadow);
|
||||
$stop-gradient: grayscale($stop-gradient);
|
||||
$text-shadow: grayscale($text-shadow);
|
||||
}
|
||||
|
||||
border: 1px solid $border;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 0 0 $inset-shadow;
|
||||
color: $color;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
@include linear-gradient ($base-color, $stop-gradient);
|
||||
padding: 7px 18px;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 1px 0 $text-shadow;
|
||||
background-clip: padding-box;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
$base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
|
||||
$inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
|
||||
$stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
|
||||
|
||||
@if $grayscale == true {
|
||||
$base-color-hover: grayscale($base-color-hover);
|
||||
$inset-shadow-hover: grayscale($inset-shadow-hover);
|
||||
$stop-gradient-hover: grayscale($stop-gradient-hover);
|
||||
}
|
||||
|
||||
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
|
||||
cursor: pointer;
|
||||
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
$border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
|
||||
$inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
|
||||
|
||||
@if $grayscale == true {
|
||||
$border-active: grayscale($border-active);
|
||||
$inset-shadow-active: grayscale($inset-shadow-active);
|
||||
}
|
||||
|
||||
border: 1px solid $border-active;
|
||||
box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Shiny Button
|
||||
//************************************************************************//
|
||||
@mixin shiny($base-color, $grayscale: false) {
|
||||
$color: hsl(0, 0, 100%);
|
||||
$border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
|
||||
$border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
|
||||
$fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
|
||||
$inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
|
||||
$second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
|
||||
$text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
|
||||
$third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
|
||||
|
||||
@if lightness($base-color) > 70% {
|
||||
$color: hsl(0, 0, 20%);
|
||||
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
|
||||
}
|
||||
|
||||
@if $grayscale == true {
|
||||
$border: grayscale($border);
|
||||
$border-bottom: grayscale($border-bottom);
|
||||
$fourth-stop: grayscale($fourth-stop);
|
||||
$inset-shadow: grayscale($inset-shadow);
|
||||
$second-stop: grayscale($second-stop);
|
||||
$text-shadow: grayscale($text-shadow);
|
||||
$third-stop: grayscale($third-stop);
|
||||
}
|
||||
|
||||
border: 1px solid $border;
|
||||
border-bottom: 1px solid $border-bottom;
|
||||
border-radius: 5px;
|
||||
box-shadow: inset 0 1px 0 0 $inset-shadow;
|
||||
color: $color;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
@include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
|
||||
padding: 8px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 1px $text-shadow;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
$first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
|
||||
$second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
|
||||
$third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
|
||||
$fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
|
||||
|
||||
@if $grayscale == true {
|
||||
$first-stop-hover: grayscale($first-stop-hover);
|
||||
$second-stop-hover: grayscale($second-stop-hover);
|
||||
$third-stop-hover: grayscale($third-stop-hover);
|
||||
$fourth-stop-hover: grayscale($fourth-stop-hover);
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
@include linear-gradient(top, $first-stop-hover 0%,
|
||||
$second-stop-hover 50%,
|
||||
$third-stop-hover 50%,
|
||||
$fourth-stop-hover 100%);
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
$inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
|
||||
|
||||
@if $grayscale == true {
|
||||
$inset-shadow-active: grayscale($inset-shadow-active);
|
||||
}
|
||||
|
||||
box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Pill Button
|
||||
//************************************************************************//
|
||||
@mixin pill($base-color, $grayscale: false) {
|
||||
$color: hsl(0, 0, 100%);
|
||||
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
|
||||
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
|
||||
$border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
|
||||
$inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
|
||||
$stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
|
||||
$text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
|
||||
|
||||
@if lightness($base-color) > 70% {
|
||||
$color: hsl(0, 0, 20%);
|
||||
$text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
|
||||
}
|
||||
|
||||
@if $grayscale == true {
|
||||
$border-bottom: grayscale($border-bottom);
|
||||
$border-sides: grayscale($border-sides);
|
||||
$border-top: grayscale($border-top);
|
||||
$inset-shadow: grayscale($inset-shadow);
|
||||
$stop-gradient: grayscale($stop-gradient);
|
||||
$text-shadow: grayscale($text-shadow);
|
||||
}
|
||||
|
||||
border: 1px solid $border-top;
|
||||
border-color: $border-top $border-sides $border-bottom;
|
||||
border-radius: 16px;
|
||||
box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3;
|
||||
color: $color;
|
||||
display: inline-block;
|
||||
font-size: 11px;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
@include linear-gradient ($base-color, $stop-gradient);
|
||||
padding: 5px 16px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 -1px 1px $text-shadow;
|
||||
background-clip: padding-box;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
$base-color-hover: adjust-color($base-color, $lightness: -4.5%);
|
||||
$border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
|
||||
$border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
|
||||
$border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
|
||||
$inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
|
||||
$stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
|
||||
$text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
|
||||
|
||||
@if $grayscale == true {
|
||||
$base-color-hover: grayscale($base-color-hover);
|
||||
$border-bottom: grayscale($border-bottom);
|
||||
$border-sides: grayscale($border-sides);
|
||||
$border-top: grayscale($border-top);
|
||||
$inset-shadow-hover: grayscale($inset-shadow-hover);
|
||||
$stop-gradient-hover: grayscale($stop-gradient-hover);
|
||||
$text-shadow-hover: grayscale($text-shadow-hover);
|
||||
}
|
||||
|
||||
border: 1px solid $border-top;
|
||||
border-color: $border-top $border-sides $border-bottom;
|
||||
box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
|
||||
cursor: pointer;
|
||||
@include linear-gradient ($base-color-hover, $stop-gradient-hover);
|
||||
text-shadow: 0 -1px 1px $text-shadow-hover;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
$active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
|
||||
$border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
|
||||
$border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
|
||||
$inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
|
||||
$text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
|
||||
|
||||
@if $grayscale == true {
|
||||
$active-color: grayscale($active-color);
|
||||
$border-active: grayscale($border-active);
|
||||
$border-bottom-active: grayscale($border-bottom-active);
|
||||
$inset-shadow-active: grayscale($inset-shadow-active);
|
||||
$text-shadow-active: grayscale($text-shadow-active);
|
||||
}
|
||||
|
||||
background: $active-color;
|
||||
border: 1px solid $border-active;
|
||||
border-bottom: 1px solid $border-bottom-active;
|
||||
box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff;
|
||||
text-shadow: 0 -1px 1px $text-shadow-active;
|
||||
}
|
||||
}
|
29
isso/css/bourbon/addons/_clearfix.scss
vendored
Normal file
29
isso/css/bourbon/addons/_clearfix.scss
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Micro clearfix provides an easy way to contain floats without adding additional markup
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Contain all floats within .wrapper
|
||||
// .wrapper {
|
||||
// @include clearfix;
|
||||
// .content,
|
||||
// .sidebar {
|
||||
// float : left;
|
||||
// }
|
||||
// }
|
||||
|
||||
@mixin clearfix {
|
||||
*zoom: 1;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
&:after {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
// Acknowledgements
|
||||
// Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/)
|
5
isso/css/bourbon/addons/_font-family.scss
vendored
Normal file
5
isso/css/bourbon/addons/_font-family.scss
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
$georgia: Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
$helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
|
||||
$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
|
||||
$verdana: Verdana, Geneva, sans-serif;
|
5
isso/css/bourbon/addons/_hide-text.scss
vendored
Normal file
5
isso/css/bourbon/addons/_hide-text.scss
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
@mixin hide-text {
|
||||
color: transparent;
|
||||
font: 0/0 a;
|
||||
text-shadow: none;
|
||||
}
|
56
isso/css/bourbon/addons/_html5-input-types.scss
vendored
Normal file
56
isso/css/bourbon/addons/_html5-input-types.scss
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
//************************************************************************//
|
||||
// Generate a variable ($all-text-inputs) with a list of all html5
|
||||
// input types that have a text-based input, excluding textarea.
|
||||
// http://diveintohtml5.org/forms.html
|
||||
//************************************************************************//
|
||||
$inputs-list: 'input[type="email"]',
|
||||
'input[type="number"]',
|
||||
'input[type="password"]',
|
||||
'input[type="search"]',
|
||||
'input[type="tel"]',
|
||||
'input[type="text"]',
|
||||
'input[type="url"]',
|
||||
|
||||
// Webkit & Gecko may change the display of these in the future
|
||||
'input[type="color"]',
|
||||
'input[type="date"]',
|
||||
'input[type="datetime"]',
|
||||
'input[type="datetime-local"]',
|
||||
'input[type="month"]',
|
||||
'input[type="time"]',
|
||||
'input[type="week"]';
|
||||
|
||||
$unquoted-inputs-list: ();
|
||||
@each $input-type in $inputs-list {
|
||||
$unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma);
|
||||
}
|
||||
|
||||
$all-text-inputs: $unquoted-inputs-list;
|
||||
|
||||
|
||||
// Hover Pseudo-class
|
||||
//************************************************************************//
|
||||
$all-text-inputs-hover: ();
|
||||
@each $input-type in $unquoted-inputs-list {
|
||||
$input-type-hover: $input-type + ":hover";
|
||||
$all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma);
|
||||
}
|
||||
|
||||
// Focus Pseudo-class
|
||||
//************************************************************************//
|
||||
$all-text-inputs-focus: ();
|
||||
@each $input-type in $unquoted-inputs-list {
|
||||
$input-type-focus: $input-type + ":focus";
|
||||
$all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma);
|
||||
}
|
||||
|
||||
// You must use interpolation on the variable:
|
||||
// #{$all-text-inputs}
|
||||
// #{$all-text-inputs-hover}
|
||||
// #{$all-text-inputs-focus}
|
||||
|
||||
// Example
|
||||
//************************************************************************//
|
||||
// #{$all-text-inputs}, textarea {
|
||||
// border: 1px solid red;
|
||||
// }
|
42
isso/css/bourbon/addons/_position.scss
vendored
Normal file
42
isso/css/bourbon/addons/_position.scss
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
@mixin position ($position: relative, $coordinates: 0 0 0 0) {
|
||||
|
||||
@if type-of($position) == list {
|
||||
$coordinates: $position;
|
||||
$position: relative;
|
||||
}
|
||||
|
||||
$top: nth($coordinates, 1);
|
||||
$right: nth($coordinates, 2);
|
||||
$bottom: nth($coordinates, 3);
|
||||
$left: nth($coordinates, 4);
|
||||
|
||||
position: $position;
|
||||
|
||||
@if $top == auto {
|
||||
top: $top;
|
||||
}
|
||||
@else if not(unitless($top)) {
|
||||
top: $top;
|
||||
}
|
||||
|
||||
@if $right == auto {
|
||||
right: $right;
|
||||
}
|
||||
@else if not(unitless($right)) {
|
||||
right: $right;
|
||||
}
|
||||
|
||||
@if $bottom == auto {
|
||||
bottom: $bottom;
|
||||
}
|
||||
@else if not(unitless($bottom)) {
|
||||
bottom: $bottom;
|
||||
}
|
||||
|
||||
@if $left == auto {
|
||||
left: $left;
|
||||
}
|
||||
@else if not(unitless($left)) {
|
||||
left: $left;
|
||||
}
|
||||
}
|
49
isso/css/bourbon/addons/_prefixer.scss
vendored
Normal file
49
isso/css/bourbon/addons/_prefixer.scss
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
//************************************************************************//
|
||||
// Example: @include prefixer(border-radius, $radii, webkit ms spec);
|
||||
//************************************************************************//
|
||||
$prefix-for-webkit: true !default;
|
||||
$prefix-for-mozilla: true !default;
|
||||
$prefix-for-microsoft: true !default;
|
||||
$prefix-for-opera: true !default;
|
||||
$prefix-for-spec: true !default; // required for keyframe mixin
|
||||
|
||||
@mixin prefixer ($property, $value, $prefixes) {
|
||||
@each $prefix in $prefixes {
|
||||
@if $prefix == webkit {
|
||||
@if $prefix-for-webkit {
|
||||
-webkit-#{$property}: $value;
|
||||
}
|
||||
}
|
||||
@else if $prefix == moz {
|
||||
@if $prefix-for-mozilla {
|
||||
-moz-#{$property}: $value;
|
||||
}
|
||||
}
|
||||
@else if $prefix == ms {
|
||||
@if $prefix-for-microsoft {
|
||||
-ms-#{$property}: $value;
|
||||
}
|
||||
}
|
||||
@else if $prefix == o {
|
||||
@if $prefix-for-opera {
|
||||
-o-#{$property}: $value;
|
||||
}
|
||||
}
|
||||
@else if $prefix == spec {
|
||||
@if $prefix-for-spec {
|
||||
#{$property}: $value;
|
||||
}
|
||||
}
|
||||
@else {
|
||||
@warn "Unrecognized prefix: #{$prefix}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin disable-prefix-for-all() {
|
||||
$prefix-for-webkit: false;
|
||||
$prefix-for-mozilla: false;
|
||||
$prefix-for-microsoft: false;
|
||||
$prefix-for-opera: false;
|
||||
$prefix-for-spec: false;
|
||||
}
|
32
isso/css/bourbon/addons/_retina-image.scss
vendored
Normal file
32
isso/css/bourbon/addons/_retina-image.scss
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $asset-pipeline: false) {
|
||||
@if $asset-pipeline {
|
||||
background-image: image-url("#{$filename}.#{$extension}");
|
||||
}
|
||||
@else {
|
||||
background-image: url("#{$filename}.#{$extension}");
|
||||
}
|
||||
|
||||
@include hidpi {
|
||||
|
||||
@if $asset-pipeline {
|
||||
@if $retina-filename {
|
||||
background-image: image-url("#{$retina-filename}.#{$extension}");
|
||||
}
|
||||
@else {
|
||||
background-image: image-url("#{$filename}@2x.#{$extension}");
|
||||
}
|
||||
}
|
||||
|
||||
@else {
|
||||
@if $retina-filename {
|
||||
background-image: url("#{$retina-filename}.#{$extension}");
|
||||
}
|
||||
@else {
|
||||
background-image: url("#{$filename}@2x.#{$extension}");
|
||||
}
|
||||
}
|
||||
|
||||
background-size: $background-size;
|
||||
|
||||
}
|
||||
}
|
44
isso/css/bourbon/addons/_size.scss
vendored
Normal file
44
isso/css/bourbon/addons/_size.scss
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
@mixin size($size) {
|
||||
@if length($size) == 1 {
|
||||
@if $size == auto {
|
||||
width: $size;
|
||||
height: $size;
|
||||
}
|
||||
|
||||
@else if unitless($size) {
|
||||
width: $size + px;
|
||||
height: $size + px;
|
||||
}
|
||||
|
||||
@else if not(unitless($size)) {
|
||||
width: $size;
|
||||
height: $size;
|
||||
}
|
||||
}
|
||||
|
||||
// Width x Height
|
||||
@if length($size) == 2 {
|
||||
$width: nth($size, 1);
|
||||
$height: nth($size, 2);
|
||||
|
||||
@if $width == auto {
|
||||
width: $width;
|
||||
}
|
||||
@else if not(unitless($width)) {
|
||||
width: $width;
|
||||
}
|
||||
@else if unitless($width) {
|
||||
width: $width + px;
|
||||
}
|
||||
|
||||
@if $height == auto {
|
||||
height: $height;
|
||||
}
|
||||
@else if not(unitless($height)) {
|
||||
height: $height;
|
||||
}
|
||||
@else if unitless($height) {
|
||||
height: $height + px;
|
||||
}
|
||||
}
|
||||
}
|
32
isso/css/bourbon/addons/_timing-functions.scss
vendored
Normal file
32
isso/css/bourbon/addons/_timing-functions.scss
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
|
||||
// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html
|
||||
|
||||
// EASE IN
|
||||
$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
|
||||
$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
|
||||
$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
|
||||
$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
|
||||
$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
|
||||
$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
|
||||
$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
|
||||
$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
|
||||
|
||||
// EASE OUT
|
||||
$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
|
||||
$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
|
||||
$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
|
||||
$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
|
||||
$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
|
||||
$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
|
||||
$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
|
||||
// EASE IN OUT
|
||||
$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
|
||||
$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
|
||||
$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
|
||||
$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
|
||||
$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
|
||||
$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
|
||||
$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
|
||||
$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);
|
45
isso/css/bourbon/addons/_triangle.scss
vendored
Normal file
45
isso/css/bourbon/addons/_triangle.scss
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
@mixin triangle ($size, $color, $direction) {
|
||||
height: 0;
|
||||
width: 0;
|
||||
|
||||
@if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: $size / 2;
|
||||
|
||||
@if $direction == up {
|
||||
border-bottom-color: $color;
|
||||
|
||||
} @else if $direction == right {
|
||||
border-left-color: $color;
|
||||
|
||||
} @else if $direction == down {
|
||||
border-top-color: $color;
|
||||
|
||||
} @else if $direction == left {
|
||||
border-right-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
@else if ($direction == up-right) or ($direction == up-left) {
|
||||
border-top: $size solid $color;
|
||||
|
||||
@if $direction == up-right {
|
||||
border-left: $size solid transparent;
|
||||
|
||||
} @else if $direction == up-left {
|
||||
border-right: $size solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@else if ($direction == down-right) or ($direction == down-left) {
|
||||
border-bottom: $size solid $color;
|
||||
|
||||
@if $direction == down-right {
|
||||
border-left: $size solid transparent;
|
||||
|
||||
} @else if $direction == down-left {
|
||||
border-right: $size solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
52
isso/css/bourbon/css3/_animation.scss
vendored
Normal file
52
isso/css/bourbon/css3/_animation.scss
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
|
||||
// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
|
||||
|
||||
// Official animation shorthand property.
|
||||
@mixin animation ($animations...) {
|
||||
@include prefixer(animation, $animations, webkit moz spec);
|
||||
}
|
||||
|
||||
// Individual Animation Properties
|
||||
@mixin animation-name ($names...) {
|
||||
@include prefixer(animation-name, $names, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-duration ($times...) {
|
||||
@include prefixer(animation-duration, $times, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-timing-function ($motions...) {
|
||||
// ease | linear | ease-in | ease-out | ease-in-out
|
||||
@include prefixer(animation-timing-function, $motions, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-iteration-count ($values...) {
|
||||
// infinite | <number>
|
||||
@include prefixer(animation-iteration-count, $values, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-direction ($directions...) {
|
||||
// normal | alternate
|
||||
@include prefixer(animation-direction, $directions, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-play-state ($states...) {
|
||||
// running | paused
|
||||
@include prefixer(animation-play-state, $states, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-delay ($times...) {
|
||||
@include prefixer(animation-delay, $times, webkit moz spec);
|
||||
}
|
||||
|
||||
|
||||
@mixin animation-fill-mode ($modes...) {
|
||||
// none | forwards | backwards | both
|
||||
@include prefixer(animation-fill-mode, $modes, webkit moz spec);
|
||||
}
|
3
isso/css/bourbon/css3/_appearance.scss
vendored
Normal file
3
isso/css/bourbon/css3/_appearance.scss
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
@mixin appearance ($value) {
|
||||
@include prefixer(appearance, $value, webkit moz ms o spec);
|
||||
}
|
6
isso/css/bourbon/css3/_backface-visibility.scss
vendored
Normal file
6
isso/css/bourbon/css3/_backface-visibility.scss
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
//************************************************************************//
|
||||
// Backface-visibility mixin
|
||||
//************************************************************************//
|
||||
@mixin backface-visibility($visibility) {
|
||||
@include prefixer(backface-visibility, $visibility, webkit spec);
|
||||
}
|
48
isso/css/bourbon/css3/_background-image.scss
vendored
Normal file
48
isso/css/bourbon/css3/_background-image.scss
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
//************************************************************************//
|
||||
// Background-image property for adding multiple background images with
|
||||
// gradients, or for stringing multiple gradients together.
|
||||
//************************************************************************//
|
||||
|
||||
@mixin background-image($images...) {
|
||||
background-image: _add-prefix($images, webkit);
|
||||
background-image: _add-prefix($images);
|
||||
}
|
||||
|
||||
@function _add-prefix($images, $vendor: false) {
|
||||
$images-prefixed: ();
|
||||
$gradient-positions: false;
|
||||
@for $i from 1 through length($images) {
|
||||
$type: type-of(nth($images, $i)); // Get type of variable - List or String
|
||||
|
||||
// If variable is a list - Gradient
|
||||
@if $type == list {
|
||||
$gradient-type: nth(nth($images, $i), 1); // linear or radial
|
||||
$gradient-pos: null;
|
||||
$gradient-args: null;
|
||||
|
||||
@if ($gradient-type == linear) or ($gradient-type == radial) {
|
||||
$gradient-pos: nth(nth($images, $i), 2); // Get gradient position
|
||||
$gradient-args: nth(nth($images, $i), 3); // Get actual gradient (red, blue)
|
||||
}
|
||||
@else {
|
||||
$gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue)
|
||||
}
|
||||
|
||||
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
|
||||
$gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
|
||||
$images-prefixed: append($images-prefixed, $gradient, comma);
|
||||
}
|
||||
// If variable is a string - Image
|
||||
@else if $type == string {
|
||||
$images-prefixed: join($images-prefixed, nth($images, $i), comma);
|
||||
}
|
||||
}
|
||||
@return $images-prefixed;
|
||||
}
|
||||
|
||||
//Examples:
|
||||
//@include background-image(linear-gradient(top, orange, red));
|
||||
//@include background-image(radial-gradient(50% 50%, cover circle, orange, red));
|
||||
//@include background-image(url("/images/a.png"), linear-gradient(orange, red));
|
||||
//@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png"));
|
||||
//@include background-image(linear-gradient(hsla(0, 100%, 100%, 0.25) 0%, hsla(0, 100%, 100%, 0.08) 50%, transparent 50%), linear-gradient(orange, red));
|
103
isso/css/bourbon/css3/_background.scss
vendored
Normal file
103
isso/css/bourbon/css3/_background.scss
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
//************************************************************************//
|
||||
// Background property for adding multiple backgrounds using shorthand
|
||||
// notation.
|
||||
//************************************************************************//
|
||||
|
||||
@mixin background(
|
||||
$background-1 , $background-2: false,
|
||||
$background-3: false, $background-4: false,
|
||||
$background-5: false, $background-6: false,
|
||||
$background-7: false, $background-8: false,
|
||||
$background-9: false, $background-10: false,
|
||||
$fallback: false
|
||||
) {
|
||||
$backgrounds: compact($background-1, $background-2,
|
||||
$background-3, $background-4,
|
||||
$background-5, $background-6,
|
||||
$background-7, $background-8,
|
||||
$background-9, $background-10);
|
||||
|
||||
$fallback-color: false;
|
||||
@if (type-of($fallback) == color) or ($fallback == "transparent") {
|
||||
$fallback-color: $fallback;
|
||||
}
|
||||
@else {
|
||||
$fallback-color: _extract-background-color($backgrounds);
|
||||
}
|
||||
|
||||
@if $fallback-color {
|
||||
background-color: $fallback-color;
|
||||
}
|
||||
background: _background-add-prefix($backgrounds, webkit);
|
||||
background: _background-add-prefix($backgrounds);
|
||||
}
|
||||
|
||||
@function _extract-background-color($backgrounds) {
|
||||
$final-bg-layer: nth($backgrounds, length($backgrounds));
|
||||
@if type-of($final-bg-layer) == list {
|
||||
@for $i from 1 through length($final-bg-layer) {
|
||||
$value: nth($final-bg-layer, $i);
|
||||
@if type-of($value) == color {
|
||||
@return $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@return false;
|
||||
}
|
||||
|
||||
@function _background-add-prefix($backgrounds, $vendor: false) {
|
||||
$backgrounds-prefixed: ();
|
||||
|
||||
@for $i from 1 through length($backgrounds) {
|
||||
$shorthand: nth($backgrounds, $i); // Get member for current index
|
||||
$type: type-of($shorthand); // Get type of variable - List (gradient) or String (image)
|
||||
|
||||
// If shorthand is a list (gradient)
|
||||
@if $type == list {
|
||||
$first-member: nth($shorthand, 1); // Get first member of shorthand
|
||||
|
||||
// Linear Gradient
|
||||
@if index(linear radial, nth($first-member, 1)) {
|
||||
$gradient-type: nth($first-member, 1); // linear || radial
|
||||
$gradient-args: false;
|
||||
$gradient-positions: false;
|
||||
$shorthand-start: false;
|
||||
@if type-of($first-member) == list { // Linear gradient plus additional shorthand values - lg(red,orange)repeat,...
|
||||
$gradient-positions: nth($first-member, 2);
|
||||
$gradient-args: nth($first-member, 3);
|
||||
$shorthand-start: 2;
|
||||
}
|
||||
@else { // Linear gradient only - lg(red,orange),...
|
||||
$gradient-positions: nth($shorthand, 2);
|
||||
$gradient-args: nth($shorthand, 3); // Get gradient (red, blue)
|
||||
}
|
||||
|
||||
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-positions);
|
||||
$gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
|
||||
|
||||
// Append any additional shorthand args to gradient
|
||||
@if $shorthand-start {
|
||||
@for $j from $shorthand-start through length($shorthand) {
|
||||
$gradient: join($gradient, nth($shorthand, $j), space);
|
||||
}
|
||||
}
|
||||
$backgrounds-prefixed: append($backgrounds-prefixed, $gradient, comma);
|
||||
}
|
||||
// Image with additional properties
|
||||
@else {
|
||||
$backgrounds-prefixed: append($backgrounds-prefixed, $shorthand, comma);
|
||||
}
|
||||
}
|
||||
// If shorthand is a simple string (color or image)
|
||||
@else if $type == string {
|
||||
$backgrounds-prefixed: join($backgrounds-prefixed, $shorthand, comma);
|
||||
}
|
||||
}
|
||||
@return $backgrounds-prefixed;
|
||||
}
|
||||
|
||||
//Examples:
|
||||
//@include background(linear-gradient(top, orange, red));
|
||||
//@include background(radial-gradient(circle at 40% 40%, orange, red));
|
||||
//@include background(url("/images/a.png") no-repeat, linear-gradient(orange, red));
|
||||
//@include background(url("image.png") center center, linear-gradient(orange, red), url("image.png"));
|
55
isso/css/bourbon/css3/_border-image.scss
vendored
Normal file
55
isso/css/bourbon/css3/_border-image.scss
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
@mixin border-image($images) {
|
||||
-webkit-border-image: _border-add-prefix($images, webkit);
|
||||
-moz-border-image: _border-add-prefix($images, moz);
|
||||
-o-border-image: _border-add-prefix($images, o);
|
||||
border-image: _border-add-prefix($images);
|
||||
}
|
||||
|
||||
@function _border-add-prefix($images, $vendor: false) {
|
||||
$border-image: null;
|
||||
$images-type: type-of(nth($images, 1));
|
||||
$first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial)
|
||||
|
||||
// If input is a gradient
|
||||
@if $images-type == string {
|
||||
@if ($first-var == "linear") or ($first-var == "radial") {
|
||||
$gradient-type: nth($images, 1); // Get type of gradient (linear || radial)
|
||||
$gradient-pos: nth($images, 2); // Get gradient position
|
||||
$gradient-args: nth($images, 3); // Get actual gradient (red, blue)
|
||||
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
|
||||
$border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
|
||||
}
|
||||
// If input is a URL
|
||||
@else {
|
||||
$border-image: $images;
|
||||
}
|
||||
}
|
||||
// If input is gradient or url + additional args
|
||||
@else if $images-type == list {
|
||||
$type: type-of(nth($images, 1)); // Get type of variable - List or String
|
||||
|
||||
// If variable is a list - Gradient
|
||||
@if $type == list {
|
||||
$gradient: nth($images, 1);
|
||||
$gradient-type: nth($gradient, 1); // Get type of gradient (linear || radial)
|
||||
$gradient-pos: nth($gradient, 2); // Get gradient position
|
||||
$gradient-args: nth($gradient, 3); // Get actual gradient (red, blue)
|
||||
$gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos);
|
||||
$border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor);
|
||||
|
||||
@for $i from 2 through length($images) {
|
||||
$border-image: append($border-image, nth($images, $i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@return $border-image;
|
||||
}
|
||||
|
||||
//Examples:
|
||||
// @include border-image(url("image.png"));
|
||||
// @include border-image(url("image.png") 20 stretch);
|
||||
// @include border-image(linear-gradient(45deg, orange, yellow));
|
||||
// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
|
||||
// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
|
||||
// @include border-image(radial-gradient(top, cover, orange, yellow, orange));
|
||||
|
22
isso/css/bourbon/css3/_border-radius.scss
vendored
Normal file
22
isso/css/bourbon/css3/_border-radius.scss
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
//************************************************************************//
|
||||
// Shorthand Border-radius mixins
|
||||
//************************************************************************//
|
||||
@mixin border-top-radius($radii) {
|
||||
@include prefixer(border-top-left-radius, $radii, spec);
|
||||
@include prefixer(border-top-right-radius, $radii, spec);
|
||||
}
|
||||
|
||||
@mixin border-bottom-radius($radii) {
|
||||
@include prefixer(border-bottom-left-radius, $radii, spec);
|
||||
@include prefixer(border-bottom-right-radius, $radii, spec);
|
||||
}
|
||||
|
||||
@mixin border-left-radius($radii) {
|
||||
@include prefixer(border-top-left-radius, $radii, spec);
|
||||
@include prefixer(border-bottom-left-radius, $radii, spec);
|
||||
}
|
||||
|
||||
@mixin border-right-radius($radii) {
|
||||
@include prefixer(border-top-right-radius, $radii, spec);
|
||||
@include prefixer(border-bottom-right-radius, $radii, spec);
|
||||
}
|
4
isso/css/bourbon/css3/_box-sizing.scss
vendored
Normal file
4
isso/css/bourbon/css3/_box-sizing.scss
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
@mixin box-sizing ($box) {
|
||||
// content-box | border-box | inherit
|
||||
@include prefixer(box-sizing, $box, webkit moz spec);
|
||||
}
|
47
isso/css/bourbon/css3/_columns.scss
vendored
Normal file
47
isso/css/bourbon/css3/_columns.scss
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
@mixin columns($arg: auto) {
|
||||
// <column-count> || <column-width>
|
||||
@include prefixer(columns, $arg, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-count($int: auto) {
|
||||
// auto || integer
|
||||
@include prefixer(column-count, $int, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-gap($length: normal) {
|
||||
// normal || length
|
||||
@include prefixer(column-gap, $length, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-fill($arg: auto) {
|
||||
// auto || length
|
||||
@include prefixer(columns-fill, $arg, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-rule($arg) {
|
||||
// <border-width> || <border-style> || <color>
|
||||
@include prefixer(column-rule, $arg, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-rule-color($color) {
|
||||
@include prefixer(column-rule-color, $color, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-rule-style($style: none) {
|
||||
// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
|
||||
@include prefixer(column-rule-style, $style, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-rule-width ($width: none) {
|
||||
@include prefixer(column-rule-width, $width, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-span($arg: none) {
|
||||
// none || all
|
||||
@include prefixer(column-span, $arg, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin column-width($length: auto) {
|
||||
// auto || length
|
||||
@include prefixer(column-width, $length, webkit moz spec);
|
||||
}
|
52
isso/css/bourbon/css3/_flex-box.scss
vendored
Normal file
52
isso/css/bourbon/css3/_flex-box.scss
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
// CSS3 Flexible Box Model and property defaults
|
||||
|
||||
// Custom shorthand notation for flexbox
|
||||
@mixin box($orient: inline-axis, $pack: start, $align: stretch) {
|
||||
@include display-box;
|
||||
@include box-orient($orient);
|
||||
@include box-pack($pack);
|
||||
@include box-align($align);
|
||||
}
|
||||
|
||||
@mixin display-box {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: box;
|
||||
}
|
||||
|
||||
@mixin box-orient($orient: inline-axis) {
|
||||
// horizontal|vertical|inline-axis|block-axis|inherit
|
||||
@include prefixer(box-orient, $orient, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-pack($pack: start) {
|
||||
// start|end|center|justify
|
||||
@include prefixer(box-pack, $pack, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-align($align: stretch) {
|
||||
// start|end|center|baseline|stretch
|
||||
@include prefixer(box-align, $align, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-direction($direction: normal) {
|
||||
// normal|reverse|inherit
|
||||
@include prefixer(box-direction, $direction, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-lines($lines: single) {
|
||||
// single|multiple
|
||||
@include prefixer(box-lines, $lines, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-ordinal-group($int: 1) {
|
||||
@include prefixer(box-ordinal-group, $int, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-flex($value: 0.0) {
|
||||
@include prefixer(box-flex, $value, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin box-flex-group($int: 1) {
|
||||
@include prefixer(box-flex-group, $int, webkit moz spec);
|
||||
}
|
23
isso/css/bourbon/css3/_font-face.scss
vendored
Normal file
23
isso/css/bourbon/css3/_font-face.scss
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// Order of the includes matters, and it is: normal, bold, italic, bold+italic.
|
||||
|
||||
@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) {
|
||||
@font-face {
|
||||
font-family: $font-family;
|
||||
font-weight: $weight;
|
||||
font-style: $style;
|
||||
|
||||
@if $asset-pipeline == true {
|
||||
src: font-url('#{$file-path}.eot');
|
||||
src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
|
||||
font-url('#{$file-path}.woff') format('woff'),
|
||||
font-url('#{$file-path}.ttf') format('truetype'),
|
||||
font-url('#{$file-path}.svg##{$font-family}') format('svg');
|
||||
} @else {
|
||||
src: url('#{$file-path}.eot');
|
||||
src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'),
|
||||
url('#{$file-path}.woff') format('woff'),
|
||||
url('#{$file-path}.ttf') format('truetype'),
|
||||
url('#{$file-path}.svg##{$font-family}') format('svg');
|
||||
}
|
||||
}
|
||||
}
|
10
isso/css/bourbon/css3/_hidpi-media-query.scss
vendored
Normal file
10
isso/css/bourbon/css3/_hidpi-media-query.scss
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/)
|
||||
@mixin hidpi($ratio: 1.3) {
|
||||
@media only screen and (-webkit-min-device-pixel-ratio: $ratio),
|
||||
only screen and (min--moz-device-pixel-ratio: $ratio),
|
||||
only screen and (-o-min-device-pixel-ratio: #{$ratio}/1),
|
||||
only screen and (min-resolution: #{round($ratio*96)}dpi),
|
||||
only screen and (min-resolution: #{$ratio}dppx) {
|
||||
@content;
|
||||
}
|
||||
}
|
13
isso/css/bourbon/css3/_image-rendering.scss
vendored
Normal file
13
isso/css/bourbon/css3/_image-rendering.scss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
@mixin image-rendering ($mode:optimizeQuality) {
|
||||
|
||||
@if ($mode == optimize-contrast) {
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: optimize-contrast;
|
||||
}
|
||||
|
||||
@else {
|
||||
image-rendering: $mode;
|
||||
}
|
||||
}
|
8
isso/css/bourbon/css3/_inline-block.scss
vendored
Normal file
8
isso/css/bourbon/css3/_inline-block.scss
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Legacy support for inline-block in IE7 (maybe IE6)
|
||||
@mixin inline-block {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
zoom: 1;
|
||||
*display: inline;
|
||||
*vertical-align: auto;
|
||||
}
|
43
isso/css/bourbon/css3/_keyframes.scss
vendored
Normal file
43
isso/css/bourbon/css3/_keyframes.scss
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content
|
||||
@mixin keyframes($name) {
|
||||
$original-prefix-for-webkit: $prefix-for-webkit;
|
||||
$original-prefix-for-mozilla: $prefix-for-mozilla;
|
||||
$original-prefix-for-microsoft: $prefix-for-microsoft;
|
||||
$original-prefix-for-opera: $prefix-for-opera;
|
||||
$original-prefix-for-spec: $prefix-for-spec;
|
||||
|
||||
@if $original-prefix-for-webkit {
|
||||
@include disable-prefix-for-all();
|
||||
$prefix-for-webkit: true;
|
||||
@-webkit-keyframes #{$name} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $original-prefix-for-mozilla {
|
||||
@include disable-prefix-for-all();
|
||||
$prefix-for-mozilla: true;
|
||||
@-moz-keyframes #{$name} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $original-prefix-for-opera {
|
||||
@include disable-prefix-for-all();
|
||||
$prefix-for-opera: true;
|
||||
@-o-keyframes #{$name} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@if $original-prefix-for-spec {
|
||||
@include disable-prefix-for-all();
|
||||
$prefix-for-spec: true;
|
||||
@keyframes #{$name} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
$prefix-for-webkit: $original-prefix-for-webkit;
|
||||
$prefix-for-mozilla: $original-prefix-for-mozilla;
|
||||
$prefix-for-microsoft: $original-prefix-for-microsoft;
|
||||
$prefix-for-opera: $original-prefix-for-opera;
|
||||
$prefix-for-spec: $original-prefix-for-spec;
|
||||
}
|
41
isso/css/bourbon/css3/_linear-gradient.scss
vendored
Normal file
41
isso/css/bourbon/css3/_linear-gradient.scss
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
@mixin linear-gradient($pos, $G1, $G2: false,
|
||||
$G3: false, $G4: false,
|
||||
$G5: false, $G6: false,
|
||||
$G7: false, $G8: false,
|
||||
$G9: false, $G10: false,
|
||||
$deprecated-pos1: left top,
|
||||
$deprecated-pos2: left bottom,
|
||||
$fallback: false) {
|
||||
// Detect what type of value exists in $pos
|
||||
$pos-type: type-of(nth($pos, 1));
|
||||
$pos-spec: null;
|
||||
$pos-degree: null;
|
||||
|
||||
// If $pos is missing from mixin, reassign vars and add default position
|
||||
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
|
||||
$G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5;
|
||||
$G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos;
|
||||
$pos: null;
|
||||
}
|
||||
|
||||
@if $pos {
|
||||
$positions: _linear-positions-parser($pos);
|
||||
$pos-degree: nth($positions, 1);
|
||||
$pos-spec: nth($positions, 2);
|
||||
}
|
||||
|
||||
$full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
|
||||
|
||||
// Set $G1 as the default fallback color
|
||||
$fallback-color: nth($G1, 1);
|
||||
|
||||
// If $fallback is a color use that color as the fallback color
|
||||
@if (type-of($fallback) == color) or ($fallback == "transparent") {
|
||||
$fallback-color: $fallback;
|
||||
}
|
||||
|
||||
background-color: $fallback-color;
|
||||
background-image: _deprecated-webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $full); // Safari <= 5.0
|
||||
background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome
|
||||
background-image: unquote("linear-gradient(#{$pos-spec}#{$full})");
|
||||
}
|
8
isso/css/bourbon/css3/_perspective.scss
vendored
Normal file
8
isso/css/bourbon/css3/_perspective.scss
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
@mixin perspective($depth: none) {
|
||||
// none | <length>
|
||||
@include prefixer(perspective, $depth, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin perspective-origin($value: 50% 50%) {
|
||||
@include prefixer(perspective-origin, $value, webkit moz spec);
|
||||
}
|
29
isso/css/bourbon/css3/_placeholder.scss
vendored
Normal file
29
isso/css/bourbon/css3/_placeholder.scss
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
$placeholders: '-webkit-input-placeholder',
|
||||
'-moz-placeholder',
|
||||
'-ms-input-placeholder';
|
||||
|
||||
@mixin placeholder {
|
||||
@each $placeholder in $placeholders {
|
||||
@if $placeholder == "-webkit-input-placeholder" {
|
||||
&::#{$placeholder} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@else if $placeholder == "-moz-placeholder" {
|
||||
// FF 18-
|
||||
&:#{$placeholder} {
|
||||
@content;
|
||||
}
|
||||
|
||||
// FF 19+
|
||||
&::#{$placeholder} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@else {
|
||||
&:#{$placeholder} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
isso/css/bourbon/css3/_radial-gradient.scss
vendored
Normal file
44
isso/css/bourbon/css3/_radial-gradient.scss
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Requires Sass 3.1+
|
||||
@mixin radial-gradient($G1, $G2,
|
||||
$G3: false, $G4: false,
|
||||
$G5: false, $G6: false,
|
||||
$G7: false, $G8: false,
|
||||
$G9: false, $G10: false,
|
||||
$pos: null,
|
||||
$shape-size: null,
|
||||
$deprecated-pos1: center center,
|
||||
$deprecated-pos2: center center,
|
||||
$deprecated-radius1: 0,
|
||||
$deprecated-radius2: 460,
|
||||
$fallback: false) {
|
||||
|
||||
$data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
|
||||
$G1: nth($data, 1);
|
||||
$G2: nth($data, 2);
|
||||
$pos: nth($data, 3);
|
||||
$shape-size: nth($data, 4);
|
||||
|
||||
$full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
|
||||
|
||||
// Strip deprecated cover/contain for spec
|
||||
$shape-size-spec: _shape-size-stripper($shape-size);
|
||||
|
||||
// Set $G1 as the default fallback color
|
||||
$first-color: nth($full, 1);
|
||||
$fallback-color: nth($first-color, 1);
|
||||
|
||||
@if (type-of($fallback) == color) or ($fallback == "transparent") {
|
||||
$fallback-color: $fallback;
|
||||
}
|
||||
|
||||
// Add Commas and spaces
|
||||
$shape-size: if($shape-size, '#{$shape-size}, ', null);
|
||||
$pos: if($pos, '#{$pos}, ', null);
|
||||
$pos-spec: if($pos, 'at #{$pos}', null);
|
||||
$shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');
|
||||
|
||||
background-color: $fallback-color;
|
||||
background-image: _deprecated-webkit-gradient(radial, $deprecated-pos1, $deprecated-pos2, $full, $deprecated-radius1, $deprecated-radius2); // Safari <= 5.0 && IOS 4
|
||||
background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));
|
||||
background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})");
|
||||
}
|
15
isso/css/bourbon/css3/_transform.scss
vendored
Normal file
15
isso/css/bourbon/css3/_transform.scss
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
@mixin transform($property: none) {
|
||||
// none | <transform-function>
|
||||
@include prefixer(transform, $property, webkit moz ms o spec);
|
||||
}
|
||||
|
||||
@mixin transform-origin($axes: 50%) {
|
||||
// x-axis - left | center | right | length | %
|
||||
// y-axis - top | center | bottom | length | %
|
||||
// z-axis - length
|
||||
@include prefixer(transform-origin, $axes, webkit moz ms o spec);
|
||||
}
|
||||
|
||||
@mixin transform-style ($style: flat) {
|
||||
@include prefixer(transform-style, $style, webkit moz ms o spec);
|
||||
}
|
34
isso/css/bourbon/css3/_transition.scss
vendored
Normal file
34
isso/css/bourbon/css3/_transition.scss
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
|
||||
// Example: @include transition (all, 2.0s, ease-in-out);
|
||||
// @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s));
|
||||
// @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s));
|
||||
|
||||
@mixin transition ($properties...) {
|
||||
@if length($properties) >= 1 {
|
||||
@include prefixer(transition, $properties, webkit moz spec);
|
||||
}
|
||||
|
||||
@else {
|
||||
$properties: all 0.15s ease-out 0;
|
||||
@include prefixer(transition, $properties, webkit moz spec);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin transition-property ($properties...) {
|
||||
-webkit-transition-property: transition-property-names($properties, 'webkit');
|
||||
-moz-transition-property: transition-property-names($properties, 'moz');
|
||||
transition-property: transition-property-names($properties, false);
|
||||
}
|
||||
|
||||
@mixin transition-duration ($times...) {
|
||||
@include prefixer(transition-duration, $times, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin transition-timing-function ($motions...) {
|
||||
// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
|
||||
@include prefixer(transition-timing-function, $motions, webkit moz spec);
|
||||
}
|
||||
|
||||
@mixin transition-delay ($times...) {
|
||||
@include prefixer(transition-delay, $times, webkit moz spec);
|
||||
}
|
3
isso/css/bourbon/css3/_user-select.scss
vendored
Normal file
3
isso/css/bourbon/css3/_user-select.scss
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
@mixin user-select($arg: none) {
|
||||
@include prefixer(user-select, $arg, webkit moz ms spec);
|
||||
}
|
11
isso/css/bourbon/functions/_compact.scss
vendored
Normal file
11
isso/css/bourbon/functions/_compact.scss
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Remove `false` values from a list
|
||||
|
||||
@function compact($vars...) {
|
||||
$list: ();
|
||||
@each $var in $vars {
|
||||
@if $var {
|
||||
$list: append($list, $var, comma);
|
||||
}
|
||||
}
|
||||
@return $list;
|
||||
}
|
39
isso/css/bourbon/functions/_flex-grid.scss
vendored
Normal file
39
isso/css/bourbon/functions/_flex-grid.scss
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// Flexible grid
|
||||
@function flex-grid($columns, $container-columns: $fg-max-columns) {
|
||||
$width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
|
||||
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
|
||||
@return percentage($width / $container-width);
|
||||
}
|
||||
|
||||
// Flexible gutter
|
||||
@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
|
||||
$container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
|
||||
@return percentage($gutter / $container-width);
|
||||
}
|
||||
|
||||
// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.
|
||||
// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.
|
||||
//
|
||||
// The calculation presumes that your column structure will be missing the last gutter:
|
||||
//
|
||||
// -- column -- gutter -- column -- gutter -- column
|
||||
//
|
||||
// $fg-column: 60px; // Column Width
|
||||
// $fg-gutter: 25px; // Gutter Width
|
||||
// $fg-max-columns: 12; // Total Columns For Main Container
|
||||
//
|
||||
// div {
|
||||
// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;
|
||||
// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;
|
||||
//
|
||||
// p {
|
||||
// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
|
||||
// float: left;
|
||||
// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;
|
||||
// }
|
||||
//
|
||||
// blockquote {
|
||||
// float: left;
|
||||
// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
|
||||
// }
|
||||
// }
|
13
isso/css/bourbon/functions/_grid-width.scss
vendored
Normal file
13
isso/css/bourbon/functions/_grid-width.scss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
@function grid-width($n) {
|
||||
@return $n * $gw-column + ($n - 1) * $gw-gutter;
|
||||
}
|
||||
|
||||
// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.
|
||||
//
|
||||
// $gw-column: 100px; // Column Width
|
||||
// $gw-gutter: 40px; // Gutter Width
|
||||
//
|
||||
// div {
|
||||
// width: grid-width(4); // returns 520px;
|
||||
// margin-left: $gw-gutter; // returns 40px;
|
||||
// }
|
13
isso/css/bourbon/functions/_linear-gradient.scss
vendored
Normal file
13
isso/css/bourbon/functions/_linear-gradient.scss
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
@function linear-gradient($pos, $gradients...) {
|
||||
$type: linear;
|
||||
$pos-type: type-of(nth($pos, 1));
|
||||
|
||||
// if $pos doesn't exist, fix $gradient
|
||||
@if ($pos-type == color) or (nth($pos, 1) == "transparent") {
|
||||
$gradients: zip($pos $gradients);
|
||||
$pos: false;
|
||||
}
|
||||
|
||||
$type-gradient: $type, $pos, $gradients;
|
||||
@return $type-gradient;
|
||||
}
|
40
isso/css/bourbon/functions/_modular-scale.scss
vendored
Normal file
40
isso/css/bourbon/functions/_modular-scale.scss
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
@function modular-scale($value, $increment, $ratio) {
|
||||
@if $increment > 0 {
|
||||
@for $i from 1 through $increment {
|
||||
$value: ($value * $ratio);
|
||||
}
|
||||
}
|
||||
|
||||
@if $increment < 0 {
|
||||
$increment: abs($increment);
|
||||
@for $i from 1 through $increment {
|
||||
$value: ($value / $ratio);
|
||||
}
|
||||
}
|
||||
|
||||
@return $value;
|
||||
}
|
||||
|
||||
// div {
|
||||
// Increment Up GR with positive value
|
||||
// font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px
|
||||
//
|
||||
// Increment Down GR with negative value
|
||||
// font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px
|
||||
//
|
||||
// Can be used with ceil(round up) or floor(round down)
|
||||
// font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px
|
||||
// font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px
|
||||
// }
|
||||
//
|
||||
// modularscale.com
|
||||
|
||||
@function golden-ratio($value, $increment) {
|
||||
@return modular-scale($value, $increment, 1.618)
|
||||
}
|
||||
|
||||
// div {
|
||||
// font-size: golden-ratio(14px, 1); // returns: 22.652px
|
||||
// }
|
||||
//
|
||||
// goldenratiocalculator.com
|
8
isso/css/bourbon/functions/_px-to-em.scss
vendored
Normal file
8
isso/css/bourbon/functions/_px-to-em.scss
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Convert pixels to ems
|
||||
// eg. for a relational value of 12px write em(12) when the parent is 16px
|
||||
// if the parent is another value say 24px write em(12, 24)
|
||||
|
||||
@function em($pxval, $base: 16) {
|
||||
@return ($pxval / $base) * 1em;
|
||||
}
|
||||
|
23
isso/css/bourbon/functions/_radial-gradient.scss
vendored
Normal file
23
isso/css/bourbon/functions/_radial-gradient.scss
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// This function is required and used by the background-image mixin.
|
||||
@function radial-gradient($G1, $G2,
|
||||
$G3: false, $G4: false,
|
||||
$G5: false, $G6: false,
|
||||
$G7: false, $G8: false,
|
||||
$G9: false, $G10: false,
|
||||
$pos: null,
|
||||
$shape-size: null) {
|
||||
|
||||
$data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
|
||||
$G1: nth($data, 1);
|
||||
$G2: nth($data, 2);
|
||||
$pos: nth($data, 3);
|
||||
$shape-size: nth($data, 4);
|
||||
|
||||
$type: radial;
|
||||
$gradient: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10);
|
||||
|
||||
$type-gradient: $type, $shape-size $pos, $gradient;
|
||||
@return $type-gradient;
|
||||
}
|
||||
|
||||
|
9
isso/css/bourbon/functions/_tint-shade.scss
vendored
Normal file
9
isso/css/bourbon/functions/_tint-shade.scss
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// Add percentage of white to a color
|
||||
@function tint($color, $percent){
|
||||
@return mix(white, $color, $percent);
|
||||
}
|
||||
|
||||
// Add percentage of black to a color
|
||||
@function shade($color, $percent){
|
||||
@return mix(black, $color, $percent);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user