├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-upload-docs.yml │ ├── docker-build.yaml │ ├── docker-testbed-periodic-rebuild.yaml │ ├── e2e-tests.yml │ ├── javascript-client.yml │ ├── python-package.yml │ └── python-tests.yml ├── .gitignore ├── CHANGES.rst ├── CONTRIBUTING.rst ├── CONTRIBUTORS.md ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── apidoc ├── _apidoc.js ├── apidoc.json ├── footer.md └── header.md ├── contrib ├── dump_comments.py ├── import_blogger.py ├── isso-dev.cfg ├── isso.sample.cfg ├── nginx │ ├── nginx.conf │ └── nginx.vhost.conf └── uwsgi.ini ├── disperse.conf ├── docker-compose.yml ├── docker └── Dockerfile-js-testbed ├── docs ├── _extensions │ └── sphinx_reredirects │ │ └── __init__.py ├── _static │ ├── css │ │ ├── bourbon │ │ │ ├── _bourbon-deprecated-upcoming.scss │ │ │ ├── _bourbon.scss │ │ │ ├── addons │ │ │ │ ├── _button.scss │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _font-family.scss │ │ │ │ ├── _hide-text.scss │ │ │ │ ├── _html5-input-types.scss │ │ │ │ ├── _position.scss │ │ │ │ ├── _prefixer.scss │ │ │ │ ├── _retina-image.scss │ │ │ │ ├── _size.scss │ │ │ │ ├── _timing-functions.scss │ │ │ │ └── _triangle.scss │ │ │ ├── css3 │ │ │ │ ├── _animation.scss │ │ │ │ ├── _appearance.scss │ │ │ │ ├── _backface-visibility.scss │ │ │ │ ├── _background-image.scss │ │ │ │ ├── _background.scss │ │ │ │ ├── _border-image.scss │ │ │ │ ├── _border-radius.scss │ │ │ │ ├── _box-sizing.scss │ │ │ │ ├── _columns.scss │ │ │ │ ├── _flex-box.scss │ │ │ │ ├── _font-face.scss │ │ │ │ ├── _hidpi-media-query.scss │ │ │ │ ├── _image-rendering.scss │ │ │ │ ├── _inline-block.scss │ │ │ │ ├── _keyframes.scss │ │ │ │ ├── _linear-gradient.scss │ │ │ │ ├── _perspective.scss │ │ │ │ ├── _placeholder.scss │ │ │ │ ├── _radial-gradient.scss │ │ │ │ ├── _transform.scss │ │ │ │ ├── _transition.scss │ │ │ │ └── _user-select.scss │ │ │ ├── functions │ │ │ │ ├── _compact.scss │ │ │ │ ├── _flex-grid.scss │ │ │ │ ├── _grid-width.scss │ │ │ │ ├── _linear-gradient.scss │ │ │ │ ├── _modular-scale.scss │ │ │ │ ├── _px-to-em.scss │ │ │ │ ├── _radial-gradient.scss │ │ │ │ ├── _tint-shade.scss │ │ │ │ └── _transition-property-name.scss │ │ │ └── helpers │ │ │ │ ├── _deprecated-webkit-gradient.scss │ │ │ │ ├── _gradient-positions-parser.scss │ │ │ │ ├── _linear-positions-parser.scss │ │ │ │ ├── _radial-arg-parser.scss │ │ │ │ ├── _radial-positions-parser.scss │ │ │ │ ├── _render-gradients.scss │ │ │ │ └── _shape-size-stripper.scss │ │ ├── neat │ │ │ ├── _neat-helpers.scss │ │ │ ├── _neat.scss │ │ │ ├── functions │ │ │ │ ├── _new-breakpoint.scss │ │ │ │ ├── _private.scss │ │ │ │ └── _px-to-em.scss │ │ │ ├── grid │ │ │ │ ├── _fill-parent.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _media.scss │ │ │ │ ├── _omega.scss │ │ │ │ ├── _outer-container.scss │ │ │ │ ├── _pad.scss │ │ │ │ ├── _private.scss │ │ │ │ ├── _reset.scss │ │ │ │ ├── _row.scss │ │ │ │ ├── _shift.scss │ │ │ │ ├── _span-columns.scss │ │ │ │ ├── _to-deprecate.scss │ │ │ │ └── _visual-grid.scss │ │ │ └── settings │ │ │ │ ├── _grid.scss │ │ │ │ └── _visual-grid.scss │ │ ├── site.css │ │ └── site.scss │ ├── duty_calls.png │ ├── favicon.ico │ ├── flattr.png │ ├── github.svg │ ├── isso.svg │ └── js │ │ └── vendor │ │ └── jquery-3.7.1.min.js ├── _theme │ ├── layout.html │ ├── page.html │ ├── remove_heading.py │ ├── search.html │ ├── searchbox.html │ ├── sidebar-docs.html │ └── theme.conf ├── community.rst ├── conf.py ├── docs │ ├── contributing │ │ ├── documentation.rst │ │ ├── index.rst │ │ └── infrastructure.rst │ ├── guides │ │ ├── advanced-integration.rst │ │ ├── faq.rst │ │ ├── quickstart.rst │ │ ├── tips-and-tricks.rst │ │ └── troubleshooting.rst │ ├── index.rst │ ├── reference │ │ ├── client-config.rst │ │ ├── deployment.rst │ │ ├── installation.rst │ │ ├── markdown-config.rst │ │ ├── multi-site-sub-uri.rst │ │ ├── releasing.rst │ │ ├── server-api.rst │ │ └── server-config.rst │ ├── technical-docs │ │ ├── client.rst │ │ ├── server.rst │ │ ├── testing-client.rst │ │ ├── testing-server.rst │ │ └── testing.rst │ └── toc.rst ├── docutils.conf ├── images │ └── apidoc-sample-latest.png ├── index.html ├── news.rst └── theme-testing.rst ├── isso ├── __init__.py ├── config.py ├── core.py ├── css │ ├── admin.css │ └── isso.css ├── db │ ├── __init__.py │ ├── comments.py │ ├── preferences.py │ ├── spam.py │ └── threads.py ├── demo │ └── index.html ├── dispatch.py ├── ext │ ├── __init__.py │ └── notifications.py ├── img │ └── isso.svg ├── isso.cfg ├── js │ ├── admin.js │ ├── app │ │ ├── api.js │ │ ├── config.js │ │ ├── count.js │ │ ├── default_config.js │ │ ├── dom.js │ │ ├── globals.js │ │ ├── i18n.js │ │ ├── i18n │ │ │ ├── ar.js │ │ │ ├── bg.js │ │ │ ├── ca.js │ │ │ ├── cs.js │ │ │ ├── da.js │ │ │ ├── de.js │ │ │ ├── el_GR.js │ │ │ ├── en.js │ │ │ ├── eo.js │ │ │ ├── es.js │ │ │ ├── fa.js │ │ │ ├── fi.js │ │ │ ├── fr.js │ │ │ ├── hr.js │ │ │ ├── hu.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── ko.js │ │ │ ├── nl.js │ │ │ ├── oc.js │ │ │ ├── pl.js │ │ │ ├── pt_BR.js │ │ │ ├── pt_PT.js │ │ │ ├── ru.js │ │ │ ├── sk.js │ │ │ ├── sv.js │ │ │ ├── tr.js │ │ │ ├── uk.js │ │ │ ├── vi.js │ │ │ ├── zh_CN.js │ │ │ └── zh_TW.js │ │ ├── isso.js │ │ ├── lib │ │ │ ├── identicons.js │ │ │ ├── promise.js │ │ │ └── ready.js │ │ ├── svg.js │ │ ├── svg │ │ │ ├── arrow-down.svg │ │ │ └── arrow-up.svg │ │ ├── template.js │ │ ├── templates │ │ │ ├── comment-loader.js │ │ │ ├── comment.js │ │ │ └── postbox.js │ │ └── utils.js │ ├── count.js │ ├── embed.js │ ├── jest-integration.config.js │ ├── jest-puppeteer.config.js │ ├── jest-unit.config.js │ ├── tests │ │ ├── integration │ │ │ ├── __snapshots__ │ │ │ │ └── puppet.test.js.snap │ │ │ ├── highlight-comments.test.js │ │ │ └── puppet.test.js │ │ ├── mocks │ │ │ └── fileTransformer.js │ │ ├── screenshots │ │ │ ├── compare-hashes.sh │ │ │ ├── reference │ │ │ │ ├── comment.png │ │ │ │ ├── comment.png.hash │ │ │ │ ├── postbox.png │ │ │ │ ├── postbox.png.hash │ │ │ │ ├── thread.png │ │ │ │ └── thread.png.hash │ │ │ └── screenshots.test.js │ │ ├── setup │ │ │ └── global-setup.js │ │ └── unit │ │ │ ├── __snapshots__ │ │ │ ├── comment-loader.test.js.snap │ │ │ ├── comment.test.js.snap │ │ │ ├── postbox.test.js.snap │ │ │ └── template-comment-newlines.test.js.snap │ │ │ ├── comment-loader.test.js │ │ │ ├── comment.test.js │ │ │ ├── config.test.js │ │ │ ├── i18n-overrides.test.js │ │ │ ├── isso.test.js │ │ │ ├── postbox-labels-optional.test.js │ │ │ ├── postbox-reply-notif.test.js │ │ │ ├── postbox.test.js │ │ │ ├── template-comment-newlines.test.js │ │ │ ├── timezone-utc.test.js │ │ │ └── utils.test.js │ └── webpack.config.js ├── migrate.py ├── run.py ├── templates │ ├── admin.html │ ├── disabled.html │ └── login.html ├── tests │ ├── disqus.xml │ ├── fixtures.py │ ├── generic.json │ ├── test_comments.py │ ├── test_config.py │ ├── test_cors.py │ ├── test_db.py │ ├── test_guard.py │ ├── test_html.py │ ├── test_migration.py │ ├── test_utils.py │ ├── test_utils_hash.py │ ├── test_vote.py │ ├── test_wsgi.py │ └── wordpress.xml ├── utils │ ├── __init__.py │ ├── cache.py │ ├── hash.py │ ├── html.py │ ├── http.py │ └── parse.py ├── views │ ├── __init__.py │ └── comments.py └── wsgi.py ├── package-lock.json ├── package.json ├── setup.cfg ├── setup.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | # Files that should not be sent to the docker daemon when building 2 | # Docker will have its own cache of node packages and its own python venv 3 | node_modules/ 4 | .venv/ 5 | # Prevent accidental inclusion: 6 | comments.db 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ## Checklist 3 | - [ ] I am running the latest version. Installing Isso from GitHub from the `master` branch does not fix my issue 4 | - [ ] I have checked the [troubleshooting guide](https://isso-comments.de/docs/guides/troubleshooting/) 5 | - [ ] I have searched the [open issues](https://github.com/isso-comments/isso/issues), but my issue has not already been reported 6 | 7 | ## What is not working? 8 | 9 | ... 10 | 11 | ## How can one reproduce this issue? 12 | 13 | ... 14 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | ## Checklist 5 | - [ ] All new and existing **tests are passing** 6 | - [ ] (If adding features:) I have added tests to cover my changes 7 | - [ ] (If docs changes needed:) I have updated the **documentation** accordingly. 8 | - [ ] I have added an entry to `CHANGES.rst` because this is a user-facing change or an important bugfix 9 | - [ ] I have written **proper commit message(s)** 10 | 13 | 14 | ## What changes does this Pull Request introduce? 15 | 16 | ... 17 | 18 | ## Why is this necessary? 19 | 21 | ... 22 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | tags: 8 | - "*" 9 | pull_request: 10 | 11 | env: 12 | build_platforms: ${{ vars.BUILD_PLATFORMS || 'linux/amd64,linux/arm64/v8' }} 13 | build_image: ${{ vars.BUILD_IMAGE || 'ghcr.io/isso-comments/isso' }} 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | packages: write 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Docker meta 25 | id: meta 26 | uses: docker/metadata-action@v4 27 | with: 28 | flavor: | 29 | latest=false 30 | images: ${{ env.build_image }} 31 | tags: | 32 | type=ref,event=pr 33 | type=semver,pattern={{major}} 34 | type=semver,pattern={{major}}.{{minor}} 35 | type=semver,pattern={{version}} 36 | type=raw,value=latest,enable={{is_default_branch}} 37 | type=raw,value=release,enable=${{ github.ref_type == 'tag' }} 38 | 39 | - name: Login to Github Container Registry 40 | if: github.event_name != 'pull_request' 41 | uses: docker/login-action@v2 42 | with: 43 | registry: ghcr.io 44 | username: ${{ github.actor }} 45 | password: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Set up Docker Buildx 48 | uses: docker/setup-buildx-action@v2 49 | 50 | - name: Build and push 51 | id: docker_build 52 | uses: docker/build-push-action@v4 53 | with: 54 | push: ${{ github.event_name != 'pull_request' }} 55 | tags: ${{ steps.meta.outputs.tags }} 56 | platforms: ${{ env.build_platforms }} 57 | 58 | - name: Image digest 59 | run: echo ${{ steps.docker_build.outputs.digest }} 60 | -------------------------------------------------------------------------------- /.github/workflows/docker-testbed-periodic-rebuild.yaml: -------------------------------------------------------------------------------- 1 | name: Rebuild js testbed image 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # Run weekly, at 2:15pm on Monday (chosen at random) 7 | # https://cron.help/#15_14_*_*_1 8 | - cron: '15 14 * * 1' 9 | 10 | env: 11 | build_platforms: ${{ vars.BUILD_PLATFORMS || 'linux/amd64' }} 12 | build_image: ${{ vars.BUILD_IMAGE || 'ghcr.io/isso-comments/isso-js-testbed' }} 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | packages: write 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Docker meta 24 | id: meta 25 | uses: docker/metadata-action@v4 26 | with: 27 | flavor: | 28 | latest=false 29 | images: ${{ env.build_image }} 30 | tags: | 31 | type=raw,value=latest,enable={{is_default_branch}} 32 | 33 | - name: Login to Github Container Registry 34 | if: github.event_name != 'pull_request' 35 | uses: docker/login-action@v2 36 | with: 37 | registry: ghcr.io 38 | username: ${{ github.actor }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Set up Docker Buildx 42 | uses: docker/setup-buildx-action@v2 43 | 44 | - name: Build docker-js-testbed 45 | run: make docker-testbed 46 | 47 | - name: Push docker-js-testbed image as ${{ env.build_image }} 48 | run: make docker-testbed-push 49 | -------------------------------------------------------------------------------- /.github/workflows/javascript-client.yml: -------------------------------------------------------------------------------- 1 | name: Javascript client 2 | 3 | on: 4 | push: 5 | paths: 6 | - "package.json" 7 | - "isso/js/**" 8 | - ".github/workflows/javascript-client.yml" 9 | pull_request: 10 | paths: 11 | - "package.json" 12 | - "isso/js/**" 13 | - ".github/workflows/javascript-client.yml" 14 | 15 | jobs: 16 | test: 17 | 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | os: [ubuntu-latest] 22 | node-version: [16] 23 | fail-fast: false 24 | 25 | steps: 26 | 27 | - name: Check out repository code 28 | uses: actions/checkout@v3 29 | 30 | - name: Set up NodeJS ${{ matrix.node-version }} on ${{ matrix.os }} 31 | uses: actions/setup-node@v2 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | cache: 'npm' 35 | cache-dependency-path: package.json 36 | 37 | - name: Install Javascript test dependencies using npm 38 | env: 39 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true 40 | run: npm install 41 | 42 | - name: Run Javascript Jest test suite - unit tests 43 | run: npm run test-unit 44 | 45 | build: 46 | 47 | runs-on: ${{ matrix.os }} 48 | strategy: 49 | matrix: 50 | os: [ubuntu-latest] 51 | node-version: [16] 52 | fail-fast: false 53 | 54 | steps: 55 | 56 | - name: Check out repository code 57 | uses: actions/checkout@v3 58 | 59 | - name: Set up NodeJS ${{ matrix.node-version }} on ${{ matrix.os }} 60 | uses: actions/setup-node@v2 61 | with: 62 | node-version: ${{ matrix.node-version }} 63 | cache: 'npm' 64 | cache-dependency-path: package.json 65 | 66 | - name: Install Javascript dependencies using npm 67 | run: make init 68 | 69 | - name: Create Javascript client files 70 | run: make js 71 | 72 | - name: Archive and upload generated minified client files 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: client-js-files 76 | path: | 77 | isso/js/embed.*.js 78 | isso/js/count.*.js 79 | isso/js/embed.dev.js.map 80 | isso/js/count.dev.js.map 81 | if-no-files-found: error 82 | -------------------------------------------------------------------------------- /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: 4 | # Always build installable package, except for docs changes 5 | push: 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest] 15 | # Use only lowest and highest supported python versions for now, 16 | # to speed up CI runs 17 | python-version: [3.8, "3.12"] 18 | node-version: [16] 19 | fail-fast: false 20 | 21 | steps: 22 | 23 | - name: Check out repository code 24 | uses: actions/checkout@v3 25 | 26 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} 27 | uses: actions/setup-python@v4 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | cache: 'pip' 31 | # Dependencies are in setup.py, so use it as cache key 32 | cache-dependency-path: 'setup.py' 33 | 34 | - name: Set up NodeJS ${{ matrix.node-version }} on ${{ matrix.os }} 35 | uses: actions/setup-node@v2 36 | with: 37 | node-version: ${{ matrix.node-version }} 38 | cache: 'npm' 39 | cache-dependency-path: package.json 40 | 41 | - name: Install Javascript dependencies using npm 42 | run: make init 43 | 44 | - name: Create Javascript artifacts (but skip manpages) 45 | run: make js 46 | 47 | - name: Generate Isso package 48 | id: generate-package 49 | run: | 50 | pip install setuptools 51 | python setup.py sdist 52 | echo "::set-output name=package_file::$(ls dist/)" 53 | 54 | - name: Install generated Isso package 55 | run: pip install dist/${{ steps.generate-package.outputs.package_file }} 56 | 57 | - name: Archive and upload generated package 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: ${{ matrix.os }}-python-${{ matrix.python-version }}-${{ steps.generate-package.outputs.package_file }} 61 | path: dist/${{ steps.generate-package.outputs.package_file }} 62 | 63 | - name: Clean up Isso package from site-packages 64 | # This ensures it isn't accidentally restored by the "setup-python" 65 | # action 66 | run: pip uninstall --y isso 67 | -------------------------------------------------------------------------------- /.github/workflows/python-tests.yml: -------------------------------------------------------------------------------- 1 | name: Python tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, macos-latest] 14 | python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] 15 | fail-fast: false 16 | 17 | steps: 18 | 19 | - name: Check out repository code 20 | uses: actions/checkout@v3 21 | 22 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} 23 | uses: actions/setup-python@v4 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | cache: 'pip' 27 | # Dependencies are in setup.py, so use it as cache key 28 | cache-dependency-path: 'setup.py' 29 | 30 | - name: Install isso dependencies 31 | # Use pip instead of python setup.py develop to get caching from 32 | # "setup-python" action 33 | run: pip install -e . 34 | 35 | - name: Install test suite dependencies 36 | run: pip install pytest pytest-cov 37 | 38 | - name: Run test suite 39 | run: make test 40 | env: 41 | PYTHONHASHSEED: random 42 | 43 | lint: 44 | 45 | runs-on: ${{ matrix.os }} 46 | strategy: 47 | matrix: 48 | # Use only single build env for linting 49 | os: [ubuntu-latest] 50 | python-version: ["3.10"] 51 | fail-fast: false 52 | 53 | steps: 54 | 55 | - name: Check out repository code 56 | uses: actions/checkout@v3 57 | 58 | - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} 59 | uses: actions/setup-python@v4 60 | with: 61 | python-version: ${{ matrix.python-version }} 62 | cache: 'pip' 63 | # Dependencies are in setup.py, so use it as cache key 64 | cache-dependency-path: 'setup.py' 65 | 66 | - name: Install isso dependencies 67 | # Use pip instead of python setup.py develop to get caching from 68 | # "setup-python" action 69 | run: pip install -e . 70 | 71 | - name: Install test suite dependencies 72 | run: pip install pytest pytest-cov 73 | 74 | - name: Install style check dependencies 75 | run: pip install flake8 76 | 77 | - name: Run style check 78 | run: make flakes 79 | 80 | - name: Run coverage check, fail if <70% 81 | run: | 82 | make coverage 83 | coverage report --fail-under 70 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | *~ 7 | 8 | /isso.egg-info/ 9 | /isso/js/components 10 | /isso/js/embed.min.js 11 | /isso/js/embed.dev.js 12 | /isso/js/count.min.js 13 | /isso/js/count.dev.js 14 | /isso/js/**/*.map 15 | 16 | # apiDoc generated output 17 | apidoc/_output/ 18 | 19 | # Javascript ecosystem 20 | node_modules/ 21 | 22 | # Database 23 | comments.db 24 | 25 | # github/gitignore 26 | # Byte-compiled / optimized / DLL files 27 | __pycache__/ 28 | *.py[cod] 29 | *$py.class 30 | 31 | # C extensions 32 | *.so 33 | 34 | # Distribution / packaging 35 | .Python 36 | build/ 37 | dist/ 38 | *.egg-info/ 39 | .installed.cfg 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | pip-selfcheck.json 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # pyenv 60 | .python-version 61 | 62 | # dotenv 63 | .env 64 | 65 | # virtualenv 66 | .venv/ 67 | venv/ 68 | ENV/ 69 | 70 | # Development tools 71 | .vagrant 72 | .eggs 73 | 74 | # Tox 75 | .tox 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | See `Docs: Contribute `_. 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2022 Martin Zimmermann. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include isso/js/embed.min.js 2 | include isso/js/embed.dev.js 3 | include isso/js/embed.dev.js.map 4 | include isso/js/count.min.js 5 | include isso/js/count.dev.js 6 | include isso/js/count.dev.js.map 7 | include isso/js/admin.js 8 | 9 | include isso/isso.cfg 10 | 11 | include isso/templates/admin.html 12 | include isso/templates/disabled.html 13 | include isso/templates/login.html 14 | 15 | include isso/css/admin.css 16 | include isso/css/isso.css 17 | 18 | include isso/img/isso.svg 19 | -------------------------------------------------------------------------------- /apidoc/apidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Isso API", 3 | "title": "Isso API", 4 | "description": "", 5 | "version": "0.13.0", 6 | "order": ["Thread", "Comment", "Demo", "Admin", 7 | "feed", "counts", "count", "config", "fetch"], 8 | "url" : "https://comments.example.com", 9 | "header": { 10 | "title": "Introduction", 11 | "filename": "header.md" 12 | }, 13 | "footer": { 14 | "title": "Help", 15 | "filename": "footer.md" 16 | }, 17 | "template": { 18 | "withCompare": "true" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apidoc/footer.md: -------------------------------------------------------------------------------- 1 | # Help 2 | 3 | To generate this documentation: 4 | 5 | 1. Install `Node.js` and `npm` 6 | 2. Run: 7 | ```console 8 | git clone https://github.com/isso-comments/isso && cd isso 9 | make apidoc-init apidoc 10 | ``` 11 | 3. View API documentation in browser at `./apidoc/_output/index.html`: 12 | ```console 13 | xdg-open apidoc/_output/index.html 14 | ``` 15 | -------------------------------------------------------------------------------- /apidoc/header.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This is the API documentation of the Isso commenting system. 4 | **[Click here](/docs/)** to get back to the regular **Isso documentation**. 5 | 6 | ### What can you do? 7 | 8 | - Fetch comment threads 9 | - Post, edit and delete comments 10 | - Get information about the server 11 | - Like and dislike comments 12 | - ...and much more! 13 | 14 | ### Technical details 15 | 16 | These API docs are automatically generated by [apiDoc][apidoc] from 17 | [isso/views/comments.py](https://github.com/isso-comments/isso/blob/master/isso/views/comments.py). 18 | 19 | You can select previous versions from a dropdown on the upper right of the 20 | page. 21 | 22 | For more information about Isso, visit **[isso-comments.de](https://isso-comments.de)** 23 | or check out the source at **[GitHub](https://github.com/isso-comments/isso)**. 24 | 25 | [apiDoc]: https://apidocjs.com/ 26 | 27 | ### Conventions 28 | 29 | This documenation uses `https://comments.example.com` as the base URL for Isso's API. 30 | 31 | For your installation, this URL will be different. Make sure to substitute the 32 | example URL for the URL you load your `embed.min.js` from. 33 | 34 | E.g. `https://example.com/isso/js/embed.min.js` would result in a base URL of 35 | `https://example.com/isso`. 36 | -------------------------------------------------------------------------------- /contrib/isso-dev.cfg: -------------------------------------------------------------------------------- 1 | # Isso configuration file 2 | # vim: set filetype=dosini 3 | 4 | # These are the settings used to run Isso inside of the vagrant-powered developer setup. 5 | 6 | [general] 7 | 8 | # Change dbpath to /db/comments.db if running in docker! 9 | dbpath = comments.db 10 | host = 11 | http://isso-dev.local/ 12 | http://localhost:8000/ 13 | http://localhost:8080/ 14 | http://localhost:5000/ 15 | name = dev 16 | max-age = 15m 17 | notify = stdout 18 | reply-notifications = false 19 | log-file = 20 | latest-enabled = true 21 | 22 | [admin] 23 | enabled = true 24 | password = strong_default_password_for_isso_admin 25 | 26 | [moderation] 27 | enabled = false 28 | purge-after = 30d 29 | 30 | [server] 31 | reload = false 32 | profile = false 33 | 34 | [guard] 35 | enabled = false 36 | reply-to-self = true 37 | 38 | [markup] 39 | options = autolink, fenced-code, no-intra-emphasis, strikethrough, superscript 40 | flags = 41 | allowed-elements = 42 | allowed-attributes = 43 | 44 | [hash] 45 | salt = Eech7co8Ohloopo9Ol6baimi 46 | algorithm = pbkdf2 47 | 48 | [rss] 49 | base = http://isso-dev.local 50 | -------------------------------------------------------------------------------- /contrib/isso.sample.cfg: -------------------------------------------------------------------------------- 1 | # Isso example configuration file 2 | # vim: set filetype=dosini 3 | 4 | [general] 5 | 6 | # Change dbpath to /db/comments.db if running in docker! 7 | dbpath = comments.db 8 | host = 9 | http://localhost:80/ 10 | # public-endpoint = 11 | -------------------------------------------------------------------------------- /contrib/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user root; 2 | worker_processes 4; 3 | worker_rlimit_nofile 8192; 4 | 5 | error_log /var/log/nginx/error.log warn; 6 | pid /run/nginx.pid; 7 | 8 | events { 9 | worker_connections 2014; 10 | multi_accept on; 11 | use epoll; 12 | } 13 | 14 | http { 15 | include /etc/nginx/mime.types; 16 | default_type application/octet-stream; 17 | 18 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 19 | '$status $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | 22 | log_format timed '$remote_addr - $remote_user [$time_local] "$request" ' 23 | '$status $body_bytes_sent "$http_referer" ' 24 | '"$http_user_agent" "$http_x_forwarded_for" ' 25 | '$request_time $upstream_response_time $upstream_addr ' 26 | ' $upstream_status $upstream_cache_status $pipe'; 27 | 28 | access_log /var/log/nginx/access.log timed; 29 | 30 | sendfile on; 31 | tcp_nopush on; 32 | 33 | keepalive_timeout 30; 34 | 35 | gzip on; 36 | 37 | include /etc/nginx/conf.d/*.conf; 38 | include /etc/nginx/sites-enabled/*; 39 | } 40 | -------------------------------------------------------------------------------- /contrib/nginx/nginx.vhost.conf: -------------------------------------------------------------------------------- 1 | server { 2 | client_max_body_size 20M; 3 | listen 80 default_server; 4 | server_name isso-dev.local; 5 | 6 | root /vagrant/isso/demo; 7 | 8 | location / { 9 | # uwsgi_pass unix:///run/uwsgi/app/isso/socket; 10 | uwsgi_pass 127.0.0.1:8080; 11 | include uwsgi_params; 12 | } 13 | } -------------------------------------------------------------------------------- /contrib/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | http = :8080 3 | master = true 4 | processes = 4 5 | cache2 = name=hash,items=10240,blocksize=32 6 | spooler = %d/mail 7 | module = isso.run 8 | virtualenv = %d 9 | env = ISSO_SETTINGS=%d/sample.cfg 10 | -------------------------------------------------------------------------------- /disperse.conf: -------------------------------------------------------------------------------- 1 | # See https://github.com/jelmer/disperse 2 | timeout_days: 5 3 | tag_name: "$VERSION" 4 | news_file: "CHANGES.rst" 5 | pre_dist_command: "make init all && pip3 install .[doc]" 6 | verify_command: "make test" 7 | update_version { 8 | path: "setup.py" 9 | match: "^ version='.*',$" 10 | new_line: " version='$VERSION'," 11 | } 12 | -------------------------------------------------------------------------------- /docker/Dockerfile-js-testbed: -------------------------------------------------------------------------------- 1 | # Prepare a testbed for Javascript testing 2 | # This image should be tagged as "isso-js-testbed" 3 | 4 | # Note: Do not use alpine images as they do not contain needed GObject, X11 5 | # etc. packages and complicate things 6 | # :22-bookworm resolves to NodeJS 22 on Debian Bookworm as of 04/2024 7 | # https://hub.docker.com/_/node 8 | FROM docker.io/node:22-bookworm AS isso-js-testbed 9 | WORKDIR /src/ 10 | 11 | # Install everything necessary to run headless 12 | RUN apt-get update && apt-get install -y --no-install-recommends libnss3 libxss1 chromium 13 | 14 | # Installing puppeteer will take some time as it pulls in 15 | # a ~400Mb headless chrome file 16 | RUN npm install puppeteer 17 | 18 | # Skip downloading headless chromium again for future steps 19 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true 20 | 21 | RUN npm install --no-save jest jest-puppeteer jest-environment-jsdom 22 | 23 | # Need to set $CI so that jest-puppeteer applies sensible launch params for 24 | # running headless chromium. Otherwise, chromium will fail with a "missing 25 | # sandbox" error. 26 | # https://github.com/smooth-code/jest-puppeteer/blob/678ce56b49100f248237757df19f89b2542a9465/packages/jest-environment-puppeteer/src/readConfig.js#L14-L28 27 | ENV CI=true 28 | 29 | # Note: No entry point, will be set by docker-compose 30 | 31 | # Example of use: 32 | # 33 | # docker build -f docker/Dockerfile-js-testbed -t isso-js-testbed . 34 | 35 | # vim: set filetype=dockerfile 36 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/_bourbon-deprecated-upcoming.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // These mixins/functions are deprecated 3 | // They will be removed in the next MAJOR version release 4 | //************************************************************************// 5 | @mixin box-shadow ($shadows...) { 6 | @include prefixer(box-shadow, $shadows, spec); 7 | @warn "box-shadow is deprecated and will be removed in the next major version release"; 8 | } 9 | 10 | @mixin background-size ($lengths...) { 11 | @include prefixer(background-size, $lengths, spec); 12 | @warn "background-size is deprecated and will be removed in the next major version release"; 13 | } 14 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/_bourbon.scss: -------------------------------------------------------------------------------- 1 | // Custom Helpers 2 | @import "helpers/deprecated-webkit-gradient"; 3 | @import "helpers/gradient-positions-parser"; 4 | @import "helpers/linear-positions-parser"; 5 | @import "helpers/radial-arg-parser"; 6 | @import "helpers/radial-positions-parser"; 7 | @import "helpers/render-gradients"; 8 | @import "helpers/shape-size-stripper"; 9 | 10 | // Custom Functions 11 | @import "functions/compact"; 12 | @import "functions/flex-grid"; 13 | @import "functions/grid-width"; 14 | @import "functions/linear-gradient"; 15 | @import "functions/modular-scale"; 16 | @import "functions/px-to-em"; 17 | @import "functions/radial-gradient"; 18 | @import "functions/tint-shade"; 19 | @import "functions/transition-property-name"; 20 | 21 | // CSS3 Mixins 22 | @import "css3/animation"; 23 | @import "css3/appearance"; 24 | @import "css3/backface-visibility"; 25 | @import "css3/background"; 26 | @import "css3/background-image"; 27 | @import "css3/border-image"; 28 | @import "css3/border-radius"; 29 | @import "css3/box-sizing"; 30 | @import "css3/columns"; 31 | @import "css3/flex-box"; 32 | @import "css3/font-face"; 33 | @import "css3/hidpi-media-query"; 34 | @import "css3/image-rendering"; 35 | @import "css3/inline-block"; 36 | @import "css3/keyframes"; 37 | @import "css3/linear-gradient"; 38 | @import "css3/perspective"; 39 | @import "css3/radial-gradient"; 40 | @import "css3/transform"; 41 | @import "css3/transition"; 42 | @import "css3/user-select"; 43 | @import "css3/placeholder"; 44 | 45 | // Addons & other mixins 46 | @import "addons/button"; 47 | @import "addons/clearfix"; 48 | @import "addons/font-family"; 49 | @import "addons/hide-text"; 50 | @import "addons/html5-input-types"; 51 | @import "addons/position"; 52 | @import "addons/prefixer"; 53 | @import "addons/retina-image"; 54 | @import "addons/size"; 55 | @import "addons/timing-functions"; 56 | @import "addons/triangle"; 57 | 58 | // Soon to be deprecated Mixins 59 | @import "bourbon-deprecated-upcoming"; 60 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // Micro clearfix provides an easy way to contain floats without adding additional markup 2 | // 3 | // Example usage: 4 | // 5 | // // Contain all floats within .wrapper 6 | // .wrapper { 7 | // @include clearfix; 8 | // .content, 9 | // .sidebar { 10 | // float : left; 11 | // } 12 | // } 13 | 14 | @mixin clearfix { 15 | *zoom: 1; 16 | 17 | &:before, 18 | &:after { 19 | content: " "; 20 | display: table; 21 | } 22 | 23 | &:after { 24 | clear: both; 25 | } 26 | } 27 | 28 | // Acknowledgements 29 | // Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/) 30 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_font-family.scss: -------------------------------------------------------------------------------- 1 | $georgia: Georgia, Cambria, "Times New Roman", Times, serif; 2 | $helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | $lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; 4 | $monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; 5 | $verdana: Verdana, Geneva, sans-serif; 6 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_hide-text.scss: -------------------------------------------------------------------------------- 1 | @mixin hide-text { 2 | color: transparent; 3 | font: 0/0 a; 4 | text-shadow: none; 5 | } 6 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_html5-input-types.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Generate a variable ($all-text-inputs) with a list of all html5 3 | // input types that have a text-based input, excluding textarea. 4 | // http://diveintohtml5.org/forms.html 5 | //************************************************************************// 6 | $inputs-list: 'input[type="email"]', 7 | 'input[type="number"]', 8 | 'input[type="password"]', 9 | 'input[type="search"]', 10 | 'input[type="tel"]', 11 | 'input[type="text"]', 12 | 'input[type="url"]', 13 | 14 | // Webkit & Gecko may change the display of these in the future 15 | 'input[type="color"]', 16 | 'input[type="date"]', 17 | 'input[type="datetime"]', 18 | 'input[type="datetime-local"]', 19 | 'input[type="month"]', 20 | 'input[type="time"]', 21 | 'input[type="week"]'; 22 | 23 | $unquoted-inputs-list: (); 24 | @each $input-type in $inputs-list { 25 | $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma); 26 | } 27 | 28 | $all-text-inputs: $unquoted-inputs-list; 29 | 30 | 31 | // Hover Pseudo-class 32 | //************************************************************************// 33 | $all-text-inputs-hover: (); 34 | @each $input-type in $unquoted-inputs-list { 35 | $input-type-hover: $input-type + ":hover"; 36 | $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma); 37 | } 38 | 39 | // Focus Pseudo-class 40 | //************************************************************************// 41 | $all-text-inputs-focus: (); 42 | @each $input-type in $unquoted-inputs-list { 43 | $input-type-focus: $input-type + ":focus"; 44 | $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma); 45 | } 46 | 47 | // You must use interpolation on the variable: 48 | // #{$all-text-inputs} 49 | // #{$all-text-inputs-hover} 50 | // #{$all-text-inputs-focus} 51 | 52 | // Example 53 | //************************************************************************// 54 | // #{$all-text-inputs}, textarea { 55 | // border: 1px solid red; 56 | // } 57 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_position.scss: -------------------------------------------------------------------------------- 1 | @mixin position ($position: relative, $coordinates: 0 0 0 0) { 2 | 3 | @if type-of($position) == list { 4 | $coordinates: $position; 5 | $position: relative; 6 | } 7 | 8 | $top: nth($coordinates, 1); 9 | $right: nth($coordinates, 2); 10 | $bottom: nth($coordinates, 3); 11 | $left: nth($coordinates, 4); 12 | 13 | position: $position; 14 | 15 | @if $top == auto { 16 | top: $top; 17 | } 18 | @else if not(unitless($top)) { 19 | top: $top; 20 | } 21 | 22 | @if $right == auto { 23 | right: $right; 24 | } 25 | @else if not(unitless($right)) { 26 | right: $right; 27 | } 28 | 29 | @if $bottom == auto { 30 | bottom: $bottom; 31 | } 32 | @else if not(unitless($bottom)) { 33 | bottom: $bottom; 34 | } 35 | 36 | @if $left == auto { 37 | left: $left; 38 | } 39 | @else if not(unitless($left)) { 40 | left: $left; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_prefixer.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Example: @include prefixer(border-radius, $radii, webkit ms spec); 3 | //************************************************************************// 4 | $prefix-for-webkit: true !default; 5 | $prefix-for-mozilla: true !default; 6 | $prefix-for-microsoft: true !default; 7 | $prefix-for-opera: true !default; 8 | $prefix-for-spec: true !default; // required for keyframe mixin 9 | 10 | @mixin prefixer ($property, $value, $prefixes) { 11 | @each $prefix in $prefixes { 12 | @if $prefix == webkit { 13 | @if $prefix-for-webkit { 14 | -webkit-#{$property}: $value; 15 | } 16 | } 17 | @else if $prefix == moz { 18 | @if $prefix-for-mozilla { 19 | -moz-#{$property}: $value; 20 | } 21 | } 22 | @else if $prefix == ms { 23 | @if $prefix-for-microsoft { 24 | -ms-#{$property}: $value; 25 | } 26 | } 27 | @else if $prefix == o { 28 | @if $prefix-for-opera { 29 | -o-#{$property}: $value; 30 | } 31 | } 32 | @else if $prefix == spec { 33 | @if $prefix-for-spec { 34 | #{$property}: $value; 35 | } 36 | } 37 | @else { 38 | @warn "Unrecognized prefix: #{$prefix}"; 39 | } 40 | } 41 | } 42 | 43 | @mixin disable-prefix-for-all() { 44 | $prefix-for-webkit: false; 45 | $prefix-for-mozilla: false; 46 | $prefix-for-microsoft: false; 47 | $prefix-for-opera: false; 48 | $prefix-for-spec: false; 49 | } 50 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_retina-image.scss: -------------------------------------------------------------------------------- 1 | @mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $asset-pipeline: false) { 2 | @if $asset-pipeline { 3 | background-image: image-url("#{$filename}.#{$extension}"); 4 | } 5 | @else { 6 | background-image: url("#{$filename}.#{$extension}"); 7 | } 8 | 9 | @include hidpi { 10 | 11 | @if $asset-pipeline { 12 | @if $retina-filename { 13 | background-image: image-url("#{$retina-filename}.#{$extension}"); 14 | } 15 | @else { 16 | background-image: image-url("#{$filename}@2x.#{$extension}"); 17 | } 18 | } 19 | 20 | @else { 21 | @if $retina-filename { 22 | background-image: url("#{$retina-filename}.#{$extension}"); 23 | } 24 | @else { 25 | background-image: url("#{$filename}@2x.#{$extension}"); 26 | } 27 | } 28 | 29 | background-size: $background-size; 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_size.scss: -------------------------------------------------------------------------------- 1 | @mixin size($size) { 2 | @if length($size) == 1 { 3 | @if $size == auto { 4 | width: $size; 5 | height: $size; 6 | } 7 | 8 | @else if unitless($size) { 9 | width: $size + px; 10 | height: $size + px; 11 | } 12 | 13 | @else if not(unitless($size)) { 14 | width: $size; 15 | height: $size; 16 | } 17 | } 18 | 19 | // Width x Height 20 | @if length($size) == 2 { 21 | $width: nth($size, 1); 22 | $height: nth($size, 2); 23 | 24 | @if $width == auto { 25 | width: $width; 26 | } 27 | @else if not(unitless($width)) { 28 | width: $width; 29 | } 30 | @else if unitless($width) { 31 | width: $width + px; 32 | } 33 | 34 | @if $height == auto { 35 | height: $height; 36 | } 37 | @else if not(unitless($height)) { 38 | height: $height; 39 | } 40 | @else if unitless($height) { 41 | height: $height + px; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_timing-functions.scss: -------------------------------------------------------------------------------- 1 | // CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) 2 | // Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html 3 | 4 | // EASE IN 5 | $ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); 6 | $ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); 7 | $ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); 8 | $ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); 9 | $ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); 10 | $ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); 11 | $ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); 12 | $ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | 14 | // EASE OUT 15 | $ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); 16 | $ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); 17 | $ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); 18 | $ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); 19 | $ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); 20 | $ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); 21 | $ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); 22 | $ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); 23 | 24 | // EASE IN OUT 25 | $ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); 26 | $ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); 27 | $ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); 28 | $ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); 29 | $ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); 30 | $ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); 31 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); 32 | $ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); 33 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/addons/_triangle.scss: -------------------------------------------------------------------------------- 1 | @mixin triangle ($size, $color, $direction) { 2 | height: 0; 3 | width: 0; 4 | 5 | @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { 6 | border-color: transparent; 7 | border-style: solid; 8 | border-width: $size / 2; 9 | 10 | @if $direction == up { 11 | border-bottom-color: $color; 12 | 13 | } @else if $direction == right { 14 | border-left-color: $color; 15 | 16 | } @else if $direction == down { 17 | border-top-color: $color; 18 | 19 | } @else if $direction == left { 20 | border-right-color: $color; 21 | } 22 | } 23 | 24 | @else if ($direction == up-right) or ($direction == up-left) { 25 | border-top: $size solid $color; 26 | 27 | @if $direction == up-right { 28 | border-left: $size solid transparent; 29 | 30 | } @else if $direction == up-left { 31 | border-right: $size solid transparent; 32 | } 33 | } 34 | 35 | @else if ($direction == down-right) or ($direction == down-left) { 36 | border-bottom: $size solid $color; 37 | 38 | @if $direction == down-right { 39 | border-left: $size solid transparent; 40 | 41 | } @else if $direction == down-left { 42 | border-right: $size solid transparent; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_animation.scss: -------------------------------------------------------------------------------- 1 | // http://www.w3.org/TR/css3-animations/#the-animation-name-property- 2 | // 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. 3 | 4 | // Official animation shorthand property. 5 | @mixin animation ($animations...) { 6 | @include prefixer(animation, $animations, webkit moz spec); 7 | } 8 | 9 | // Individual Animation Properties 10 | @mixin animation-name ($names...) { 11 | @include prefixer(animation-name, $names, webkit moz spec); 12 | } 13 | 14 | 15 | @mixin animation-duration ($times...) { 16 | @include prefixer(animation-duration, $times, webkit moz spec); 17 | } 18 | 19 | 20 | @mixin animation-timing-function ($motions...) { 21 | // ease | linear | ease-in | ease-out | ease-in-out 22 | @include prefixer(animation-timing-function, $motions, webkit moz spec); 23 | } 24 | 25 | 26 | @mixin animation-iteration-count ($values...) { 27 | // infinite | 28 | @include prefixer(animation-iteration-count, $values, webkit moz spec); 29 | } 30 | 31 | 32 | @mixin animation-direction ($directions...) { 33 | // normal | alternate 34 | @include prefixer(animation-direction, $directions, webkit moz spec); 35 | } 36 | 37 | 38 | @mixin animation-play-state ($states...) { 39 | // running | paused 40 | @include prefixer(animation-play-state, $states, webkit moz spec); 41 | } 42 | 43 | 44 | @mixin animation-delay ($times...) { 45 | @include prefixer(animation-delay, $times, webkit moz spec); 46 | } 47 | 48 | 49 | @mixin animation-fill-mode ($modes...) { 50 | // none | forwards | backwards | both 51 | @include prefixer(animation-fill-mode, $modes, webkit moz spec); 52 | } 53 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_appearance.scss: -------------------------------------------------------------------------------- 1 | @mixin appearance ($value) { 2 | @include prefixer(appearance, $value, webkit moz ms o spec); 3 | } 4 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_backface-visibility.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Backface-visibility mixin 3 | //************************************************************************// 4 | @mixin backface-visibility($visibility) { 5 | @include prefixer(backface-visibility, $visibility, webkit spec); 6 | } 7 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_background-image.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Background-image property for adding multiple background images with 3 | // gradients, or for stringing multiple gradients together. 4 | //************************************************************************// 5 | 6 | @mixin background-image($images...) { 7 | background-image: _add-prefix($images, webkit); 8 | background-image: _add-prefix($images); 9 | } 10 | 11 | @function _add-prefix($images, $vendor: false) { 12 | $images-prefixed: (); 13 | $gradient-positions: false; 14 | @for $i from 1 through length($images) { 15 | $type: type-of(nth($images, $i)); // Get type of variable - List or String 16 | 17 | // If variable is a list - Gradient 18 | @if $type == list { 19 | $gradient-type: nth(nth($images, $i), 1); // linear or radial 20 | $gradient-pos: null; 21 | $gradient-args: null; 22 | 23 | @if ($gradient-type == linear) or ($gradient-type == radial) { 24 | $gradient-pos: nth(nth($images, $i), 2); // Get gradient position 25 | $gradient-args: nth(nth($images, $i), 3); // Get actual gradient (red, blue) 26 | } 27 | @else { 28 | $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue) 29 | } 30 | 31 | $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos); 32 | $gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor); 33 | $images-prefixed: append($images-prefixed, $gradient, comma); 34 | } 35 | // If variable is a string - Image 36 | @else if $type == string { 37 | $images-prefixed: join($images-prefixed, nth($images, $i), comma); 38 | } 39 | } 40 | @return $images-prefixed; 41 | } 42 | 43 | //Examples: 44 | //@include background-image(linear-gradient(top, orange, red)); 45 | //@include background-image(radial-gradient(50% 50%, cover circle, orange, red)); 46 | //@include background-image(url("/images/a.png"), linear-gradient(orange, red)); 47 | //@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png")); 48 | //@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)); 49 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_border-radius.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Shorthand Border-radius mixins 3 | //************************************************************************// 4 | @mixin border-top-radius($radii) { 5 | @include prefixer(border-top-left-radius, $radii, spec); 6 | @include prefixer(border-top-right-radius, $radii, spec); 7 | } 8 | 9 | @mixin border-bottom-radius($radii) { 10 | @include prefixer(border-bottom-left-radius, $radii, spec); 11 | @include prefixer(border-bottom-right-radius, $radii, spec); 12 | } 13 | 14 | @mixin border-left-radius($radii) { 15 | @include prefixer(border-top-left-radius, $radii, spec); 16 | @include prefixer(border-bottom-left-radius, $radii, spec); 17 | } 18 | 19 | @mixin border-right-radius($radii) { 20 | @include prefixer(border-top-right-radius, $radii, spec); 21 | @include prefixer(border-bottom-right-radius, $radii, spec); 22 | } 23 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_box-sizing.scss: -------------------------------------------------------------------------------- 1 | @mixin box-sizing ($box) { 2 | // content-box | border-box | inherit 3 | @include prefixer(box-sizing, $box, webkit moz spec); 4 | } 5 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_columns.scss: -------------------------------------------------------------------------------- 1 | @mixin columns($arg: auto) { 2 | // || 3 | @include prefixer(columns, $arg, webkit moz spec); 4 | } 5 | 6 | @mixin column-count($int: auto) { 7 | // auto || integer 8 | @include prefixer(column-count, $int, webkit moz spec); 9 | } 10 | 11 | @mixin column-gap($length: normal) { 12 | // normal || length 13 | @include prefixer(column-gap, $length, webkit moz spec); 14 | } 15 | 16 | @mixin column-fill($arg: auto) { 17 | // auto || length 18 | @include prefixer(columns-fill, $arg, webkit moz spec); 19 | } 20 | 21 | @mixin column-rule($arg) { 22 | // || || 23 | @include prefixer(column-rule, $arg, webkit moz spec); 24 | } 25 | 26 | @mixin column-rule-color($color) { 27 | @include prefixer(column-rule-color, $color, webkit moz spec); 28 | } 29 | 30 | @mixin column-rule-style($style: none) { 31 | // none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid 32 | @include prefixer(column-rule-style, $style, webkit moz spec); 33 | } 34 | 35 | @mixin column-rule-width ($width: none) { 36 | @include prefixer(column-rule-width, $width, webkit moz spec); 37 | } 38 | 39 | @mixin column-span($arg: none) { 40 | // none || all 41 | @include prefixer(column-span, $arg, webkit moz spec); 42 | } 43 | 44 | @mixin column-width($length: auto) { 45 | // auto || length 46 | @include prefixer(column-width, $length, webkit moz spec); 47 | } 48 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_flex-box.scss: -------------------------------------------------------------------------------- 1 | // CSS3 Flexible Box Model and property defaults 2 | 3 | // Custom shorthand notation for flexbox 4 | @mixin box($orient: inline-axis, $pack: start, $align: stretch) { 5 | @include display-box; 6 | @include box-orient($orient); 7 | @include box-pack($pack); 8 | @include box-align($align); 9 | } 10 | 11 | @mixin display-box { 12 | display: -webkit-box; 13 | display: -moz-box; 14 | display: box; 15 | } 16 | 17 | @mixin box-orient($orient: inline-axis) { 18 | // horizontal|vertical|inline-axis|block-axis|inherit 19 | @include prefixer(box-orient, $orient, webkit moz spec); 20 | } 21 | 22 | @mixin box-pack($pack: start) { 23 | // start|end|center|justify 24 | @include prefixer(box-pack, $pack, webkit moz spec); 25 | } 26 | 27 | @mixin box-align($align: stretch) { 28 | // start|end|center|baseline|stretch 29 | @include prefixer(box-align, $align, webkit moz spec); 30 | } 31 | 32 | @mixin box-direction($direction: normal) { 33 | // normal|reverse|inherit 34 | @include prefixer(box-direction, $direction, webkit moz spec); 35 | } 36 | 37 | @mixin box-lines($lines: single) { 38 | // single|multiple 39 | @include prefixer(box-lines, $lines, webkit moz spec); 40 | } 41 | 42 | @mixin box-ordinal-group($int: 1) { 43 | @include prefixer(box-ordinal-group, $int, webkit moz spec); 44 | } 45 | 46 | @mixin box-flex($value: 0.0) { 47 | @include prefixer(box-flex, $value, webkit moz spec); 48 | } 49 | 50 | @mixin box-flex-group($int: 1) { 51 | @include prefixer(box-flex-group, $int, webkit moz spec); 52 | } 53 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_font-face.scss: -------------------------------------------------------------------------------- 1 | // Order of the includes matters, and it is: normal, bold, italic, bold+italic. 2 | 3 | @mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) { 4 | @font-face { 5 | font-family: $font-family; 6 | font-weight: $weight; 7 | font-style: $style; 8 | 9 | @if $asset-pipeline == true { 10 | src: font-url('#{$file-path}.eot'); 11 | src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'), 12 | font-url('#{$file-path}.woff') format('woff'), 13 | font-url('#{$file-path}.ttf') format('truetype'), 14 | font-url('#{$file-path}.svg##{$font-family}') format('svg'); 15 | } @else { 16 | src: url('#{$file-path}.eot'); 17 | src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'), 18 | url('#{$file-path}.woff') format('woff'), 19 | url('#{$file-path}.ttf') format('truetype'), 20 | url('#{$file-path}.svg##{$font-family}') format('svg'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_hidpi-media-query.scss: -------------------------------------------------------------------------------- 1 | // HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/) 2 | @mixin hidpi($ratio: 1.3) { 3 | @media only screen and (-webkit-min-device-pixel-ratio: $ratio), 4 | only screen and (min--moz-device-pixel-ratio: $ratio), 5 | only screen and (-o-min-device-pixel-ratio: #{$ratio}/1), 6 | only screen and (min-resolution: #{round($ratio*96)}dpi), 7 | only screen and (min-resolution: #{$ratio}dppx) { 8 | @content; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_image-rendering.scss: -------------------------------------------------------------------------------- 1 | @mixin image-rendering ($mode:optimizeQuality) { 2 | 3 | @if ($mode == optimize-contrast) { 4 | image-rendering: -moz-crisp-edges; 5 | image-rendering: -o-crisp-edges; 6 | image-rendering: -webkit-optimize-contrast; 7 | image-rendering: optimize-contrast; 8 | } 9 | 10 | @else { 11 | image-rendering: $mode; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_inline-block.scss: -------------------------------------------------------------------------------- 1 | // Legacy support for inline-block in IE7 (maybe IE6) 2 | @mixin inline-block { 3 | display: inline-block; 4 | vertical-align: baseline; 5 | zoom: 1; 6 | *display: inline; 7 | *vertical-align: auto; 8 | } 9 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_keyframes.scss: -------------------------------------------------------------------------------- 1 | // Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content 2 | @mixin keyframes($name) { 3 | $original-prefix-for-webkit: $prefix-for-webkit; 4 | $original-prefix-for-mozilla: $prefix-for-mozilla; 5 | $original-prefix-for-microsoft: $prefix-for-microsoft; 6 | $original-prefix-for-opera: $prefix-for-opera; 7 | $original-prefix-for-spec: $prefix-for-spec; 8 | 9 | @if $original-prefix-for-webkit { 10 | @include disable-prefix-for-all(); 11 | $prefix-for-webkit: true; 12 | @-webkit-keyframes #{$name} { 13 | @content; 14 | } 15 | } 16 | @if $original-prefix-for-mozilla { 17 | @include disable-prefix-for-all(); 18 | $prefix-for-mozilla: true; 19 | @-moz-keyframes #{$name} { 20 | @content; 21 | } 22 | } 23 | @if $original-prefix-for-opera { 24 | @include disable-prefix-for-all(); 25 | $prefix-for-opera: true; 26 | @-o-keyframes #{$name} { 27 | @content; 28 | } 29 | } 30 | @if $original-prefix-for-spec { 31 | @include disable-prefix-for-all(); 32 | $prefix-for-spec: true; 33 | @keyframes #{$name} { 34 | @content; 35 | } 36 | } 37 | 38 | $prefix-for-webkit: $original-prefix-for-webkit; 39 | $prefix-for-mozilla: $original-prefix-for-mozilla; 40 | $prefix-for-microsoft: $original-prefix-for-microsoft; 41 | $prefix-for-opera: $original-prefix-for-opera; 42 | $prefix-for-spec: $original-prefix-for-spec; 43 | } 44 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_linear-gradient.scss: -------------------------------------------------------------------------------- 1 | @mixin linear-gradient($pos, $G1, $G2: false, 2 | $G3: false, $G4: false, 3 | $G5: false, $G6: false, 4 | $G7: false, $G8: false, 5 | $G9: false, $G10: false, 6 | $deprecated-pos1: left top, 7 | $deprecated-pos2: left bottom, 8 | $fallback: false) { 9 | // Detect what type of value exists in $pos 10 | $pos-type: type-of(nth($pos, 1)); 11 | $pos-spec: null; 12 | $pos-degree: null; 13 | 14 | // If $pos is missing from mixin, reassign vars and add default position 15 | @if ($pos-type == color) or (nth($pos, 1) == "transparent") { 16 | $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5; 17 | $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos; 18 | $pos: null; 19 | } 20 | 21 | @if $pos { 22 | $positions: _linear-positions-parser($pos); 23 | $pos-degree: nth($positions, 1); 24 | $pos-spec: nth($positions, 2); 25 | } 26 | 27 | $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); 28 | 29 | // Set $G1 as the default fallback color 30 | $fallback-color: nth($G1, 1); 31 | 32 | // If $fallback is a color use that color as the fallback color 33 | @if (type-of($fallback) == color) or ($fallback == "transparent") { 34 | $fallback-color: $fallback; 35 | } 36 | 37 | background-color: $fallback-color; 38 | background-image: _deprecated-webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $full); // Safari <= 5.0 39 | background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome 40 | background-image: unquote("linear-gradient(#{$pos-spec}#{$full})"); 41 | } 42 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_perspective.scss: -------------------------------------------------------------------------------- 1 | @mixin perspective($depth: none) { 2 | // none | 3 | @include prefixer(perspective, $depth, webkit moz spec); 4 | } 5 | 6 | @mixin perspective-origin($value: 50% 50%) { 7 | @include prefixer(perspective-origin, $value, webkit moz spec); 8 | } 9 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_placeholder.scss: -------------------------------------------------------------------------------- 1 | $placeholders: '-webkit-input-placeholder', 2 | '-moz-placeholder', 3 | '-ms-input-placeholder'; 4 | 5 | @mixin placeholder { 6 | @each $placeholder in $placeholders { 7 | @if $placeholder == "-webkit-input-placeholder" { 8 | &::#{$placeholder} { 9 | @content; 10 | } 11 | } 12 | @else if $placeholder == "-moz-placeholder" { 13 | // FF 18- 14 | &:#{$placeholder} { 15 | @content; 16 | } 17 | 18 | // FF 19+ 19 | &::#{$placeholder} { 20 | @content; 21 | } 22 | } 23 | @else { 24 | &:#{$placeholder} { 25 | @content; 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_radial-gradient.scss: -------------------------------------------------------------------------------- 1 | // Requires Sass 3.1+ 2 | @mixin radial-gradient($G1, $G2, 3 | $G3: false, $G4: false, 4 | $G5: false, $G6: false, 5 | $G7: false, $G8: false, 6 | $G9: false, $G10: false, 7 | $pos: null, 8 | $shape-size: null, 9 | $deprecated-pos1: center center, 10 | $deprecated-pos2: center center, 11 | $deprecated-radius1: 0, 12 | $deprecated-radius2: 460, 13 | $fallback: false) { 14 | 15 | $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); 16 | $G1: nth($data, 1); 17 | $G2: nth($data, 2); 18 | $pos: nth($data, 3); 19 | $shape-size: nth($data, 4); 20 | 21 | $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); 22 | 23 | // Strip deprecated cover/contain for spec 24 | $shape-size-spec: _shape-size-stripper($shape-size); 25 | 26 | // Set $G1 as the default fallback color 27 | $first-color: nth($full, 1); 28 | $fallback-color: nth($first-color, 1); 29 | 30 | @if (type-of($fallback) == color) or ($fallback == "transparent") { 31 | $fallback-color: $fallback; 32 | } 33 | 34 | // Add Commas and spaces 35 | $shape-size: if($shape-size, '#{$shape-size}, ', null); 36 | $pos: if($pos, '#{$pos}, ', null); 37 | $pos-spec: if($pos, 'at #{$pos}', null); 38 | $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} '); 39 | 40 | background-color: $fallback-color; 41 | background-image: _deprecated-webkit-gradient(radial, $deprecated-pos1, $deprecated-pos2, $full, $deprecated-radius1, $deprecated-radius2); // Safari <= 5.0 && IOS 4 42 | background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full})); 43 | background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})"); 44 | } 45 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_transform.scss: -------------------------------------------------------------------------------- 1 | @mixin transform($property: none) { 2 | // none | 3 | @include prefixer(transform, $property, webkit moz ms o spec); 4 | } 5 | 6 | @mixin transform-origin($axes: 50%) { 7 | // x-axis - left | center | right | length | % 8 | // y-axis - top | center | bottom | length | % 9 | // z-axis - length 10 | @include prefixer(transform-origin, $axes, webkit moz ms o spec); 11 | } 12 | 13 | @mixin transform-style ($style: flat) { 14 | @include prefixer(transform-style, $style, webkit moz ms o spec); 15 | } 16 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_transition.scss: -------------------------------------------------------------------------------- 1 | // Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. 2 | // Example: @include transition (all, 2.0s, ease-in-out); 3 | // @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s)); 4 | // @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s)); 5 | 6 | @mixin transition ($properties...) { 7 | @if length($properties) >= 1 { 8 | @include prefixer(transition, $properties, webkit moz spec); 9 | } 10 | 11 | @else { 12 | $properties: all 0.15s ease-out 0; 13 | @include prefixer(transition, $properties, webkit moz spec); 14 | } 15 | } 16 | 17 | @mixin transition-property ($properties...) { 18 | -webkit-transition-property: transition-property-names($properties, 'webkit'); 19 | -moz-transition-property: transition-property-names($properties, 'moz'); 20 | transition-property: transition-property-names($properties, false); 21 | } 22 | 23 | @mixin transition-duration ($times...) { 24 | @include prefixer(transition-duration, $times, webkit moz spec); 25 | } 26 | 27 | @mixin transition-timing-function ($motions...) { 28 | // ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() 29 | @include prefixer(transition-timing-function, $motions, webkit moz spec); 30 | } 31 | 32 | @mixin transition-delay ($times...) { 33 | @include prefixer(transition-delay, $times, webkit moz spec); 34 | } 35 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/css3/_user-select.scss: -------------------------------------------------------------------------------- 1 | @mixin user-select($arg: none) { 2 | @include prefixer(user-select, $arg, webkit moz ms spec); 3 | } 4 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_compact.scss: -------------------------------------------------------------------------------- 1 | // Remove `false` values from a list 2 | 3 | @function compact($vars...) { 4 | $list: (); 5 | @each $var in $vars { 6 | @if $var { 7 | $list: append($list, $var, comma); 8 | } 9 | } 10 | @return $list; 11 | } 12 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_flex-grid.scss: -------------------------------------------------------------------------------- 1 | // Flexible grid 2 | @function flex-grid($columns, $container-columns: $fg-max-columns) { 3 | $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; 4 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 5 | @return percentage($width / $container-width); 6 | } 7 | 8 | // Flexible gutter 9 | @function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { 10 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 11 | @return percentage($gutter / $container-width); 12 | } 13 | 14 | // The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function. 15 | // This function takes the fluid grid equation (target / context = result) and uses columns to help define each. 16 | // 17 | // The calculation presumes that your column structure will be missing the last gutter: 18 | // 19 | // -- column -- gutter -- column -- gutter -- column 20 | // 21 | // $fg-column: 60px; // Column Width 22 | // $fg-gutter: 25px; // Gutter Width 23 | // $fg-max-columns: 12; // Total Columns For Main Container 24 | // 25 | // div { 26 | // width: flex-grid(4); // returns (315px / 995px) = 31.65829%; 27 | // margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%; 28 | // 29 | // p { 30 | // width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; 31 | // float: left; 32 | // margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%; 33 | // } 34 | // 35 | // blockquote { 36 | // float: left; 37 | // width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; 38 | // } 39 | // } -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_grid-width.scss: -------------------------------------------------------------------------------- 1 | @function grid-width($n) { 2 | @return $n * $gw-column + ($n - 1) * $gw-gutter; 3 | } 4 | 5 | // The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function. 6 | // 7 | // $gw-column: 100px; // Column Width 8 | // $gw-gutter: 40px; // Gutter Width 9 | // 10 | // div { 11 | // width: grid-width(4); // returns 520px; 12 | // margin-left: $gw-gutter; // returns 40px; 13 | // } 14 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_linear-gradient.scss: -------------------------------------------------------------------------------- 1 | @function linear-gradient($pos, $gradients...) { 2 | $type: linear; 3 | $pos-type: type-of(nth($pos, 1)); 4 | 5 | // if $pos doesn't exist, fix $gradient 6 | @if ($pos-type == color) or (nth($pos, 1) == "transparent") { 7 | $gradients: zip($pos $gradients); 8 | $pos: false; 9 | } 10 | 11 | $type-gradient: $type, $pos, $gradients; 12 | @return $type-gradient; 13 | } 14 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_modular-scale.scss: -------------------------------------------------------------------------------- 1 | @function modular-scale($value, $increment, $ratio) { 2 | @if $increment > 0 { 3 | @for $i from 1 through $increment { 4 | $value: ($value * $ratio); 5 | } 6 | } 7 | 8 | @if $increment < 0 { 9 | $increment: abs($increment); 10 | @for $i from 1 through $increment { 11 | $value: ($value / $ratio); 12 | } 13 | } 14 | 15 | @return $value; 16 | } 17 | 18 | // div { 19 | // Increment Up GR with positive value 20 | // font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px 21 | // 22 | // Increment Down GR with negative value 23 | // font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px 24 | // 25 | // Can be used with ceil(round up) or floor(round down) 26 | // font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px 27 | // font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px 28 | // } 29 | // 30 | // modularscale.com 31 | 32 | @function golden-ratio($value, $increment) { 33 | @return modular-scale($value, $increment, 1.618) 34 | } 35 | 36 | // div { 37 | // font-size: golden-ratio(14px, 1); // returns: 22.652px 38 | // } 39 | // 40 | // goldenratiocalculator.com 41 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_px-to-em.scss: -------------------------------------------------------------------------------- 1 | // Convert pixels to ems 2 | // eg. for a relational value of 12px write em(12) when the parent is 16px 3 | // if the parent is another value say 24px write em(12, 24) 4 | 5 | @function em($pxval, $base: 16) { 6 | @return ($pxval / $base) * 1em; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_radial-gradient.scss: -------------------------------------------------------------------------------- 1 | // This function is required and used by the background-image mixin. 2 | @function radial-gradient($G1, $G2, 3 | $G3: false, $G4: false, 4 | $G5: false, $G6: false, 5 | $G7: false, $G8: false, 6 | $G9: false, $G10: false, 7 | $pos: null, 8 | $shape-size: null) { 9 | 10 | $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); 11 | $G1: nth($data, 1); 12 | $G2: nth($data, 2); 13 | $pos: nth($data, 3); 14 | $shape-size: nth($data, 4); 15 | 16 | $type: radial; 17 | $gradient: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); 18 | 19 | $type-gradient: $type, $shape-size $pos, $gradient; 20 | @return $type-gradient; 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_tint-shade.scss: -------------------------------------------------------------------------------- 1 | // Add percentage of white to a color 2 | @function tint($color, $percent){ 3 | @return mix(white, $color, $percent); 4 | } 5 | 6 | // Add percentage of black to a color 7 | @function shade($color, $percent){ 8 | @return mix(black, $color, $percent); 9 | } 10 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/functions/_transition-property-name.scss: -------------------------------------------------------------------------------- 1 | // Return vendor-prefixed property names if appropriate 2 | // Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background 3 | //************************************************************************// 4 | @function transition-property-names($props, $vendor: false) { 5 | $new-props: (); 6 | 7 | @each $prop in $props { 8 | $new-props: append($new-props, transition-property-name($prop, $vendor), comma); 9 | } 10 | 11 | @return $new-props; 12 | } 13 | 14 | @function transition-property-name($prop, $vendor: false) { 15 | // put other properties that need to be prefixed here aswell 16 | @if $vendor and $prop == transform { 17 | @return unquote('-'+$vendor+'-'+$prop); 18 | } 19 | @else { 20 | @return $prop; 21 | } 22 | } -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_deprecated-webkit-gradient.scss: -------------------------------------------------------------------------------- 1 | // Render Deprecated Webkit Gradient - Linear || Radial 2 | //************************************************************************// 3 | @function _deprecated-webkit-gradient($type, 4 | $deprecated-pos1, $deprecated-pos2, 5 | $full, 6 | $deprecated-radius1: false, $deprecated-radius2: false) { 7 | $gradient-list: (); 8 | $gradient: false; 9 | $full-length: length($full); 10 | $percentage: false; 11 | $gradient-type: $type; 12 | 13 | @for $i from 1 through $full-length { 14 | $gradient: nth($full, $i); 15 | 16 | @if length($gradient) == 2 { 17 | $color-stop: color-stop(nth($gradient, 2), nth($gradient, 1)); 18 | $gradient-list: join($gradient-list, $color-stop, comma); 19 | } 20 | @else if $gradient != null { 21 | @if $i == $full-length { 22 | $percentage: 100%; 23 | } 24 | @else { 25 | $percentage: ($i - 1) * (100 / ($full-length - 1)) + "%"; 26 | } 27 | $color-stop: color-stop(unquote($percentage), $gradient); 28 | $gradient-list: join($gradient-list, $color-stop, comma); 29 | } 30 | } 31 | 32 | @if $type == radial { 33 | $gradient: -webkit-gradient(radial, $deprecated-pos1, $deprecated-radius1, $deprecated-pos2, $deprecated-radius2, $gradient-list); 34 | } 35 | @else if $type == linear { 36 | $gradient: -webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $gradient-list); 37 | } 38 | @return $gradient; 39 | } 40 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_gradient-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _gradient-positions-parser($gradient-type, $gradient-positions) { 2 | @if $gradient-positions 3 | and ($gradient-type == linear) 4 | and (type-of($gradient-positions) != color) { 5 | $gradient-positions: _linear-positions-parser($gradient-positions); 6 | } 7 | @else if $gradient-positions 8 | and ($gradient-type == radial) 9 | and (type-of($gradient-positions) != color) { 10 | $gradient-positions: _radial-positions-parser($gradient-positions); 11 | } 12 | @return $gradient-positions; 13 | } 14 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_linear-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _linear-positions-parser($pos) { 2 | $type: type-of(nth($pos, 1)); 3 | $spec: null; 4 | $degree: null; 5 | $side: null; 6 | $corner: null; 7 | $length: length($pos); 8 | // Parse Side and corner positions 9 | @if ($length > 1) { 10 | @if nth($pos, 1) == "to" { // Newer syntax 11 | $side: nth($pos, 2); 12 | 13 | @if $length == 2 { // eg. to top 14 | // Swap for backwards compatability 15 | $degree: _position-flipper(nth($pos, 2)); 16 | } 17 | @else if $length == 3 { // eg. to top left 18 | $corner: nth($pos, 3); 19 | } 20 | } 21 | @else if $length == 2 { // Older syntax ("top left") 22 | $side: _position-flipper(nth($pos, 1)); 23 | $corner: _position-flipper(nth($pos, 2)); 24 | } 25 | 26 | @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { 27 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 28 | } 29 | @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { 30 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 31 | } 32 | @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { 33 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 34 | } 35 | @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { 36 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 37 | } 38 | $spec: to $side $corner; 39 | } 40 | @else if $length == 1 { 41 | // Swap for backwards compatability 42 | @if $type == string { 43 | $degree: $pos; 44 | $spec: to _position-flipper($pos); 45 | } 46 | @else { 47 | $degree: -270 - $pos; //rotate the gradient opposite from spec 48 | $spec: $pos; 49 | } 50 | } 51 | $degree: unquote($degree + ","); 52 | $spec: unquote($spec + ","); 53 | @return $degree $spec; 54 | } 55 | 56 | @function _position-flipper($pos) { 57 | @return if($pos == left, right, null) 58 | if($pos == right, left, null) 59 | if($pos == top, bottom, null) 60 | if($pos == bottom, top, null); 61 | } 62 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_radial-arg-parser.scss: -------------------------------------------------------------------------------- 1 | @function _radial-arg-parser($G1, $G2, $pos, $shape-size) { 2 | @each $value in $G1, $G2 { 3 | $first-val: nth($value, 1); 4 | $pos-type: type-of($first-val); 5 | $spec-at-index: null; 6 | 7 | // Determine if spec was passed to mixin 8 | @if type-of($value) == list { 9 | $spec-at-index: if(index($value, at), index($value, at), false); 10 | } 11 | @if $spec-at-index { 12 | @if $spec-at-index > 1 { 13 | @for $i from 1 through ($spec-at-index - 1) { 14 | $shape-size: $shape-size nth($value, $i); 15 | } 16 | @for $i from ($spec-at-index + 1) through length($value) { 17 | $pos: $pos nth($value, $i); 18 | } 19 | } 20 | @else if $spec-at-index == 1 { 21 | @for $i from ($spec-at-index + 1) through length($value) { 22 | $pos: $pos nth($value, $i); 23 | } 24 | } 25 | $G1: false; 26 | } 27 | 28 | // If not spec calculate correct values 29 | @else { 30 | @if ($pos-type != color) or ($first-val != "transparent") { 31 | @if ($pos-type == number) 32 | or ($first-val == "center") 33 | or ($first-val == "top") 34 | or ($first-val == "right") 35 | or ($first-val == "bottom") 36 | or ($first-val == "left") { 37 | 38 | $pos: $value; 39 | 40 | @if $pos == $G1 { 41 | $G1: false; 42 | } 43 | } 44 | 45 | @else if 46 | ($first-val == "ellipse") 47 | or ($first-val == "circle") 48 | or ($first-val == "closest-side") 49 | or ($first-val == "closest-corner") 50 | or ($first-val == "farthest-side") 51 | or ($first-val == "farthest-corner") 52 | or ($first-val == "contain") 53 | or ($first-val == "cover") { 54 | 55 | $shape-size: $value; 56 | 57 | @if $value == $G1 { 58 | $G1: false; 59 | } 60 | 61 | @else if $value == $G2 { 62 | $G2: false; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | @return $G1, $G2, $pos, $shape-size; 69 | } 70 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_radial-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _radial-positions-parser($gradient-pos) { 2 | $shape-size: nth($gradient-pos, 1); 3 | $pos: nth($gradient-pos, 2); 4 | $shape-size-spec: _shape-size-stripper($shape-size); 5 | 6 | $pre-spec: unquote(if($pos, "#{$pos}, ", null)) 7 | unquote(if($shape-size, "#{$shape-size},", null)); 8 | $pos-spec: if($pos, "at #{$pos}", null); 9 | 10 | $spec: "#{$shape-size-spec} #{$pos-spec}"; 11 | 12 | // Add comma 13 | @if ($spec != ' ') { 14 | $spec: "#{$spec}," 15 | } 16 | 17 | @return $pre-spec $spec; 18 | } 19 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_render-gradients.scss: -------------------------------------------------------------------------------- 1 | // User for linear and radial gradients within background-image or border-image properties 2 | 3 | @function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { 4 | $pre-spec: null; 5 | $spec: null; 6 | $vendor-gradients: null; 7 | @if $gradient-type == linear { 8 | @if $gradient-positions { 9 | $pre-spec: nth($gradient-positions, 1); 10 | $spec: nth($gradient-positions, 2); 11 | } 12 | } 13 | @else if $gradient-type == radial { 14 | $pre-spec: nth($gradient-positions, 1); 15 | $spec: nth($gradient-positions, 2); 16 | } 17 | 18 | @if $vendor { 19 | $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); 20 | } 21 | @else if $vendor == false { 22 | $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; 23 | $vendor-gradients: unquote($vendor-gradients); 24 | } 25 | @return $vendor-gradients; 26 | } 27 | -------------------------------------------------------------------------------- /docs/_static/css/bourbon/helpers/_shape-size-stripper.scss: -------------------------------------------------------------------------------- 1 | @function _shape-size-stripper($shape-size) { 2 | $shape-size-spec: null; 3 | @each $value in $shape-size { 4 | @if ($value == "cover") or ($value == "contain") { 5 | $value: null; 6 | } 7 | $shape-size-spec: "#{$shape-size-spec} #{$value}"; 8 | } 9 | @return $shape-size-spec; 10 | } 11 | -------------------------------------------------------------------------------- /docs/_static/css/neat/_neat-helpers.scss: -------------------------------------------------------------------------------- 1 | // Functions 2 | @import "functions/private"; 3 | @import "functions/new-breakpoint"; 4 | @import "functions/px-to-em"; 5 | 6 | // Settings 7 | @import "settings/grid"; 8 | @import "settings/visual-grid"; 9 | -------------------------------------------------------------------------------- /docs/_static/css/neat/_neat.scss: -------------------------------------------------------------------------------- 1 | // Bourbon Neat 2 | // MIT Licensed 3 | // Copyright (c) 2012-2013 thoughtbot, inc. 4 | 5 | // Helpers 6 | @import "neat-helpers"; 7 | 8 | // Grid 9 | @import "grid/private"; 10 | @import "grid/reset"; 11 | @import "grid/grid"; 12 | @import "grid/omega"; 13 | @import "grid/outer-container"; 14 | @import "grid/span-columns"; 15 | @import "grid/row"; 16 | @import "grid/shift"; 17 | @import "grid/pad"; 18 | @import "grid/fill-parent"; 19 | @import "grid/media"; 20 | @import "grid/to-deprecate"; 21 | @import "grid/visual-grid"; 22 | -------------------------------------------------------------------------------- /docs/_static/css/neat/functions/_new-breakpoint.scss: -------------------------------------------------------------------------------- 1 | @function new-breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) { 2 | 3 | @if length($query) == 1 { 4 | $query: $default-feature nth($query, 1) $total-columns; 5 | } 6 | 7 | @else if length($query) == 2 or length($query) == 4 { 8 | $query: append($query, $total-columns); 9 | } 10 | 11 | @if not belongs-to($query, $visual-grid-breakpoints) { 12 | $visual-grid-breakpoints: append($visual-grid-breakpoints, $query, comma); 13 | } 14 | 15 | @return $query; 16 | } 17 | -------------------------------------------------------------------------------- /docs/_static/css/neat/functions/_px-to-em.scss: -------------------------------------------------------------------------------- 1 | @function em($pxval, $base: 16) { 2 | @return ($pxval / $base) * 1em; 3 | } 4 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_fill-parent.scss: -------------------------------------------------------------------------------- 1 | @mixin fill-parent() { 2 | width: 100%; 3 | 4 | @if $border-box-sizing == false { 5 | @include box-sizing(border-box); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_grid.scss: -------------------------------------------------------------------------------- 1 | @if $border-box-sizing == true { 2 | * { 3 | @include box-sizing(border-box); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_media.scss: -------------------------------------------------------------------------------- 1 | @mixin media($query:$feature $value $columns, $total-columns: $grid-columns) { 2 | 3 | @if length($query) == 1 { 4 | @media screen and ($default-feature: nth($query, 1)) { 5 | $default-grid-columns: $grid-columns; 6 | $grid-columns: $total-columns; 7 | @content; 8 | $grid-columns: $default-grid-columns; 9 | } 10 | } 11 | 12 | @else if length($query) == 2 { 13 | @media screen and (nth($query, 1): nth($query, 2)) { 14 | $default-grid-columns: $grid-columns; 15 | $grid-columns: $total-columns; 16 | @content; 17 | $grid-columns: $default-grid-columns; 18 | } 19 | } 20 | 21 | @else if length($query) == 3 { 22 | @media screen and (nth($query, 1): nth($query, 2)) { 23 | $default-grid-columns: $grid-columns; 24 | $grid-columns: nth($query, 3); 25 | @content; 26 | $grid-columns: $default-grid-columns; 27 | } 28 | } 29 | 30 | @else if length($query) == 4 { 31 | @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { 32 | $default-grid-columns: $grid-columns; 33 | $grid-columns: $total-columns; 34 | @content; 35 | $grid-columns: $default-grid-columns; 36 | } 37 | } 38 | 39 | @else if length($query) == 5 { 40 | @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { 41 | $default-grid-columns: $grid-columns; 42 | $grid-columns: nth($query, 5); 43 | @content; 44 | $grid-columns: $default-grid-columns; 45 | } 46 | } 47 | 48 | @else { 49 | @warn "Wrong number of arguments for breakpoint(). Read the documentation for more details."; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_omega.scss: -------------------------------------------------------------------------------- 1 | // Remove last element gutter 2 | @mixin omega($query: block, $direction: default) { 3 | $table: if(belongs-to(table, $query), true, false); 4 | $auto: if(belongs-to(auto, $query), true, false); 5 | 6 | @if $direction != default { 7 | @warn "The omega mixin will no longer take a $direction argument. To change the layout direction, use row($direction) or set $default-layout-direction instead." 8 | } @else { 9 | $direction: get-direction($layout-direction, $default-layout-direction); 10 | } 11 | 12 | @if length($query) == 1 { 13 | @if $auto { 14 | &:last-child { 15 | margin-#{$direction}: 0; 16 | } 17 | } 18 | 19 | @else if contains-display-value($query) { 20 | @if $table { 21 | padding-#{$direction}: 0; 22 | } 23 | 24 | @else { 25 | margin-#{$direction}: 0; 26 | } 27 | } 28 | 29 | @else { 30 | @include nth-child($query, $direction); 31 | } 32 | } 33 | 34 | @else if length($query) == 2 { 35 | @if $table { 36 | @if $auto { 37 | &:last-child { 38 | padding-#{$direction}: 0; 39 | } 40 | } 41 | 42 | @else { 43 | &:nth-child(#{nth($query, 1)}) { 44 | padding-#{$direction}: 0; 45 | } 46 | } 47 | } 48 | 49 | @else { 50 | @if $auto { 51 | &:last-child { 52 | margin-#{$direction}: 0; 53 | } 54 | } 55 | 56 | @else { 57 | @include nth-child(nth($query, 1), $direction); 58 | } 59 | } 60 | } 61 | 62 | @else { 63 | @warn "Too many arguments passed to the omega() mixin." 64 | } 65 | } 66 | 67 | @mixin nth-child($query, $direction) { 68 | $opposite-direction: get-opposite-direction($direction); 69 | 70 | &:nth-child(#{$query}) { 71 | margin-#{$direction}: 0; 72 | } 73 | 74 | @if type-of($query) == number { 75 | &:nth-child(#{$query}+1) { 76 | clear: $opposite-direction; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_outer-container.scss: -------------------------------------------------------------------------------- 1 | @mixin outer-container { 2 | @include clearfix; 3 | max-width: $max-width; 4 | margin: { 5 | left: auto; 6 | right: auto; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_pad.scss: -------------------------------------------------------------------------------- 1 | @mixin pad($padding: flex-gutter()) { 2 | $padding-list: null; 3 | @each $value in $padding { 4 | $value: if($value == 'default', flex-gutter(), $value); 5 | $padding-list: join($padding-list, $value); 6 | } 7 | padding: $padding-list; 8 | } 9 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_private.scss: -------------------------------------------------------------------------------- 1 | $parent-columns: $grid-columns !default; 2 | $fg-column: $column; 3 | $fg-gutter: $gutter; 4 | $fg-max-columns: $grid-columns; 5 | $container-display-table: false !default; 6 | $layout-direction: nil !default; 7 | 8 | @function flex-grid($columns, $container-columns: $fg-max-columns) { 9 | $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; 10 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 11 | @return percentage($width / $container-width); 12 | } 13 | 14 | @function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { 15 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 16 | @return percentage($gutter / $container-width); 17 | } 18 | 19 | @function grid-width($n) { 20 | @return $n * $gw-column + ($n - 1) * $gw-gutter; 21 | } 22 | 23 | @function get-parent-columns($columns) { 24 | @if $columns != $grid-columns { 25 | $parent-columns: $columns; 26 | } @else { 27 | $parent-columns: $grid-columns; 28 | } 29 | 30 | @return $parent-columns; 31 | } 32 | 33 | @function is-display-table($container-is-display-table, $display) { 34 | $display-table: false; 35 | 36 | @if $container-is-display-table == true { 37 | $display-table: true; 38 | } @else if $display == table { 39 | $display-table: true; 40 | } 41 | 42 | @return $display-table; 43 | } 44 | 45 | @function get-padding-for-table-layout($columns, $total-columns) { 46 | $total-padding: flex-gutter($total-columns) * ($columns - 1); 47 | $padding: $total-padding / $columns; 48 | 49 | @return $padding; 50 | } 51 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_reset.scss: -------------------------------------------------------------------------------- 1 | @mixin reset-display { 2 | $container-display-table: false; 3 | } 4 | 5 | @mixin reset-layout-direction { 6 | $layout-direction: $default-layout-direction; 7 | } 8 | 9 | @mixin reset-all { 10 | @include reset-display; 11 | @include reset-layout-direction; 12 | } 13 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_row.scss: -------------------------------------------------------------------------------- 1 | @mixin row($display: block, $direction: $default-layout-direction) { 2 | @include clearfix; 3 | $layout-direction: $direction; 4 | 5 | @if $display == table { 6 | display: table; 7 | @include fill-parent; 8 | table-layout: fixed; 9 | $container-display-table: true; 10 | } 11 | 12 | @else { 13 | display: block; 14 | $container-display-table: false; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_shift.scss: -------------------------------------------------------------------------------- 1 | @mixin shift($n-columns: 1) { 2 | $direction: get-direction($layout-direction, $default-layout-direction); 3 | $opposite-direction: get-opposite-direction($direction); 4 | 5 | margin-#{$opposite-direction}: $n-columns * flex-grid(1, $parent-columns) + $n-columns * flex-gutter($parent-columns); 6 | 7 | // Reset nesting context 8 | $parent-columns: $grid-columns; 9 | } 10 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_span-columns.scss: -------------------------------------------------------------------------------- 1 | @mixin span-columns($span: $columns of $container-columns, $display: block) { 2 | $columns: nth($span, 1); 3 | $container-columns: container-span($span); 4 | 5 | // Set nesting context (used by shift()) 6 | $parent-columns: get-parent-columns($container-columns); 7 | 8 | $direction: get-direction($layout-direction, $default-layout-direction); 9 | $opposite-direction: get-opposite-direction($direction); 10 | 11 | $display-table: is-display-table($container-display-table, $display); 12 | 13 | @if $display-table { 14 | $padding: get-padding-for-table-layout($columns, $container-columns); 15 | display: table-cell; 16 | padding-#{$direction}: $padding; 17 | width: flex-grid($columns, $container-columns) + $padding; 18 | } @else { 19 | display: block; 20 | float: #{$opposite-direction}; 21 | 22 | @if $display == collapse { 23 | width: flex-grid($columns, $container-columns) + flex-gutter($container-columns); 24 | 25 | &:last-child { 26 | width: flex-grid($columns, $container-columns); 27 | } 28 | 29 | } @else { 30 | margin-#{$direction}: flex-gutter($container-columns); 31 | width: flex-grid($columns, $container-columns); 32 | 33 | &:last-child { 34 | margin-#{$direction}: 0; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_to-deprecate.scss: -------------------------------------------------------------------------------- 1 | @mixin breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) { 2 | @warn "The breakpoint() mixin was renamed to media() in Neat 1.0. Please update your project with the new syntax before the next version bump."; 3 | 4 | @if length($query) == 1 { 5 | @media screen and ($default-feature: nth($query, 1)) { 6 | $default-grid-columns: $grid-columns; 7 | $grid-columns: $total-columns; 8 | @content; 9 | $grid-columns: $default-grid-columns; 10 | } 11 | } 12 | 13 | @else if length($query) == 2 { 14 | @media screen and (nth($query, 1): nth($query, 2)) { 15 | $default-grid-columns: $grid-columns; 16 | $grid-columns: $total-columns; 17 | @content; 18 | $grid-columns: $default-grid-columns; 19 | } 20 | } 21 | 22 | @else if length($query) == 3 { 23 | @media screen and (nth($query, 1): nth($query, 2)) { 24 | $default-grid-columns: $grid-columns; 25 | $grid-columns: nth($query, 3); 26 | @content; 27 | $grid-columns: $default-grid-columns; 28 | } 29 | } 30 | 31 | @else if length($query) == 4 { 32 | @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { 33 | $default-grid-columns: $grid-columns; 34 | $grid-columns: $total-columns; 35 | @content; 36 | $grid-columns: $default-grid-columns; 37 | } 38 | } 39 | 40 | @else if length($query) == 5 { 41 | @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { 42 | $default-grid-columns: $grid-columns; 43 | $grid-columns: nth($query, 5); 44 | @content; 45 | $grid-columns: $default-grid-columns; 46 | } 47 | } 48 | 49 | @else { 50 | @warn "Wrong number of arguments for breakpoint(). Read the documentation for more details."; 51 | } 52 | } 53 | 54 | @mixin nth-omega($nth, $display: block, $direction: default) { 55 | @warn "The nth-omega() mixin is deprecated. Please use omega() instead."; 56 | @include omega($nth $display, $direction); 57 | } 58 | -------------------------------------------------------------------------------- /docs/_static/css/neat/grid/_visual-grid.scss: -------------------------------------------------------------------------------- 1 | @mixin grid-column-gradient($values...) { 2 | background-image: deprecated-webkit-gradient(linear, left top, left bottom, $values); 3 | background-image: -webkit-linear-gradient(left, $values); 4 | background-image: -moz-linear-gradient(left, $values); 5 | background-image: -ms-linear-gradient(left, $values); 6 | background-image: -o-linear-gradient(left, $values); 7 | background-image: unquote("linear-gradient(left, #{$values})"); 8 | } 9 | 10 | @if $visual-grid == true or $visual-grid == yes { 11 | body:before { 12 | content: ''; 13 | display: inline-block; 14 | @include grid-column-gradient(gradient-stops($grid-columns)); 15 | height: 100%; 16 | left: 0; 17 | margin: 0 auto; 18 | max-width: $max-width; 19 | opacity: $visual-grid-opacity; 20 | position: fixed; 21 | right: 0; 22 | width: 100%; 23 | pointer-events: none; 24 | 25 | @if $visual-grid-index == back { 26 | z-index: -1; 27 | } 28 | 29 | @else if $visual-grid-index == front { 30 | z-index: 9999; 31 | } 32 | 33 | @each $breakpoint in $visual-grid-breakpoints { 34 | @if $breakpoint != nil { 35 | @include media($breakpoint) { 36 | @include grid-column-gradient(gradient-stops($grid-columns)); 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/_static/css/neat/settings/_grid.scss: -------------------------------------------------------------------------------- 1 | $column: golden-ratio(1em, 3) !default; // Column width 2 | $gutter: golden-ratio(1em, 1) !default; // Gutter between each two columns 3 | $grid-columns: 12 !default; // Total number of columns in the grid 4 | $max-width: em(1088) !default; // Max-width of the outer container 5 | $border-box-sizing: true !default; // Makes all elements have a border-box layout 6 | $default-feature: min-width; // Default @media feature for the breakpoint() mixin 7 | $default-layout-direction: LTR !default; 8 | -------------------------------------------------------------------------------- /docs/_static/css/neat/settings/_visual-grid.scss: -------------------------------------------------------------------------------- 1 | $visual-grid: false !default; // Display the base grid 2 | $visual-grid-color: #EEE !default; 3 | $visual-grid-index: back !default; // Show grid behind content (back) or overlay it over the content (front) 4 | $visual-grid-opacity: 0.4 !default; 5 | $visual-grid-breakpoints: () !default; 6 | -------------------------------------------------------------------------------- /docs/_static/duty_calls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/docs/_static/duty_calls.png -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/flattr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/docs/_static/flattr.png -------------------------------------------------------------------------------- /docs/_static/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/_theme/page.html: -------------------------------------------------------------------------------- 1 | {%- extends "layout.html" %} 2 | {% block header %} 3 |
4 |
5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /docs/_theme/remove_heading.py: -------------------------------------------------------------------------------- 1 | 2 | from docutils import nodes 3 | from sphinx.writers.html import HTMLTranslator 4 | 5 | 6 | class IssoTranslator(HTMLTranslator): 7 | 8 | def visit_title(self, node): 9 | if self.section_level == 1: 10 | raise nodes.SkipNode 11 | HTMLTranslator.visit_title(self, node) 12 | -------------------------------------------------------------------------------- /docs/_theme/search.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block header %} 3 |
4 |

Search

5 |
6 | {% endblock %} 7 | {% set title = _('Search') %} 8 | {% set script_files = script_files + ['_static/searchtools.js'] + ['_static/language_data.js'] 9 | + ['_static/js/vendor/jquery-3.7.1.min.js'] %} 10 | {% block footer %} 11 | 14 | {# this is used when loading the search index using $.ajax fails, 15 | such as on Chrome for documents on localhost #} 16 | 17 | {{ super() }} 18 | {% endblock %} 19 | {% block main %} 20 |
21 | 29 |

30 | {% trans %}From here you can search these documents. Enter your search 31 | words into the box below and click "search". Note that the search 32 | function will automatically search for all of the words. Pages 33 | containing fewer words won't appear in the result list.{% endtrans %} 34 |

35 |
36 | 37 | 38 | 39 |
40 | {% if search_performed %} 41 |

{{ _('Search Results') }}

42 | {% if not search_results %} 43 |

{{ _("Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.") }}

44 | {% endif %} 45 | {% endif %} 46 |
47 | {% if search_results %} 48 |
    49 | {% for href, caption, context in search_results %} 50 |
  • {{ caption }} 51 |
    {{ context|e }}
    52 |
  • 53 | {% endfor %} 54 |
55 | {% endif %} 56 |
57 |
58 | {% endblock %} 59 | -------------------------------------------------------------------------------- /docs/_theme/searchbox.html: -------------------------------------------------------------------------------- 1 | {%- if pagename != "search" %} 2 | 17 | 18 | {%- endif %} 19 | -------------------------------------------------------------------------------- /docs/_theme/sidebar-docs.html: -------------------------------------------------------------------------------- 1 | {%- block sidebarsearch %} 2 | {%- include "searchbox.html" %} 3 | {%- endblock %} 4 | 5 | {%- block menu %} 6 | {%- set toctree = toctree(maxdepth=theme_navigation_depth|int, 7 | collapse=theme_collapse_navigation|tobool, 8 | includehidden=theme_includehidden|tobool, 9 | titles_only=theme_titles_only|tobool) %} 10 | {%- if toctree %} 11 | {{ toctree }} 12 | {%- else %} 13 | 14 |
{{ toc }}
15 | {%- endif %} 16 | {%- endblock %} 17 | -------------------------------------------------------------------------------- /docs/_theme/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = css/site.css 4 | 5 | [options] 6 | collapse_navigation = True 7 | navigation_depth = 4 8 | includehidden = True 9 | titles_only = 10 | logo_only = 11 | display_version = True 12 | vcs_pageview_mode = 13 | -------------------------------------------------------------------------------- /docs/docs/guides/advanced-integration.rst: -------------------------------------------------------------------------------- 1 | Advanced integration 2 | ==================== 3 | 4 | Comment counter 5 | --------------- 6 | 7 | If you want to display a comment counter for a given thread, simply 8 | put a link to that comments thread anchor: 9 | 10 | .. code-block:: html 11 | 12 | Comments 13 | 14 | The *isso js client* willl replace the content of this tag with a human readable 15 | counter like *"5 comments"*. 16 | 17 | Alternatively, if guessing from `href` is not relevant, you could use a 18 | `data-isso-id` attribute on the `` to indicate which thread to count for. 19 | 20 | Now, either include `count.min.js` if you want to show only the comment count 21 | (e.g. on an index page) or `embed.min.js` for the full comment client (see 22 | :doc:`quickstart`); do not mix both. 23 | 24 | You can have as many comments counters as you want in a page, and they will be 25 | merged into a single `GET` request. 26 | 27 | Asynchronous comments loading 28 | ----------------------------- 29 | 30 | Isso will automatically fetch comments after `DOMContentLoaded` event. However 31 | in the case where your website is creating content dynamically (eg. via ajax), 32 | you need to re-fetch comment thread manually. Here is how you can re-fetch the 33 | comment thread: 34 | 35 | .. code-block:: js 36 | 37 | window.Isso.fetchComments() 38 | 39 | It will delete all comments under the thread but not the PostBox, fetch 40 | comments with `data-isso-id` attribute of the element `section#isso-thread` (if 41 | that attribute does not exist, fallback to `window.location.pathname`), then 42 | fill comments into the thread. In other words, you should change `data-isso-id` 43 | attribute of the element `section#isso-thread` (or modify the pathname with 44 | `location.pushState`) before you can get new comments. And the thread element 45 | itself should *NOT* be touched or removed. 46 | 47 | If you removed the `section#isso-thread` element, just create another element 48 | with same TagName and ID in which you wish comments to be placed, then call the 49 | `init` method of `Isso`: 50 | 51 | .. code-block:: js 52 | 53 | window.Isso.init() 54 | 55 | Then Isso will initialize the comment section and fetch comments, as if the page 56 | was loaded. 57 | -------------------------------------------------------------------------------- /docs/docs/guides/faq.rst: -------------------------------------------------------------------------------- 1 | Frequently asked questions 2 | ========================== 3 | 4 | Why SQLite3? 5 | ------------ 6 | 7 | Although partially answered on the index page, here is a more complete answer: If 8 | you manage massive amounts of comments, Isso is a really bad choice. Isso is 9 | designed to be simple and easy to setup, it is not optimized for high-traffic 10 | websites. 11 | 12 | **Comments are not big data.** 13 | 14 | For example, if you have 209 threads and 778 comments in total this only needs 620 kilobytes 15 | of memory. This is an excellent use case for SQLite. 16 | 17 | What does "Isso" mean? 18 | ---------------------- 19 | 20 | Isso is an informal, german abbreviation for "Ich schrei sonst!", which can 21 | roughly be translated to "I'm yelling otherwise". It usually ends the 22 | discussion without any further arguments. 23 | 24 | In germany, Isso `is also pokémon N° 360`__. 25 | 26 | .. __: http://bulbapedia.bulbagarden.net/wiki/Wynaut_(Pok%C3%A9mon) 27 | 28 | .. attention:: 29 | 30 | This section of the Isso documentation is incomplete. Please help by expanding it. 31 | 32 | Click the ``Edit on GitHub`` button in the top right corner and read the 33 | GitHub Issue named 34 | `Improve & Expand Documentation `_ 35 | for further information. 36 | 37 | **What's missing?** 38 | 39 | - How is Isso different from Disqus, Facebook Comments, and the rest? 40 | - How can I report bugs or request features? 41 | - In which ways can I contribute? 42 | - I am seeing an error! 43 | 44 | ... and other frequently asked questions. 45 | -------------------------------------------------------------------------------- /docs/docs/guides/troubleshooting.rst: -------------------------------------------------------------------------------- 1 | Troubleshooting 2 | =============== 3 | 4 | For uberspace users 5 | ------------------- 6 | Some uberspace users experienced problems with isso and they solved their 7 | issues by adding `DirectoryIndex disabled` as the first line in the `.htaccess` 8 | file for the domain the isso server is running on. 9 | 10 | The `Installing Isso on Uberspace `_ 11 | guide should also be helpful. 12 | 13 | pkg_ressources.DistributionNotFound 14 | ----------------------------------- 15 | 16 | This is usually caused by messing up the system's Python with newer packages 17 | from PyPi (e.g. by executing `easy_install --upgrade pip` as root) and is not 18 | related to Isso at all. 19 | 20 | Install Isso in a virtual environment as described in 21 | :ref:`install-interludium`. Alternatively, you can use `pip install --user` 22 | to install Isso into the user's home. 23 | 24 | Why isn't markdown in my comments rendering as I expect? 25 | -------------------------------------------------------- 26 | 27 | Please :ref:`configure ` Isso's markup parser to your 28 | requirements as described in :doc:`/docs/reference/markdown-config`. 29 | 30 | UnicodeDecodeError: 'ascii' codec can't decode byte 0xff 31 | -------------------------------------------------------- 32 | 33 | Likely an issue with your environment, check you set your preferred file 34 | encoding either in :envvar:`LANG`, :envvar:`LANGUAGE`, :envvar:`LC_ALL` or 35 | :envvar:`LC_CTYPE`: 36 | 37 | .. code-block:: text 38 | 39 | $ env LANG=C.UTF-8 isso [-h] [--version] ... 40 | 41 | If none of the mentioned variables are set, the interaction with Isso will 42 | likely fail (unable to print non-ascii characters to stdout/err, unable to 43 | parse configuration file with non-ascii characters and so forth). 44 | 45 | The web console shows 404 Not Found responses 46 | --------------------------------------------- 47 | 48 | Isso returned "404 Not Found" to indicate "No comments" in versions prior to 49 | 0.12.3. This behaviour was changed in 50 | `a pull request `_ to return a code 51 | of "200" with an empty array. 52 | -------------------------------------------------------------------------------- /docs/docs/index.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Getting started 4 | =============== 5 | 6 | Welcome to Isso's documentation. This documentation will help you get started 7 | fast. If you run into any problems when using Isso, you can find the answer in 8 | `troubleshooting guide `_ or you can 9 | :ref:`get in contact ` 10 | 11 | **Start here:** 12 | 13 | 1. :doc:`Install Isso ` 14 | 2. :doc:`Follow the Quickstart guide ` 15 | 3. If you get in stuck, see :doc:`Troubleshooting ` 16 | 17 | Once you're familiar with Isso, check out the sidebar and the search on the 18 | left to find further information. 19 | 20 | .. note:: 21 | Contributions to the documentation and to the project as a whole are 22 | welcome! See :doc:`/docs/contributing/index` and 23 | :doc:`/docs/contributing/documentation` for more details. 24 | 25 | What's Isso? 26 | ------------ 27 | 28 | Isso is a lightweight commenting server similar to Disqus. It allows anonymous 29 | comments, maintains identity and is simple to administrate. It uses JavaScript 30 | and cross-origin ressource sharing for easy integration into (static) websites. 31 | 32 | What's wrong with Disqus? 33 | ------------------------- 34 | 35 | No anonymous comments (IP address, email and name recorded), hosted in the USA, 36 | third-party. Just like IntenseDebate etc. When you embed Disqus, they 37 | can do anything with your readers (and probably mine Bitcoins, see the loading 38 | times). 39 | -------------------------------------------------------------------------------- /docs/docs/reference/releasing.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Releasing steps 4 | =============== 5 | 6 | * Run ``make test`` 7 | * Update version number in ``setup.py`` and ``CHANGES.rst`` 8 | * ``git commit -m "Preparing ${VERSION}" setup.py CHANGES.rst`` 9 | * ``git tag -as ${VERSION}`` 10 | * ``make init all`` 11 | * ``python3 setup.py sdist`` 12 | * ``twine upload --sign dist/isso-${VERSION}.tar.gz`` 13 | -------------------------------------------------------------------------------- /docs/docs/technical-docs/client.rst: -------------------------------------------------------------------------------- 1 | Technical Documentation: Client 2 | =============================== 3 | 4 | .. attention:: 5 | 6 | This section of the Isso documentation is incomplete. Please help by expanding it. 7 | 8 | Click the ``Edit on GitHub`` button in the top right corner and read the 9 | GitHub Issue named 10 | `Improve & Expand Documentation `_ 11 | for further information. 12 | 13 | **What's missing?** 14 | 15 | - JS, webpack (explain why each is needed) 16 | - Coding standards (ES5?), goals (compartmentalization, testability, 17 | ease of maintenance) 18 | - Compatibility: Browser/IE versions, used ES versions - 19 | keep updated for JS and CSS features (e.g. CSS vars) 20 | 21 | ... and other things about the client that should be documented. 22 | -------------------------------------------------------------------------------- /docs/docs/technical-docs/server.rst: -------------------------------------------------------------------------------- 1 | Technical Documentation: Server 2 | =============================== 3 | 4 | Dependencies 5 | ------------ 6 | 7 | Isso uses some of the following dependencies: 8 | 9 | - `werkzeug `_ – WSGI toolkit 10 | - `itsdangerous `_ – store signed data on untrusted clients 11 | - `misaka `_ – fast Markdown processor written in C 12 | - `html5lib `_ – HTML(5) parser and sanitizer 13 | 14 | .. attention:: 15 | 16 | This section of the Isso documentation is incomplete. Please help by expanding it. 17 | 18 | Click the ``Edit on GitHub`` button in the top right corner and read the 19 | GitHub Issue named 20 | `Improve & Expand Documentation `_ 21 | for further information. 22 | 23 | **What's missing?** 24 | 25 | - Technologies used (flask, werkzeug, misaka, ...) 26 | - Explain code structure 27 | - Request handling and HTTP 28 | - Database handling code 29 | - Comment schema 30 | - Comment (Markdown) rendering using misaka and custom extensions 31 | - Cross-Origin Resource Sharing (CORS) 32 | - Content Security Policy 33 | - Future plans: Rewrite/Refactor, SQLAlchemy, MVC 34 | 35 | ... and other things about the server that should be documented. 36 | -------------------------------------------------------------------------------- /docs/docs/toc.rst: -------------------------------------------------------------------------------- 1 | .. This toctree will be displayed on all docs/ pages 2 | 3 | .. toctree:: 4 | :hidden: 5 | :maxdepth: 1 6 | :caption: Guides 7 | 8 | Quickstart 9 | Tips & Tricks 10 | Troubleshooting 11 | Advanced Integration 12 | FAQ 13 | 14 | .. toctree:: 15 | :hidden: 16 | :maxdepth: 1 17 | :caption: Reference 18 | 19 | Installation 20 | Server Configuration 21 | Client Configuration 22 | Markdown Configuration 23 | Deployment 24 | Multiple Sites & Sub-URI 25 | Server API 26 | Releasing 27 | 28 | .. toctree:: 29 | :hidden: 30 | :maxdepth: 1 31 | :caption: Technical documentation 32 | 33 | Server 34 | Client 35 | Development & Testing 36 | Testing the Client 37 | Testing the Server 38 | 39 | .. toctree:: 40 | :hidden: 41 | :maxdepth: 1 42 | :caption: Contributing 43 | 44 | Contributing to Isso 45 | Writing Documentation 46 | Isso Project Infrastructure 47 | -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [html4css1 writer] 2 | initial_header_level: 2 3 | -------------------------------------------------------------------------------- /docs/images/apidoc-sample-latest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/docs/images/apidoc-sample-latest.png -------------------------------------------------------------------------------- /docs/theme-testing.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Theme testing page 4 | ================== 5 | 6 | This page is for testing out and QA of various features of the ``_isso`` custom 7 | sphinx theme. 8 | 9 | This page should be hidden. 10 | 11 | Admonitions 12 | ----------- 13 | 14 | ``attention``, ``caution``, ``danger``, ``error``, ``hint``, ``important``, ``note``, ``tip``, ``warning``, ``admonition`` 15 | 16 | .. attention:: 17 | Testing a note 18 | 19 | .. caution:: 20 | Testing a note 21 | 22 | .. danger:: 23 | Testing a note 24 | 25 | .. error:: 26 | Testing a note 27 | 28 | .. hint:: 29 | Testing a note 30 | 31 | .. important:: 32 | Testing a note 33 | 34 | .. note:: 35 | Testing a note 36 | 37 | .. tip:: 38 | Testing a note 39 | 40 | .. warning:: 41 | Testing a note 42 | 43 | .. admonition:: Take note 44 | 45 | Testing an admonition which needs its own content block. Does not render 46 | correctly and there shouldn't be a need to use it. 47 | 48 | TODOs 49 | ----- 50 | 51 | .. todo: 52 | This is a TODO. It should not be visible. 53 | -------------------------------------------------------------------------------- /isso/db/preferences.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import os 4 | import binascii 5 | 6 | 7 | class Preferences: 8 | 9 | defaults = [ 10 | ("session-key", binascii.b2a_hex(os.urandom(24)).decode('utf-8')), 11 | ] 12 | 13 | def __init__(self, db): 14 | 15 | self.db = db 16 | self.db.execute([ 17 | 'CREATE TABLE IF NOT EXISTS preferences (', 18 | ' key VARCHAR PRIMARY KEY, value VARCHAR', 19 | ');']) 20 | 21 | for (key, value) in Preferences.defaults: 22 | if self.get(key) is None: 23 | self.set(key, value) 24 | 25 | def get(self, key, default=None): 26 | rv = self.db.execute( 27 | 'SELECT value FROM preferences WHERE key=?', (key, )).fetchone() 28 | 29 | if rv is None: 30 | return default 31 | 32 | return rv[0] 33 | 34 | def set(self, key, value): 35 | self.db.execute( 36 | 'INSERT INTO preferences (key, value) VALUES (?, ?)', (key, value)) 37 | -------------------------------------------------------------------------------- /isso/db/threads.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | 4 | def Thread(id, uri, title): 5 | return { 6 | "id": id, 7 | "uri": uri, 8 | "title": title 9 | } 10 | 11 | 12 | class Threads(object): 13 | 14 | def __init__(self, db): 15 | 16 | self.db = db 17 | self.db.execute([ 18 | 'CREATE TABLE IF NOT EXISTS threads (', 19 | ' id INTEGER PRIMARY KEY, uri VARCHAR(256) UNIQUE, title VARCHAR(256))']) 20 | 21 | def __contains__(self, uri): 22 | return self.db.execute("SELECT title FROM threads WHERE uri=?", (uri, )) \ 23 | .fetchone() is not None 24 | 25 | def __getitem__(self, uri): 26 | return Thread(*self.db.execute("SELECT * FROM threads WHERE uri=?", (uri, )).fetchone()) 27 | 28 | def get(self, id): 29 | return Thread(*self.db.execute("SELECT * FROM threads WHERE id=?", (id, )).fetchone()) 30 | 31 | def new(self, uri, title): 32 | self.db.execute( 33 | "INSERT INTO threads (uri, title) VALUES (?, ?)", (uri, title)) 34 | return self[uri] 35 | -------------------------------------------------------------------------------- /isso/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Isso Demo 4 | 5 | 6 | 7 | 8 |
9 |
10 |

Isso Demo

11 | 12 | 15 | 16 | 17 | 18 |
19 |

This is a link to a thead, which will display a comment counter: 20 | How many Comments?

21 | 22 |

Below is the actual comment field.

23 |
24 | 25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /isso/dispatch.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import sys 4 | import os 5 | import logging 6 | 7 | from glob import glob 8 | 9 | from werkzeug.middleware.dispatcher import DispatcherMiddleware 10 | from werkzeug.wrappers import Response 11 | 12 | from isso import make_app, wsgi, config 13 | 14 | logger = logging.getLogger("isso") 15 | 16 | 17 | class Dispatcher(DispatcherMiddleware): 18 | """ 19 | A dispatcher to support different websites. Dispatches based on 20 | a relative URI, e.g. /foo.example and /other.bar. 21 | """ 22 | 23 | def __init__(self, *confs): 24 | self.isso = {} 25 | 26 | default = config.default_file() 27 | for i, path in enumerate(confs): 28 | conf = config.load(default, path) 29 | 30 | if not conf.get("general", "name"): 31 | logger.warning("unable to dispatch %r, no 'name' set", confs[i]) 32 | continue 33 | 34 | self.isso["/" + conf.get("general", "name")] = make_app(conf) 35 | 36 | super(Dispatcher, self).__init__(self.default, mounts=self.isso) 37 | 38 | def __call__(self, environ, start_response): 39 | 40 | # clear X-Script-Name as the PATH_INFO is already adjusted 41 | environ.pop('HTTP_X_SCRIPT_NAME', None) 42 | 43 | return super(Dispatcher, self).__call__(environ, start_response) 44 | 45 | def default(self, environ, start_response): 46 | resp = Response("\n".join(self.isso.keys()), 47 | 404, content_type="text/plain") 48 | return resp(environ, start_response) 49 | 50 | 51 | settings = os.environ.get("ISSO_SETTINGS") 52 | if settings: 53 | if os.path.isdir(settings): 54 | conf_glob = os.path.join(settings, '*.cfg') 55 | confs = glob(conf_glob) 56 | application = wsgi.SubURI(Dispatcher(*confs)) 57 | else: 58 | confs = settings.split(";") 59 | for path in confs: 60 | if not os.path.isfile(path): 61 | logger.fatal("%s: no such file", path) 62 | sys.exit(1) 63 | application = wsgi.SubURI(Dispatcher(*confs)) 64 | else: 65 | logger.fatal('environment variable ISSO_SETTINGS must be set') 66 | -------------------------------------------------------------------------------- /isso/ext/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from collections import defaultdict 4 | 5 | 6 | class Signal(object): 7 | 8 | def __init__(self, *subscriber): 9 | self.subscriptions = defaultdict(list) 10 | 11 | for sub in subscriber: 12 | for signal, func in sub: 13 | self.subscriptions[signal].append(func) 14 | 15 | def __call__(self, origin, *args, **kwargs): 16 | for subscriber in self.subscriptions[origin]: 17 | subscriber(*args, **kwargs) 18 | -------------------------------------------------------------------------------- /isso/js/app/count.js: -------------------------------------------------------------------------------- 1 | var api = require("app/api"); 2 | var $ = require("app/dom"); 3 | var i18n = require("app/i18n"); 4 | 5 | module.exports = function () { 6 | 7 | var objs = {}; 8 | 9 | $.each("a", function(el) { 10 | if (! el.href.match || ! el.href.match(/#isso-thread$/)) { 11 | return; 12 | } 13 | 14 | var tid = el.getAttribute("data-isso-id") || 15 | el.href.match(/^(.+)#isso-thread$/)[1] 16 | .replace(/^.*\/\/[^\/]+/, ''); 17 | 18 | if (tid in objs) { 19 | objs[tid].push(el); 20 | } else { 21 | objs[tid] = [el]; 22 | } 23 | }); 24 | 25 | var urls = Object.keys(objs); 26 | 27 | if (urls.length > 0) { 28 | api.count(urls).then(function(rv) { 29 | for (var key in objs) { 30 | if (objs.hasOwnProperty(key)) { 31 | 32 | var index = urls.indexOf(key); 33 | 34 | for (var i = 0; i < objs[key].length; i++) { 35 | objs[key][i].textContent = i18n.pluralize("num-comments", rv[index]); 36 | } 37 | } 38 | } 39 | }); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /isso/js/app/default_config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var default_config = { 4 | "css": true, 5 | "css-url": null, 6 | "lang": null, 7 | "default-lang": "en", 8 | "reply-to-self": false, 9 | "require-email": false, 10 | "require-author": false, 11 | "reply-notifications": false, 12 | "reply-notifications-default-enabled": false, 13 | "max-comments-top": "inf", 14 | "max-comments-nested": 5, 15 | "reveal-on-click": 5, 16 | "sorting": "oldest", 17 | "gravatar": false, 18 | "avatar": true, 19 | "avatar-bg": "#f0f0f0", 20 | "avatar-fg": ["#9abf88", "#5698c4", "#e279a3", "#9163b6", 21 | "#be5168", "#f19670", "#e4bf80", "#447c69"].join(" "), 22 | "vote": true, 23 | "vote-levels": null, 24 | "feed": false, 25 | "page-author-hashes": "", 26 | }; 27 | Object.freeze(default_config); 28 | 29 | module.exports = default_config; 30 | -------------------------------------------------------------------------------- /isso/js/app/globals.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Offset = function() { 4 | this.values = []; 5 | }; 6 | 7 | Offset.prototype.update = function(remoteTime) { 8 | this.values.push((new Date()).getTime() - remoteTime.getTime()); 9 | }; 10 | 11 | Offset.prototype.localTime = function() { 12 | return new Date((new Date()).getTime() - this.values.reduce( 13 | function(a, b) { return a + b; }) / this.values.length); 14 | }; 15 | 16 | var offset = new Offset(); 17 | 18 | module.exports = { 19 | offset: offset, 20 | } 21 | -------------------------------------------------------------------------------- /isso/js/app/i18n/ar.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "ضع تعليقًا هُنا (على الأقل ثلاثة أحرف)", 3 | "postbox-author": "الاسم (اختياري)", 4 | "postbox-author-placeholder": "جون سميث", 5 | "postbox-email": "عنوان البريد الإلكتروني (اختياري)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "الموقع الشخصي (اختياري)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "معاينة", 10 | "postbox-edit": "تعديل", 11 | "postbox-submit": "نشر", 12 | "postbox-notification": "الاشتراك بالإشعارات عبر البريد الإلكتروني", 13 | 14 | "num-comments": "تعليق واحد\n{{ n }} تعليق", 15 | "no-comments": "لا يوجد تعليقات حتى الآن", 16 | "atom-feed": "تقليم آتوم", 17 | 18 | "comment-reply": "رد", 19 | "comment-edit": "تعديل", 20 | "comment-save": "حفظ", 21 | "comment-delete": "حذف", 22 | "comment-confirm": "تأكيد", 23 | "comment-close": "إغلاق", 24 | "comment-cancel": "إلغاء", 25 | "comment-deleted": "تم حذف التعليق.", 26 | "comment-queued": "التعليق بانتظار المراجعة.", 27 | "comment-anonymous": "مجهول", 28 | "comment-hidden": "{{ n }} مخفي", 29 | "comment-page-author-suffix": "الكاتب", 30 | 31 | "date-now": "الآن", 32 | "date-minute": "دقيقة مضت\n{{ n }} دقائق مضت", 33 | "date-hour": "منذ ساعة\n{{ n }} ساعات مضت", 34 | "date-day": "البارحة\n{{ n }} أيام مضت", 35 | "date-week": "الأسبوع الماضي\n{{ n }} أسابيع مضت", 36 | "date-month": "الشهر الماضي\n{{ n }} أشهر مضت", 37 | "date-year": "السنة الماضية\n{{ n }} سنين مضت" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/bg.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Въведете коментара си тук (поне 3 знака)", 3 | "postbox-author": "Име/псевдоним (незадължително)", 4 | "postbox-email": "Ел. поща (незадължително)", 5 | "postbox-website": "Уебсайт (незадължително)", 6 | "postbox-preview": "преглед", 7 | "postbox-edit": "Редактиране", 8 | "postbox-submit": "Публикуване", 9 | "num-comments": "1 коментар\n{{ n }} коментара", 10 | "no-comments": "Все още няма коментари", 11 | "comment-reply": "Отговор", 12 | "comment-edit": "Редактиране", 13 | "comment-save": "Запис", 14 | "comment-delete": "Изтриване", 15 | "comment-confirm": "Потвърждение", 16 | "comment-close": "Затваряне", 17 | "comment-cancel": "Отказ", 18 | "comment-deleted": "Коментарът е изтрит.", 19 | "comment-queued": "Коментарът чака на опашката за модериране.", 20 | "comment-anonymous": "анонимен", 21 | "comment-hidden": "{{ n }} скрити", 22 | "date-now": "сега", 23 | "date-minute": "преди 1 минута\nпреди {{ n }} минути", 24 | "date-hour": "преди 1 час\nпреди {{ n }} часа", 25 | "date-day": "вчера\nпреди {{ n }} дни", 26 | "date-week": "миналата седмица\nпреди {{ n }} седмици", 27 | "date-month": "миналия месец\nпреди {{ n }} месеца", 28 | "date-year": "миналата година\nпреди {{ n }} години" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/ca.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "Escriu el teu comentari aquí (almenys 3 caràcters)", 3 | "postbox-author": "Nom (opcional)", 4 | "postbox-author-placeholder": "John Doe", 5 | "postbox-email": "Correu electrònic (opcional)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "Lloc web (opcional)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "Vista prèvia", 10 | "postbox-edit": "Editar", 11 | "postbox-submit": "Enviar", 12 | "postbox-notification": "Suscriu-te a les notificacions per correu electrònic", 13 | 14 | "num-comments": "Un comentari\n{{ n }} comentaris", 15 | "no-comments": "Encara no hi ha comentaris", 16 | "atom-feed": "Canal web Atom", 17 | 18 | "comment-reply": "Respondre", 19 | "comment-edit": "Editar", 20 | "comment-save": "Desar", 21 | "comment-delete": "Eliminar", 22 | "comment-confirm": "Confirmar", 23 | "comment-close": "Tancar", 24 | "comment-cancel": "Cancel·lar", 25 | "comment-deleted": "Comentari eliminat.", 26 | "comment-queued": "Comentari en espera de moderació.", 27 | "comment-anonymous": "Anònim", 28 | "comment-hidden": "1 ocult\n{{ n }} ocults", 29 | "comment-page-author-suffix": "Autor", 30 | 31 | "date-now": "ara", 32 | "date-minute": "fa un minut\nfa {{ n }} minuts", 33 | "date-hour": "fa una hora\nfa {{ n }} hores", 34 | "date-day": "ahir\nfa {{ n }} dies", 35 | "date-week": "la setmana passada\nfa {{ n }} setmanes", 36 | "date-month": "el mes passat\nfa {{ n }} mesos", 37 | "date-year": "l'any passat\nfa {{ n }} anys" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/cs.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Sem napiště svůj komentář (nejméně 3 znaky)", 3 | "postbox-author": "Jméno (nepovinné)", 4 | "postbox-email": "E-mail (nepovinný)", 5 | "postbox-website": "Web (nepovinný)", 6 | "postbox-preview": "Náhled", 7 | "postbox-edit": "Upravit", 8 | "postbox-submit": "Publikovat", 9 | "num-comments": "Jeden komentář\n{{ n }} Komentářů", 10 | "no-comments": "Zatím bez komentářů", 11 | "comment-reply": "Odpovědět", 12 | "comment-edit": "Upravit", 13 | "comment-save": "Uložit", 14 | "comment-delete": "Smazat", 15 | "comment-confirm": "Potvrdit", 16 | "comment-close": "Zavřít", 17 | "comment-cancel": "Zrušit", 18 | "comment-deleted": "Komentář smazán", 19 | "comment-queued": "Komentář ve frontě na schválení", 20 | "comment-anonymous": "Anonym", 21 | "comment-hidden": "{{ n }} skryto", 22 | "date-now": "právě teď", 23 | "date-minute": "před minutou\npřed {{ n }} minutami", 24 | "date-hour": "před hodinou\npřed {{ n }} hodinami", 25 | "date-day": "včera\npřed {{ n }} dny", 26 | "date-week": "minulý týden\npřed {{ n }} týdny", 27 | "date-month": "minulý měsíc\npřed {{ n }} měsíci", 28 | "date-year": "minulý rok\npřed {{ n }} lety" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/da.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Skriv din kommentar her (mindst 3 tegn)", 3 | "postbox-author": "Navn (valgfrit)", 4 | "postbox-email": "E-mail (valgfrit)", 5 | "postbox-website": "Hjemmeside (valgfrit)", 6 | "postbox-preview": "Forhåndsvisning", 7 | "postbox-edit": "Rediger", 8 | "postbox-submit": "Send", 9 | 10 | "num-comments": "En Kommentar\n{{ n }} Kommentarer", 11 | "no-comments": "Ingen kommentarer endnu", 12 | 13 | "comment-reply": "Svar", 14 | "comment-edit": "Rediger", 15 | "comment-save": "Gem", 16 | "comment-delete": "Fjern", 17 | "comment-confirm": "Bekræft", 18 | "comment-close": "Luk", 19 | "comment-cancel": "Annuller", 20 | "comment-deleted": "Kommentar slettet.", 21 | "comment-queued": "Kommentar i kø for moderation.", 22 | "comment-anonymous": "Anonym", 23 | "comment-hidden": "{{ n }} Skjult", 24 | 25 | "date-now": "lige nu", 26 | "date-minute": "et minut siden\n{{ n }} minutter siden", 27 | "date-hour": "en time siden\n{{ n }} timer siden", 28 | "date-day": "Igår\n{{ n }} dage siden", 29 | "date-week": "sidste uge\n{{ n }} uger siden", 30 | "date-month": "sidste måned\n{{ n }} måneder siden", 31 | "date-year": "sidste år\n{{ n }} år siden" 32 | }; 33 | -------------------------------------------------------------------------------- /isso/js/app/i18n/de.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "Kommentar hier eingeben (mindestens 3 Zeichen)", 3 | "postbox-author": "Name (optional)", 4 | "postbox-author-placeholder": "Max Mustermann", 5 | "postbox-email": "E-Mail (optional)", 6 | "postbox-email-placeholder": "mustermann@beispiel.de", 7 | "postbox-website": "Website (optional)", 8 | "postbox-website-placeholder": "https://beispiel.de", 9 | "postbox-preview": "Vorschau", 10 | "postbox-edit": "Bearbeiten", 11 | "postbox-submit": "Abschicken", 12 | "postbox-notification": "wenn auf meinen Kommentar geantwortet wird, möchte ich eine E-Mail bekommen", 13 | 14 | "num-comments": "1 Kommentar\n{{ n }} Kommentare", 15 | "no-comments": "Bisher keine Kommentare", 16 | "atom-feed": "Atom-feed", 17 | 18 | "comment-reply": "Antworten", 19 | "comment-edit": "Bearbeiten", 20 | "comment-save": "Speichern", 21 | "comment-delete": "Löschen", 22 | "comment-confirm": "Bestätigen", 23 | "comment-close": "Schließen", 24 | "comment-cancel": "Abbrechen", 25 | "comment-deleted": "Kommentar gelöscht.", 26 | "comment-queued": "Kommentar muss noch freigeschaltet werden.", 27 | "comment-anonymous": "Anonym", 28 | "comment-hidden": "{{ n }} versteckt", 29 | "comment-page-author-suffix": "Autor", 30 | 31 | "date-now": "eben gerade", 32 | "date-minute": "vor einer Minute\nvor {{ n }} Minuten", 33 | "date-hour": "vor einer Stunde\nvor {{ n }} Stunden", 34 | "date-day": "Gestern\nvor {{ n }} Tagen", 35 | "date-week": "letzte Woche\nvor {{ n }} Wochen", 36 | "date-month": "letzten Monat\nvor {{ n }} Monaten", 37 | "date-year": "letztes Jahr\nvor {{ n }} Jahren" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/el_GR.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Γράψτε το σχόλιο εδώ (τουλάχιστον 3 χαρακτήρες)", 3 | "postbox-author": "Όνομα (προαιρετικό)", 4 | "postbox-email": "E-mail (προαιρετικό)", 5 | "postbox-website": "Ιστοσελίδα (προαιρετικό)", 6 | "postbox-preview": "Πρεμιέρα", 7 | "postbox-edit": "Επεξεργασία", 8 | "postbox-submit": "Υποβολή", 9 | "num-comments": "Ένα σχόλιο\n{{ n }} σχόλια", 10 | "no-comments": "Δεν υπάρχουν σχόλια", 11 | "comment-reply": "Απάντηση", 12 | "comment-edit": "Επεξεργασία", 13 | "comment-save": "Αποθήκευση", 14 | "comment-delete": "Διαγραφή", 15 | "comment-confirm": "Επιβεβαίωση", 16 | "comment-close": "Κλείσιμο", 17 | "comment-cancel": "Ακύρωση", 18 | "comment-deleted": "Διαγραμμένο σχόλιο ", 19 | "comment-queued": "Το σχόλιο αναμένει έγκριση", 20 | "comment-anonymous": "Ανώνυμος", 21 | "comment-hidden": "{{ n }} Κρυμμένα", 22 | "date-now": "τώρα", 23 | "date-minute": "πριν ένα λεπτό\nπριν {{ n }} λεπτά", 24 | "date-hour": "πριν μία ώρα\nπριν {{ n }} ώρες", 25 | "date-day": "Χτες\nπριν {{ n }} μέρες", 26 | "date-week": "την προηγούμενη εβδομάδα\nπριν {{ n }} εβδομάδες", 27 | "date-month": "τον προηγούμενο μήνα\nπριν {{ n }} μήνες", 28 | "date-year": "πέρυσι\nπριν {{ n }} χρόνια" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "Type Comment Here (at least 3 chars)", 3 | "postbox-author": "Name (optional)", 4 | "postbox-author-placeholder": "John Doe", 5 | "postbox-email": "E-mail (optional)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "Website (optional)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "Preview", 10 | "postbox-edit": "Edit", 11 | "postbox-submit": "Submit", 12 | "postbox-notification": "Subscribe to email notification of replies", 13 | 14 | "num-comments": "One Comment\n{{ n }} Comments", 15 | "no-comments": "No Comments Yet", 16 | "atom-feed": "Atom feed", 17 | 18 | "comment-reply": "Reply", 19 | "comment-edit": "Edit", 20 | "comment-save": "Save", 21 | "comment-delete": "Delete", 22 | "comment-confirm": "Confirm", 23 | "comment-close": "Close", 24 | "comment-cancel": "Cancel", 25 | "comment-deleted": "Comment deleted.", 26 | "comment-queued": "Comment in queue for moderation.", 27 | "comment-anonymous": "Anonymous", 28 | "comment-hidden": "{{ n }} Hidden", 29 | "comment-page-author-suffix": "Author", 30 | 31 | "date-now": "right now", 32 | "date-minute": "a minute ago\n{{ n }} minutes ago", 33 | "date-hour": "an hour ago\n{{ n }} hours ago", 34 | "date-day": "Yesterday\n{{ n }} days ago", 35 | "date-week": "last week\n{{ n }} weeks ago", 36 | "date-month": "last month\n{{ n }} months ago", 37 | "date-year": "last year\n{{ n }} years ago" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/eo.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Tajpu komenton ĉi-tie (almenaŭ 3 signoj)", 3 | "postbox-author": "Nomo (malnepra)", 4 | "postbox-email": "Retadreso (malnepra)", 5 | "postbox-website": "Retejo (malnepra)", 6 | "postbox-preview": "Antaŭrigardo", 7 | "postbox-edit": "Redaktu", 8 | "postbox-submit": "Sendu", 9 | "num-comments": "{{ n }} komento\n{{ n }} komentoj", 10 | "no-comments": "Neniu komento ankoraŭ", 11 | "comment-reply": "Respondu", 12 | "comment-edit": "Redaktu", 13 | "comment-save": "Savu", 14 | "comment-delete": "Forviŝu", 15 | "comment-confirm": "Konfirmu", 16 | "comment-close": "Fermu", 17 | "comment-cancel": "Malfaru", 18 | "comment-deleted": "Komento forviŝita", 19 | "comment-queued": "Komento en atendovico por kontrolo.", 20 | "comment-anonymous": "Sennoma", 21 | "comment-hidden": "{{ n }} kaŝitaj", 22 | "date-now": "ĵus nun", 23 | "date-minute": "antaŭ unu minuto\nantaŭ {{ n }} minutoj", 24 | "date-hour": "antaŭ unu horo\nantaŭ {{ n }} horoj", 25 | "date-day": "hieraŭ\nantaŭ {{ n }} tagoj", 26 | "date-week": "lasta semajno\nantaŭ {{ n }} semajnoj", 27 | "date-month": "lasta monato\nantaŭ {{ n }} monatoj", 28 | "date-year": "lasta jaro\nantaŭ {{ n }} jaroj" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/es.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "Escribe tu comentario aquí (al menos 3 caracteres)", 3 | "postbox-author": "Nombre (opcional)", 4 | "postbox-author-placeholder": "John Doe", 5 | "postbox-email": "Correo electrónico (opcional)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "Página web (opcional)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "Vista previa", 10 | "postbox-edit": "Editar", 11 | "postbox-submit": "Enviar", 12 | "postbox-notification": "Suscríbete a las notificaciones por correo electrónico", 13 | 14 | "num-comments": "Un comentario\n{{ n }} comentarios", 15 | "no-comments": "Aún no hay comentarios", 16 | "atom-feed": "Fuente web Atom", 17 | 18 | "comment-reply": "Responder", 19 | "comment-edit": "Editar", 20 | "comment-save": "Guardar", 21 | "comment-delete": "Eliminar", 22 | "comment-confirm": "Confirmar", 23 | "comment-close": "Cerrar", 24 | "comment-cancel": "Cancelar", 25 | "comment-deleted": "Comentario eliminado.", 26 | "comment-queued": "Comentario pendiente de moderación.", 27 | "comment-anonymous": "Anónimo", 28 | "comment-hidden": "1 oculto\n{{ n }} ocultos", 29 | "comment-page-author-suffix": "Autor", 30 | 31 | "date-now": "ahora", 32 | "date-minute": "hace un minuto\nhace {{ n }} minutos", 33 | "date-hour": "hace una hora\nhace {{ n }} horas", 34 | "date-day": "ayer\nhace {{ n }} días", 35 | "date-week": "la semana pasada\nhace {{ n }} semanas", 36 | "date-month": "el mes pasado\nhace {{ n }} meses", 37 | "date-year": "el año pasado\nhace {{ n }} años" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/fa.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "نظر خود را اینجا بنویسید (حداقل سه نویسه)", 3 | "postbox-author": "اسم (اختیاری)", 4 | "postbox-email": "ایمیل (اختیاری)", 5 | "postbox-website": "سایت (اختیاری)", 6 | "postbox-preview": "پیش‌نمایش", 7 | "postbox-edit": "ویرایش", 8 | "postbox-submit": "ارسال", 9 | 10 | "num-comments": "یک نظر\n{{ n }} نظر", 11 | "no-comments": "هنوز نظری نوشته نشده است", 12 | 13 | "comment-reply": "پاسخ", 14 | "comment-edit": "ویرایش", 15 | "comment-save": "ذخیره", 16 | "comment-delete": "حذف", 17 | "comment-confirm": "تایید", 18 | "comment-close": "بستن", 19 | "comment-cancel": "انصراف", 20 | "comment-deleted": "نظر حذف شد.", 21 | "comment-queued": "نظر در صف بررسی مدیر قرار دارد.", 22 | "comment-anonymous": "ناشناس", 23 | "comment-hidden": "{{ n }} مخفی", 24 | 25 | "date-now": "هم اکنون", 26 | "date-minute": "یک دقیقه پیش\n{{ n }} دقیقه پیش", 27 | "date-hour": "یک ساعت پیش\n{{ n }} ساعت پیش", 28 | "date-day": "دیروز\n{{ n }} روز پیش", 29 | "date-week": "یک هفته پیش\n{{ n }} هفته پیش", 30 | "date-month": "یک ماه پیش\n{{ n }} ماه پیش", 31 | "date-year": "یک سال پیش\n{{ n }} سال پیش" 32 | }; 33 | -------------------------------------------------------------------------------- /isso/js/app/i18n/fi.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Kirjoita kommentti tähän (vähintään 3 merkkiä)", 3 | "postbox-author": "Nimi (valinnainen)", 4 | "postbox-email": "Sähköposti (valinnainen)", 5 | "postbox-website": "Web-sivu (valinnainen)", 6 | "postbox-preview": "Esikatselu", 7 | "postbox-edit": "Muokkaa", 8 | "postbox-submit": "Lähetä", 9 | 10 | "num-comments": "Yksi kommentti\n{{ n }} kommenttia", 11 | "no-comments": "Ei vielä kommentteja", 12 | 13 | "comment-reply": "Vastaa", 14 | "comment-edit": "Muokkaa", 15 | "comment-save": "Tallenna", 16 | "comment-delete": "Poista", 17 | "comment-confirm": "Vahvista", 18 | "comment-close": "Sulje", 19 | "comment-cancel": "Peru", 20 | "comment-deleted": "Kommentti on poistettu.", 21 | "comment-queued": "Kommentti on laitettu jonoon odottamaan moderointia.", 22 | "comment-anonymous": "Nimetön", 23 | "comment-hidden": "{{ n }} piilotettua", 24 | 25 | "date-now": "hetki sitten", 26 | "date-minute": "minuutti sitten\n{{ n }} minuuttia sitten", 27 | "date-hour": "tunti sitten\n{{ n }} tuntia sitten", 28 | "date-day": "eilen\n{{ n }} päivää sitten", 29 | "date-week": "viime viikolla\n{{ n }} viikkoa sitten", 30 | "date-month": "viime kuussa\n{{ n }} kuukautta sitten", 31 | "date-year": "viime vuonna\n{{ n }} vuotta sitten" 32 | }; 33 | -------------------------------------------------------------------------------- /isso/js/app/i18n/fr.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Insérez votre commentaire ici (au moins 3 lettres)", 3 | "postbox-author": "Nom (optionnel)", 4 | "postbox-email": "Courriel (optionnel)", 5 | "postbox-website": "Site web (optionnel)", 6 | "postbox-preview": "Aperçu", 7 | "postbox-edit": "Éditer", 8 | "postbox-submit": "Soumettre", 9 | "postbox-notification": "S’abonner aux notifications de réponses", 10 | "num-comments": "{{ n }} commentaire\n{{ n }} commentaires", 11 | "no-comments": "Aucun commentaire pour l’instant", 12 | "atom-feed": "Flux Atom", 13 | "comment-reply": "Répondre", 14 | "comment-edit": "Éditer", 15 | "comment-save": "Enregistrer", 16 | "comment-delete": "Supprimer", 17 | "comment-confirm": "Confirmer", 18 | "comment-close": "Fermer", 19 | "comment-cancel": "Annuler", 20 | "comment-deleted": "Commentaire supprimé.", 21 | "comment-queued": "Commentaire en attente de modération.", 22 | "comment-anonymous": "Anonyme", 23 | "comment-hidden": "1 caché\n{{ n }} cachés", 24 | "date-now": "À l’instant", 25 | "date-minute": "Il y a une minute\nIl y a {{ n }} minutes", 26 | "date-hour": "Il y a une heure\nIl y a {{ n }} heures ", 27 | "date-day": "Hier\nIl y a {{ n }} jours", 28 | "date-week": "Il y a une semaine\nIl y a {{ n }} semaines", 29 | "date-month": "Il y a un mois\nIl y a {{ n }} mois", 30 | "date-year": "Il y a un an\nIl y a {{ n }} ans" 31 | }; 32 | -------------------------------------------------------------------------------- /isso/js/app/i18n/hr.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Napiši komentar ovdje (najmanje 3 znaka)", 3 | "postbox-author": "Ime (neobavezno)", 4 | "postbox-email": "E-mail (neobavezno)", 5 | "postbox-website": "Web stranica (neobavezno)", 6 | "postbox-preview": "Pregled", 7 | "postbox-edit": "Uredi", 8 | "postbox-submit": "Pošalji", 9 | "num-comments": "Jedan komentar\n{{ n }} komentara", 10 | "no-comments": "Još nema komentara", 11 | "comment-reply": "Odgovori", 12 | "comment-edit": "Uredi", 13 | "comment-save": "Spremi", 14 | "comment-delete": "Obriši", 15 | "comment-confirm": "Potvrdi", 16 | "comment-close": "Zatvori", 17 | "comment-cancel": "Odustani", 18 | "comment-deleted": "Komentar obrisan", 19 | "comment-queued": "Komentar u redu za provjeru.", 20 | "comment-anonymous": "Anonimno", 21 | "comment-hidden": "{{ n }} Skrivenih", 22 | "date-now": "upravo", 23 | "date-minute": "prije minutu\nprije {{ n }} minuta", 24 | "date-hour": "prije sat vremena\nprije {{ n }} sati", 25 | "date-day": "jučer\nprije {{ n }} dana", 26 | "date-week": "prošli tjedan\nprije {{ n }} tjedana", 27 | "date-month": "prošli mjesec\nprije {{ n }} mjeseci", 28 | "date-year": "prošle godine\nprije {{ n }} godina" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/hu.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Hozzászólást ide írd be (legalább 3 betűt)", 3 | "postbox-author": "Név (nem kötelező)", 4 | "postbox-email": "Email (nem kötelező)", 5 | "postbox-website": "Website (nem kötelező)", 6 | "postbox-preview": "Előnézet", 7 | "postbox-edit": "Szerekesztés", 8 | "postbox-submit": "Elküld", 9 | "num-comments": "Egy hozzászólás\n{{ n }} hozzászólás", 10 | "no-comments": "Eddig nincs hozzászólás", 11 | "comment-reply": "Válasz", 12 | "comment-edit": "Szerekesztés", 13 | "comment-save": "Mentés", 14 | "comment-delete": "Törlés", 15 | "comment-confirm": "Megerősít", 16 | "comment-close": "Bezár", 17 | "comment-cancel": "Törlés", 18 | "comment-deleted": "Hozzászólás törölve.", 19 | "comment-queued": "A hozzászólást előbb ellenőrizzük.", 20 | "comment-anonymous": "Névtelen", 21 | "comment-hidden": "{{ n }} rejtve", 22 | "date-now": "pillanatokkal ezelőtt", 23 | "date-minute": "egy perce\n{{ n }} perce", 24 | "date-hour": "egy órája\n{{ n }} órája", 25 | "date-day": "tegnap\n{{ n }} napja", 26 | "date-week": "múlt héten\n{{ n }} hete", 27 | "date-month": "múlt hónapban\n{{ n }} hónapja", 28 | "date-year": "tavaly\n{{ n }} éve" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/it.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Scrivi un commento qui (minimo 3 caratteri)", 3 | "postbox-author": "Nome (opzionale)", 4 | "postbox-email": "E-mail (opzionale)", 5 | "postbox-website": "Sito web (opzionale)", 6 | "postbox-preview": "Anteprima", 7 | "postbox-edit": "Modifica", 8 | "postbox-submit": "Invia", 9 | "num-comments": "Un Commento\n{{ n }} Commenti", 10 | "no-comments": "Ancora Nessun Commento", 11 | "comment-reply": "Rispondi", 12 | "comment-edit": "Modifica", 13 | "comment-save": "Salva", 14 | "comment-delete": "Elimina", 15 | "comment-confirm": "Conferma", 16 | "comment-close": "Chiudi", 17 | "comment-cancel": "Cancella", 18 | "comment-deleted": "Commento eliminato.", 19 | "comment-queued": "Commento in coda per moderazione.", 20 | "comment-anonymous": "Anonimo", 21 | "comment-hidden": "{{ n }} Nascosto", 22 | "date-now": "poco fa", 23 | "date-minute": "un minuto fa\n{{ n }} minuti fa", 24 | "date-hour": "un ora fa\n{{ n }} ore fa", 25 | "date-day": "Ieri\n{{ n }} giorni fa", 26 | "date-week": "questa settimana\n{{ n }} settimane fa", 27 | "date-month": "questo mese\n{{ n }} mesi fa", 28 | "date-year": "quest'anno\n{{ n }} anni fa" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/ja.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "コメントを入力してください (3文字以上)", 3 | "postbox-author": "名前 (任意)", 4 | "postbox-author-placeholder": "John Doe", 5 | "postbox-email": "E-mail (任意)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "ウェブサイト (任意)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "プレビュー", 10 | "postbox-edit": "編集", 11 | "postbox-submit": "送信", 12 | "postbox-notification": "返信があった場合にメールで通知する", 13 | 14 | "num-comments": "コメント 1件\nコメント {{ n }}件", 15 | "no-comments": "まだコメントはありません", 16 | "atom-feed": "Atomフィード", 17 | 18 | "comment-reply": "返信", 19 | "comment-edit": "編集", 20 | "comment-save": "保存", 21 | "comment-delete": "削除", 22 | "comment-confirm": "確認", 23 | "comment-close": "閉じる", 24 | "comment-cancel": "キャンセル", 25 | "comment-deleted": "コメントは削除されました", 26 | "comment-queued": "コメントは承認待ちです", 27 | "comment-anonymous": "名無し", 28 | "comment-hidden": "{{ n }}件 非表示", 29 | "comment-page-author-suffix": "管理人", 30 | 31 | "date-now": "たった今", 32 | "date-minute": "1分前\n{{ n }}分前", 33 | "date-hour": "1時間前\n{{ n }}時間前", 34 | "date-day": "昨日\n{{ n }}日前", 35 | "date-week": "先週\n{{ n }}週間前", 36 | "date-month": "先月\n{{ n }}ヶ月前", 37 | "date-year": "1年前\n{{ n }}年前" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/ko.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "여기에 댓글을 입력해주세요(최소 3문자 이상)", 3 | "postbox-author": "이름 (선택)", 4 | "postbox-email": "이메일 (선택)", 5 | "postbox-website": "웹사이트 (선택)", 6 | "postbox-preview": "미리보기", 7 | "postbox-edit": "수정", 8 | "postbox-submit": "댓글쓰기", 9 | "postbox-notification": "댓글이 달리면 이메일로 알립니다", 10 | 11 | "num-comments": "한 개의 댓글\n{{ n }} 개의 댓글", 12 | "no-comments": "아직 댓글이 없습니다", 13 | "atom-feed": "Atom 피드", 14 | 15 | "comment-reply": "댓글", 16 | "comment-edit": "수정", 17 | "comment-save": "저장", 18 | "comment-delete": "삭제", 19 | "comment-confirm": "확인", 20 | "comment-close": "닫기", 21 | "comment-cancel": "취소", 22 | "comment-deleted": "댓글이 삭제됨.", 23 | "comment-queued": "검토 대기 중인 댓글.", 24 | "comment-anonymous": "익명", 25 | "comment-hidden": "{{ n }} 개의 숨김 댓글", 26 | 27 | "date-now": "방금 전", 28 | "date-minute": "1 분 전\n{{ n }} 분 전", 29 | "date-hour": "1 시간 전\n{{ n }} 시간 전", 30 | "date-day": "어제\n{{ n }} 일 전", 31 | "date-week": "저번 주\n{{ n }} 주 전", 32 | "date-month": "저번 달\n{{ n }} 개월 전", 33 | "date-year": "작년\n{{ n }} 년 전" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/nl.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Typ reactie hier (minstens 3 karakters)", 3 | "postbox-author": "Naam (optioneel)", 4 | "postbox-email": "E-mail (optioneel)", 5 | "postbox-website": "Website (optioneel)", 6 | "postbox-preview": "Voorbeeld", 7 | "postbox-edit": "Bewerken", 8 | "postbox-submit": "Versturen", 9 | "num-comments": "Één reactie\n{{ n }} reacties", 10 | "no-comments": "Nog geen reacties", 11 | "comment-reply": "Beantwoorden", 12 | "comment-edit": "Bewerken", 13 | "comment-save": "Opslaan", 14 | "comment-delete": "Verwijderen", 15 | "comment-confirm": "Bevestigen", 16 | "comment-close": "Sluiten", 17 | "comment-cancel": "Annuleren", 18 | "comment-deleted": "Reactie verwijderd.", 19 | "comment-queued": "Reactie staat in de wachtrij voor goedkeuring.", 20 | "comment-anonymous": "Anoniem", 21 | "comment-hidden": "{{ n }} verborgen", 22 | "date-now": "zojuist", 23 | "date-minute": "een minuut geleden\n{{ n }} minuten geleden", 24 | "date-hour": "een uur geleden\n{{ n }} uur geleden", 25 | "date-day": "gisteren\n{{ n }} dagen geleden", 26 | "date-week": "vorige week\n{{ n }} weken geleden", 27 | "date-month": "vorige maand\n{{ n }} maanden geleden", 28 | "date-year": "vorig jaar\n{{ n }} jaar geleden" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/oc.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Escriure lo comentari aquí (almens 3 caractèrs)", 3 | "postbox-author": "Nom (opcional)", 4 | "postbox-email": "Corrièl (opcional)", 5 | "postbox-website": "Site web (opcional)", 6 | "postbox-preview": "Apercebut", 7 | "postbox-edit": "Modificar", 8 | "postbox-submit": "Enviar", 9 | "postbox-notification": "S'abonar per corrièl a las notificacions de responsas", 10 | 11 | "num-comments": "Un comentari\n{{ n }} comentaris", 12 | "no-comments": "Cap de comentari pel moment", 13 | "atom-feed": "Flux Atom", 14 | 15 | "comment-reply": "Respondre", 16 | "comment-edit": "Modificar", 17 | "comment-save": "Salvar", 18 | "comment-delete": "Suprimir", 19 | "comment-confirm": "Confirmar", 20 | "comment-close": "Tampar", 21 | "comment-cancel": "Anullar", 22 | "comment-deleted": "Comentari suprimit.", 23 | "comment-queued": "Comentari en espèra de moderacion.", 24 | "comment-anonymous": "Anonim", 25 | "comment-hidden": "1 rescondut\n{{ n }} resconduts", 26 | 27 | "date-now": "ara meteis", 28 | "date-minute": "fa una minuta \nfa {{ n }} minutas", 29 | "date-hour": "fa una ora\nfa {{ n }} oras", 30 | "date-day": "Ièr\nfa {{ n }} jorns", 31 | "date-week": "la setmana passada\nfa {{ n }} setmanas", 32 | "date-month": "lo mes passat\nfa {{ n }} meses", 33 | "date-year": "l'an passat\nfa {{ n }} ans" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/pl.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Tutaj wpisz komentarz (co najmniej 3 znaki)", 3 | "postbox-author": "Imię/nick (opcjonalnie)", 4 | "postbox-email": "E-mail (opcjonalnie)", 5 | "postbox-website": "Strona (opcjonalnie)", 6 | "postbox-preview": "Podgląd", 7 | "postbox-edit": "Edytuj", 8 | "postbox-submit": "Wyślij", 9 | "postbox-notification": "Otrzymuj powiadomienia o odpowiedziach na e-mail", 10 | 11 | "num-comments": "Jeden komentarz\n{{ n }} komentarze\n{{ n }} komentarzy", 12 | "no-comments": "Nie ma jeszcze komentarzy", 13 | "atom-feed": "Kanał Atom", 14 | 15 | "comment-reply": "Odpowiedz", 16 | "comment-edit": "Edytuj", 17 | "comment-save": "Zapisz", 18 | "comment-delete": "Usuń", 19 | "comment-confirm": "Potwierdź", 20 | "comment-close": "Zamknij", 21 | "comment-cancel": "Anuluj", 22 | "comment-deleted": "Komentarz usunięty.", 23 | "comment-queued": "Komentarz w kolejce do moderacji.", 24 | "comment-anonymous": "Anonim", 25 | "comment-hidden": "{{ n }} ukryty\n{{ n }} ukryte\n{{ n }} ukrytych", 26 | 27 | "date-now": "teraz", 28 | "date-minute": "minutę temu\n{{ n }} minuty temu\n{{ n }} minut temu", 29 | "date-hour": "godzinę temu\n{{ n }} godziny temu\n{{ n }} godzin temu", 30 | "date-day": "wczoraj\n{{ n }} dni temu", 31 | "date-week": "w ubiegłym tygodniu\n{{ n }} tygodnie temu\n{{ n }} tygodni temu", 32 | "date-month": "w ubiegłym miesiącu\n{{ n }} miesiące temu\n{{ n }} miesięcy temu", 33 | "date-year": "w ubiegłym roku\n{{ n }} lata temu\n{{ n }} lat temu" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/pt_BR.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Digite seu comentário aqui (pelo menos 3 letras)", 3 | "postbox-author": "Nome (opcional)", 4 | "postbox-email": "E-mail (opcional)", 5 | "postbox-website": "Website (opcional)", 6 | "postbox-preview": "Prévia", 7 | "postbox-edit": "Editar", 8 | "postbox-submit": "Enviar", 9 | "postbox-notification": "Receber emails de notificação de respostas", 10 | 11 | "num-comments": "Um Comentário\n{{ n }} Comentários", 12 | "no-comments": "Nenhum comentário ainda", 13 | "atom-feed": "Feed Atom", 14 | 15 | "comment-reply": "Responder", 16 | "comment-edit": "Editar", 17 | "comment-save": "Salvar", 18 | "comment-delete": "Excluir", 19 | "comment-confirm": "Confirmar", 20 | "comment-close": "Fechar", 21 | "comment-cancel": "Cancelar", 22 | "comment-deleted": "Comentário apagado.", 23 | "comment-queued": "Comentário na fila de moderação.", 24 | "comment-anonymous": "Anônimo", 25 | "comment-hidden": "{{ n }} Oculto(s)", 26 | 27 | "date-now": "agora mesmo", 28 | "date-minute": "um minuto atrás\n{{ n }} minutos atrás", 29 | "date-hour": "uma hora atrás\n{{ n }} horas atrás", 30 | "date-day": "ontem\n{{ n }} dias", 31 | "date-week": "semana passada\n{{ n }} semanas atrás", 32 | "date-month": "mês passado\n{{ n }} meses atrás", 33 | "date-year": "ano passado\n{{ n }} anos atrás" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/pt_PT.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Escreva o seu comentário aqui (pelo menos 3 letras)", 3 | "postbox-author": "Nome (opcional)", 4 | "postbox-email": "E-mail (opcional)", 5 | "postbox-website": "Website (opcional)", 6 | "postbox-preview": "Testar", 7 | "postbox-edit": "Editar", 8 | "postbox-submit": "Enviar", 9 | "postbox-notification": "Receber emails de notificação de respostas", 10 | 11 | "num-comments": "Um Comentário\n{{ n }} Comentários", 12 | "no-comments": "Nenhum comentário ainda", 13 | "atom-feed": "Feed Atom", 14 | 15 | "comment-reply": "Responder", 16 | "comment-edit": "Editar", 17 | "comment-save": "Guardar", 18 | "comment-delete": "Excluir", 19 | "comment-confirm": "Confirmar", 20 | "comment-close": "Fechar", 21 | "comment-cancel": "Cancelar", 22 | "comment-deleted": "Comentário apagado.", 23 | "comment-queued": "Comentário na fila de moderação.", 24 | "comment-anonymous": "Anónimo", 25 | "comment-hidden": "{{ n }} Oculto(s)", 26 | 27 | "date-now": "agora mesmo", 28 | "date-minute": "um minuto atrás\n{{ n }} minutos atrás", 29 | "date-hour": "uma hora atrás\n{{ n }} horas atrás", 30 | "date-day": "ontem\n{{ n }} dias", 31 | "date-week": "semana passada\n{{ n }} semanas atrás", 32 | "date-month": "mês passado\n{{ n }} meses atrás", 33 | "date-year": "ano passado\n{{ n }} anos atrás" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/ru.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Оставить комментарий (минимум 3 символа)", 3 | "postbox-author": "Имя (необязательно)", 4 | "postbox-email": "Email (необязательно)", 5 | "postbox-website": "Сайт (необязательно)", 6 | "postbox-preview": "Предпросмотр", 7 | "postbox-edit": "Правка", 8 | "postbox-submit": "Отправить", 9 | "postbox-notification": "Подписаться на уведомление об ответах", 10 | "num-comments": "{{ n }} комментарий\n{{ n }} комментария\n{{ n }} комментариев", 11 | "no-comments": "Пока нет комментариев", 12 | "comment-reply": "Ответить", 13 | "comment-edit": "Правка", 14 | "comment-save": "Сохранить", 15 | "comment-delete": "Удалить", 16 | "comment-confirm": "Подтвердить удаление", 17 | "comment-close": "Закрыть", 18 | "comment-cancel": "Отменить", 19 | "comment-deleted": "Комментарий удалён", 20 | "comment-queued": "Комментарий будет проверен модератором", 21 | "comment-anonymous": "Аноним", 22 | "comment-hidden": "Скрыт {{ n }} комментарий\nСкрыто {{ n }} комментария\nСкрыто {{ n }} комментариев", 23 | "date-now": "Только что", 24 | "date-minute": "{{ n }} минуту назад\n{{ n }} минуты назад\n{{ n }} минут назад", 25 | "date-hour": "{{ n }} час назад\n{{ n }} часа назад\n{{ n }} часов назад", 26 | "date-day": "{{ n }} день назад\n{{ n }} дня назад\n{{ n }} дней назад", 27 | "date-week": "{{ n }} неделю назад\n{{ n }} недели назад\n{{ n }} недель назад", 28 | "date-month": "{{ n }} месяц назад\n{{ n }} месяца назад\n{{ n }} месяцев назад", 29 | "date-year": "{{ n }} год назад\n{{ n }} года назад\n{{ n }} лет назад" 30 | }; 31 | -------------------------------------------------------------------------------- /isso/js/app/i18n/sk.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Sem napíšte svoj komentár (minimálne 3 znaky)", 3 | "postbox-author": "Meno (nepovinné)", 4 | "postbox-email": "E-mail (nepovinný)", 5 | "postbox-website": "Web (nepovinný)", 6 | "postbox-preview": "Náhľad", 7 | "postbox-edit": "Upraviť", 8 | "postbox-submit": "Publikovať", 9 | "num-comments": "Jeden komentár\n{{ n }} komentáre\n{{ n }} komentárov", 10 | "no-comments": "Zatiaľ bez komentárov", 11 | "comment-reply": "Odpovedať", 12 | "comment-edit": "Upraviť", 13 | "comment-save": "Uložiť", 14 | "comment-delete": "Zmazať", 15 | "comment-confirm": "Potvrdit", 16 | "comment-close": "Zavrieť", 17 | "comment-cancel": "Zrušiť", 18 | "comment-deleted": "Komentár bol vymazaný", 19 | "comment-queued": "Komentár zaradený na schválenie", 20 | "comment-anonymous": "Anonym", 21 | "comment-hidden": "{{ n }} skrytý\n{{ n }} skryté\n{{ n }} skrytých", 22 | "date-now": "práve teraz", 23 | "date-minute": "pred minútou\npred {{ n }} minútami", 24 | "date-hour": "pred hodinou\npred {{ n }} hodinami", 25 | "date-day": "včera\npred {{ n }} dňami", 26 | "date-week": "minulý týždeň\npred {{ n }} týždňami", 27 | "date-month": "minulý mesiac\npred {{ n }} mesiacmi", 28 | "date-year": "minulý rok\npred {{ n }} rokmi" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/sv.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Skriv din kommentar här (minst 3 tecken)", 3 | "postbox-author": "Namn (frivilligt)", 4 | "postbox-email": "E-mail (frivilligt)", 5 | "postbox-website": "Hemsida (frivilligt)", 6 | "postbox-preview": "Förhandsvisning", 7 | "postbox-edit": "Redigera", 8 | "postbox-submit": "Skicka", 9 | "num-comments": "En kommentar\n{{ n }} kommentarer", 10 | "no-comments": "Inga kommentarer än", 11 | "comment-reply": "Svara", 12 | "comment-edit": "Redigera", 13 | "comment-save": "Spara", 14 | "comment-delete": "Radera", 15 | "comment-confirm": "Bekräfta", 16 | "comment-close": "Stäng", 17 | "comment-cancel": "Avbryt", 18 | "comment-deleted": "Kommentar raderad.", 19 | "comment-queued": "Kommentaren inväntar granskning.", 20 | "comment-anonymous": "Anonym", 21 | "comment-hidden": "{{ n }} Gömd", 22 | "date-now": "just nu", 23 | "date-minute": "en minut sedan\n{{ n }} minuter sedan", 24 | "date-hour": "en timme sedan\n{{ n }} timmar sedan", 25 | "date-day": "igår\n{{ n }} dagar sedan", 26 | "date-week": "förra veckan\n{{ n }} veckor sedan", 27 | "date-month": "förra månaden\n{{ n }} månader sedan", 28 | "date-year": "förra året\n{{ n }} år sedan" 29 | }; 30 | -------------------------------------------------------------------------------- /isso/js/app/i18n/tr.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Yorumunuzu buraya yazın (en az üç karakter)", 3 | "postbox-author": "İsim (zorunlu değil)", 4 | "postbox-email": "E-posta (zorunlu değil)", 5 | "postbox-website": "Web sitesi (zorunlu değil)", 6 | "postbox-preview": "Ön izleme", 7 | "postbox-edit": "Düzenle", 8 | "postbox-submit": "Gönder", 9 | "postbox-notification": "Yanıtlar için e-posta bildirimlerine abone ol", 10 | 11 | "num-comments": "Bir yorum\n{{ n }} yorum", 12 | "no-comments": "Henüz yorum yok", 13 | "atom-feed": "Atom akışı", 14 | 15 | "comment-reply": "Yanıtla", 16 | "comment-edit": "Düzenle", 17 | "comment-save": "Kaydet", 18 | "comment-delete": "Sil", 19 | "comment-confirm": "Onayla", 20 | "comment-close": "Kapat", 21 | "comment-cancel": "İptal", 22 | "comment-deleted": "Yorum silindi.", 23 | "comment-queued": "Yorumunuz yönetici onayını bekliyor.", 24 | "comment-anonymous": "Anonim", 25 | "comment-hidden": "{{ n }} gizli", 26 | 27 | "date-now": "şimdi", 28 | "date-minute": "bir dakika önce\n{{ n }} dakika önce", 29 | "date-hour": "bir saat önce\n{{ n }} saat önce", 30 | "date-day": "dün\n{{ n }} gün önce", 31 | "date-week": "geçen hafta\n{{ n }} hafta önce", 32 | "date-month": "geçen ay\n{{ n }} ay önce", 33 | "date-year": "geçen yıl\n{{ n }} yıl önce" 34 | }; 35 | -------------------------------------------------------------------------------- /isso/js/app/i18n/uk.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "postbox-text": "Введіть коментар тут (принаймні 3 символи)", 3 | "postbox-author": "Ім'я (необов'язково)", 4 | "postbox-author-placeholder": "John Doe", 5 | "postbox-email": "Пошта (необов'язково)", 6 | "postbox-email-placeholder": "johndoe@example.com", 7 | "postbox-website": "Веб-сайт (необов'язково)", 8 | "postbox-website-placeholder": "https://example.com", 9 | "postbox-preview": "Прев'ю", 10 | "postbox-edit": "Редагувати", 11 | "postbox-submit": "Відправити", 12 | "postbox-notification": "Підписатися на повідомлення про відповіді поштою", 13 | 14 | "num-comments": "{{ n }} коментар\n{{ n }} коментарі\n{{ n }} коментарів", 15 | "no-comments": "Коментарів поки нема", 16 | "atom-feed": "Атомна стрічка", 17 | 18 | "comment-reply": "Відповісти", 19 | "comment-edit": "Редагувати", 20 | "comment-save": "Зберегти", 21 | "comment-delete": "Видалити", 22 | "comment-confirm": "Підтвердити", 23 | "comment-close": "Закрити", 24 | "comment-cancel": "Скасувати", 25 | "comment-deleted": "Коментар видалено.", 26 | "comment-queued": "Коментар в черзі на модерацію.", 27 | "comment-anonymous": "Анонімний", 28 | "comment-hidden": "{{ n }} Прихований", 29 | "comment-page-author-suffix": "Автор", 30 | 31 | "date-now": "прямо зараз", 32 | "date-minute": "хвилину тому\n{{ n }} хвилини тому\n{{ n }}хвилин тому", 33 | "date-hour": "годину тому\n{{ n }} години тому\n{{ n }} годин тому", 34 | "date-day": "Вчора\n{{ n }} дні тому\n{{ n }} днів тому", 35 | "date-week": "минулого тижня\n{{ n }} тижні тому\n{{ n }} тижнів тому", 36 | "date-month": "останній місяць\n{{ n }} місяці тому\n{{ n }} місяців тому", 37 | "date-year": "минулого року\n{{ n }} роки тому\n{{ n }} років тому" 38 | }; 39 | -------------------------------------------------------------------------------- /isso/js/app/i18n/vi.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "Nhập bình luận tại đây (tối thiểu 3 ký tự)", 3 | "postbox-author": "Tên (tùy chọn)", 4 | "postbox-email": "E-mail (tùy chọn)", 5 | "postbox-website": "Website (tùy chọn)", 6 | "postbox-preview": "Xem trước", 7 | "postbox-edit": "Sửa", 8 | "postbox-submit": "Gửi", 9 | "postbox-notification": "Nhận thông báo email cho các bình luận phản hồi", 10 | 11 | "num-comments": "Một bình luận\n{{ n }} bình luận", 12 | "no-comments": "Chưa có bình luận nào", 13 | 14 | "comment-reply": "Trả lời", 15 | "comment-edit": "Sửa", 16 | "comment-save": "Lưu", 17 | "comment-delete": "Xóa", 18 | "comment-confirm": "Xác nhận", 19 | "comment-close": "Đóng", 20 | "comment-cancel": "Hủy", 21 | "comment-deleted": "Đã xóa bình luận.", 22 | "comment-queued": "Bình luận đang chờ duyệt", 23 | "comment-anonymous": "Nặc danh", 24 | "comment-hidden": "{{ n }} đã ẩn", 25 | 26 | "date-now": "vừa mới", 27 | "date-minute": "một phút trước\n{{ n }} phút trước", 28 | "date-hour": "một giờ trước\n{{ n }} giờ trước", 29 | "date-day": "Hôm qua\n{{ n }} ngày trước", 30 | "date-week": "Tuần qua\n{{ n }} tuần trước", 31 | "date-month": "Tháng trước\n{{ n }} tháng trước", 32 | "date-year": "Năm trước\n{{ n }} năm trước" 33 | }; 34 | -------------------------------------------------------------------------------- /isso/js/app/i18n/zh_CN.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "在此输入评论 (最少 3 个字符)", 3 | "postbox-author": "名字 (可选)", 4 | "postbox-email": "电子邮箱 (可选)", 5 | "postbox-website": "网站 (可选)", 6 | "postbox-preview": "预览", 7 | "postbox-edit": "编辑", 8 | "postbox-submit": "提交", 9 | "postbox-notification": "有新回复时发送邮件通知", 10 | 11 | "num-comments": "1 条评论\n{{ n }} 条评论", 12 | "no-comments": "还没有评论", 13 | 14 | "comment-reply": "回复", 15 | "comment-edit": "编辑", 16 | "comment-save": "保存", 17 | "comment-delete": "删除", 18 | "comment-confirm": "确认", 19 | "comment-close": "关闭", 20 | "comment-cancel": "取消", 21 | "comment-deleted": "评论已删除.", 22 | "comment-queued": "评论待审核.", 23 | "comment-anonymous": "匿名", 24 | "comment-hidden": "{{ n }} 条评论已隐藏", 25 | 26 | "date-now": "刚刚", 27 | "date-minute": "1 分钟前\n{{ n }} 分钟前", 28 | "date-hour": "1 小时前\n{{ n }} 小时前", 29 | "date-day": "昨天\n{{ n }} 天前", 30 | "date-week": "上周\n{{ n }} 周前", 31 | "date-month": "上个月\n{{ n }} 个月前", 32 | "date-year": "去年\n{{ n }} 年前" 33 | }; 34 | -------------------------------------------------------------------------------- /isso/js/app/i18n/zh_TW.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | "postbox-text": "在此輸入留言 (至少 3 個字元)", 3 | "postbox-author": "名稱 (非必填)", 4 | "postbox-email": "電子信箱 (非必填)", 5 | "postbox-website": "個人網站 (非必填)", 6 | "postbox-preview": "預覽", 7 | "postbox-edit": "編輯", 8 | "postbox-submit": "送出", 9 | "postbox-notification": "訂閱回覆的電子郵件通知", 10 | 11 | "num-comments": "1 則留言\n{{ n }} 則留言", 12 | "no-comments": "尚無留言", 13 | 14 | "comment-reply": "回覆", 15 | "comment-edit": "編輯", 16 | "comment-save": "儲存", 17 | "comment-delete": "刪除", 18 | "comment-confirm": "確認", 19 | "comment-close": "關閉", 20 | "comment-cancel": "取消", 21 | "comment-deleted": "留言已刪", 22 | "comment-queued": "留言待審", 23 | "comment-anonymous": "匿名", 24 | "comment-hidden": "{{ n }} 則隱藏留言", 25 | 26 | "date-now": "剛剛", 27 | "date-minute": "1 分鐘前\n{{ n }} 分鐘前", 28 | "date-hour": "1 小時前\n{{ n }} 小時前", 29 | "date-day": "昨天\n{{ n }} 天前", 30 | "date-week": "上週\n{{ n }} 週前", 31 | "date-month": "上個月\n{{ n }} 個月前", 32 | "date-year": "去年\n{{ n }} 年前" 33 | }; 34 | -------------------------------------------------------------------------------- /isso/js/app/lib/promise.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var stderr = function(text) { console.log(text); }; 4 | 5 | var Promise = function() { 6 | this.success = []; 7 | this.errors = []; 8 | }; 9 | 10 | Promise.prototype.then = function(onSuccess, onError) { 11 | this.success.push(onSuccess); 12 | if (onError) { 13 | this.errors.push(onError); 14 | } else { 15 | this.errors.push(stderr); 16 | } 17 | }; 18 | 19 | var Defer = function() { 20 | this.promise = new Promise(); 21 | }; 22 | 23 | Defer.prototype = { 24 | promise: Promise, 25 | resolve: function(rv) { 26 | this.promise.success.forEach(function(callback) { 27 | window.setTimeout(function() { 28 | callback(rv); 29 | }, 0); 30 | }); 31 | }, 32 | 33 | reject: function(error) { 34 | this.promise.errors.forEach(function(callback) { 35 | window.setTimeout(function() { 36 | callback(error); 37 | }, 0); 38 | }); 39 | } 40 | }; 41 | 42 | var when = function(obj, func) { 43 | if (obj instanceof Promise) { 44 | return obj.then(func); 45 | } else { 46 | return func(obj); 47 | } 48 | }; 49 | 50 | var defer = function() { return new Defer(); }; 51 | 52 | module.exports = { 53 | defer: defer, 54 | when: when, 55 | }; 56 | -------------------------------------------------------------------------------- /isso/js/app/lib/ready.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var loaded = false; 4 | var once = function(callback) { 5 | if (! loaded) { 6 | loaded = true; 7 | callback(); 8 | } 9 | }; 10 | 11 | var domready = function(callback) { 12 | 13 | // HTML5 standard to listen for dom readiness 14 | document.addEventListener('DOMContentLoaded', function() { 15 | once(callback); 16 | }); 17 | 18 | // if dom is already ready, just run callback 19 | if (document.readyState === "interactive" || document.readyState === "complete" ) { 20 | once(callback); 21 | } 22 | }; 23 | 24 | module.exports = domready; 25 | -------------------------------------------------------------------------------- /isso/js/app/svg.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "arrow-down": require("app/svg/arrow-down.svg"), 3 | "arrow-up": require("app/svg/arrow-up.svg"), 4 | }; 5 | -------------------------------------------------------------------------------- /isso/js/app/svg/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /isso/js/app/svg/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /isso/js/app/template.js: -------------------------------------------------------------------------------- 1 | var utils = require("app/utils"); 2 | 3 | var tmpl_postbox = require("app/templates/postbox"); 4 | var tmpl_comment = require("app/templates/comment"); 5 | var tmpl_comment_loader = require("app/templates/comment-loader"); 6 | 7 | "use strict"; 8 | 9 | var globals = {}, 10 | templates = {}; 11 | 12 | var load_tmpl = function(name, tmpl) { 13 | templates[name] = tmpl; 14 | }; 15 | 16 | var set = function(name, value) { 17 | globals[name] = value; 18 | }; 19 | 20 | load_tmpl("postbox", tmpl_postbox); 21 | load_tmpl("comment", tmpl_comment); 22 | load_tmpl("comment-loader", tmpl_comment_loader); 23 | 24 | set("humanize", function(date) { 25 | if (typeof date !== "object") { 26 | date = new Date(parseInt(date, 10) * 1000); 27 | } 28 | 29 | return date.toString(); 30 | }); 31 | set("datetime", function(date) { 32 | if (typeof date !== "object") { 33 | date = new Date(parseInt(date, 10) * 1000); 34 | } 35 | 36 | return [ 37 | date.getUTCFullYear(), 38 | // getUTCMonth returns zero-based month! 39 | utils.pad(date.getUTCMonth() + 1, 2), 40 | // getUTCDay returns day of week, not month! 41 | utils.pad(date.getUTCDate(), 2) 42 | ].join("-") + "T" + [ 43 | utils.pad(date.getUTCHours(), 2), 44 | utils.pad(date.getUTCMinutes(), 2), 45 | utils.pad(date.getUTCSeconds(), 2) 46 | ].join(":") + "Z"; 47 | }); 48 | 49 | var render = function(name, locals) { 50 | var rv, t = templates[name]; 51 | if (! t) { 52 | throw new Error("Template not found: '" + name + "'"); 53 | } 54 | 55 | locals = locals || {}; 56 | 57 | var keys = []; 58 | for (var key in locals) { 59 | if (locals.hasOwnProperty(key) && !globals.hasOwnProperty(key)) { 60 | keys.push(key); 61 | globals[key] = locals[key]; 62 | } 63 | } 64 | 65 | rv = templates[name](globals); 66 | 67 | for (var i = 0; i < keys.length; i++) { 68 | delete globals[keys[i]]; 69 | } 70 | 71 | return rv; 72 | }; 73 | 74 | module.exports = { 75 | set: set, 76 | render: render, 77 | }; 78 | -------------------------------------------------------------------------------- /isso/js/app/templates/comment-loader.js: -------------------------------------------------------------------------------- 1 | var html = function (globals) { 2 | var comment = globals.comment; 3 | var pluralize = globals.pluralize; 4 | 5 | return "" + 6 | "" 9 | }; 10 | module.exports = html; 11 | -------------------------------------------------------------------------------- /isso/js/count.js: -------------------------------------------------------------------------------- 1 | var domready = require("app/lib/ready"); 2 | var count = require("app/count"); 3 | 4 | domready(function() { 5 | count(); 6 | }); 7 | -------------------------------------------------------------------------------- /isso/js/jest-integration.config.js: -------------------------------------------------------------------------------- 1 | /* Puppeteer End-to-End Integration Tests 2 | * 3 | * Puppeteer is a tool to use a (headless) Chrome browser to simulate client 4 | * interaction. 5 | * 6 | * See also: 7 | * https://puppeteer.github.io/puppeteer/ 8 | * https://jestjs.io/docs/configuration 9 | */ 10 | 11 | // https://github.com/smooth-code/jest-puppeteer/issues/160#issuecomment-491975158 12 | // For `jest-puppeteer` package, currently empty but good to have 13 | process.env.JEST_PUPPETEER_CONFIG = require.resolve('./jest-puppeteer.config.js'); 14 | 15 | const config = { 16 | /* puppeteer end-to-end integration testing 17 | * https://jestjs.io/docs/configuration#preset-string */ 18 | preset: "jest-puppeteer", 19 | 20 | // Highlight failing lines in GH Actions reports 21 | // https://jestjs.io/docs/configuration#reporters-arraymodulename--modulename-options 22 | "reporters": ["default", "github-actions"], 23 | }; 24 | 25 | module.exports = config; 26 | -------------------------------------------------------------------------------- /isso/js/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | /* Configuration specific to `jest-puppeteer`, for E2E integration tests 2 | * Could be used to set e.g. Firefox as test runner 3 | * https://github.com/smooth-code/jest-puppeteer */ 4 | 5 | /* 6 | module.exports = { 7 | launch: { 8 | dumpio: true, 9 | headless: process.env.HEADLESS !== 'false', 10 | product: 'chrome', 11 | }, 12 | browserContext: 'default', 13 | } 14 | */ 15 | -------------------------------------------------------------------------------- /isso/js/jest-unit.config.js: -------------------------------------------------------------------------------- 1 | /* https://jestjs.io/docs/configuration 2 | * 3 | * Best practices by mailchimp: 4 | * https://mailchimp.com/developer/open-commerce/docs/testing-requirements/ 5 | * When writing your test: 6 | * - Do not add extra describe blocks. Jest tests are automatically file-scoped, 7 | * so a file that performs a single test does not need a describe block in it. 8 | * You may add multiple describe blocks to group related tests within one 9 | * file, but you should not have a file with only a single describe block in 10 | * it. 11 | * - Always use test() instead of it() to define test functions. 12 | * - Do not import describe, test, jest, jasmine, or expect. They are automatic 13 | * globals in all test files. 14 | * - Use arrow functions for all describe and test functions. 15 | * - Use Jest’s built-in expect function for assertions. 16 | * - You might need to test asynchronous code: functions that either return a 17 | * Promise or take a callback argument. You should use Promises unless you 18 | * need to use a callback, such as when the API of another package requires 19 | * it. When using callbacks, make sure to add a done argument to your test 20 | * function and call done when all testing is complete. 21 | */ 22 | 23 | const config = { 24 | /* modulePaths: 25 | * An alternative API to setting the NODE_PATH env variable, modulePaths is 26 | * an array of absolute paths to additional locations to search when 27 | * resolving modules. Use the string token to include the path to 28 | * your project's root directory. Example: ["/app/"]. 29 | */ 30 | modulePaths: [""], 31 | /* rootDir already set to pwd when running jest with this config as arg */ 32 | moduleNameMapper: { 33 | "\.svg$": "/tests/mocks/fileTransformer.js", 34 | }, 35 | 36 | /* Run tests in a virtual DOM environment 37 | * See https://jestjs.io/docs/tutorial-jquery 38 | * -> use per-file testEnvironment stanza instead 39 | */ 40 | //testEnvironment: "jsdom", 41 | 42 | "globalSetup": "/tests/setup/global-setup.js", 43 | 44 | // Highlight failing lines in GH Actions reports 45 | // https://jestjs.io/docs/configuration#reporters-arraymodulename--modulename-options 46 | "reporters": ["default", "github-actions"], 47 | }; 48 | 49 | module.exports = config; 50 | -------------------------------------------------------------------------------- /isso/js/tests/integration/highlight-comments.test.js: -------------------------------------------------------------------------------- 1 | const ISSO_ENDPOINT = process.env.ISSO_ENDPOINT ? 2 | process.env.ISSO_ENDPOINT : 'http://localhost:8080'; 3 | 4 | beforeEach(async () => { 5 | await page.goto( 6 | ISSO_ENDPOINT + '/demo#isso-1', 7 | { waitUntil: 'load' } 8 | ) 9 | await expect(page.url()).toBe(ISSO_ENDPOINT + '/demo/#isso-1'); 10 | }) 11 | 12 | test('Linked should be highlighted', async () => { 13 | await expect(page).toFill( 14 | '.isso-textarea', 15 | 'A comment with *italics* and [a link](http://link.com)' 16 | ); 17 | const [postCreated] = await Promise.all([ 18 | page.waitForResponse(async (response) => 19 | response.url().includes('/new') && response.ok(), 20 | { timout: 500 } 21 | ), 22 | expect(page).toClick('.isso-post-action > input[type=submit]'), 23 | ]); 24 | 25 | // Refresh page to arm window.location.hash listeners 26 | await page.goto( 27 | ISSO_ENDPOINT + '/demo#isso-1', 28 | { waitUntil: 'load' } 29 | ) 30 | 31 | let highlightedComment = await expect(page).toMatchElement('#isso-1'); 32 | let wrapper = await expect(highlightedComment).toMatchElement('.isso-target'); 33 | 34 | // Use another (invalid) hash 35 | await page.goto( 36 | ISSO_ENDPOINT + '/demo#isso-x', 37 | { waitUntil: 'load' } 38 | ) 39 | await expect(page).not.toMatchElement('.isso-target'); 40 | 41 | // Cleanup 42 | // Need to click once to surface "confirm" and then again to confirm 43 | await page.waitForSelector('#isso-1 > .isso-text-wrapper > .isso-comment-footer > .isso-delete'); 44 | await expect(page).toClick('#isso-1 > .isso-text-wrapper > .isso-comment-footer > .isso-delete'); 45 | await expect(page).toClick('#isso-1 > .isso-text-wrapper > .isso-comment-footer > .isso-delete'); 46 | }); 47 | -------------------------------------------------------------------------------- /isso/js/tests/mocks/fileTransformer.js: -------------------------------------------------------------------------------- 1 | // https://jestjs.io/docs/next/code-transformation#transforming-images-to-their-path 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | process(sourceText, sourcePath, options) { 7 | return { 8 | code: `module.exports = ${JSON.stringify(path.basename(sourcePath))};`, 9 | }; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/isso/js/tests/screenshots/reference/comment.png -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/comment.png.hash: -------------------------------------------------------------------------------- 1 | 56314990cb566160044f46033a7bd0c4d8be5bb41afa44fa54cf156aed21bbe3 2 | -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/postbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/isso/js/tests/screenshots/reference/postbox.png -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/postbox.png.hash: -------------------------------------------------------------------------------- 1 | a218440951dbc7312118647c955d0d4480fc46bb6dddd72e0d9459744b471f32 2 | -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/thread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isso-comments/isso/14de6b17b8a8261b5ab3d2e1bd5d16bf8ef64c56/isso/js/tests/screenshots/reference/thread.png -------------------------------------------------------------------------------- /isso/js/tests/screenshots/reference/thread.png.hash: -------------------------------------------------------------------------------- 1 | a10040e07c4285acea4a36d41ee2ff9877b70fd399d908e6eaf7a4dfad100333 2 | -------------------------------------------------------------------------------- /isso/js/tests/setup/global-setup.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/56261381/how-do-i-set-a-timezone-in-my-jest-config/56482581#56482581 2 | module.exports = async () => { 3 | process.env.TZ = 'UTC'; 4 | }; 5 | -------------------------------------------------------------------------------- /isso/js/tests/unit/__snapshots__/comment-loader.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Create comment loader 1`] = `""`; 4 | -------------------------------------------------------------------------------- /isso/js/tests/unit/__snapshots__/comment.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Rendered comment should match snapshot 1`] = ` 4 | "

A comment with

5 |
code blocks
 6 | New line: preformatted
 7 | 
 8 | Double newline
 9 | 
" 10 | `; 11 | -------------------------------------------------------------------------------- /isso/js/tests/unit/__snapshots__/postbox.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Create Postbox 1`] = `"

"`; 4 | -------------------------------------------------------------------------------- /isso/js/tests/unit/__snapshots__/template-comment-newlines.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Code blocks in rendered comment should not be clipped 1`] = ` 4 | "

A comment with

5 |
code blocks
 6 | New line: preformatted
 7 | 
 8 | Double newline
 9 | 
" 10 | `; 11 | 12 | exports[`Simple comment text should render on one line 1`] = `"

A comment

"`; 13 | -------------------------------------------------------------------------------- /isso/js/tests/unit/comment-loader.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | test('Create comment loader', () => { 13 | // Set up our document body 14 | document.body.innerHTML = 15 | '
' + 16 | // Note: `src` and `data-isso` need to be set, 17 | // else `api` fails to initialize! 18 | // data-isso-id needed for insert_loader api.fetch() 19 | ''; 22 | 23 | const isso = require("app/isso"); 24 | const $ = require("app/dom"); 25 | 26 | const config = require("app/config"); 27 | const i18n = require("app/i18n"); 28 | const svg = require("app/svg"); 29 | 30 | const template = require("app/template"); 31 | template.set("pluralize", i18n.pluralize); 32 | 33 | let comment = { 34 | 'id': null, 35 | 'hidden_replies': 5, 36 | } 37 | 38 | var isso_thread = $('#isso-thread'); 39 | isso_thread.append('
'); 40 | 41 | isso.insert_loader(comment, null); 42 | 43 | // Will create a `.snap` file in `./__snapshots__/`. 44 | // Don't forget to check in those files when changing anything! 45 | expect(isso_thread.innerHTML).toMatchSnapshot(); 46 | }); 47 | -------------------------------------------------------------------------------- /isso/js/tests/unit/comment.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | 13 | test('Rendered comment should match snapshot', () => { 14 | // Set up our document body 15 | document.body.innerHTML = 16 | '
' + 17 | // Note: `src` and `data-isso` need to be set, 18 | // else `api` fails to initialize! 19 | // data-isso-id needed for insert_loader api.fetch() 20 | ''; 23 | 24 | const isso = require("app/isso"); 25 | const $ = require("app/dom"); 26 | const config = require("app/config"); 27 | const template = require("app/template"); 28 | 29 | const i18n = require("app/i18n"); 30 | const svg = require("app/svg"); 31 | 32 | template.set("conf", config); 33 | template.set("i18n", i18n.translate); 34 | template.set("pluralize", i18n.pluralize); 35 | template.set("svg", svg); 36 | 37 | let comment = { 38 | "id": 2, 39 | "created": 1651788192.4473603, 40 | "mode": 1, 41 | "text": "

A comment with

\n
code blocks\nNew line: preformatted\n\nDouble newline\n
", 42 | "author": "John", 43 | "website": "http://website.org", 44 | "hash": "4505c1eeda98", 45 | "parent": null, 46 | } 47 | 48 | // globals.offset.localTime() will be passed to i18n.ago() 49 | // localTime param will then be called as localTime.getTime() 50 | jest.mock('app/globals', () => ({ 51 | offset: { 52 | localTime: jest.fn(() => ({ 53 | getTime: jest.fn(() => 0), 54 | })), 55 | }, 56 | })); 57 | 58 | var isso_thread = $('#isso-thread'); 59 | isso_thread.append('
'); 60 | 61 | isso.insert({ comment, scrollIntoView: false, offset: 0 }); 62 | 63 | // Will create a `.snap` file in `./__snapshots__/`. 64 | // Don't forget to check in those files when changing anything! 65 | expect(isso_thread.innerHTML).toMatchSnapshot(); 66 | }); 67 | -------------------------------------------------------------------------------- /isso/js/tests/unit/config.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | beforeEach(() => { 13 | jest.resetModules(); 14 | document.body.innerHTML = ''; 15 | }); 16 | 17 | test("Client configuration - no languages", () => { 18 | // Mock navigator.languages = [] 19 | global.languages = jest.spyOn(navigator, "languages", "get") 20 | global.languages.mockReturnValue([]); 21 | 22 | // Mock navigator.language = null 23 | global.language = jest.spyOn(navigator, "language", "get") 24 | global.language.mockReturnValue(null); 25 | 26 | let config = require("app/config"); 27 | 28 | /* Expected: 29 | * - no config["lang"] 30 | * - navigator.languages empty 31 | * - fall back on navigator.language 32 | * - navigator.language empty 33 | * - fall back on navigator.userLanguage 34 | * - navigator.userLanguage empty 35 | * (jsdom doesn't set it) 36 | * - config["default-lang"] = "en" 37 | * - final manual insertion of "en" 38 | */ 39 | let expected_langs = ["en", "en"]; 40 | 41 | expect(config["langs"]).toStrictEqual(expected_langs); 42 | }); 43 | 44 | test("data-isso-* i18n strings should be accepted with newline characters", () => { 45 | 46 | document.body.innerHTML = 47 | '
' + 48 | // Note: `src` and `data-isso` need to be set, 49 | // else `api` fails to initialize! 50 | ''; 28 | 29 | let placeholder = 'Type here' 30 | let html = "
Type here
" 31 | 32 | jest.mock('app/i18n', () => ({ 33 | translate: jest.fn(key => placeholder), 34 | })); 35 | 36 | const isso = require("app/isso"); 37 | const $ = require("app/dom"); 38 | 39 | var textarea = $.htmlify(html); 40 | var isso_thread = $('#isso-thread'); 41 | isso_thread.append(textarea); 42 | let area = document.querySelectorAll('.isso-textarea')[0]; 43 | 44 | isso.editorify(textarea); 45 | expect(textarea.getAttribute('contentEditable')).toBe('true'); 46 | 47 | // textarea.focus() does not work here, 48 | // Maybe some JSDOM oddities prevent addEventListener()? 49 | area.dispatchEvent(new window.MouseEvent('focus')); 50 | 51 | // classList returns {'0': 'class1, '1': 'class2', ...} 52 | expect(textarea.innerHTML).toBe(""); 53 | expect(Object.values(textarea.classList)).not.toContain("isso-placeholder"); 54 | 55 | // textarea.blur() does not work here 56 | area.dispatchEvent(new window.MouseEvent('blur')); 57 | 58 | expect(Object.values(textarea.classList)).toContain("isso-placeholder"); 59 | expect(textarea.innerHTML).toBe("Type here"); 60 | }); 61 | -------------------------------------------------------------------------------- /isso/js/tests/unit/postbox-labels-optional.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | test('"(optional)" labels in Postox vanish if require-author/-email set', () => { 13 | // Set up our document body 14 | document.body.innerHTML = 15 | '
' + 16 | ''; 20 | 21 | const isso = require("app/isso"); 22 | const $ = require("app/dom"); 23 | 24 | var config = require("app/config"); 25 | config['require-author'] = true; 26 | config['require-email'] = true; 27 | 28 | const i18n = require("app/i18n"); 29 | const svg = require("app/svg"); 30 | 31 | const template = require("app/template"); 32 | 33 | template.set("conf", config); 34 | template.set("i18n", i18n.translate); 35 | template.set("pluralize", i18n.pluralize); 36 | template.set("svg", svg); 37 | 38 | var isso_thread = $('#isso-thread'); 39 | isso_thread.append('
'); 40 | isso_thread.append(new isso.Postbox(null)); 41 | 42 | expect($("#isso-postbox-author").placeholder).toBe('Max Mustermann'); 43 | expect($("#isso-postbox-email").placeholder).toBe('mustermann@beispiel.de'); 44 | // Instead of "Name (optional)" 45 | expect($("[for='isso-postbox-author']").textContent).toBe('Name'); 46 | // Instead of "E-mail (optional)" 47 | expect($("[for='isso-postbox-email']").textContent).toBe('E-Mail'); 48 | }); 49 | -------------------------------------------------------------------------------- /isso/js/tests/unit/postbox.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | test('Create Postbox', () => { 13 | // Set up our document body 14 | document.body.innerHTML = 15 | '
' + 16 | // Note: `src` and `data-isso` need to be set, 17 | // else `api` fails to initialize! 18 | ''; 19 | 20 | const isso = require("app/isso"); 21 | const $ = require("app/dom"); 22 | 23 | const config = require("app/config"); 24 | const i18n = require("app/i18n"); 25 | const svg = require("app/svg"); 26 | 27 | const template = require("app/template"); 28 | 29 | template.set("conf", config); 30 | template.set("i18n", i18n.translate); 31 | template.set("pluralize", i18n.pluralize); 32 | template.set("svg", svg); 33 | 34 | var isso_thread = $('#isso-thread'); 35 | isso_thread.append('
'); 36 | isso_thread.append(new isso.Postbox(null)); 37 | 38 | // Will create a `.snap` file in `./__snapshots__/`. 39 | // Don't forget to check in those files when changing anything! 40 | expect(isso_thread.innerHTML).toMatchSnapshot(); 41 | }); 42 | -------------------------------------------------------------------------------- /isso/js/tests/unit/template-comment-newlines.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | "use strict"; 11 | 12 | /* Test rendered code blocks inside "comment" template 13 | * See https://github.com/isso-comments/isso/discussions/856 14 | * and https://github.com/isso-comments/isso/pull/857 15 | */ 16 | 17 | // Set up our document body 18 | document.body.innerHTML = 19 | '
' + 20 | ''; 24 | 25 | const isso = require("app/isso"); 26 | const $ = require("app/dom"); 27 | const config = require("app/config"); 28 | const template = require("app/template"); 29 | 30 | const i18n = require("app/i18n"); 31 | const svg = require("app/svg"); 32 | 33 | template.set("conf", config); 34 | template.set("i18n", i18n.translate); 35 | template.set("pluralize", i18n.pluralize); 36 | template.set("svg", svg); 37 | 38 | test('Simple comment text should render on one line', () => { 39 | let comment = { 40 | "id": 1, 41 | "created": 1651788192.4473603, 42 | "mode": 1, 43 | "text": "

A comment

", 44 | "author": "John", 45 | "website": "http://website.org", 46 | "hash": "4505c1eeda98", 47 | } 48 | let rendered = template.render("comment", {"comment": comment}); 49 | let el = $.htmlify(rendered); 50 | expect($('.isso-text', el).innerHTML).toMatchSnapshot(); 51 | 52 | }); 53 | 54 | test('Code blocks in rendered comment should not be clipped', () => { 55 | let comment = { 56 | "id": 2, 57 | "created": 1651788192.4473603, 58 | "mode": 1, 59 | "text": "

A comment with

\n
code blocks\nNew line: preformatted\n\nDouble newline\n
", 60 | "author": "John", 61 | "website": "http://website.org", 62 | "hash": "4505c1eeda98", 63 | } 64 | let rendered = template.render("comment", {"comment": comment}); 65 | let el = $.htmlify(rendered); 66 | expect($('.isso-text', el).innerHTML).toMatchSnapshot(); 67 | }); 68 | -------------------------------------------------------------------------------- /isso/js/tests/unit/timezone-utc.test.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/56261381/how-do-i-set-a-timezone-in-my-jest-config/56482581#56482581 2 | // Requires global-setup.js 3 | 4 | test('Timezones should always be UTC', () => { 5 | expect(new Date().getTimezoneOffset()).toBe(0); 6 | }); 7 | -------------------------------------------------------------------------------- /isso/js/tests/unit/utils.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | 5 | /* Keep the above exactly as-is! 6 | * https://jestjs.io/docs/configuration#testenvironment-string 7 | * https://jestjs.io/docs/configuration#testenvironmentoptions-object 8 | */ 9 | 10 | const utils = require("app/utils"); 11 | 12 | test("Pad string with zeros", function() { 13 | let to_be_padded = "12345"; 14 | let pad_to = 10; 15 | let padding_char = "0"; 16 | let expected = "0000012345" 17 | expect(utils.pad(to_be_padded, pad_to, padding_char)).toStrictEqual(expected); 18 | }); 19 | -------------------------------------------------------------------------------- /isso/run.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | from isso import config, make_app 7 | 8 | # Mock make_app because it is run by pytest 9 | # with the --doctest-modules flag 10 | # which will fail because make_app will exit 11 | # without valid configuration 12 | # https://stackoverflow.com/a/44595269/1279355 13 | if "pytest" in sys.modules: 14 | make_app = lambda config, multiprocessing: True # noqa 15 | 16 | application = make_app( 17 | config.load( 18 | config.default_file(), 19 | os.environ.get('ISSO_SETTINGS')), 20 | multiprocessing=True) 21 | -------------------------------------------------------------------------------- /isso/templates/disabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Isso admin 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 19 |
20 |
21 |
22 |
23 | Administration is disabled on this instance of isso. Set enabled=true 24 | in the admin section of your isso configuration to enable it. 25 |
26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /isso/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Isso admin 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 | 19 |
20 |
21 |
22 |
23 | Administration secured by password: 24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /isso/tests/fixtures.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import json 4 | 5 | from werkzeug.test import Client 6 | 7 | 8 | class FakeIP(object): 9 | 10 | def __init__(self, app, ip): 11 | self.app = app 12 | self.ip = ip 13 | 14 | def __call__(self, environ, start_response): 15 | environ['REMOTE_ADDR'] = self.ip 16 | return self.app(environ, start_response) 17 | 18 | 19 | class FakeHost(object): 20 | 21 | def __init__(self, app, host, scheme): 22 | self.app = app 23 | self.host = host 24 | self.scheme = scheme 25 | 26 | def __call__(self, environ, start_response): 27 | environ['HTTP_HOST'] = self.host 28 | environ['wsgi.url_scheme'] = self.scheme 29 | return self.app(environ, start_response) 30 | 31 | 32 | class JSONClient(Client): 33 | 34 | def open(self, *args, **kwargs): 35 | kwargs.setdefault('content_type', 'application/json') 36 | return super(JSONClient, self).open(*args, **kwargs) 37 | 38 | 39 | class Dummy: 40 | 41 | status = 200 42 | 43 | def __enter__(self): 44 | return self 45 | 46 | def read(self): 47 | return '' 48 | 49 | def __exit__(self, exc_type, exc_val, exc_tb): 50 | pass 51 | 52 | 53 | def curl(method, host, path): 54 | return Dummy() 55 | 56 | 57 | def loads(data): 58 | return json.loads(data.decode('utf-8')) 59 | -------------------------------------------------------------------------------- /isso/tests/generic.json: -------------------------------------------------------------------------------- 1 | [{"comments": [{"email": "", "remote_addr": "0.0.0.0", "website": "http://www.tigerspice.com", "created": "2005-02-24 04:03:37", "author": "texas holdem", "id": 0, "text": "Great men can't be ruled. by free online poker"}], "id": "/posts/0001/", "title": "Test+post"}, {"comments": [{"email": "105421439@87750645.com", "remote_addr": "0.0.0.0", "website": "", "created": "2005-05-08 06:50:26", "author": "Richard Crinshaw", "id": 0, "text": "Ja-make-a me crazzy mon :)\n"}], "id": "/posts/0007/", "title": "Nat+%26+Miguel"}] -------------------------------------------------------------------------------- /isso/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from isso import utils 6 | from isso.utils import parse 7 | 8 | 9 | class TestUtils(unittest.TestCase): 10 | 11 | def test_anonymize(self): 12 | 13 | examples = [ 14 | (u'12.34.56.78', u'12.34.56.0'), 15 | (u'1234:5678:90ab:cdef:fedc:ba09:8765:4321', 16 | u'1234:5678:90ab:0000:0000:0000:0000:0000'), 17 | (u'::ffff:127.0.0.1', u'127.0.0.0')] 18 | 19 | for (addr, anonymized) in examples: 20 | self.assertEqual(utils.anonymize(addr), anonymized) 21 | 22 | def test_str(self): 23 | # Accept a str on both Python 2 and Python 3, for 24 | # convenience. 25 | examples = [ 26 | ('12.34.56.78', u'12.34.56.0'), 27 | ('1234:5678:90ab:cdef:fedc:ba09:8765:4321', 28 | '1234:5678:90ab:0000:0000:0000:0000:0000'), 29 | ('::ffff:127.0.0.1', u'127.0.0.0')] 30 | 31 | for (addr, anonymized) in examples: 32 | self.assertEqual(utils.anonymize(addr), anonymized) 33 | 34 | 35 | class TestParse(unittest.TestCase): 36 | 37 | def test_thread(self): 38 | self.assertEqual(parse.thread("asdf"), (None, 'Untitled.')) 39 | 40 | self.assertEqual(parse.thread(""" 41 | 42 | 43 | Foo! 44 | 45 | 46 |
47 |

generic website title.

48 |

subtile title.

49 |
50 |
51 |
52 |

Can you find me?

53 |
54 |
55 |
56 |
57 | 58 | """), (None, 'Can you find me?')) 59 | 60 | self.assertEqual(parse.thread(""" 61 | 62 | 63 |

I'm the real title!1 64 |
65 | """), (None, 'No way!')) 66 | 67 | self.assertEqual(parse.thread(""" 68 |
69 | """), ('test', 'Test')) 70 | 71 | self.assertEqual(parse.thread('
'), 72 | ('Fuu.', 'Untitled.')) 73 | -------------------------------------------------------------------------------- /isso/tests/test_utils_hash.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from isso import config 6 | 7 | from isso.utils.hash import Hash, PBKDF2, new 8 | 9 | 10 | class TestHasher(unittest.TestCase): 11 | 12 | def test_hash(self): 13 | self.assertRaises(TypeError, Hash, "Foo") 14 | 15 | self.assertEqual(Hash(b"").salt, b"") 16 | self.assertEqual(Hash().salt, Hash.salt) 17 | 18 | h = Hash(b"", func=None) 19 | 20 | self.assertRaises(TypeError, h.hash, "...") 21 | self.assertEqual(h.hash(b"..."), b"...") 22 | self.assertIsInstance(h.uhash("..."), (str, )) 23 | 24 | def test_uhash(self): 25 | h = Hash(b"", func=None) 26 | self.assertRaises(TypeError, h.uhash, b"...") 27 | 28 | 29 | class TestPBKDF2(unittest.TestCase): 30 | 31 | def test_default(self): 32 | # original setting (and still default) 33 | pbkdf2 = PBKDF2(iterations=1000) 34 | self.assertEqual(pbkdf2.uhash(""), "42476aafe2e4") 35 | 36 | def test_different_salt(self): 37 | a = PBKDF2(b"a", iterations=1) 38 | b = PBKDF2(b"b", iterations=1) 39 | self.assertNotEqual(a.hash(b""), b.hash(b"")) 40 | 41 | 42 | class TestCreate(unittest.TestCase): 43 | 44 | def test_custom(self): 45 | 46 | def _new(val): 47 | conf = config.new({ 48 | "hash": { 49 | "algorithm": val, 50 | "salt": "" 51 | } 52 | }) 53 | return new(conf.section("hash")) 54 | 55 | sha1 = _new("sha1") 56 | self.assertIsInstance(sha1, Hash) 57 | self.assertEqual(sha1.func, "sha1") 58 | self.assertRaises(ValueError, _new, "foo") 59 | 60 | pbkdf2 = _new("pbkdf2:16") 61 | self.assertIsInstance(pbkdf2, PBKDF2) 62 | self.assertEqual(pbkdf2.iterations, 16) 63 | 64 | pbkdf2 = _new("pbkdf2:16:2:md5") 65 | self.assertIsInstance(pbkdf2, PBKDF2) 66 | self.assertEqual(pbkdf2.dklen, 2) 67 | self.assertEqual(pbkdf2.func, "md5") 68 | -------------------------------------------------------------------------------- /isso/utils/http.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import socket 4 | 5 | import http.client as httplib 6 | 7 | from urllib.parse import urlparse 8 | 9 | from isso import dist 10 | from isso.wsgi import urlsplit 11 | 12 | MAX_RETRY_COUNT = 3 13 | 14 | 15 | class curl(object): 16 | """Easy to use wrapper around :module:`httplib`. Use as context-manager 17 | so we can close the response properly. 18 | 19 | .. code-block:: python 20 | 21 | with http.curl('GET', 'http://localhost:8080', '/') as resp: 22 | if resp: # may be None if request failed 23 | return resp.status 24 | """ 25 | 26 | headers = { 27 | "User-Agent": "Isso/{0} (+https://isso-comments.de)".format(dist.version) 28 | } 29 | 30 | def __init__(self, method, host, path, timeout=3): 31 | self.method = method 32 | self.host = host 33 | self.path = path 34 | self.timeout = timeout 35 | 36 | def __enter__(self): 37 | host, port, ssl = urlsplit(self.host) 38 | http = httplib.HTTPSConnection if ssl else httplib.HTTPConnection 39 | 40 | for _ in range(MAX_RETRY_COUNT): 41 | self.con = http(host, port, timeout=self.timeout) 42 | try: 43 | self.con.request(self.method, self.path, headers=self.headers) 44 | except (httplib.HTTPException, socket.error): 45 | return None 46 | 47 | try: 48 | resp = self.con.getresponse() 49 | if resp.status == 301: 50 | location = resp.getheader('Location') 51 | if location: 52 | self.con.close() 53 | self.path = urlparse(location).path 54 | else: 55 | return None 56 | else: 57 | return resp 58 | except (httplib.HTTPException, socket.timeout, socket.error): 59 | return None 60 | 61 | def __exit__(self, exc_type, exc_value, traceback): 62 | self.con.close() 63 | -------------------------------------------------------------------------------- /isso/utils/parse.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import html5lib 4 | 5 | from itertools import chain 6 | from urllib.parse import unquote 7 | 8 | 9 | def thread(data, default="Untitled.", id=None): 10 | """ 11 | Extract

title from web page. The title is *probably* the text node, 12 | which is the nearest H1 node in context to an element with the `isso-thread` id. 13 | """ 14 | 15 | html = html5lib.parse(data, treebuilder="dom") 16 | 17 | assert html.lastChild.nodeName == "html" 18 | html = html.lastChild 19 | 20 | # aka getElementById, but limited to div and section tags 21 | el = list(filter(lambda i: i.attributes["id"].value == "isso-thread", 22 | filter(lambda i: "id" in i.attributes, 23 | chain(*map(html.getElementsByTagName, ("div", "section")))))) 24 | 25 | if not el: 26 | return id, default 27 | 28 | el = el[0] 29 | visited = [] 30 | 31 | def recurse(node): 32 | for child in node.childNodes: 33 | if child.nodeType != child.ELEMENT_NODE: 34 | continue 35 | if child.nodeName.upper() == "H1": 36 | return child 37 | if child not in visited: 38 | return recurse(child) 39 | 40 | def gettext(rv): 41 | for child in rv.childNodes: 42 | if child.nodeType == child.TEXT_NODE: 43 | yield child.nodeValue 44 | if child.nodeType == child.ELEMENT_NODE: 45 | for item in gettext(child): 46 | yield item 47 | 48 | try: 49 | id = unquote(el.attributes["data-isso-id"].value) 50 | except (KeyError, AttributeError): 51 | pass 52 | 53 | try: 54 | return id, unquote(el.attributes["data-title"].value) 55 | except (KeyError, AttributeError): 56 | pass 57 | 58 | while el is not None: # el.parentNode is None in the very end 59 | 60 | visited.append(el) 61 | rv = recurse(el) 62 | 63 | if rv: 64 | return id, ''.join(gettext(rv)).strip() 65 | 66 | el = el.parentNode 67 | 68 | return id, default 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "isso", 3 | "author": "Martin Zimmermann", 4 | "description": "lightweight Disquis alternative", 5 | "license": "MIT", 6 | "repository": "github:isso-comments/isso", 7 | "scripts": { 8 | "test": "jest --config isso/js/jest.config.js isso/js/tests/", 9 | "test-unit": "jest --config isso/js/jest-unit.config.js isso/js/tests/unit/", 10 | "test-integration": "jest --runInBand --config isso/js/jest-integration.config.js isso/js/tests/integration/", 11 | "test-screenshots": "jest --runInBand --config isso/js/jest-integration.config.js isso/js/tests/screenshots/", 12 | "build-dev": "webpack --config isso/js/webpack.config.js --config-name dev", 13 | "watch-dev": "webpack --config isso/js/webpack.config.js --config-name dev --watch", 14 | "build-prod": "webpack --config isso/js/webpack.config.js --merge --config-name dev --config-name prod" 15 | }, 16 | "devDependencies": { 17 | "webpack": "^5.89.0", 18 | "webpack-cli": "^5.1.4" 19 | }, 20 | "optionalDependencies": { 21 | "jest": "^29.7.0", 22 | "jest-environment-jsdom": "^29.7.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501, E402 3 | exclude = docs/conf.py,node_modules,.tox,.eggs,.git 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- encoding: utf-8 -*- 3 | 4 | from pathlib import Path 5 | from re import sub as re_sub 6 | from setuptools import setup, find_packages 7 | 8 | # https://packaging.python.org/en/latest/guides/making-a-pypi-friendly-readme/ 9 | this_directory = Path(__file__).parent 10 | long_description = (this_directory / "README.md").read_text() 11 | # Filter out "License" section since license already displayed in PyPi sidebar 12 | # Remember to keep this in sync with changes to README! 13 | long_description = re_sub(r"\n## License\n.*LICENSE.*\n", "", long_description) 14 | 15 | setup( 16 | name='isso', 17 | version='0.13.1.dev1', 18 | author='Martin Zimmermann', 19 | author_email='info@posativ.org', 20 | packages=find_packages(), 21 | include_package_data=True, 22 | zip_safe=False, 23 | url='https://github.com/isso-comments/isso/', 24 | license='MIT', 25 | description='lightweight Disqus alternative', 26 | long_description=long_description, 27 | long_description_content_type='text/markdown', 28 | python_requires='>=3.8', 29 | classifiers=[ 30 | "Development Status :: 4 - Beta", 31 | "Topic :: Internet", 32 | "Topic :: Internet :: WWW/HTTP :: HTTP Servers", 33 | "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", 34 | "License :: OSI Approved :: MIT License", 35 | "Programming Language :: Python :: 3.8", 36 | "Programming Language :: Python :: 3.9", 37 | "Programming Language :: Python :: 3.10", 38 | "Programming Language :: Python :: 3.11", 39 | "Programming Language :: Python :: 3.12", 40 | ], 41 | install_requires=[ 42 | 'itsdangerous', 'Jinja2', 'misaka>=2.0,<3.0', 'html5lib', 43 | 'werkzeug>=1.0', 'bleach', 'setuptools'], 44 | tests_require=['pytest', 'pytest-cov'], 45 | extras_require={ 46 | 'doc': ['Sphinx'], 47 | }, 48 | entry_points={ 49 | 'console_scripts': 50 | ['isso = isso:main'], 51 | }, 52 | ) 53 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py35,py36,py37,py38,py39,py310 3 | 4 | [testenv] 5 | deps = 6 | pytest 7 | pytest-cov 8 | setuptools>=6.1 9 | commands = 10 | pytest isso/ 11 | 12 | [testenv:debian] 13 | deps= 14 | itsdangerous 15 | Jinja2 16 | misaka>=2.0,<3.0 17 | html5lib 18 | werkzeug>=1.0 19 | bleach 20 | flask-caching>=1.9 21 | --------------------------------------------------------------------------------