2019-11-10 17:38:47 +00:00
|
|
|
import {Except} from 'type-fest';
|
|
|
|
import debounce from 'debounce';
|
2020-10-03 11:20:12 +00:00
|
|
|
import {ColorKey, themeColors} from '../theme-colors';
|
2019-11-10 17:38:47 +00:00
|
|
|
import {
|
|
|
|
getSettings,
|
|
|
|
Settings,
|
|
|
|
log,
|
|
|
|
createElementFromString,
|
|
|
|
UserLabel,
|
|
|
|
isInTopicListing,
|
2020-10-03 11:20:12 +00:00
|
|
|
getCSSCustomPropertyValue,
|
2019-11-10 17:38:47 +00:00
|
|
|
isColorBright,
|
|
|
|
setSettings,
|
|
|
|
appendStyleAttribute,
|
|
|
|
querySelector
|
|
|
|
} from '../utilities';
|
|
|
|
import {
|
|
|
|
getLabelForm,
|
|
|
|
getLabelFormValues,
|
|
|
|
hideLabelForm,
|
|
|
|
getLabelFormID
|
|
|
|
} from './user-labels/label-form';
|
|
|
|
import {
|
|
|
|
editLabelHandler,
|
|
|
|
addLabelHandler,
|
|
|
|
labelTextInputHandler,
|
|
|
|
presetColorSelectHandler,
|
|
|
|
labelColorInputHandler
|
|
|
|
} from './user-labels/handlers';
|
|
|
|
|
|
|
|
(async (): Promise<void> => {
|
|
|
|
const settings: Settings = await getSettings();
|
|
|
|
if (!settings.features.userLabels) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
addLabelsToUsernames(settings);
|
|
|
|
const existingLabelForm: HTMLElement | null = document.querySelector(
|
|
|
|
'#trx-user-label-form'
|
|
|
|
);
|
|
|
|
if (existingLabelForm !== null) {
|
|
|
|
existingLabelForm.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
const themeSelectOptions: string[] = [];
|
2020-10-03 11:20:12 +00:00
|
|
|
for (const color in themeColors) {
|
|
|
|
if (Object.hasOwnProperty.call(themeColors, color)) {
|
|
|
|
const colorValue = getCSSCustomPropertyValue(
|
|
|
|
themeColors[color as ColorKey]
|
|
|
|
);
|
2019-11-10 17:38:47 +00:00
|
|
|
themeSelectOptions.push(
|
2020-10-03 11:20:12 +00:00
|
|
|
`<option value="${colorValue}">${color}</option>`
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const labelFormTemplate = `<form id="trx-user-label-form" class="trx-hidden">
|
|
|
|
<div>
|
|
|
|
<label for="trx-user-label-form-username">Add New Label</label>
|
|
|
|
<label for="trx-user-label-priority">Priority</label>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<input type="text" id="trx-user-label-form-username" class="form-input" placeholder="Username">
|
|
|
|
<input id="trx-user-label-priority" type="number" class="form-input" value="0">
|
|
|
|
</div>
|
|
|
|
<label>Pick A Color</label>
|
|
|
|
<div id="trx-user-label-form-color">
|
|
|
|
<input type="text" class="form-input" placeholder="Color">
|
|
|
|
<select class="form-select">
|
|
|
|
${themeSelectOptions.join('\n')}
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
<label>Label</label>
|
|
|
|
<div id="trx-user-label-input">
|
|
|
|
<input type="text" class="form-input" placeholder="Label">
|
|
|
|
<div id="trx-user-label-preview">
|
|
|
|
<p></p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div id="trx-user-label-actions">
|
|
|
|
<a id="trx-user-label-save" class="btn-post-action">Save</a>
|
|
|
|
<a id="trx-user-label-close" class="btn-post-action">Close</a>
|
|
|
|
<a id="trx-user-label-remove" class="btn-post-action">Remove</a>
|
|
|
|
</div>
|
|
|
|
</form>`;
|
|
|
|
const labelForm: HTMLFormElement = createElementFromString(labelFormTemplate);
|
|
|
|
document.body.append(labelForm);
|
|
|
|
labelForm.setAttribute(
|
|
|
|
'style',
|
2020-10-03 11:20:12 +00:00
|
|
|
`background-color: var(${themeColors.backgroundPrimary});` +
|
|
|
|
`border-color: var(${themeColors.foregroundSecondary});`
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const labelColorInput: HTMLInputElement = querySelector(
|
|
|
|
'#trx-user-label-form-color > input'
|
|
|
|
);
|
|
|
|
labelColorInput.addEventListener(
|
|
|
|
'keyup',
|
|
|
|
debounce(labelColorInputHandler, 250)
|
|
|
|
);
|
|
|
|
|
|
|
|
const presetColorSelect: HTMLSelectElement = querySelector(
|
|
|
|
'#trx-user-label-form-color > select'
|
|
|
|
);
|
|
|
|
presetColorSelect.addEventListener('change', presetColorSelectHandler);
|
2020-10-03 11:20:12 +00:00
|
|
|
presetColorSelect.value = getCSSCustomPropertyValue(
|
|
|
|
themeColors.backgroundSecondary
|
|
|
|
);
|
2019-11-10 17:38:47 +00:00
|
|
|
|
|
|
|
const labelTextInput: HTMLInputElement = querySelector(
|
|
|
|
'#trx-user-label-input > input'
|
|
|
|
);
|
|
|
|
labelTextInput.addEventListener('keyup', labelTextInputHandler);
|
|
|
|
|
|
|
|
const labelPreview: HTMLDivElement = querySelector('#trx-user-label-preview');
|
|
|
|
labelPreview.setAttribute(
|
|
|
|
'style',
|
2020-10-03 11:20:12 +00:00
|
|
|
`background-color: var(${themeColors.backgroundPrimary});` +
|
|
|
|
`border-color: var(${themeColors.foregroundSecondary});`
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
const formSaveButton: HTMLAnchorElement = querySelector(
|
|
|
|
'#trx-user-label-save'
|
|
|
|
);
|
|
|
|
formSaveButton.addEventListener('click', saveUserLabel);
|
|
|
|
|
|
|
|
const formCloseButton: HTMLAnchorElement = querySelector(
|
|
|
|
'#trx-user-label-close'
|
|
|
|
);
|
|
|
|
formCloseButton.addEventListener('click', hideLabelForm);
|
|
|
|
|
|
|
|
const formRemoveButton: HTMLAnchorElement = querySelector(
|
|
|
|
'#trx-user-label-remove'
|
|
|
|
);
|
|
|
|
formRemoveButton.addEventListener('click', removeUserLabel);
|
|
|
|
|
|
|
|
const commentObserver = new window.MutationObserver(
|
|
|
|
async (mutations: MutationRecord[]): Promise<void> => {
|
|
|
|
const commentElements: HTMLElement[] = mutations
|
2020-06-05 21:55:11 +00:00
|
|
|
.map((value) => value.target as HTMLElement)
|
2019-11-10 17:38:47 +00:00
|
|
|
.filter(
|
2020-06-05 21:55:11 +00:00
|
|
|
(value) =>
|
|
|
|
value.classList.contains('comment-itself') ||
|
|
|
|
value.classList.contains('comment')
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
if (commentElements.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
commentObserver.disconnect();
|
|
|
|
addLabelsToUsernames(await getSettings());
|
|
|
|
startObserver();
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
function startObserver(): void {
|
|
|
|
const topicComments: HTMLElement | null = document.querySelector(
|
|
|
|
'.topic-comments'
|
|
|
|
);
|
|
|
|
if (topicComments !== null) {
|
|
|
|
commentObserver.observe(topicComments, {
|
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const postListing: HTMLElement | null = document.querySelector(
|
|
|
|
'.post-listing'
|
|
|
|
);
|
|
|
|
if (postListing !== null) {
|
|
|
|
commentObserver.observe(postListing, {
|
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
startObserver();
|
|
|
|
})();
|
|
|
|
|
|
|
|
// TODO: Refactor this function to be able to only add labels to specific
|
|
|
|
// elements. At the moment it goes through all `.link-user` elements which is
|
|
|
|
// inefficient.
|
|
|
|
function addLabelsToUsernames(settings: Settings): void {
|
|
|
|
for (const element of [
|
|
|
|
...document.querySelectorAll('.trx-user-label'),
|
|
|
|
...document.querySelectorAll('.trx-user-label-add')
|
|
|
|
]) {
|
|
|
|
element.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const element of document.querySelectorAll('.link-user')) {
|
|
|
|
const username: string = element
|
|
|
|
.textContent!.replace(/@/g, '')
|
|
|
|
.toLowerCase();
|
2019-11-10 22:28:51 +00:00
|
|
|
|
2019-11-10 17:38:47 +00:00
|
|
|
const addLabelSpan: HTMLSpanElement = createElementFromString(
|
|
|
|
`<span class="trx-user-label-add" data-trx-username="${username}">[+]</span>`
|
|
|
|
);
|
|
|
|
addLabelSpan.addEventListener('click', (event: MouseEvent): void =>
|
|
|
|
addLabelHandler(event, addLabelSpan)
|
|
|
|
);
|
|
|
|
if (!isInTopicListing()) {
|
|
|
|
element.insertAdjacentElement('afterend', addLabelSpan);
|
2020-10-03 11:20:12 +00:00
|
|
|
appendStyleAttribute(
|
|
|
|
addLabelSpan,
|
|
|
|
`color: var(${themeColors.foregroundPrimary});`
|
|
|
|
);
|
2019-11-10 17:38:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const userLabels: UserLabel[] = settings.data.userLabels.filter(
|
2020-06-05 21:55:11 +00:00
|
|
|
(value) => value.username === username
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
if (userLabels.length === 0) {
|
|
|
|
if (
|
|
|
|
isInTopicListing() &&
|
|
|
|
(element.nextElementSibling === null ||
|
|
|
|
!element.nextElementSibling.className.includes('trx-user-label'))
|
|
|
|
) {
|
|
|
|
element.insertAdjacentElement('afterend', addLabelSpan);
|
2020-10-03 11:20:12 +00:00
|
|
|
appendStyleAttribute(
|
|
|
|
addLabelSpan,
|
|
|
|
`color: var(${themeColors.foregroundPrimary});`
|
|
|
|
);
|
2019-11-10 17:38:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInTopicListing()) {
|
|
|
|
userLabels.sort((a, b) => b.priority - a.priority);
|
|
|
|
} else {
|
|
|
|
userLabels.sort((a, b): number => {
|
|
|
|
if (a.priority !== b.priority) {
|
|
|
|
return a.priority - b.priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.text.localeCompare(a.text);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const userLabel of userLabels) {
|
|
|
|
const userLabelSpan: HTMLSpanElement = createElementFromString(
|
|
|
|
`<span class="trx-user-label" data-trx-user-label-id="${userLabel.id}">${userLabel.text}</span>`
|
|
|
|
);
|
|
|
|
userLabelSpan.addEventListener(
|
|
|
|
'click',
|
|
|
|
async (event: MouseEvent): Promise<void> =>
|
|
|
|
editLabelHandler(event, userLabelSpan)
|
|
|
|
);
|
|
|
|
element.insertAdjacentElement('afterend', userLabelSpan);
|
|
|
|
// Set the inline-style after the element gets added to the DOM, this
|
|
|
|
// will prevent a CSP error saying inline-styles aren't permitted.
|
|
|
|
userLabelSpan.setAttribute(
|
|
|
|
'style',
|
|
|
|
`background-color: ${userLabel.color};`
|
|
|
|
);
|
2020-10-03 11:20:12 +00:00
|
|
|
if (isColorBright(userLabel.color.trim())) {
|
2019-11-10 17:38:47 +00:00
|
|
|
userLabelSpan.classList.add('trx-bright');
|
|
|
|
} else {
|
|
|
|
userLabelSpan.classList.remove('trx-bright');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isInTopicListing()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function saveUserLabel(): Promise<void> {
|
|
|
|
const settings: Settings = await getSettings();
|
|
|
|
const labelForm: HTMLFormElement = getLabelForm();
|
|
|
|
const labelNoID: Except<UserLabel, 'id'> | undefined = getLabelFormValues();
|
|
|
|
if (typeof labelNoID === 'undefined') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingIDString: string | null = labelForm.getAttribute(
|
|
|
|
'data-trx-user-label-id'
|
|
|
|
);
|
|
|
|
if (existingIDString === null) {
|
|
|
|
settings.data.userLabels.push({
|
|
|
|
id: (await getHighestLabelID(settings)) + 1,
|
|
|
|
...labelNoID
|
|
|
|
});
|
|
|
|
await setSettings(settings);
|
|
|
|
hideLabelForm();
|
|
|
|
addLabelsToUsernames(settings);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingID = Number(existingIDString);
|
|
|
|
const existingLabel: UserLabel | undefined = settings.data.userLabels.find(
|
2020-06-05 21:55:11 +00:00
|
|
|
(value) => value.id === existingID
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
if (typeof existingLabel === 'undefined') {
|
|
|
|
log(`Tried to find label with ID that doesn't exist: ${existingID}`, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const existingLabelIndex: number = settings.data.userLabels.findIndex(
|
2020-06-05 21:55:11 +00:00
|
|
|
(value) => value.id === existingID
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
settings.data.userLabels.splice(existingLabelIndex, 1);
|
|
|
|
settings.data.userLabels.push({
|
|
|
|
id: existingID,
|
|
|
|
...labelNoID
|
|
|
|
});
|
|
|
|
await setSettings(settings);
|
|
|
|
hideLabelForm();
|
|
|
|
addLabelsToUsernames(settings);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function removeUserLabel(): Promise<void> {
|
|
|
|
const labelNoID: Except<UserLabel, 'id'> | undefined = getLabelFormValues();
|
|
|
|
if (typeof labelNoID === 'undefined') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const id: number | undefined = getLabelFormID();
|
|
|
|
if (typeof id === 'undefined') {
|
|
|
|
log('Attempted to remove user label without an ID.');
|
|
|
|
hideLabelForm();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const settings: Settings = await getSettings();
|
|
|
|
const labelIndex: number = settings.data.userLabels.findIndex(
|
2020-06-05 21:55:11 +00:00
|
|
|
(value) => value.id === id
|
2019-11-10 17:38:47 +00:00
|
|
|
);
|
|
|
|
if (typeof findLabelByID(id) === 'undefined') {
|
|
|
|
log(
|
|
|
|
`Attempted to remove user label with an ID that doesn't exist ${id}.`,
|
|
|
|
true
|
|
|
|
);
|
|
|
|
hideLabelForm();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
settings.data.userLabels.splice(labelIndex, 1);
|
|
|
|
await setSettings(settings);
|
|
|
|
hideLabelForm();
|
|
|
|
addLabelsToUsernames(settings);
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function findLabelByID(
|
|
|
|
id: number,
|
|
|
|
settings?: Settings
|
|
|
|
): Promise<UserLabel | undefined> {
|
|
|
|
if (typeof settings === 'undefined') {
|
|
|
|
settings = await getSettings();
|
|
|
|
}
|
|
|
|
|
2020-06-05 21:55:11 +00:00
|
|
|
return settings.data.userLabels.find((value) => value.id === id);
|
2019-11-10 17:38:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function getHighestLabelID(settings?: Settings): Promise<number> {
|
|
|
|
if (typeof settings === 'undefined') {
|
|
|
|
settings = await getSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.data.userLabels.length === 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-05 21:55:11 +00:00
|
|
|
return Math.max(...settings.data.userLabels.map((value) => value.id));
|
2019-11-10 17:38:47 +00:00
|
|
|
}
|