mirror of
https://github.com/the1812/Bilibili-Evolved.git
synced 2025-11-04 21:22:45 +08:00
Trying to add reloadable settings
This commit is contained in:
parent
0178c54eb0
commit
4bcdf19ec9
2
@types/global/index.d.ts
vendored
2
@types/global/index.d.ts
vendored
@ -237,6 +237,8 @@ declare global
|
|||||||
function logError(message: Error | string): void;
|
function logError(message: Error | string): void;
|
||||||
function loadSettings(): void
|
function loadSettings(): void
|
||||||
function saveSettings(newSettings: BilibiliEvolvedSettings): void;
|
function saveSettings(newSettings: BilibiliEvolvedSettings): void;
|
||||||
|
function addSettingsListener(key: keyof BilibiliEvolvedSettings, handler: (newValue: any, oldValue: any) => void): void;
|
||||||
|
function removeSettingsListener(key: keyof BilibiliEvolvedSettings, handler: (newValue: any, oldValue: any) => void): void;
|
||||||
function onSettingsChange(change: (key: string, oldValue: any, newValue: any) => void): void;
|
function onSettingsChange(change: (key: string, oldValue: any, newValue: any) => void): void;
|
||||||
function downloadText(url: string, load: (text: string) => void, error: (text: string) => void): void;
|
function downloadText(url: string, load: (text: string) => void, error: (text: string) => void): void;
|
||||||
function downloadText(url: string): Promise<string>;
|
function downloadText(url: string): Promise<string>;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -124,6 +124,27 @@ const fixedSettings = {
|
|||||||
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/preview/bilibili-evolved.preview.user.js",
|
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/preview/bilibili-evolved.preview.user.js",
|
||||||
currentVersion: GM_info.script.version,
|
currentVersion: GM_info.script.version,
|
||||||
};
|
};
|
||||||
|
const settingsChangeHandlers = {};
|
||||||
|
function addSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
if (!settingsChangeHandlers[key])
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key] = [handler];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key].push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (!handlers)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlers.splice(handlers.indexOf(handler), 1);
|
||||||
|
}
|
||||||
function loadSettings()
|
function loadSettings()
|
||||||
{
|
{
|
||||||
for (const key in fixedSettings)
|
for (const key in fixedSettings)
|
||||||
@ -150,6 +171,11 @@ function loadSettings()
|
|||||||
},
|
},
|
||||||
set(newValue)
|
set(newValue)
|
||||||
{
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (handlers)
|
||||||
|
{
|
||||||
|
handlers.forEach(h => h(newValue, value));
|
||||||
|
}
|
||||||
value = newValue;
|
value = newValue;
|
||||||
GM_setValue(key, newValue);
|
GM_setValue(key, newValue);
|
||||||
},
|
},
|
||||||
@ -410,10 +436,10 @@ function loadResources()
|
|||||||
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/";
|
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/";
|
||||||
Resource.all = {};
|
Resource.all = {};
|
||||||
Resource.displayNames = {};
|
Resource.displayNames = {};
|
||||||
// Resource.reloadables = {
|
Resource.reloadables = {
|
||||||
// useDarkStyle: "useDarkStyle",
|
useDarkStyle: "useDarkStyle",
|
||||||
// showBanner: "overrideNavBar",
|
hideBanner: "hideBanner",
|
||||||
// };
|
};
|
||||||
for (const [key, data] of Object.entries(Resource.manifest))
|
for (const [key, data] of Object.entries(Resource.manifest))
|
||||||
{
|
{
|
||||||
const resource = new Resource(data.path, data.styles);
|
const resource = new Resource(data.path, data.styles);
|
||||||
@ -1967,8 +1993,52 @@ class ResourceManager
|
|||||||
{
|
{
|
||||||
loadingToast.dismiss();
|
loadingToast.dismiss();
|
||||||
}
|
}
|
||||||
|
this.applyReloadables(); // reloadables run sync
|
||||||
await this.applyDropdownOptions();
|
await this.applyDropdownOptions();
|
||||||
this.applyWidgets();
|
this.applyWidgets(); // No need to wait the widgets
|
||||||
|
}
|
||||||
|
applyReloadables()
|
||||||
|
{
|
||||||
|
const checkAttribute = (key, attributes) =>
|
||||||
|
{
|
||||||
|
if (attributes.reload && attributes.unload)
|
||||||
|
{
|
||||||
|
addSettingsListener(key, newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
attributes.reload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
attributes.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const [key, targetKey] of Object.entries(Resource.reloadables))
|
||||||
|
{
|
||||||
|
const attributes = this.attributes[targetKey];
|
||||||
|
if (attributes === undefined)
|
||||||
|
{
|
||||||
|
const fetchListener = newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
this.fetchByKey(targetKey).then(() =>
|
||||||
|
{
|
||||||
|
removeSettingsListener(key, fetchListener);
|
||||||
|
checkAttribute(key, this.attributes[targetKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addSettingsListener(key, fetchListener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkAttribute(key, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
applyComponent(key, text)
|
applyComponent(key, text)
|
||||||
{
|
{
|
||||||
@ -2160,6 +2230,7 @@ try
|
|||||||
contentLoaded,
|
contentLoaded,
|
||||||
fixed,
|
fixed,
|
||||||
settings,
|
settings,
|
||||||
|
settingsChangeHandlers,
|
||||||
resources,
|
resources,
|
||||||
theWorld: waitTime =>
|
theWorld: waitTime =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -124,6 +124,27 @@ const fixedSettings = {
|
|||||||
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/master/bilibili-evolved.user.js",
|
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/master/bilibili-evolved.user.js",
|
||||||
currentVersion: GM_info.script.version,
|
currentVersion: GM_info.script.version,
|
||||||
};
|
};
|
||||||
|
const settingsChangeHandlers = {};
|
||||||
|
function addSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
if (!settingsChangeHandlers[key])
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key] = [handler];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key].push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function removeSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (!handlers)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlers.splice(handlers.indexOf(handler), 1);
|
||||||
|
}
|
||||||
function loadSettings()
|
function loadSettings()
|
||||||
{
|
{
|
||||||
for (const key in fixedSettings)
|
for (const key in fixedSettings)
|
||||||
@ -150,6 +171,11 @@ function loadSettings()
|
|||||||
},
|
},
|
||||||
set(newValue)
|
set(newValue)
|
||||||
{
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (handlers)
|
||||||
|
{
|
||||||
|
handlers.forEach(h => h(newValue, value));
|
||||||
|
}
|
||||||
value = newValue;
|
value = newValue;
|
||||||
GM_setValue(key, newValue);
|
GM_setValue(key, newValue);
|
||||||
},
|
},
|
||||||
@ -410,10 +436,10 @@ function loadResources()
|
|||||||
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/";
|
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/master/";
|
||||||
Resource.all = {};
|
Resource.all = {};
|
||||||
Resource.displayNames = {};
|
Resource.displayNames = {};
|
||||||
// Resource.reloadables = {
|
Resource.reloadables = {
|
||||||
// useDarkStyle: "useDarkStyle",
|
useDarkStyle: "useDarkStyle",
|
||||||
// showBanner: "overrideNavBar",
|
hideBanner: "hideBanner",
|
||||||
// };
|
};
|
||||||
for (const [key, data] of Object.entries(Resource.manifest))
|
for (const [key, data] of Object.entries(Resource.manifest))
|
||||||
{
|
{
|
||||||
const resource = new Resource(data.path, data.styles);
|
const resource = new Resource(data.path, data.styles);
|
||||||
@ -1967,8 +1993,52 @@ class ResourceManager
|
|||||||
{
|
{
|
||||||
loadingToast.dismiss();
|
loadingToast.dismiss();
|
||||||
}
|
}
|
||||||
|
this.applyReloadables(); // reloadables run sync
|
||||||
await this.applyDropdownOptions();
|
await this.applyDropdownOptions();
|
||||||
this.applyWidgets();
|
this.applyWidgets(); // No need to wait the widgets
|
||||||
|
}
|
||||||
|
applyReloadables()
|
||||||
|
{
|
||||||
|
const checkAttribute = (key, attributes) =>
|
||||||
|
{
|
||||||
|
if (attributes.reload && attributes.unload)
|
||||||
|
{
|
||||||
|
addSettingsListener(key, newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
attributes.reload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
attributes.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const [key, targetKey] of Object.entries(Resource.reloadables))
|
||||||
|
{
|
||||||
|
const attributes = this.attributes[targetKey];
|
||||||
|
if (attributes === undefined)
|
||||||
|
{
|
||||||
|
const fetchListener = newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
this.fetchByKey(targetKey).then(() =>
|
||||||
|
{
|
||||||
|
removeSettingsListener(key, fetchListener);
|
||||||
|
checkAttribute(key, this.attributes[targetKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addSettingsListener(key, fetchListener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkAttribute(key, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
applyComponent(key, text)
|
applyComponent(key, text)
|
||||||
{
|
{
|
||||||
@ -2160,6 +2230,7 @@ try
|
|||||||
contentLoaded,
|
contentLoaded,
|
||||||
fixed,
|
fixed,
|
||||||
settings,
|
settings,
|
||||||
|
settingsChangeHandlers,
|
||||||
resources,
|
resources,
|
||||||
theWorld: waitTime =>
|
theWorld: waitTime =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
// @icon https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo-small.png
|
// @icon https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo-small.png
|
||||||
// @icon64 https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo.png
|
// @icon64 https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/images/logo.png
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
import { settings, loadSettings, saveSettings, onSettingsChange } from './settings';
|
import { settings, loadSettings, saveSettings, onSettingsChange, settingsChangeHandlers } from './settings';
|
||||||
import { logError, raiseEvent, loadLazyPanel, contentLoaded, fixed } from './utils';
|
import { logError, raiseEvent, loadLazyPanel, contentLoaded, fixed } from './utils';
|
||||||
import { Ajax, setupAjaxHook } from './ajax';
|
import { Ajax, setupAjaxHook } from './ajax';
|
||||||
import { loadResources } from './resource-loader';
|
import { loadResources } from './resource-loader';
|
||||||
@ -110,6 +110,7 @@ try
|
|||||||
contentLoaded,
|
contentLoaded,
|
||||||
fixed,
|
fixed,
|
||||||
settings,
|
settings,
|
||||||
|
settingsChangeHandlers,
|
||||||
resources,
|
resources,
|
||||||
theWorld: waitTime =>
|
theWorld: waitTime =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,10 +3,10 @@ export function loadResources()
|
|||||||
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/";
|
Resource.root = "https://raw.githubusercontent.com/the1812/Bilibili-Evolved/preview/";
|
||||||
Resource.all = {};
|
Resource.all = {};
|
||||||
Resource.displayNames = {};
|
Resource.displayNames = {};
|
||||||
// Resource.reloadables = {
|
Resource.reloadables = {
|
||||||
// useDarkStyle: "useDarkStyle",
|
useDarkStyle: "useDarkStyle",
|
||||||
// showBanner: "overrideNavBar",
|
hideBanner: "hideBanner",
|
||||||
// };
|
};
|
||||||
for (const [key, data] of Object.entries(Resource.manifest))
|
for (const [key, data] of Object.entries(Resource.manifest))
|
||||||
{
|
{
|
||||||
const resource = new Resource(data.path, data.styles);
|
const resource = new Resource(data.path, data.styles);
|
||||||
|
|||||||
@ -175,8 +175,52 @@ export class ResourceManager
|
|||||||
{
|
{
|
||||||
loadingToast.dismiss();
|
loadingToast.dismiss();
|
||||||
}
|
}
|
||||||
|
this.applyReloadables(); // reloadables run sync
|
||||||
await this.applyDropdownOptions();
|
await this.applyDropdownOptions();
|
||||||
this.applyWidgets();
|
this.applyWidgets(); // No need to wait the widgets
|
||||||
|
}
|
||||||
|
applyReloadables()
|
||||||
|
{
|
||||||
|
const checkAttribute = (key, attributes) =>
|
||||||
|
{
|
||||||
|
if (attributes.reload && attributes.unload)
|
||||||
|
{
|
||||||
|
addSettingsListener(key, newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
attributes.reload();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
attributes.unload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (const [key, targetKey] of Object.entries(Resource.reloadables))
|
||||||
|
{
|
||||||
|
const attributes = this.attributes[targetKey];
|
||||||
|
if (attributes === undefined)
|
||||||
|
{
|
||||||
|
const fetchListener = newValue =>
|
||||||
|
{
|
||||||
|
if (newValue === true)
|
||||||
|
{
|
||||||
|
this.fetchByKey(targetKey).then(() =>
|
||||||
|
{
|
||||||
|
removeSettingsListener(key, fetchListener);
|
||||||
|
checkAttribute(key, this.attributes[targetKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addSettingsListener(key, fetchListener);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkAttribute(key, attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
applyComponent(key, text)
|
applyComponent(key, text)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -99,6 +99,27 @@ const fixedSettings = {
|
|||||||
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/preview/bilibili-evolved.preview.user.js",
|
latestVersionLink: "https://github.com/the1812/Bilibili-Evolved/raw/preview/bilibili-evolved.preview.user.js",
|
||||||
currentVersion: GM_info.script.version,
|
currentVersion: GM_info.script.version,
|
||||||
};
|
};
|
||||||
|
export const settingsChangeHandlers = {};
|
||||||
|
export function addSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
if (!settingsChangeHandlers[key])
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key] = [handler];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settingsChangeHandlers[key].push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function removeSettingsListener(key, handler)
|
||||||
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (!handlers)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handlers.splice(handlers.indexOf(handler), 1);
|
||||||
|
}
|
||||||
export function loadSettings()
|
export function loadSettings()
|
||||||
{
|
{
|
||||||
for (const key in fixedSettings)
|
for (const key in fixedSettings)
|
||||||
@ -125,6 +146,11 @@ export function loadSettings()
|
|||||||
},
|
},
|
||||||
set(newValue)
|
set(newValue)
|
||||||
{
|
{
|
||||||
|
const handlers = settingsChangeHandlers[key];
|
||||||
|
if (handlers)
|
||||||
|
{
|
||||||
|
handlers.forEach(h => h(newValue, value));
|
||||||
|
}
|
||||||
value = newValue;
|
value = newValue;
|
||||||
GM_setValue(key, newValue);
|
GM_setValue(key, newValue);
|
||||||
},
|
},
|
||||||
|
|||||||
2
min/custom-navbar.min.js
vendored
2
min/custom-navbar.min.js
vendored
File diff suppressed because one or more lines are too long
2
min/dark-styles.min.js
vendored
2
min/dark-styles.min.js
vendored
@ -1 +1 @@
|
|||||||
(()=>{return(e,l)=>{if(e.useDarkStyle){l.applyStyle("scrollbarStyle");SpinQuery.any(()=>$(".custom-scrollbar"),e=>e.removeClass("custom-scrollbar"));if(!e.useNewStyle&&($("#banner_link").length===0||$("#banner_link").length>0&&e.overrideNavBar&&e.hideBanner)){l.applyImportantStyle("darkStyleNavBar")}l.applyStyle("darkStyle");l.applyImportantStyle("darkStyleImportant")}else{l.removeStyle("scrollbarStyle");l.removeStyle("darkStyleNavBar");l.removeStyle("darkStyle");l.removeStyle("darkStyleImportant")}}})();
|
(()=>{return(e,r)=>{SpinQuery.any(()=>$(".custom-scrollbar"),e=>e.removeClass("custom-scrollbar"));const l=()=>{r.applyStyle("scrollbarStyle");SpinQuery.any(()=>$(".custom-scrollbar"),e=>e.removeClass("custom-scrollbar"));if(!e.useNewStyle&&($("#banner_link").length===0||$("#banner_link").length>0&&e.overrideNavBar&&e.hideBanner)){r.applyImportantStyle("darkStyleNavBar")}r.applyStyle("darkStyle");r.applyImportantStyle("darkStyleImportant")};l();return{reload:l,unload:()=>{r.removeStyle("scrollbarStyle");r.removeStyle("darkStyleNavBar");r.removeStyle("darkStyle");r.removeStyle("darkStyleImportant")}}}})();
|
||||||
2
min/hide-banner.min.js
vendored
2
min/hide-banner.min.js
vendored
@ -1 +1 @@
|
|||||||
(()=>{return(e,l)=>{l.applyStyle("hideBannerStyle")}})();
|
(()=>{return(e,l)=>{const r="hideBannerStyle";l.applyStyle(r);return{reload:()=>l.applyStyle(r),unload:()=>l.removeStyle(r)}}})();
|
||||||
@ -744,11 +744,13 @@ class HistoryList extends VideoList
|
|||||||
navbar.classList.add(className);
|
navbar.classList.add(className);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.useDarkStyle)
|
const darkHandler = value =>
|
||||||
{
|
{
|
||||||
navbar.classList.add("dark");
|
document.querySelector(".custom-navbar").classList[value ? "add" : "remove"]("dark");
|
||||||
document.querySelector(".custom-navbar-settings").classList.add("dark");
|
document.querySelector(".custom-navbar-settings").classList[value ? "add" : "remove"]("dark");
|
||||||
}
|
};
|
||||||
|
addSettingsListener("useDarkStyle", darkHandler);
|
||||||
|
darkHandler(settings.useDarkStyle);
|
||||||
const components = [
|
const components = [
|
||||||
new Logo,
|
new Logo,
|
||||||
new Category,
|
new Category,
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
if (settings.useDarkStyle)
|
SpinQuery.any(
|
||||||
|
() => $(".custom-scrollbar"),
|
||||||
|
it => it.removeClass("custom-scrollbar")
|
||||||
|
);
|
||||||
|
const load = () =>
|
||||||
{
|
{
|
||||||
resources.applyStyle("scrollbarStyle");
|
resources.applyStyle("scrollbarStyle");
|
||||||
SpinQuery.any(
|
SpinQuery.any(
|
||||||
@ -14,11 +18,15 @@ if (settings.useDarkStyle)
|
|||||||
}
|
}
|
||||||
resources.applyStyle("darkStyle");
|
resources.applyStyle("darkStyle");
|
||||||
resources.applyImportantStyle("darkStyleImportant");
|
resources.applyImportantStyle("darkStyleImportant");
|
||||||
}
|
};
|
||||||
else
|
load();
|
||||||
{
|
export default {
|
||||||
|
reload: load,
|
||||||
|
unload: () =>
|
||||||
|
{
|
||||||
resources.removeStyle("scrollbarStyle");
|
resources.removeStyle("scrollbarStyle");
|
||||||
resources.removeStyle("darkStyleNavBar");
|
resources.removeStyle("darkStyleNavBar");
|
||||||
resources.removeStyle("darkStyle");
|
resources.removeStyle("darkStyle");
|
||||||
resources.removeStyle("darkStyleImportant");
|
resources.removeStyle("darkStyleImportant");
|
||||||
}
|
},
|
||||||
|
};
|
||||||
@ -1 +1,6 @@
|
|||||||
resources.applyStyle("hideBannerStyle");
|
const key = "hideBannerStyle";
|
||||||
|
resources.applyStyle(key);
|
||||||
|
export default {
|
||||||
|
reload: () => resources.applyStyle(key),
|
||||||
|
unload: () => resources.removeStyle(key),
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user