update diff rendering logic (#419)
* update diff to seperate removals from additions, removals on the first line greens on the 2nd line (being the current message) * Add an option to go back to the legacy diffs or to the current updated seperated diff logic * Update index.tsx * Quick Fix * Fixes * NewPluginsManager --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> Co-authored-by: justjxke <Jake0300@protonmai.com>
This commit is contained in:
parent
835ec92c59
commit
d695e3cac0
6 changed files with 435 additions and 26 deletions
205
src/equicordplugins/newPluginsManager/NewPluginsModal.tsx
Normal file
205
src/equicordplugins/newPluginsManager/NewPluginsModal.tsx
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { PluginCard } from "@components/settings/tabs/plugins/PluginCard";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
import { classes, Margins } from "@utils/index";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import { findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Flex, Forms, React, Text, Tooltip, useMemo } from "@webpack/common";
|
||||
import { JSX } from "react";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { getNewPlugins, getNewSettings, KnownPluginSettingsMap, writeKnownSettings } from "./knownSettings";
|
||||
|
||||
const cl = classNameFactory("vc-plugins-");
|
||||
|
||||
const Checkbox = findComponentByCodeLazy(".checkboxWrapperDisabled:");
|
||||
|
||||
let hasSeen = false;
|
||||
|
||||
// Most of this was stolen from PluginSettings directly.
|
||||
|
||||
export function NewPluginsModal({ modalProps, newPlugins, newSettings }: { modalProps: ModalProps; newPlugins: Set<string>; newSettings: KnownPluginSettingsMap; }) {
|
||||
const settings = useSettings();
|
||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||
let updateContinueButton = () => { };
|
||||
|
||||
const depMap = React.useMemo(() => {
|
||||
const o = {} as Record<string, string[]>;
|
||||
for (const plugin in Plugins) {
|
||||
const deps = Plugins[plugin].dependencies;
|
||||
if (deps) {
|
||||
for (const dep of deps) {
|
||||
o[dep] ??= [];
|
||||
o[dep].push(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}, []);
|
||||
|
||||
const mapPlugins = (array: string[]) => array.map(pn => Plugins[pn])
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const sortedPlugins = useMemo(() => [
|
||||
...mapPlugins([...newPlugins]),
|
||||
...mapPlugins([...newSettings.keys()].filter(p => !newPlugins.has(p)))
|
||||
], []);
|
||||
|
||||
const plugins = [] as JSX.Element[];
|
||||
const requiredPlugins = [] as JSX.Element[];
|
||||
|
||||
for (const p of sortedPlugins) {
|
||||
if (p.hidden)
|
||||
continue;
|
||||
|
||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
|
||||
if (isRequired) {
|
||||
const tooltipText = p.required
|
||||
? "This plugin is required for Vencord to function."
|
||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||
|
||||
requiredPlugins.push(
|
||||
<Tooltip text={tooltipText} key={p.name}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<PluginCard
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onRestartNeeded={name => {
|
||||
changes.handleChange(name);
|
||||
updateContinueButton();
|
||||
}}
|
||||
disabled={true}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
isNew={newPlugins.has(p.name)}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
plugins.push(
|
||||
<PluginCard
|
||||
onRestartNeeded={name => {
|
||||
changes.handleChange(name);
|
||||
updateContinueButton();
|
||||
}}
|
||||
disabled={false}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
isNew={newPlugins.has(p.name)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return <ModalRoot {...modalProps} size={ModalSize.MEDIUM} >
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>New Plugins and Settings ({[...plugins, ...requiredPlugins].length})</Text>
|
||||
<Tooltip text="Dismiss for this session">
|
||||
{tooltipProps =>
|
||||
<div {...tooltipProps}>
|
||||
<ModalCloseButton
|
||||
onClick={modalProps.onClose}
|
||||
className={classes(cl("close"), "vc-newPluginsManager-close")}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</Tooltip>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<div className={cl("grid")}>
|
||||
{[...plugins, ...requiredPlugins]}
|
||||
</div>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex direction={Flex.Direction.HORIZONTAL_REVERSE}>
|
||||
<ContinueButton
|
||||
close={modalProps.onClose}
|
||||
changes={changes}
|
||||
callback={(v: () => void) => updateContinueButton = v}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex direction={Flex.Direction.HORIZONTAL}>
|
||||
<div className="vc-newPluginsManager-disable-wrapper">
|
||||
<Checkbox
|
||||
type="inverted"
|
||||
value={!settings?.plugins?.NewPluginsManager?.enabled}
|
||||
onChange={() => {
|
||||
Settings.plugins.NewPluginsManager.enabled = !settings?.plugins?.NewPluginsManager?.enabled;
|
||||
}}
|
||||
>
|
||||
<Text>Don't show this again</Text>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>;
|
||||
}
|
||||
|
||||
function ContinueButton(props: { callback: (update: () => void) => void; changes: ChangeList<string>; close: () => any; }) {
|
||||
const update = useForceUpdater();
|
||||
props.callback(update);
|
||||
return <Tooltip
|
||||
tooltipClassName="vc-newPluginsManager-restart-tooltip"
|
||||
text={<>
|
||||
The following plugins require a restart:
|
||||
<div className={Margins.bottom8} />
|
||||
<ul className="vc-newPluginsManager-restart-list">
|
||||
{props.changes.map(p => <li key={p}>{p}</li>)}
|
||||
</ul>
|
||||
</>}
|
||||
shouldShow={props.changes.hasChanges}
|
||||
>
|
||||
{tooltipProps =>
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={async () => {
|
||||
await writeKnownSettings();
|
||||
props.changes.hasChanges ? location.reload() : props.close();
|
||||
}}
|
||||
>
|
||||
{props.changes.hasChanges ? "Restart" : "Continue"}
|
||||
</Button>
|
||||
}
|
||||
</Tooltip>;
|
||||
}
|
||||
|
||||
|
||||
function makeDependencyList(deps: string[]) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export async function openNewPluginsModal() {
|
||||
const newPlugins = await getNewPlugins();
|
||||
const newSettings = await getNewSettings();
|
||||
if ((newPlugins.size || newSettings.size) && !hasSeen) {
|
||||
hasSeen = true;
|
||||
const modalKey = openModal(modalProps => <ErrorBoundary noop onError={() => { closeModal(modalKey); }}>
|
||||
<NewPluginsModal
|
||||
modalProps={modalProps}
|
||||
newPlugins={newPlugins}
|
||||
newSettings={newSettings}
|
||||
/>
|
||||
</ErrorBoundary>);
|
||||
}
|
||||
}
|
||||
28
src/equicordplugins/newPluginsManager/index.tsx
Normal file
28
src/equicordplugins/newPluginsManager/index.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
import * as KnownSettings from "./knownSettings";
|
||||
import { KNOWN_PLUGINS_LEGACY_DATA_KEY, KNOWN_SETTINGS_DATA_KEY } from "./knownSettings";
|
||||
import { openNewPluginsModal } from "./NewPluginsModal";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NewPluginsManager",
|
||||
description: "Utility that notifies you when new plugins are added to Equicord",
|
||||
authors: [Devs.Sqaaakoi],
|
||||
enabledByDefault: true,
|
||||
flux: {
|
||||
async POST_CONNECTION_OPEN() {
|
||||
openNewPluginsModal();
|
||||
}
|
||||
},
|
||||
openNewPluginsModal,
|
||||
KNOWN_PLUGINS_LEGACY_DATA_KEY,
|
||||
KNOWN_SETTINGS_DATA_KEY,
|
||||
KnownSettings
|
||||
});
|
||||
91
src/equicordplugins/newPluginsManager/knownSettings.ts
Normal file
91
src/equicordplugins/newPluginsManager/knownSettings.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
|
||||
import plugins from "~plugins";
|
||||
|
||||
export type KnownPluginSettingsMap = Map<string, Set<string>>;
|
||||
|
||||
export const KNOWN_PLUGINS_LEGACY_DATA_KEY = "NewPluginsManager_KnownPlugins";
|
||||
export const KNOWN_SETTINGS_DATA_KEY = "NewPluginsManager_KnownSettings";
|
||||
|
||||
function getSettingsSetForPlugin(plugin: string): Set<string> {
|
||||
const settings = plugins[plugin]?.settings?.def || {};
|
||||
return new Set(Object.keys(settings).filter(setting => setting !== "enabled"));
|
||||
}
|
||||
|
||||
function getCurrentSettings(pluginList: string[]): KnownPluginSettingsMap {
|
||||
return new Map(pluginList.map(name => [
|
||||
name,
|
||||
getSettingsSetForPlugin(name)
|
||||
]));
|
||||
}
|
||||
|
||||
export async function getKnownSettings(): Promise<KnownPluginSettingsMap> {
|
||||
let map = await DataStore.get(KNOWN_SETTINGS_DATA_KEY) as KnownPluginSettingsMap;
|
||||
if (map === undefined) {
|
||||
const knownPlugins = await DataStore.get(KNOWN_PLUGINS_LEGACY_DATA_KEY) ?? [] as string[];
|
||||
const Plugins = [...Object.keys(plugins), ...knownPlugins];
|
||||
map = getCurrentSettings(Plugins);
|
||||
DataStore.set(KNOWN_SETTINGS_DATA_KEY, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function getNewSettings(): Promise<KnownPluginSettingsMap> {
|
||||
const map = getCurrentSettings(Object.keys(plugins));
|
||||
const knownSettings = await getKnownSettings();
|
||||
map.forEach((settings, plugin) => {
|
||||
const filteredSettings = [...settings].filter(setting => !knownSettings.get(plugin)?.has(setting));
|
||||
if (!filteredSettings.length) return map.delete(plugin);
|
||||
map.set(plugin, new Set(filteredSettings));
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
export async function getKnownPlugins(): Promise<Set<string>> {
|
||||
const knownSettings = await getKnownSettings();
|
||||
return new Set(knownSettings.keys());
|
||||
}
|
||||
|
||||
export async function getNewPlugins(): Promise<Set<string>> {
|
||||
const currentPlugins = Object.keys(plugins);
|
||||
const knownPlugins = await getKnownPlugins();
|
||||
return new Set(currentPlugins.filter(p => !knownPlugins.has(p)));
|
||||
}
|
||||
|
||||
export async function writeKnownSettings() {
|
||||
const currentSettings = getCurrentSettings(Object.keys(plugins));
|
||||
const knownSettings = await getKnownSettings();
|
||||
const allSettings = new Map();
|
||||
new Set([...currentSettings.keys(), ...knownSettings.keys()]).forEach(plugin => {
|
||||
allSettings.set(plugin, new Set([
|
||||
...(currentSettings.get(plugin) || []),
|
||||
...(knownSettings.get(plugin) || [])
|
||||
]));
|
||||
});
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, allSettings);
|
||||
}
|
||||
|
||||
export async function debugWipeSomeData() {
|
||||
const settings = await getKnownSettings();
|
||||
settings.forEach((value, key) => {
|
||||
if (Math.random() > 0.8) {
|
||||
if (Math.random() > 0.5) return settings.set(key, new Set([...value].filter(() => Math.random() > 0.5)));
|
||||
return settings.delete(key);
|
||||
}
|
||||
});
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, settings);
|
||||
}
|
||||
|
||||
export async function editRawData(patcher: (data: KnownPluginSettingsMap) => (Promise<any> | any)) {
|
||||
if (!patcher) return;
|
||||
const map = await DataStore.get(KNOWN_SETTINGS_DATA_KEY) as KnownPluginSettingsMap;
|
||||
const newMap = new Map(map);
|
||||
await patcher(newMap);
|
||||
await DataStore.set(KNOWN_SETTINGS_DATA_KEY, newMap ?? map);
|
||||
}
|
||||
20
src/equicordplugins/newPluginsManager/styles.css
Normal file
20
src/equicordplugins/newPluginsManager/styles.css
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.vc-newPluginsManager-restart-tooltip {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-restart-list {
|
||||
padding-left: 1.25em;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-restart-list li {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-close > div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vc-newPluginsManager-disable-wrapper {
|
||||
display: flex;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
|
@ -166,42 +166,80 @@ const patchChannelContextMenu: NavContextMenuPatchCallback = (
|
|||
);
|
||||
};
|
||||
|
||||
function renderDiffParts(diffParts: DiffPart[], message: Message) {
|
||||
return diffParts.map((part, index) => {
|
||||
const parsedContent = Parser.parse(part.text, true, {
|
||||
channelId: message.channel_id,
|
||||
messageId: message.id,
|
||||
allowLinks: true,
|
||||
allowHeading: true,
|
||||
allowList: true,
|
||||
allowEmojiLinks: true,
|
||||
viewingChannelId: SelectedChannelStore.getChannelId(),
|
||||
});
|
||||
|
||||
if (part.type === "unchanged") {
|
||||
return React.createElement("span", { key: index }, parsedContent);
|
||||
} else if (part.type === "added") {
|
||||
return React.createElement("span", {
|
||||
key: index,
|
||||
className: "messagelogger-diff-added"
|
||||
}, parsedContent);
|
||||
} else if (part.type === "removed") {
|
||||
return React.createElement("span", {
|
||||
key: index,
|
||||
className: "messagelogger-diff-removed"
|
||||
}, parsedContent);
|
||||
}
|
||||
return React.createElement("span", { key: index }, parsedContent);
|
||||
function createDiffSegment(part: DiffPart, message: Message, key: React.Key, highlightType?: "removed" | "added") {
|
||||
const parsedContent = Parser.parse(part.text, true, {
|
||||
channelId: message.channel_id,
|
||||
messageId: message.id,
|
||||
allowLinks: true,
|
||||
allowHeading: true,
|
||||
allowList: true,
|
||||
allowEmojiLinks: true,
|
||||
viewingChannelId: SelectedChannelStore.getChannelId(),
|
||||
});
|
||||
|
||||
let className: string | undefined;
|
||||
if (part.type === "added" || part.type === "removed") {
|
||||
if (!highlightType || highlightType === part.type) {
|
||||
className = `messagelogger-diff-${part.type}`;
|
||||
}
|
||||
}
|
||||
|
||||
return React.createElement("span", { key, className }, parsedContent);
|
||||
}
|
||||
|
||||
function renderDiffParts(diffParts: DiffPart[], message: Message) {
|
||||
return diffParts.map((part, index) => createDiffSegment(part, message, index));
|
||||
}
|
||||
|
||||
function renderFilteredDiffParts(diffParts: DiffPart[], message: Message, mode: "removed" | "added") {
|
||||
const segments: React.ReactNode[] = [];
|
||||
|
||||
diffParts.forEach((part, index) => {
|
||||
if (mode === "removed" && part.type === "added") return;
|
||||
if (mode === "added" && part.type === "removed") return;
|
||||
|
||||
segments.push(createDiffSegment(part, message, `${mode}-${index}`, mode));
|
||||
});
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
export function parseEditContent(content: string, message: Message, previousContent?: string) {
|
||||
const perMessageDiffEnabled = !disabledDiffMessages.has(message.id);
|
||||
if (previousContent && content !== previousContent && settings.store.showEditDiffs && perMessageDiffEnabled) {
|
||||
const diffParts = createMessageDiff(content, previousContent);
|
||||
const useSeparatedDiffs = settings.store.separatedDiffs;
|
||||
|
||||
if (useSeparatedDiffs) {
|
||||
const highlightCurrent = previousContent === message.content;
|
||||
const originalView = renderFilteredDiffParts(diffParts, message, "removed");
|
||||
|
||||
if (highlightCurrent) {
|
||||
(message as any).customRenderedContent = {
|
||||
__messageloggerDiff: true,
|
||||
content: renderFilteredDiffParts(diffParts, message, "added"),
|
||||
};
|
||||
} else if ((message as any).customRenderedContent?.__messageloggerDiff) {
|
||||
delete (message as any).customRenderedContent;
|
||||
}
|
||||
|
||||
return React.createElement("div", { className: "messagelogger-diff-view" },
|
||||
React.createElement("div", { className: "messagelogger-diff-original" }, originalView),
|
||||
!highlightCurrent && React.createElement("div", { className: "messagelogger-diff-updated" }, renderFilteredDiffParts(diffParts, message, "added")),
|
||||
);
|
||||
}
|
||||
|
||||
if ((message as any).customRenderedContent?.__messageloggerDiff) {
|
||||
delete (message as any).customRenderedContent;
|
||||
}
|
||||
|
||||
return renderDiffParts(diffParts, message);
|
||||
}
|
||||
|
||||
if ((message as any).customRenderedContent?.__messageloggerDiff) {
|
||||
delete (message as any).customRenderedContent;
|
||||
}
|
||||
|
||||
return Parser.parse(content, true, {
|
||||
channelId: message.channel_id,
|
||||
messageId: message.id,
|
||||
|
|
@ -274,6 +312,21 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
description: "Show visual differences between edited message versions",
|
||||
default: false,
|
||||
onChange: value => {
|
||||
if (value && settings.store.separatedDiffs) {
|
||||
settings.store.separatedDiffs = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
separatedDiffs: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Separate addition and removals in diffs for a more readable differential",
|
||||
default: false,
|
||||
onChange: value => {
|
||||
if (value && settings.store.showEditDiffs) {
|
||||
settings.store.showEditDiffs = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -84,6 +84,18 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.messagelogger-diff-view {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.messagelogger-diff-original,
|
||||
.messagelogger-diff-updated {
|
||||
display: block;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* when diffs are disabled for a specific message, remove diff colors */
|
||||
.messagelogger-diff-disabled .messagelogger-diff-added,
|
||||
.messagelogger-diff-disabled .messagelogger-diff-removed {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue