Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow arbitrary number of threads per page #115

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/client.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/embed.js

Large diffs are not rendered by default.

2,308 changes: 2,243 additions & 65 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"scripts": {
"format": "prettier 'src/**/*.js' --write",
"lint": "prettier --check 'src/**/*.js' && healthier 'src/**/*.js'",
"start": "node index.js",
"start": "pm2 start index.js -i 4 --watch --output NULL --error NULL",
"build": "rollup -c",
"build-watch": "rollup -cw",
"test-server": "http-server test -o 'http://localhost:8080/' -P 'http://localhost:3000/'",
Expand All @@ -49,6 +49,7 @@
"passport-google-oauth20": "^2.0.0",
"passport-mastodon": "^0.1.3",
"passport-twitter": "^1.0.4",
"pm2": "^4.4.0",
"pushover-notifications": "^0.2.4",
"request": "^2.88.0",
"rss": "^1.2.2",
Expand Down
4 changes: 2 additions & 2 deletions rsync.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ rsync -avz \
--exclude '*.swp' \
--exclude 'node_modules' \
--exclude '*.db' \
--exclude 'config.json' \
--exclude 'npm-debug.log' \
--exclude '.git*' \
--exclude 'build/' \
. \
yggdrasil:/root/setup/dockerfiles/etatismus/schnack-image/schnack
yggdrasil:/root/data/etatismus/schnack
4 changes: 2 additions & 2 deletions src/db/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ module.exports = {
NOT user.blocked AND NOT comment.rejected
AND (comment.approved OR user.trusted))
OR user.id = ?)
ORDER BY comment.created_at DESC`,
ORDER BY comment.created_at ASC`,

admin_get_comments: `SELECT user_id, user.name, user.display_name, comment.id,
comment.created_at, comment, approved, trusted, provider,
reply_to
FROM comment INNER JOIN user ON (user_id=user.id)
WHERE slug = ? AND NOT user.blocked
AND NOT comment.rejected
ORDER BY comment.created_at DESC`,
ORDER BY comment.created_at ASC`,

get_last_comment: `SELECT comment, user_id FROM comment WHERE slug = ?
ORDER BY comment.created_at DESC LIMIT 1`,
Expand Down
84 changes: 45 additions & 39 deletions src/embed/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ const $$ = sel => document.querySelectorAll(sel);
export default class Schnack {
constructor(options) {
this.options = options;
this.options.endpoint = `${options.host}/comments/${options.slug}`;
this.initialized = false;
this.options.endpoint = `${options.host}/comments/${options.target}`;
this.firstLoad = true;

const url = new URL(options.host);
Expand All @@ -25,7 +24,8 @@ export default class Schnack {
}

refresh() {
const { target, slug, host, endpoint, partials } = this.options;
const { target, host, endpoint, partials } = this.options;
const targetSelector = `#${target}`;

fetch(endpoint, {
credentials: 'include',
Expand All @@ -37,22 +37,22 @@ export default class Schnack {
.then(data => {
data.comments_tpl = comments_tpl;
data.partials = partials;
$(target).innerHTML = schnack_tpl(data);
$(targetSelector).innerHTML = schnack_tpl(data);
// console.log('data', data);

const above = $(`${target} div.schnack-above`);
const form = $(`${target} div.schnack-form`);
const textarea = $(`${target} textarea.schnack-body`);
const preview = $(`${target} .schnack-form blockquote.schnack-body`);
const above = $(`${targetSelector} div.schnack-above`);
const form = $(`${targetSelector} div.schnack-form`);
const textarea = $(`${targetSelector} textarea.schnack-body`);
const preview = $(`${targetSelector} .schnack-form blockquote.schnack-body`);

const draft = window.localStorage.getItem(`schnack-draft-${slug}`);
const draft = window.localStorage.getItem(`schnack-draft-${target}`);
if (draft && textarea) textarea.value = draft;

const postBtn = $(target + ' .schnack-button');
const previewBtn = $(target + ' .schnack-preview');
const writeBtn = $(target + ' .schnack-write');
const cancelReplyBtn = $(target + ' .schnack-cancel-reply');
const replyBtns = $$(target + ' .schnack-reply');
const postBtn = $(targetSelector + ' .schnack-button');
const previewBtn = $(targetSelector + ' .schnack-preview');
const writeBtn = $(targetSelector + ' .schnack-write');
const cancelReplyBtn = $(targetSelector + ' .schnack-cancel-reply');
const replyBtns = $$(targetSelector + ' .schnack-reply');

if (postBtn) {
postBtn.addEventListener('click', d => {
Expand All @@ -72,12 +72,12 @@ export default class Schnack {
.then(res => {
textarea.value = '';
window.localStorage.setItem(
`schnack-draft-${slug}`,
`schnack-draft-${target}`,
textarea.value
);
if (res.id) {
this.firstLoad = true;
window.location.hash = '#comment-' + res.id;
window.location.hash = `#comment-${res.id}~${target}`;
}
this.refresh();
});
Expand Down Expand Up @@ -114,7 +114,7 @@ export default class Schnack {
});

textarea.addEventListener('keyup', () => {
window.localStorage.setItem(`schnack-draft-${slug}`, textarea.value);
window.localStorage.setItem(`schnack-draft-${target}`, textarea.value);
});

replyBtns.forEach(btn => {
Expand Down Expand Up @@ -145,7 +145,7 @@ export default class Schnack {
});
} else {
data.auth.forEach(provider => {
const btn = $(target + ' .schnack-signin-' + provider.id);
const btn = $(targetSelector + ' .schnack-signin-' + provider.id);
if (btn)
btn.addEventListener('click', d => {
const signin = (provider_domain = '') => {
Expand All @@ -157,6 +157,7 @@ export default class Schnack {
);
window.__schnack_wait_for_oauth = () => {
windowRef.close();
window.schnackAdminInitialized = false;
this.refresh();
};
};
Expand Down Expand Up @@ -193,35 +194,40 @@ export default class Schnack {
}

if (data.user && data.user.admin) {
if (!this.initialized) {
if (!window.schnackPushInitialized) {
const push = document.createElement('script');
push.setAttribute('src', `${host}/push.js`);
document.head.appendChild(push);
this.initialized = true;
window.schnackPushInitialized = true;
}

const action = evt => {
const btn = evt.target;
const data = btn.dataset;
fetch(`${host}/${data.class}/${data.target}/${data.action}`, {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: ''
}).then(() => this.refresh());
};
document.querySelectorAll('.schnack-action').forEach(btn => {
btn.addEventListener('click', action);
});
if (!window.schnackAdminInitialized) {
const action = evt => {
const btn = evt.target;
const data = btn.dataset;
fetch(`${host}/${data.class}/${data.target}/${data.action}`, {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: ''
}).then(() => this.refresh());
};
document.querySelectorAll('.schnack-action').forEach(btn => {
btn.addEventListener('click', action);
});
window.schnackAdminInitialized = true;
}
}

if (this.firstLoad && window.location.hash.match(/^#comment-\d+$/)) {
const hl = document.querySelector(window.location.hash);
hl.scrollIntoView();
if (window.schnackFirstLoad && window.location.hash.match(/^#comment-\d+~.+$/)) {
const thread_id = window.location.hash.split(/~comments-div-(.+)/)[1];
comments_show(thread_id);
const hl = document.querySelector(window.location.hash.replace('~', '\\~'));
hl.scrollIntoView({ behavior: "smooth", block: "center" });
hl.classList.add('schnack-highlight');
this.firstLoad = false;
window.schnackFirstLoad = false;
}
});
}
Expand Down
6 changes: 3 additions & 3 deletions src/embed/comments.jst.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<ul class="schnack-comments">
<% data.comments.forEach((comment) => { %>
<li id="comment-<%= comment.id %>" data-id="<%= comment.id %>" class="schnack-comment<% if (!comment.approved && !comment.trusted) { %> schnack-not-approved<% } %>">
<li id="comment-<%= comment.id %>~<%= comment.threadid %>" data-id="<%= comment.id %>" class="schnack-comment<% if (!comment.approved && !comment.trusted) { %> schnack-not-approved<% } %>">
<div class="schnack-comment-inner">
<div class="schnack-dateline">
<span class="schnack-author"><% if (comment.author_url) { %><a href="<%= comment.author_url %>" target="_blank"><% } %><%= comment.display_name || comment.name %><% if (comment.author_url) { %></a><% } %></span>
<% if (data.user && data.user.admin &&!comment.trusted) {
['trust', 'block'].forEach((action) => { %>
<button class="schnack-action" data-target="<%= comment.user_id %>" data-class="user" data-action="<%= action %>"><i class="icon schnack-icon-<%= action %>"></i> <span><%= action %></span></button>
<% }); } %>
<span class="schnack-date"><a href="#comment-<%= comment.id %>"><%= comment.created_at_s %></a></span>
<span class="schnack-date"><a href="#comment-<%= comment.id %>~<%= comment.threadid %>"><%= comment.created_at_s %></a></span>
</div>
<blockquote class="schnack-body">
<%= comment.comment %>
Expand All @@ -22,7 +22,7 @@
<%= data.user.admin ? data.partials.AdminApproval : data.partials.WaitingForApproval %>
</div>
<% } else if (data.user) { %>
<button class="schnack-reply" data-reply-to="<%= comment.id %>"><%= data.partials.Reply %></button>
<button class="schnack-reply" data-reply-to="<%= (comment.reply_to == null) ? comment.id : comment.reply_to %>"><%= data.partials.Reply %></button>
<% } %>
</div>
<% if (data.replies[comment.id]) {
Expand Down
29 changes: 19 additions & 10 deletions src/embed/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import Schnack from './client';

(function() {
const script = document.querySelector('script[data-schnack-target]');
if (!script) return console.warn('schnack script tag needs some data attributes');

const script = document.currentScript;
const opts = script.dataset;
const slug = opts.schnackSlug;
if(!("schnackTargetClass" in opts)) {
return console.warn('schnack script tag data attribute "data-schnack-target-class" missing');
}
const elements = document.querySelectorAll(opts.schnackTargetClass);
const targets = [].map.call(elements, e => e.id);
const url = new URL(script.getAttribute('src'));
const host = `${url.protocol}//${url.host}`;
const partials = {
Expand All @@ -30,11 +32,18 @@ import Schnack from './client';
partials[k] = script.dataset[`schnackPartial${k}`];
});

// global variable for initialization status of the push script
window.schnackPushInitialized = false;
// ... of the admin buttons
window.schnackAdminInitialized = false;
// ... scroll status
window.schnackFirstLoad = true;

// eslint-disable-next-line no-new
new Schnack({
target: opts.schnackTarget,
slug,
host,
partials
});
targets.forEach(target =>
new Schnack({
target,
host,
partials
}));
})();
9 changes: 7 additions & 2 deletions src/embed/schnack.jst.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@
</div>
<% } else { %>
<div class="schnack-signin">
<%= data.partials.SignInVia %><br>
<%= data.partials.SignInVia %>
<% data.auth.forEach((provider, i) => { %>
<%= i ? data.partials.Or : '' %><button class="schnack-signin-<%= provider.id %>"><i class="icon schnack-icon-<%= provider.id %>"></i> <%= provider.name %></button>
<%= i ? data.partials.Or : '' %>
<button
class="schnack-signin schnack-signin-<%= provider.id %>"
title="Anmelden via <%= provider.name %>">
<i class="fab fa-<%= provider.id %>"></i>
</button>
<% }) %>
<% } %>
</div>
Expand Down
1 change: 1 addition & 0 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ function run(db) {
c.created_at_s = date_format ? m.format(date_format) : m.fromNow();
c.comment = marked(c.comment.trim());
c.author_url = auth.getAuthorUrl(c);
c.threadid = slug;
});
reply.send({ user, auth: providers, slug, comments });
});
Expand Down