mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-14 00:23:29 +01:00
Add ability to blacklist phrases in chat (#30902)
* add ability to blacklist phrases in chat * support exact * support exact * i think this fixes the tgui thing * prettier linter eeeeeeeeeeee
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
* Circumvents the message queue and sends the message to the recipient (target) as soon as possible.
|
||||
* trailing_newline, confidential, and handle_whitespace currently have no effect, please fix this in the future or remove the arguments to lower cache!
|
||||
*/
|
||||
/proc/to_chat_immediate(target, html, type, text, avoid_highlighting = FALSE, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE, ticket_id = -1)
|
||||
/proc/to_chat_immediate(target, html, type, text, avoid_highlighting = FALSE, avoid_blacklisting = FALSE, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE, ticket_id = -1)
|
||||
// Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
|
||||
html = "[html]"
|
||||
text = "[text]"
|
||||
@@ -29,6 +29,8 @@
|
||||
message["html"] = html
|
||||
if(avoid_highlighting)
|
||||
message["avoidHighlighting"] = avoid_highlighting
|
||||
if(avoid_blacklisting)
|
||||
message["avoidBlacklisting"] = avoid_blacklisting
|
||||
if(ticket_id != -1)
|
||||
message["ticket_id"] = ticket_id
|
||||
|
||||
@@ -53,7 +55,7 @@
|
||||
*
|
||||
* `trailing_newline`, `confidential`, and `handle_whitespace` currently have no effect, please fix this in the future or remove the arguments to lower cache!
|
||||
*/
|
||||
/proc/to_chat(target, html, type, text, avoid_highlighting, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE, ticket_id = -1)
|
||||
/proc/to_chat(target, html, type, text, avoid_highlighting, avoid_blacklisting, handle_whitespace = TRUE, trailing_newline = TRUE, confidential = FALSE, ticket_id = -1)
|
||||
if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
|
||||
to_chat_immediate(target, html, type, text)
|
||||
return
|
||||
@@ -79,6 +81,8 @@
|
||||
message["html"] = html
|
||||
if(avoid_highlighting)
|
||||
message["avoidHighlighting"] = avoid_highlighting
|
||||
if(avoid_blacklisting)
|
||||
message["avoidBlacklisting"] = avoid_blacklisting
|
||||
if(ticket_id != -1)
|
||||
message["ticket_id"] = ticket_id
|
||||
SSchat.queue(target, message)
|
||||
|
||||
@@ -8,9 +8,12 @@ import { storage } from 'common/storage';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
import {
|
||||
addBlacklistSetting,
|
||||
addHighlightSetting,
|
||||
loadSettings,
|
||||
removeBlacklistSetting,
|
||||
removeHighlightSetting,
|
||||
updateBlacklistSetting,
|
||||
updateHighlightSetting,
|
||||
updateSettings,
|
||||
} from '../settings/actions';
|
||||
@@ -180,11 +183,15 @@ export const chatMiddleware = (store) => {
|
||||
type === loadSettings.type ||
|
||||
type === addHighlightSetting.type ||
|
||||
type === removeHighlightSetting.type ||
|
||||
type === updateHighlightSetting.type
|
||||
type === updateHighlightSetting.type ||
|
||||
type === addBlacklistSetting.type ||
|
||||
type === removeBlacklistSetting.type ||
|
||||
type === updateBlacklistSetting.type
|
||||
) {
|
||||
next(action);
|
||||
const settings = selectSettings(store.getState());
|
||||
chatRenderer.setHighlight(settings.highlightSettings, settings.highlightSettingById);
|
||||
chatRenderer.setBlacklist(settings.blacklistSettings, settings.blacklistSettingById);
|
||||
return;
|
||||
}
|
||||
if (type === 'roundrestart') {
|
||||
|
||||
@@ -16,12 +16,14 @@ import {
|
||||
IMAGE_RETRY_MESSAGE_AGE,
|
||||
MAX_VISIBLE_MESSAGES,
|
||||
MESSAGE_PRUNE_INTERVAL,
|
||||
MESSAGE_TYPE_ADMINPM,
|
||||
MESSAGE_TYPE_INTERNAL,
|
||||
MESSAGE_TYPE_MENTORPM,
|
||||
MESSAGE_TYPE_UNKNOWN,
|
||||
MESSAGE_TYPES,
|
||||
} from './constants';
|
||||
import { canPageAcceptType, createMessage, isSameMessage } from './model';
|
||||
import { highlightNode, linkifyNode } from './replaceInTextNode';
|
||||
import { highlightNode, linkifyNode, processBlacklistNode } from './replaceInTextNode';
|
||||
|
||||
const logger = createLogger('chatRenderer');
|
||||
|
||||
@@ -264,6 +266,89 @@ class ChatRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
setBlacklist(blacklistSettings, blacklistSettingById) {
|
||||
this.blacklistParsers = null;
|
||||
if (!blacklistSettings) {
|
||||
return;
|
||||
}
|
||||
blacklistSettings.map((id) => {
|
||||
const setting = blacklistSettingById[id];
|
||||
const text = setting.blacklistText;
|
||||
const censor = setting.censor;
|
||||
const matchWord = setting.matchWord;
|
||||
const matchCase = setting.matchCase;
|
||||
const allowedRegex = /^[a-zа-яё0-9_\-$/^[\s\]\\]+$/gi;
|
||||
const regexEscapeCharacters = /[!#$%^&*)(+=.<>{}[\]:;'"|~`_\-\\/]/g;
|
||||
const lines = String(text)
|
||||
.split(/[,|]/)
|
||||
.map((str) => str.trim())
|
||||
.filter(
|
||||
(str) =>
|
||||
// Must be longer than one character
|
||||
str &&
|
||||
str.length > 1 &&
|
||||
// Must be alphanumeric (with some punctuation)
|
||||
allowedRegex.test(str) &&
|
||||
// Reset last
|
||||
((allowedRegex.lastIndex = 0) || true)
|
||||
);
|
||||
let blacklistWords;
|
||||
let blacklistRegex;
|
||||
// Nothing to blacklist
|
||||
if (lines.length === 0) {
|
||||
return;
|
||||
}
|
||||
let regexExpressions = [];
|
||||
// Organize each blacklist entry into regex expressions and words
|
||||
for (let line of lines) {
|
||||
// Regex expression syntax is /[exp]/
|
||||
if (line.charAt(0) === '/' && line.charAt(line.length - 1) === '/') {
|
||||
const expr = line.substring(1, line.length - 1);
|
||||
// Check if this is more than one character
|
||||
if (/^(\[.*\]|\\.|.)$/.test(expr)) {
|
||||
continue;
|
||||
}
|
||||
regexExpressions.push(expr);
|
||||
} else {
|
||||
// Lazy init
|
||||
if (!blacklistWords) {
|
||||
blacklistWords = [];
|
||||
}
|
||||
// We're not going to let regex characters fuck up our RegEx operation.
|
||||
line = line.replace(regexEscapeCharacters, '\\$&');
|
||||
|
||||
blacklistWords.push(line);
|
||||
}
|
||||
}
|
||||
const regexStr = regexExpressions.join('|');
|
||||
const flags = 'g' + (matchCase ? '' : 'i');
|
||||
try {
|
||||
// setting regex overrides blacklistWords
|
||||
if (regexStr) {
|
||||
blacklistRegex = new RegExp('(' + regexStr + ')', flags);
|
||||
} else {
|
||||
const pattern = `${matchWord ? '\\b' : ''}(${blacklistWords.join('|')})${matchWord ? '\\b' : ''}`;
|
||||
blacklistRegex = new RegExp(pattern, flags);
|
||||
}
|
||||
} catch {
|
||||
// We just reset it if it's invalid.
|
||||
blacklistRegex = null;
|
||||
}
|
||||
// Lazy init
|
||||
if (!this.blacklistParsers) {
|
||||
this.blacklistParsers = [];
|
||||
}
|
||||
this.blacklistParsers.push({
|
||||
blacklistWords,
|
||||
blacklistRegex,
|
||||
censor,
|
||||
});
|
||||
});
|
||||
|
||||
// Rebuild chat to apply new blacklist settings to existing messages
|
||||
this.rebuildChat();
|
||||
}
|
||||
|
||||
scrollToBottom() {
|
||||
// scrollHeight is always bigger than scrollTop and is
|
||||
// automatically clamped to the valid range.
|
||||
@@ -382,18 +467,38 @@ class ChatRenderer {
|
||||
}
|
||||
});
|
||||
}
|
||||
// Linkify text
|
||||
const linkifyNodes = node.querySelectorAll('.linkify');
|
||||
for (let i = 0; i < linkifyNodes.length; ++i) {
|
||||
linkifyNode(linkifyNodes[i]);
|
||||
}
|
||||
// Assign an image error handler
|
||||
if (now < message.createdAt + IMAGE_RETRY_MESSAGE_AGE) {
|
||||
const imgNodes = node.querySelectorAll('img');
|
||||
for (let i = 0; i < imgNodes.length; i++) {
|
||||
const imgNode = imgNodes[i];
|
||||
imgNode.addEventListener('error', handleImageError);
|
||||
}
|
||||
|
||||
// Process blacklist patterns
|
||||
let isBlacklisted = false;
|
||||
// No blacklisting for Admin PMs and Mentor PMs
|
||||
if (
|
||||
!message.avoidBlacklisting &&
|
||||
message.type !== MESSAGE_TYPE_ADMINPM &&
|
||||
message.type !== MESSAGE_TYPE_MENTORPM &&
|
||||
this.blacklistParsers
|
||||
) {
|
||||
this.blacklistParsers.forEach((parser) => {
|
||||
const foundMatch = processBlacklistNode(node, parser.blacklistRegex, parser.blacklistWords, parser.censor);
|
||||
|
||||
if (!parser.censor && foundMatch) {
|
||||
// Mark for removal
|
||||
isBlacklisted = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Linkify text
|
||||
const linkifyNodes = node.querySelectorAll('.linkify');
|
||||
for (let i = 0; i < linkifyNodes.length; ++i) {
|
||||
linkifyNode(linkifyNodes[i]);
|
||||
}
|
||||
// Assign an image error handler
|
||||
if (now < message.createdAt + IMAGE_RETRY_MESSAGE_AGE) {
|
||||
const imgNodes = node.querySelectorAll('img');
|
||||
for (let i = 0; i < imgNodes.length; i++) {
|
||||
const imgNode = imgNodes[i];
|
||||
imgNode.addEventListener('error', handleImageError);
|
||||
}
|
||||
}
|
||||
// Store the node in the message
|
||||
@@ -410,7 +515,7 @@ class ChatRenderer {
|
||||
countByType[message.type] += 1;
|
||||
// TODO: Detect duplicates
|
||||
this.messages.push(message);
|
||||
if (canPageAcceptType(this.page, message.type)) {
|
||||
if (canPageAcceptType(this.page, message.type) && !isBlacklisted) {
|
||||
fragment.appendChild(node);
|
||||
this.visibleMessages.push(message);
|
||||
}
|
||||
|
||||
@@ -197,3 +197,71 @@ const linkifyTextNode = replaceInTextNode(URL_REGEX, null, (text) => {
|
||||
node.textContent = text;
|
||||
return node;
|
||||
});
|
||||
|
||||
// Blacklist
|
||||
// --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Censored text node replaces non-space characters with asterisks
|
||||
*/
|
||||
const createCensorNode = (text) => {
|
||||
const node = document.createTextNode(text.replace(/[^\s]/g, '*'));
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if blacklisted content is contained within a node
|
||||
*/
|
||||
const checkBlacklistMatch = (node, regex, words) => {
|
||||
const childNodes = node.childNodes;
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const childNode = childNodes[i];
|
||||
// Is a text node
|
||||
if (childNode.nodeType === 3) {
|
||||
const text = childNode.textContent;
|
||||
// Use regex if available
|
||||
if (regex && regex.test(text)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Recursively check child nodes
|
||||
if (checkBlacklistMatch(childNode, regex, words)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Censor or detect blacklist content in a node
|
||||
*
|
||||
* @param {Node} node Node which you want to process
|
||||
* @param {RegExp} regex Regular expression to blacklist
|
||||
* @param {string[]} words Array of words to blacklist
|
||||
* @param {boolean} censor If true, censor the content with asterisks; if false, just detect matches
|
||||
* @returns {boolean} True if blacklisted content was found
|
||||
*/
|
||||
export const processBlacklistNode = (node, regex, words, censor = false) => {
|
||||
if (!censor) {
|
||||
return checkBlacklistMatch(node, regex, words);
|
||||
}
|
||||
|
||||
// Use replacement
|
||||
let matchCount = 0;
|
||||
const childNodes = node.childNodes;
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const childNode = childNodes[i];
|
||||
// Is a text node
|
||||
if (childNode.nodeType === 3) {
|
||||
matchCount += replaceInTextNode(regex, words, createCensorNode)(childNode);
|
||||
} else {
|
||||
// Recursively process child nodes
|
||||
if (processBlacklistNode(childNode, regex, words, censor)) {
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchCount > 0;
|
||||
};
|
||||
|
||||
@@ -27,14 +27,24 @@ import { ChatPageSettings } from '../chat';
|
||||
import { clearChat, rebuildChat, saveChatToDisk } from '../chat/actions';
|
||||
import { THEMES } from '../themes';
|
||||
import {
|
||||
addBlacklistSetting,
|
||||
addHighlightSetting,
|
||||
changeSettingsTab,
|
||||
removeBlacklistSetting,
|
||||
removeHighlightSetting,
|
||||
updateBlacklistSetting,
|
||||
updateHighlightSetting,
|
||||
updateSettings,
|
||||
} from './actions';
|
||||
import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
|
||||
import { selectActiveTab, selectHighlightSettingById, selectHighlightSettings, selectSettings } from './selectors';
|
||||
import {
|
||||
selectActiveTab,
|
||||
selectBlacklistSettingById,
|
||||
selectBlacklistSettings,
|
||||
selectHighlightSettingById,
|
||||
selectHighlightSettings,
|
||||
selectSettings,
|
||||
} from './selectors';
|
||||
import { SettingsStatPanel } from './SettingsStatPanel';
|
||||
|
||||
export const SettingsPanel = (props) => {
|
||||
@@ -67,6 +77,7 @@ export const SettingsPanel = (props) => {
|
||||
{activeTab === 'general' && <SettingsGeneral />}
|
||||
{activeTab === 'chatPage' && <ChatPageSettings />}
|
||||
{activeTab === 'textHighlight' && <TextHighlightSettings />}
|
||||
{activeTab === 'textBlacklist' && <TextBlacklistSettings />}
|
||||
{activeTab === 'statPanel' && <SettingsStatPanel />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
@@ -387,3 +398,133 @@ const TextHighlightSetting = (props) => {
|
||||
</Stack.Item>
|
||||
);
|
||||
};
|
||||
|
||||
const TextBlacklistSettings = (props) => {
|
||||
const blacklistSettings = useSelector(selectBlacklistSettings) || [];
|
||||
const dispatch = useDispatch();
|
||||
return (
|
||||
<Section fill scrollable height="230px">
|
||||
<Section>
|
||||
<Stack vertical>
|
||||
{blacklistSettings.map((id, i) => (
|
||||
<TextBlacklistSetting key={i} id={id} mb={i + 1 === blacklistSettings.length ? 0 : '10px'} />
|
||||
))}
|
||||
{blacklistSettings.length < MAX_HIGHLIGHT_SETTINGS && (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="plus"
|
||||
onClick={() => {
|
||||
dispatch(addBlacklistSetting());
|
||||
}}
|
||||
>
|
||||
Add Blacklist Setting
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
<Stack.Divider />
|
||||
<Box>
|
||||
<Button icon="check" onClick={() => dispatch(rebuildChat())}>
|
||||
Apply now
|
||||
</Button>
|
||||
<Box inline fontSize="0.9em" ml={1} color="label">
|
||||
Can freeze the chat for a while.
|
||||
</Box>
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const TextBlacklistSetting = (props) => {
|
||||
const { id, ...rest } = props;
|
||||
const blacklistSettingById = useSelector(selectBlacklistSettingById) || {};
|
||||
const dispatch = useDispatch();
|
||||
const { blacklistText = '', censor = false, matchWord = false, matchCase = false } = blacklistSettingById[id] || {};
|
||||
return (
|
||||
<Stack.Item {...rest}>
|
||||
<Stack mb={1} color="label" align="baseline">
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
color="transparent"
|
||||
icon="times"
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
removeBlacklistSetting({
|
||||
id: id,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={censor}
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="If this option is selected, the phrase will be censored instead of removing the whole message."
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
updateBlacklistSetting({
|
||||
id: id,
|
||||
censor: !censor,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Censor
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={matchWord}
|
||||
tooltipPosition="bottom-start"
|
||||
tooltip="If this option is selected, only exact matches (no extra letters before or after) will trigger. Not compatible with punctuation. Overriden if regex is used."
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
updateBlacklistSetting({
|
||||
id: id,
|
||||
matchWord: !matchWord,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Exact
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
tooltip="If this option is selected, the blacklist will be case-sensitive."
|
||||
checked={matchCase}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
updateBlacklistSetting({
|
||||
id: id,
|
||||
matchCase: !matchCase,
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Case
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<TextArea
|
||||
fluid
|
||||
height="3em"
|
||||
value={blacklistText}
|
||||
placeholder="Put terms to blacklist here. Separate terms with commas or vertical bars, i.e. (term1 | term2) or (term1, term2). Regex syntax is /[regex]/"
|
||||
onChange={(value) =>
|
||||
dispatch(
|
||||
updateBlacklistSetting({
|
||||
id: id,
|
||||
blacklistText: value,
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { createAction } from 'common/redux';
|
||||
|
||||
import { createHighlightSetting } from './model';
|
||||
import { createBlacklistSetting, createHighlightSetting } from './model';
|
||||
|
||||
export const updateSettings = createAction('settings/update');
|
||||
export const loadSettings = createAction('settings/load');
|
||||
@@ -18,3 +18,8 @@ export const addHighlightSetting = createAction('settings/addHighlightSetting',
|
||||
}));
|
||||
export const removeHighlightSetting = createAction('settings/removeHighlightSetting');
|
||||
export const updateHighlightSetting = createAction('settings/updateHighlightSetting');
|
||||
export const addBlacklistSetting = createAction('settings/addBlacklistSetting', () => ({
|
||||
payload: createBlacklistSetting(),
|
||||
}));
|
||||
export const removeBlacklistSetting = createAction('settings/removeBlacklistSetting');
|
||||
export const updateBlacklistSetting = createAction('settings/updateBlacklistSetting');
|
||||
|
||||
@@ -13,6 +13,10 @@ export const SETTINGS_TABS = [
|
||||
id: 'textHighlight',
|
||||
name: 'Text Highlights',
|
||||
},
|
||||
{
|
||||
id: 'textBlacklist',
|
||||
name: 'Text Blacklist',
|
||||
},
|
||||
{
|
||||
id: 'chatPage',
|
||||
name: 'Chat Tabs',
|
||||
|
||||
@@ -8,9 +8,12 @@ import { storage } from 'common/storage';
|
||||
|
||||
import { setClientTheme } from '../themes';
|
||||
import {
|
||||
addBlacklistSetting,
|
||||
addHighlightSetting,
|
||||
loadSettings,
|
||||
removeBlacklistSetting,
|
||||
removeHighlightSetting,
|
||||
updateBlacklistSetting,
|
||||
updateHighlightSetting,
|
||||
updateSettings,
|
||||
} from './actions';
|
||||
@@ -77,7 +80,10 @@ export const settingsMiddleware = (store) => {
|
||||
type === loadSettings.type ||
|
||||
type === addHighlightSetting.type ||
|
||||
type === removeHighlightSetting.type ||
|
||||
type === updateHighlightSetting.type
|
||||
type === updateHighlightSetting.type ||
|
||||
type === addBlacklistSetting.type ||
|
||||
type === removeBlacklistSetting.type ||
|
||||
type === updateBlacklistSetting.type
|
||||
) {
|
||||
// Set client theme
|
||||
const theme = payload?.theme;
|
||||
|
||||
@@ -18,3 +18,18 @@ export const createDefaultHighlightSetting = (obj?: Record<string, any>) =>
|
||||
id: 'default',
|
||||
...obj,
|
||||
});
|
||||
|
||||
export const createBlacklistSetting = (obj?: Record<string, any>) => ({
|
||||
id: createUuid(),
|
||||
blacklistText: '',
|
||||
censor: false,
|
||||
matchWord: false,
|
||||
matchCase: false,
|
||||
...obj,
|
||||
});
|
||||
|
||||
export const createDefaultBlacklistSetting = (obj?: Record<string, any>) =>
|
||||
createBlacklistSetting({
|
||||
id: 'default',
|
||||
...obj,
|
||||
});
|
||||
|
||||
@@ -7,19 +7,23 @@
|
||||
import { storage } from 'common/storage';
|
||||
|
||||
import {
|
||||
addBlacklistSetting,
|
||||
addHighlightSetting,
|
||||
changeSettingsTab,
|
||||
loadSettings,
|
||||
openChatSettings,
|
||||
removeBlacklistSetting,
|
||||
removeHighlightSetting,
|
||||
toggleSettings,
|
||||
updateBlacklistSetting,
|
||||
updateHighlightSetting,
|
||||
updateSettings,
|
||||
} from './actions';
|
||||
import { FONTS, MAX_HIGHLIGHT_SETTINGS, SETTINGS_TABS } from './constants';
|
||||
import { createDefaultHighlightSetting } from './model';
|
||||
import { createDefaultBlacklistSetting, createDefaultHighlightSetting } from './model';
|
||||
|
||||
const defaultHighlightSetting = createDefaultHighlightSetting();
|
||||
const defaultBlacklistSetting = createDefaultBlacklistSetting();
|
||||
|
||||
const initialState = {
|
||||
version: 1,
|
||||
@@ -36,6 +40,10 @@ const initialState = {
|
||||
highlightSettingById: {
|
||||
[defaultHighlightSetting.id]: defaultHighlightSetting,
|
||||
},
|
||||
blacklistSettings: [defaultBlacklistSetting.id],
|
||||
blacklistSettingById: {
|
||||
[defaultBlacklistSetting.id]: defaultBlacklistSetting,
|
||||
},
|
||||
view: {
|
||||
visible: false,
|
||||
activeTab: SETTINGS_TABS[0].id,
|
||||
@@ -80,6 +88,16 @@ export const settingsReducer = (state = initialState, action) => {
|
||||
nextState.highlightSettings = [defaultHighlightSetting.id, ...nextState.highlightSettings];
|
||||
nextState.highlightSettingById[defaultHighlightSetting.id] = defaultHighlightSetting;
|
||||
}
|
||||
// Lazy init blacklist
|
||||
if (!nextState.blacklistSettings) {
|
||||
nextState.blacklistSettings = [defaultBlacklistSetting.id];
|
||||
nextState.blacklistSettingById[defaultBlacklistSetting.id] = defaultBlacklistSetting;
|
||||
}
|
||||
// Just doing the same as above
|
||||
else if (!nextState.blacklistSettingById[defaultBlacklistSetting.id]) {
|
||||
nextState.blacklistSettings = [defaultBlacklistSetting.id, ...nextState.blacklistSettings];
|
||||
nextState.blacklistSettingById[defaultBlacklistSetting.id] = defaultBlacklistSetting;
|
||||
}
|
||||
// Update the highlight settings for default highlight
|
||||
// settings compatibility
|
||||
const highlightSetting = nextState.highlightSettingById[defaultHighlightSetting.id];
|
||||
@@ -181,5 +199,51 @@ export const settingsReducer = (state = initialState, action) => {
|
||||
|
||||
return nextState;
|
||||
}
|
||||
if (type === addBlacklistSetting.type) {
|
||||
const blacklistSetting = payload;
|
||||
if (state.blacklistSettings.length >= MAX_HIGHLIGHT_SETTINGS) {
|
||||
return state;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
blacklistSettings: [...state.blacklistSettings, blacklistSetting.id],
|
||||
blacklistSettingById: {
|
||||
...state.blacklistSettingById,
|
||||
[blacklistSetting.id]: blacklistSetting,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === removeBlacklistSetting.type) {
|
||||
const { id } = payload;
|
||||
const nextState = {
|
||||
...state,
|
||||
blacklistSettings: [...state.blacklistSettings],
|
||||
blacklistSettingById: {
|
||||
...state.blacklistSettingById,
|
||||
},
|
||||
};
|
||||
delete nextState.blacklistSettingById[id];
|
||||
nextState.blacklistSettings = nextState.blacklistSettings.filter((sid) => sid !== id);
|
||||
return nextState;
|
||||
}
|
||||
if (type === updateBlacklistSetting.type) {
|
||||
const { id, ...settings } = payload;
|
||||
const nextState = {
|
||||
...state,
|
||||
blacklistSettings: [...state.blacklistSettings],
|
||||
blacklistSettingById: {
|
||||
...state.blacklistSettingById,
|
||||
},
|
||||
};
|
||||
|
||||
if (nextState.blacklistSettingById[id]) {
|
||||
nextState.blacklistSettingById[id] = {
|
||||
...nextState.blacklistSettingById[id],
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
|
||||
return nextState;
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
@@ -8,3 +8,5 @@ export const selectSettings = (state) => state.settings;
|
||||
export const selectActiveTab = (state) => state.settings.view.activeTab;
|
||||
export const selectHighlightSettings = (state) => state.settings.highlightSettings;
|
||||
export const selectHighlightSettingById = (state) => state.settings.highlightSettingById;
|
||||
export const selectBlacklistSettings = (state) => state.settings.blacklistSettings;
|
||||
export const selectBlacklistSettingById = (state) => state.settings.blacklistSettingById;
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user