mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-17 11:35:33 -04:00
[user] mod messages
This commit is contained in:
parent
48d45d3dfc
commit
e70a635eb9
@ -88,6 +88,46 @@
|
|||||||
<ModerationRules type="rulesUsers" emphasise class="mt-4"/>
|
<ModerationRules type="rulesUsers" emphasise class="mt-4"/>
|
||||||
<AbuseReports v-if="abuseReports.length" :abuseReports="abuseReports" allowResolving/>
|
<AbuseReports v-if="abuseReports.length" :abuseReports="abuseReports" allowResolving/>
|
||||||
</section>
|
</section>
|
||||||
|
<section v-if="$isGranted('users')">
|
||||||
|
<a v-if="!showMessages" href="#" @click.prevent="showMessages = true" class="small">
|
||||||
|
<Icon v="comment-exclamation"/>
|
||||||
|
Mod messages
|
||||||
|
</a>
|
||||||
|
<div v-else>
|
||||||
|
<h5>
|
||||||
|
<Icon v="comment-exclamation"/>
|
||||||
|
Mod messages
|
||||||
|
</h5>
|
||||||
|
<p>
|
||||||
|
You can use this feature to warn a user without banning them, etc.
|
||||||
|
</p>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Sent on</th>
|
||||||
|
<th>Sent by</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="message in messages">
|
||||||
|
<td>{{$datetime($ulidTime(message.id))}}</td>
|
||||||
|
<td>
|
||||||
|
<a :href="`https://pronouns.page/@${message.adminUsername}`" target="_blank" rel="noopener">
|
||||||
|
@{{message.adminUsername}}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td v-html="nl2br(message.message)"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<textarea v-model="message" class="form-control" rows="5" :disabled="saving" required></textarea>
|
||||||
|
<button class="btn btn-danger d-block w-100 mt-2" :disabled="saving" @click="sendMessage">
|
||||||
|
<Icon v="comment-exclamation"/>
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -109,6 +149,10 @@
|
|||||||
showBanForm: !!this.user.bannedReason,
|
showBanForm: !!this.user.bannedReason,
|
||||||
isBanned: !!this.user.bannedReason,
|
isBanned: !!this.user.bannedReason,
|
||||||
|
|
||||||
|
showMessages: false,
|
||||||
|
messages: undefined,
|
||||||
|
message: '',
|
||||||
|
|
||||||
saving: false,
|
saving: false,
|
||||||
|
|
||||||
forbidden,
|
forbidden,
|
||||||
@ -124,6 +168,10 @@
|
|||||||
if (this.banProposals.length > 0) {
|
if (this.banProposals.length > 0) {
|
||||||
this.showBanForm = true;
|
this.showBanForm = true;
|
||||||
}
|
}
|
||||||
|
this.messages = await this.$axios.$get(`/admin/mod-messages/${encodeURIComponent(this.user.username)}`);
|
||||||
|
if (this.messages.length > 0) {
|
||||||
|
this.showMessages = true;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async ban() {
|
async ban() {
|
||||||
@ -166,6 +214,22 @@
|
|||||||
this.user.bannedReason = proposal.bannedReason;
|
this.user.bannedReason = proposal.bannedReason;
|
||||||
this.user.bannedTerms = proposal.bannedTerms.split(',');
|
this.user.bannedTerms = proposal.bannedTerms.split(',');
|
||||||
},
|
},
|
||||||
|
async sendMessage() {
|
||||||
|
if (!this.message) { return; }
|
||||||
|
await this.$confirm(`<strong>Please proof-read and confirm sending:</strong><br/><br/><div class="text-start">${this.nl2br(this.message)}</div>`, 'danger');
|
||||||
|
this.saving = true;
|
||||||
|
try {
|
||||||
|
this.messages = await this.$post(`/admin/mod-message/${encodeURIComponent(this.user.username)}`, {
|
||||||
|
message: this.message,
|
||||||
|
});
|
||||||
|
this.message = '';
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nl2br(text) {
|
||||||
|
return text.replace(new RegExp('\\n', 'g'), '<br/>');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
canApplyBan() {
|
canApplyBan() {
|
||||||
|
@ -583,6 +583,10 @@ user:
|
|||||||
qr:
|
qr:
|
||||||
header: 'QR code'
|
header: 'QR code'
|
||||||
download: 'Download QR code'
|
download: 'Download QR code'
|
||||||
|
modMessage:
|
||||||
|
subject: 'A message from a moderator'
|
||||||
|
intro: 'Hi! One of our moderators has sent you the following message:'
|
||||||
|
respond: 'If you want to respond, you can simply reply to this email.'
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
description: 'Description'
|
description: 'Description'
|
||||||
|
@ -693,6 +693,10 @@ user:
|
|||||||
qr:
|
qr:
|
||||||
header: 'QR code'
|
header: 'QR code'
|
||||||
download: 'Download QR code'
|
download: 'Download QR code'
|
||||||
|
modMessage:
|
||||||
|
subject: 'A message from a moderator'
|
||||||
|
intro: 'Hi! One of our moderators has sent you the following message:'
|
||||||
|
respond: 'If you want to respond, you can simply reply to this email.'
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
description: 'Description'
|
description: 'Description'
|
||||||
|
@ -1318,6 +1318,10 @@ user:
|
|||||||
qr:
|
qr:
|
||||||
header: 'Kod QR'
|
header: 'Kod QR'
|
||||||
download: 'Pobierz kod QR'
|
download: 'Pobierz kod QR'
|
||||||
|
modMessage:
|
||||||
|
subject: 'Wiadomość od osoby moderującej'
|
||||||
|
intro: 'Hej! Jedna z naszych osób moderujących wysłała Ci następującą wiadomość:'
|
||||||
|
respond: 'Jeśli chcesz odpowiedzieć, możesz po prostu odpisać na tego maila.'
|
||||||
|
|
||||||
profile:
|
profile:
|
||||||
description: 'Opis'
|
description: 'Opis'
|
||||||
|
11
migrations/062-user-messages.sql
Normal file
11
migrations/062-user-messages.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
-- Up
|
||||||
|
|
||||||
|
CREATE TABLE user_messages (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
userId TEXT NOT NULL,
|
||||||
|
adminId TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Down
|
||||||
|
|
@ -371,6 +371,60 @@ router.post('/admin/reports/handle/:id', handleErrorAsync(async (req, res) => {
|
|||||||
return res.json(true);
|
return res.json(true);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const fetchModMessages = async (db, user) => {
|
||||||
|
return db.all(SQL`
|
||||||
|
SELECT m.id, a.username as adminUsername, m.message
|
||||||
|
FROM user_messages m
|
||||||
|
LEFT JOIN users a ON m.adminId = a.id
|
||||||
|
WHERE m.userId = ${user.id}
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.post('/admin/mod-message/:username', handleErrorAsync(async (req, res) => {
|
||||||
|
if (!req.isGranted('users')) {
|
||||||
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.body.message) {
|
||||||
|
return res.status(400).json({error: 'Bad request'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await fetchUserByUsername(req.db, req.params.username);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).json({error: 'No such user'});
|
||||||
|
}
|
||||||
|
|
||||||
|
await req.db.get(SQL`INSERT INTO user_messages (id, userId, adminId, message) VALUES (
|
||||||
|
${ulid()},
|
||||||
|
${user.id},
|
||||||
|
${req.user.id},
|
||||||
|
${req.body.message}
|
||||||
|
)`);
|
||||||
|
|
||||||
|
mailer(user.email, 'modMessage', {
|
||||||
|
message: req.body.message,
|
||||||
|
username: req.params.username,
|
||||||
|
modUsername: req.user.username,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(await fetchModMessages(req.db, user));
|
||||||
|
}));
|
||||||
|
|
||||||
|
router.get('/admin/mod-messages/:username', handleErrorAsync(async (req, res) => {
|
||||||
|
if (!req.isGranted('users')) {
|
||||||
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await fetchUserByUsername(req.db, req.params.username);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(400).json({error: 'No such user'});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(await fetchModMessages(req.db, user));
|
||||||
|
}));
|
||||||
|
|
||||||
router.get('/admin/moderation', handleErrorAsync(async (req, res) => {
|
router.get('/admin/moderation', handleErrorAsync(async (req, res) => {
|
||||||
if (!req.isGranted('users')) {
|
if (!req.isGranted('users')) {
|
||||||
return res.status(401).json({error: 'Unauthorised'});
|
return res.status(401).json({error: 'Unauthorised'});
|
||||||
|
@ -89,6 +89,16 @@ const templates = {
|
|||||||
text: 'Check them out here: https://[[domain]]/admin',
|
text: 'Check them out here: https://[[domain]]/admin',
|
||||||
html: '<p>Check them out here: <a href="https://[[domain]]/admin" target="_blank" rel="noopener">[[domain]]/admin</a></p>',
|
html: '<p>Check them out here: <a href="https://[[domain]]/admin" target="_blank" rel="noopener">[[domain]]/admin</a></p>',
|
||||||
},
|
},
|
||||||
|
modMessage: {
|
||||||
|
subject: '[[user.modMessage.subject]]',
|
||||||
|
text: `[[user.modMessage.intro]]\n\n{{nl2br:message}}\n\n[[user.modMessage.respond]]`,
|
||||||
|
html: `
|
||||||
|
<p>[[user.modMessage.intro]]</p>
|
||||||
|
<p style="color: #222; padding-left: 1em;padding-right: 1em;font-style: italic;">{{nl2br:message}}</p>
|
||||||
|
<p>[[user.modMessage.respond]]</p>
|
||||||
|
<p style="color: #999; font-size: 10px;">@{{modUsername}} → @{{username}}</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyTemplate = (template, context, params) => {
|
const applyTemplate = (template, context, params) => {
|
||||||
@ -122,6 +132,12 @@ const applyTemplate = (template, context, params) => {
|
|||||||
: Object.keys(value).map(s => ` - ${s}: ${value[s]}`).join('\n');
|
: Object.keys(value).map(s => ` - ${s}: ${value[s]}`).join('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (key.startsWith('nl2br:')) {
|
||||||
|
const value = params[key.substring(6)];
|
||||||
|
return context === 'html'
|
||||||
|
? value.replace(new RegExp('\\n', 'g'), '<br/>')
|
||||||
|
: value;
|
||||||
|
}
|
||||||
return params[key];
|
return params[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user