Skip to content

Commit

Permalink
Improvements to modbar module (#608)
Browse files Browse the repository at this point in the history
  • Loading branch information
eritbh committed Sep 1, 2022
1 parent 63e892b commit 10de512
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 127 deletions.
264 changes: 141 additions & 123 deletions extension/data/modules/modnotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Module} from '../tbmodule.js';
import {getModSubs, link, modsSub} from '../tbcore.js';
import {escapeHTML, htmlEncode} from '../tbhelpers.js';
import * as TBApi from '../tbapi.js';
import {actionButton, drawPosition, icons, popup} from '../tbui.js';
import {actionButton, drawPosition, icons, pagerForItems, popup} from '../tbui.js';
import TBListener from '../tblistener.js';

/**
Expand Down Expand Up @@ -205,20 +205,26 @@ function createModNotesPopup ({
}) {
const $popup = popup({
title: `Mod notes for /u/${user} in /r/${subreddit}`,
tabs: [{
title: 'All Activity',
content: `
<div class="tb-modnote-table-wrap">
<p class="error">loading...</p>
</div>
<div class="tb-modnote-create-wrap">
<b>Add a Note</b>
<!-- TODO: erin put a label select here too thx -->
<input type="text" class="tb-modnote-text-input tb-input">
</div>
`,
footer: actionButton('Create Note', 'tb-modnote-create-button'),
}],
tabs: [
{
title: 'All Activity',
id: 'tb-modnote-tab-all',
},
{
title: 'Notes',
id: 'tb-modnote-tab-notes',
},
{
id: 'tb-modnote-tab-actions',
title: 'Mod Actions',
},
],
footer: $(`
<span>
<input type="text" class="tb-modnote-text-input tb-input">
</span>
`)
.append(actionButton('Create Note', 'tb-modnote-create-button')),
cssClass: 'tb-modnote-popup',
});
$popup.attr('data-user', user);
Expand All @@ -240,129 +246,141 @@ function createModNotesPopup ({
function updateModNotesPopup ($popup, {
notes,
}) {
const $content = $popup.find('.tb-modnote-table-wrap');
$content.empty();

if (!notes) {
// Notes being null/undefined indicates notes couldn't be fetched
// TODO: probably pass errors into this function for display, and also
// to distinguish "failed to load" from "still loading"
$content.append(`
<p class="error">
Error fetching mod notes
</p>
`);
} else if (!notes.length) {
// If the notes list is empty, our job is very easy
$content.append(`
<p>
No notes
</p>
`);
} else {
// Generate a table for the notes we have and display that
$content.append(generateNotesTable(notes));
}
// Build a table for each tab containing the right subset of notes
$popup.find('.tb-window-tab').each(function () {
const $tabContainer = $(this);

const $content = $tabContainer.find('.tb-window-content');
$content.empty();

if (!notes) {
// Notes being null/undefined indicates notes couldn't be fetched
// TODO: probably pass errors into this function for display, and
// also to distinguish "failed to load" from "still loading"
$content.append(`
<p class="error">
Error fetching mod notes
</p>
`);
return;
}

// Filter notes as appropriate for this tab
let filteredNotes = notes;
if ($tabContainer.hasClass('tb-modnote-tab-notes')) {
filteredNotes = notes.filter(note => note.user_note_data?.note);
}
if ($tabContainer.hasClass('tb-modnote-tab-actions')) {
filteredNotes = notes.filter(note => note.mod_action_data?.action);
}

if (!filteredNotes.length) {
// If the notes list is empty, our job is very easy
$content.append(`
<p>
No notes
</p>
`);
} else {
// Generate a table for the notes we have and display that
const $notesPager = pagerForItems({
items: filteredNotes,
perPage: 10,
displayItem: generateNoteTableRow,
wrapper: `
<table class="tb-modnote-table">
<thead>
<tr>
<th>Author</th>
<th>Type</th>
<th>Details</th>
<th></th>
</tr>
</thead>
</table>
`,
});
$content.append($notesPager);
}
});
}

/**
* Generates a table of the given notes.
* @param {object[]} notes An array of note objects
* @returns {jQuery} The generated table
*/
function generateNotesTable (notes) {
const $notesTable = $(`
<table class="tb-modnote-table">
<thead>
<tr>
<th>Author</th>
<th>Type</th>
<th>Details</th>
<th></th>
</tr>
</thead>
</table>
function generateNoteTableRow (note) {
const createdAt = new Date(note.created_at * 1000);
const mod = note.operator; // TODO: can [deleted] show up here?

const $noteRow = $(`
<tr>
<td>
<a href="${link(`/user/${encodeURIComponent(mod)}`)}">
/u/${escapeHTML(mod)}
</a>
<br>
<small>
<time datetime="${escapeHTML(createdAt.toISOString())}">
${escapeHTML(createdAt.toLocaleString())}
</time>
</small>
</td>
<td>
${typeNames[note.type]}
</td>
</tr>
`);

// Build the body of the table
const $notesTableBody = $('<tbody>');
for (const note of notes) {
const createdAt = new Date(note.created_at * 1000);
const mod = note.operator; // TODO: can [deleted] show up here?

const $noteRow = $(`
<tr>
<td>
<a href="${link(`/user/${encodeURIComponent(mod)}`)}">
/u/${escapeHTML(mod)}
</a>
<br>
<small>
<time datetime="${escapeHTML(createdAt.toISOString())}">
${escapeHTML(createdAt.toLocaleString())}
</time>
</small>
</td>
<td>
${typeNames[note.type]}
</td>
</tr>
`);

// Build the note details based on what sort of information is present
const $noteDetails = $('<td>');

if (note.mod_action_data?.action) {
$noteDetails.append(`
<span class="tb-modnote-action-summary">
Took action "${escapeHTML(note.mod_action_data.action)}"${note.mod_action_data.details ? ` (${escapeHTML(note.mod_action_data.details)})` : ''}${note.mod_action_data.description ? `: ${escapeHTML(note.mod_action_data.description)}` : ''}
</span>
`);
}
// Build the note details based on what sort of information is present
const $noteDetails = $('<td>');

if (note.user_note_data?.note) {
$noteDetails.append(`
<blockquote>
${note.user_note_data.label ? `
<span style="color:${labelColors[note.user_note_data.label]}">
[${labelNames[note.user_note_data.label] || escapeHTML(note.user_note_data.label)}]
</span>
` : ''}
${escapeHTML(note.user_note_data.note)}
</blockquote>
`);
}
if (note.mod_action_data?.action) {
$noteDetails.append(`
<span class="tb-modnote-action-summary">
Took action "${escapeHTML(note.mod_action_data.action)}"${note.mod_action_data.details ? ` (${escapeHTML(note.mod_action_data.details)})` : ''}${note.mod_action_data.description ? `: ${escapeHTML(note.mod_action_data.description)}` : ''}
</span>
`);
}

$noteRow.append($noteDetails);

// Only manually added notes can be deleted
if (note.type === 'NOTE') {
$noteRow.append(`
<td>
<a
href="#"
role="button"
class="tb-modnote-delete-button tb-icons tb-icons-negative"
data-note-id="${escapeHTML(note.id)}"
>
${icons.delete}
</a>
</td>
`);
} else {
// append an empty td to avoid weird border stuff
$noteRow.append('<td>');
}
if (note.user_note_data?.note) {
$noteDetails.append(`
<blockquote>
${note.user_note_data.label ? `
<span style="color:${labelColors[note.user_note_data.label]}">
[${labelNames[note.user_note_data.label] || escapeHTML(note.user_note_data.label)}]
</span>
` : ''}
${escapeHTML(note.user_note_data.note)}
</blockquote>
`);
}

$notesTableBody.append($noteRow);
$noteRow.append($noteDetails);

// Only manually added notes can be deleted
if (note.type === 'NOTE') {
$noteRow.append(`
<td>
<a
href="#"
role="button"
class="tb-modnote-delete-button tb-icons tb-icons-negative"
data-note-id="${escapeHTML(note.id)}"
>
${icons.delete}
</a>
</td>
`);
} else {
// append an empty td to avoid weird border stuff
$noteRow.append('<td>');
}

// Update dates in a nice format
$notesTableBody.find('time.timeago').timeago();
$noteRow.find('time').timeago();

// We're done generating the body, add it to the table and return the result
$notesTable.append($notesTableBody);
return $notesTable;
return $noteRow;
}

export default new Module({
Expand Down
2 changes: 1 addition & 1 deletion extension/data/styles/modnotes.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.mod-toolbox-rd .tb-modnote-table :is(th, td) {
/* stolen from usernotes, break this out into a generic table class later */
padding: 2px !important;
padding: 2px 4px !important;
border: solid 1px #C1BFBF;
vertical-align: top;
}
Expand Down
20 changes: 17 additions & 3 deletions extension/data/tbui.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ $body.on('click', '.tb-notification', function () {
* @param {object} options Options for the popup
* @param {string} options.title The popup's title (raw HTML)
* @param {object[]} options.tabs The tabs for the popup
* @param {string} options.footer The popup footer (used for all tabs; if
* provided, tab footers are ignored)
* @param {string?} options.cssClass Extra CSS class to add to the popup
* @param {string?} options.meta Raw HTML to add to a "meta" container
* @param {boolean?} [draggable=true] Whether the user can move the popup
Expand All @@ -184,6 +186,7 @@ $body.on('click', '.tb-notification', function () {
export function popup ({
title,
tabs,
footer,
cssClass = '',
meta,
draggable = true,
Expand All @@ -206,7 +209,7 @@ export function popup ({
if (tabs.length === 1) {
// We don't use template literals here as the content can be a jquery object.
$popup.append($('<div class="tb-window-content"></div>').append(tabs[0].content));
$popup.append($('<div class="tb-window-footer"></div>').append(tabs[0].footer));
$popup.append($('<div class="tb-window-footer"></div>').append(footer || tabs[0].footer));
} else {
const $tabs = $('<div class="tb-window-tabs"></div>');
$popup.append($tabs);
Expand Down Expand Up @@ -248,7 +251,10 @@ export function popup ({
// We don't use template literals here as the content can be a jquery object.
const $tab = $(`<div class="tb-window-tab ${tab.id}"></div>`);
$tab.append($('<div class="tb-window-content"></div>').append(tab.content));
$tab.append($('<div class="tb-window-footer""></div>').append(tab.footer));
if (!footer) {
// Only display tab footer if whole-popup footer not set
$tab.append($('<div class="tb-window-footer""></div>').append(tab.footer));
}

// default first tab is visible; hide others
if (i === 0) {
Expand All @@ -259,6 +265,11 @@ export function popup ({

$tab.appendTo($popup);
}

// If we have a whole-popup footer, add it underneath the tabbed portion
if (footer) {
$popup.append($('<div class="tb-window-footer"></div>').append(footer));
}
}

if (draggable) {
Expand Down Expand Up @@ -1787,19 +1798,22 @@ export function pager ({pageCount, controlPosition = 'top'}, contentFunction) {
* content for each individual item in the dataset
* @param {string} controlPosition Where to display the pager's controls,
* either 'top' or 'bottom'
* @param {string} [wrapper='<div>'] Used to provide custom wrapper markup for
* each page of items
* @returns {jQuery}
*/
export function pagerForItems ({
items,
perPage,
displayItem,
controlPosition,
wrapper = '<div>',
}) {
return pager({
controlPosition,
pageCount: Math.ceil(items.length / perPage),
}, page => {
const $wrapper = $('<div>');
const $wrapper = $(wrapper);
const start = page * perPage;
const end = (page + 1) * perPage;
for (let i = start; i < end && i < items.length; i += 1) {
Expand Down

0 comments on commit 10de512

Please sign in to comment.