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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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"}] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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; }, 0) / this.values.length); 14 | }; 15 | 16 | var offset = new Offset(); 17 | 18 | module.exports = { 19 | offset: offset, 20 | } 21 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /isso/js/app/svg/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /isso/js/app/svg/arrow-up.svg: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /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/css3/_transform.scss: -------------------------------------------------------------------------------- 1 | @mixin transform($property: none) { 2 | // none |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 |A comment with
\ncode 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/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
\ncode 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 |
--------------------------------------------------------------------------------
/.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: [22]
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@v4
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: [22]
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@v4
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: [22]
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@v4
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 |
--------------------------------------------------------------------------------
/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/_theme/search.html:
--------------------------------------------------------------------------------
1 | {% extends "layout.html" %}
2 | {% block header %}
3 | 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 | 40 | {% if search_performed %} 41 |{{ _("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 |A comment with
5 |code blocks
6 | New line: preformatted
7 |
8 | Double newline
9 |