vault backup: 2024-08-29 15:40:10
This commit is contained in:
parent
787f0de516
commit
b0f11b3dbd
3
.obsidian/community-plugins.json
vendored
3
.obsidian/community-plugins.json
vendored
@ -11,6 +11,5 @@
|
||||
"obsidian-admonition",
|
||||
"obsidian-git",
|
||||
"obsidian-full-calendar",
|
||||
"highlightr-plugin",
|
||||
"obsidian-comments"
|
||||
"highlightr-plugin"
|
||||
]
|
||||
6
.obsidian/hotkeys.json
vendored
6
.obsidian/hotkeys.json
vendored
@ -96,5 +96,11 @@
|
||||
"modifiers": [],
|
||||
"key": "F7"
|
||||
}
|
||||
],
|
||||
"highlightr-plugin:Red": [
|
||||
{
|
||||
"modifiers": [],
|
||||
"key": "F3"
|
||||
}
|
||||
]
|
||||
}
|
||||
293
.obsidian/plugins/obsidian-comments/main.js
vendored
293
.obsidian/plugins/obsidian-comments/main.js
vendored
@ -1,293 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var obsidian = require('obsidian');
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __awaiter(thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
}
|
||||
|
||||
class CommentsSettingTab extends obsidian.PluginSettingTab {
|
||||
constructor(app, plugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
display() {
|
||||
let { containerEl } = this;
|
||||
containerEl.empty();
|
||||
containerEl.createEl('h2', { text: 'Comments Plugin Settings' });
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Default text color')
|
||||
.setDesc("Change from the style.css in the package folder")
|
||||
.addText(text => text
|
||||
.setPlaceholder("....")
|
||||
.setValue('')
|
||||
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
||||
this.plugin.settings.DEFAULT_COLOR = value;
|
||||
})));
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Default background color')
|
||||
.setDesc('Change from the style.css in the package folder')
|
||||
.addText(text => text
|
||||
.setPlaceholder("....")
|
||||
.setValue('')
|
||||
.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
||||
this.plugin.settings.DEFAULT_BACKGROUND_COLOR = value;
|
||||
})));
|
||||
new obsidian.Setting(containerEl)
|
||||
.setName('Hide Comment Plugin Ribbon')
|
||||
.setDesc('After changing this setting unload then reload the plugin for the change to take place')
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.SHOW_RIBBON);
|
||||
toggle.onChange((value) => __awaiter(this, void 0, void 0, function* () {
|
||||
this.plugin.settings.SHOW_RIBBON = value;
|
||||
yield this.plugin.saveSettings();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const VIEW_TYPE_OB_COMMENTS = 'ob_comments';
|
||||
const DEFAULT_SETTINGS = {
|
||||
SHOW_RIBBON: true,
|
||||
DEFAULT_COLOR: '#b30202',
|
||||
DEFAULT_BACKGROUND_COLOR: '#FFDE5C'
|
||||
};
|
||||
|
||||
// Delay passed function for specified timeout
|
||||
function debounce(func, wait, immediate) {
|
||||
let timeout;
|
||||
return function executedFunction() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
let later = function () {
|
||||
timeout = null;
|
||||
if (!immediate)
|
||||
func.apply(context, args);
|
||||
};
|
||||
let callNow = immediate && !timeout;
|
||||
clearTimeout(timeout);
|
||||
timeout = +setTimeout(later, wait);
|
||||
if (callNow)
|
||||
func.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
class CommentsView extends obsidian.ItemView {
|
||||
constructor(leaf) {
|
||||
super(leaf);
|
||||
this.redraw_debounced = debounce(function () {
|
||||
this.redraw();
|
||||
}, 1000);
|
||||
this.redraw = this.redraw.bind(this);
|
||||
this.redraw_debounced = this.redraw_debounced.bind(this);
|
||||
this.containerEl = this.containerEl;
|
||||
this.registerEvent(this.app.workspace.on("layout-ready", this.redraw_debounced));
|
||||
this.registerEvent(this.app.workspace.on("file-open", this.redraw_debounced));
|
||||
this.registerEvent(this.app.workspace.on("quick-preview", this.redraw_debounced));
|
||||
this.registerEvent(this.app.vault.on("delete", this.redraw));
|
||||
}
|
||||
getViewType() {
|
||||
return VIEW_TYPE_OB_COMMENTS;
|
||||
}
|
||||
getDisplayText() {
|
||||
return "Comments";
|
||||
}
|
||||
getIcon() {
|
||||
return "lines-of-text";
|
||||
}
|
||||
onClose() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
onOpen() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.redraw();
|
||||
});
|
||||
}
|
||||
redraw() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let active_leaf = this.app.workspace.getActiveFile();
|
||||
this.containerEl.empty();
|
||||
this.containerEl.setAttribute('class', 'comment-panel');
|
||||
// Condition if current leaf is present
|
||||
if (active_leaf) {
|
||||
let page_content = yield this.app.vault.read(active_leaf);
|
||||
// Convert into HTML element
|
||||
let page_html = document.createElement('Div');
|
||||
page_html.innerHTML = page_content;
|
||||
// Use HTML parser to find the desired elements
|
||||
// Get all .ob-comment elements
|
||||
let comment_list = page_html.querySelectorAll("label[class='ob-comment']");
|
||||
let El = document.createElement("h3");
|
||||
El.setAttribute('class', 'comment-count');
|
||||
this.containerEl.appendChild(El);
|
||||
El.setText('Comments: ' + comment_list.length);
|
||||
for (let i = 0; i < comment_list.length; i++) {
|
||||
let div = document.createElement('Div');
|
||||
div.setAttribute('class', 'comment-pannel-bubble');
|
||||
let labelEl = document.createElement("label");
|
||||
let pEl = document.createElement("p");
|
||||
pEl.setAttribute('class', 'comment-pannel-p1');
|
||||
// Check if user specified a title for this comment
|
||||
if (!comment_list[i].title || comment_list[i].title === "") {
|
||||
// if no title specified, use the line number
|
||||
pEl.setText('--');
|
||||
}
|
||||
else {
|
||||
// Use the given title
|
||||
pEl.setText(comment_list[i].title);
|
||||
}
|
||||
labelEl.appendChild(pEl);
|
||||
let inputEl = document.createElement("input");
|
||||
inputEl.setAttribute('type', 'checkbox');
|
||||
inputEl.setAttribute('style', 'display:none;');
|
||||
labelEl.appendChild(inputEl);
|
||||
pEl = document.createElement("p");
|
||||
pEl.setAttribute('class', 'comment-pannel-p2');
|
||||
pEl.setText(comment_list[i].innerHTML.substring(0, comment_list[i].innerHTML.length - comment_list[i].querySelector('input[type=checkbox]+span').outerHTML.length - comment_list[i].querySelector('input[type=checkbox]').outerHTML.length - 1));
|
||||
labelEl.appendChild(pEl);
|
||||
div.appendChild(labelEl);
|
||||
labelEl = document.createElement("label");
|
||||
inputEl = document.createElement("input");
|
||||
inputEl.setAttribute('type', 'checkbox');
|
||||
inputEl.setAttribute('style', 'display:none;');
|
||||
labelEl.appendChild(inputEl);
|
||||
pEl = document.createElement("p");
|
||||
pEl.setAttribute('class', 'comment-pannel-p3');
|
||||
// Check if user specified additional style for this note
|
||||
if (!comment_list[i].style.cssText) {
|
||||
// if no style was assigned, use default
|
||||
pEl.setText(comment_list[i].querySelector('input[type=checkbox]+span').innerHTML);
|
||||
}
|
||||
else {
|
||||
// Add the new style
|
||||
pEl.setText(comment_list[i].querySelector('input[type=checkbox]+span').innerHTML);
|
||||
pEl.setAttribute('style', comment_list[i].style.cssText);
|
||||
}
|
||||
labelEl.appendChild(pEl);
|
||||
div.appendChild(labelEl);
|
||||
this.containerEl.appendChild(div);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CommentsPlugin extends obsidian.Plugin {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.showPanel = function () {
|
||||
this.app.workspace.getRightLeaf(true)
|
||||
.setViewState({ type: VIEW_TYPE_OB_COMMENTS });
|
||||
};
|
||||
}
|
||||
onload() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Load message
|
||||
yield this.loadSettings();
|
||||
console.log('Loaded Comments Plugin');
|
||||
this.addSettingTab(new CommentsSettingTab(this.app, this));
|
||||
this.positionComment = this.positionComment.bind(this);
|
||||
this.registerEvent(this.app.workspace.on("click", this.positionComment));
|
||||
this.registerView(VIEW_TYPE_OB_COMMENTS, (leaf) => this.view = new CommentsView(leaf));
|
||||
this.addCommand({
|
||||
id: "show-comments-panel",
|
||||
name: "Open Comments Panel",
|
||||
callback: () => this.showPanel()
|
||||
});
|
||||
this.addCommand({
|
||||
id: "add-comment",
|
||||
name: "Add Comment",
|
||||
callback: () => this.addComment()
|
||||
});
|
||||
if (this.settings.SHOW_RIBBON) {
|
||||
this.addRibbonIcon('lines-of-text', "Show Comments Panel", (e) => this.showPanel());
|
||||
}
|
||||
});
|
||||
}
|
||||
positionComment() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let ob_elements = document.querySelectorAll('.ob-comment');
|
||||
for (let el = 0; el < ob_elements.length; el++) {
|
||||
let elements = ob_elements[el].querySelector('input');
|
||||
if (elements) {
|
||||
elements.addEventListener('change', function () {
|
||||
if (this.checked) {
|
||||
let elSpan = ob_elements[el].querySelector('span');
|
||||
if (elSpan) {
|
||||
elSpan.style.setProperty('position', 'fixed');
|
||||
elSpan.style.setProperty('top', `${Math.round(ob_elements[el].getBoundingClientRect().top)}px`);
|
||||
elSpan.style.setProperty('right', '0px');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
onunload() {
|
||||
console.log('unloading plugin');
|
||||
}
|
||||
loadSettings() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
|
||||
});
|
||||
}
|
||||
saveSettings() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield this.saveData(this.settings);
|
||||
});
|
||||
}
|
||||
addComment() {
|
||||
let editor = this.getEditor();
|
||||
const lines = this.getLines(editor);
|
||||
if (!lines)
|
||||
return;
|
||||
this.setLines(editor, ['<label class="ob-comment" title="" style=""> ' + lines + ' <input type="checkbox"> <span style=""> Comment </span></label>']);
|
||||
}
|
||||
getEditor() {
|
||||
let view = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView);
|
||||
if (!view)
|
||||
return;
|
||||
let cm = view.sourceMode.cmEditor;
|
||||
return cm;
|
||||
}
|
||||
getLines(editor) {
|
||||
if (!editor)
|
||||
return;
|
||||
const selection = editor.getSelection();
|
||||
return [selection];
|
||||
}
|
||||
setLines(editor, lines) {
|
||||
const selection = editor.getSelection();
|
||||
if (selection != "") {
|
||||
editor.replaceSelection(lines.join("\n"));
|
||||
}
|
||||
else {
|
||||
editor.setValue(lines.join("\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CommentsPlugin;
|
||||
//# sourceMappingURL=main.js.map
|
||||
@ -1,10 +0,0 @@
|
||||
{
|
||||
"id": "obsidian-comments",
|
||||
"name": "Comments",
|
||||
"version": "0.2.0",
|
||||
"minAppVersion": "0.10.11",
|
||||
"description": "Add, track and easily navigate between a note's Comments",
|
||||
"author": "darakah",
|
||||
"authorUrl": "https://github.com/Darakah/",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
133
.obsidian/plugins/obsidian-comments/styles.css
vendored
133
.obsidian/plugins/obsidian-comments/styles.css
vendored
@ -1,133 +0,0 @@
|
||||
.ob-comment {
|
||||
position: relative;
|
||||
border-bottom: 1px dotted black;
|
||||
color: #8f0303;
|
||||
font-weight: bold;
|
||||
background-color: #CCA300;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.ob-comment:hover {
|
||||
background-color: #FFDE5C;
|
||||
}
|
||||
|
||||
.ob-comment span {
|
||||
visibility: hidden;
|
||||
min-width: auto;
|
||||
max-width: 500px;
|
||||
width: max-content;
|
||||
background-color: #FFDE5C;
|
||||
color: #b30202;
|
||||
text-align: left;
|
||||
border-radius: 6px;
|
||||
padding: 10px 10px;
|
||||
z-index: 1;
|
||||
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
|
||||
box-shadow: 1px 1px 10px 5px var(--background-secondary);
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
|
||||
.ob-comment input {
|
||||
display:none;
|
||||
}
|
||||
.ob-comment input:checked+span {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.comment-panel{
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.comment-count{
|
||||
border: none;
|
||||
padding: 1px 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comment-pannel-bubble{
|
||||
white-space: wrap;
|
||||
height:auto;
|
||||
width:95%;
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
-webkit-transition: height 1s;
|
||||
transition: opacity 1s, line-height 0.1s;
|
||||
padding: 5px 5px;
|
||||
background-color: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
border: none;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 0px 0px;
|
||||
cursor: pointer;
|
||||
border-radius: 14px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.comment-pannel-p1 {
|
||||
width: 100%;
|
||||
border-top-left-radius: 14px;
|
||||
border-top-right-radius: 14px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--text-a);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.comment-pannel-p2 {
|
||||
display: none;
|
||||
width: 100%;
|
||||
text-overflow: hidden;
|
||||
margin-bottom: 20px;
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
.comment-pannel-p3 {
|
||||
position: relative;
|
||||
height: min-content;
|
||||
width: auto;
|
||||
max-width: max-content;
|
||||
max-height: 100px;
|
||||
margin-block-end: 0em;
|
||||
|
||||
white-space: wrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-transition: height 1s;
|
||||
transition: opacity 1s, line-height 0.1s;
|
||||
cursor: pointer;
|
||||
|
||||
background-color:#FFDE5C;
|
||||
margin-top: 0;
|
||||
color: #b30202;
|
||||
padding: 5px 5px;
|
||||
border-radius: 14px;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
input:checked+ span{
|
||||
height:auto;
|
||||
}
|
||||
|
||||
input:checked+p{
|
||||
height:auto;
|
||||
max-height: max-content;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
18
.obsidian/workspace.json
vendored
18
.obsidian/workspace.json
vendored
@ -139,24 +139,10 @@
|
||||
}
|
||||
],
|
||||
"currentTab": 3
|
||||
},
|
||||
{
|
||||
"id": "43265e80eaf8fd59",
|
||||
"type": "tabs",
|
||||
"children": [
|
||||
{
|
||||
"id": "24b71dd37395d011",
|
||||
"type": "leaf",
|
||||
"state": {
|
||||
"type": "ob_comments",
|
||||
"state": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 372.5
|
||||
"width": 245.5
|
||||
},
|
||||
"left-ribbon": {
|
||||
"hiddenItems": {
|
||||
@ -173,8 +159,8 @@
|
||||
},
|
||||
"active": "a7f5a9ce2764a9ae",
|
||||
"lastOpenFiles": [
|
||||
"QE Abstract Take 1 Edits.md",
|
||||
"4. Qualifying Exam/2. Writing/QE Abstract.md",
|
||||
"QE Abstract Take 1 Edits.md",
|
||||
"1. Daily Notes/8. August/2024-08-29.md",
|
||||
"300s School/302. NUCE 2100 - Fundamentals of Nuclear Engineering/2024-09-03 Homework 1.md",
|
||||
"2. Cole Group Meeting Notes/Weekly Note 2024-08-21.md",
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
# Take 1
|
||||
Controllers in the real world control plants that are not perfectly represented by a mathematical model. For this reason, controllers that operate in high-assurance environments must be evaluated to be robust. Validation and verification of controller robustness is done today, but is a strenuous, manual task. Examining the robustness of a controller requires additional mathematical formalism of a system, and analysis of perturbations to a nominal plant model. Perturbations can be considered as two forms--structured and unstructured perturbation. Unstructured perturbation in particular allows the perturbation to take on any transfer function (or transfer matrix), such that the gain of the transfer function remains less than one. If a controlled system remains stable for the nominal plant and all possible perturbations, it is considered robust. To actually validate this statement with actual perturbations is a difficult problem. Generating perturbations is not trivial because there is so much possibility for the form of an unstructured perturbation. For an engineer to accomplish this task today one of two things happens:
|
||||
<mark style="background: #ABF7F7A6;">Controllers in the real world control plants that are not perfectly represented by a mathematical model. For this reason, controllers that operate in high-assurance environments must be evaluated to be robust.</mark>
|
||||
<mark style="background: #FFB8EBA6;">Validation and verification of controller robustness is done today, but is a strenuous, manual task.</mark>
|
||||
Examining the robustness of a <mark style="background: #FFF3A3A6;">controller</mark> requires additional mathematical formalism of a system, and analysis of perturbations to a nominal plant model.
|
||||
Perturbations can be considered as two forms--structured and unstructured perturbation.
|
||||
Unstructured perturbation in particular allows the perturbation to take on any transfer function (or transfer matrix), such that the gain of the transfer function remains less than one.
|
||||
If a controlled system remains stable for the nominal plant and all possible perturbations, it is considered robust.
|
||||
To actually validate this statement with actual perturbations is a difficult problem.
|
||||
Generating perturbations is not trivial because there is so much possibility for the form of an unstructured perturbation.
|
||||
For an engineer to accomplish this task today one of two things happens:
|
||||
1. An engineer can randomly generate transfer functions as a perturbation and evaluate if their gain is less than one, or
|
||||
2. An engineer creates a structured perturbation instead using some parameters (transfer function order, physical attributes, etc.)
|
||||
We suggest a different way of generating unstructured perturbations. Instead of an engineer creating the perturbations, we suggest using a diffusion generative model to create perturbed plants directly. To do this, the diffusion generative model is trained to create Bode plots of transfer functions from noise. (WORD LIMIT) This model is then given a warm-start on a slightly-noisy nominal plant with which it will create a perturbed plant as it attempts to remove noise from the nominal plant. This generated plant can then be analyzed using analytical fitting of a transfer function to the bode plot, and evaluated whether or not it is within the set of allowed perturbations. Due to the ability of diffusion generative models to create novel samples, we can perform this process numerous times to collect a large number of perturbed plants. We can then perform our validation using this subset of the unstructured perturbed set.
|
||||
|
||||
We suggest a different way of generating unstructured perturbations.
|
||||
Instead of an engineer creating the perturbations, we suggest using a diffusion generative model to create perturbed plants directly.
|
||||
To do this, the diffusion generative model is trained to create Bode plots of transfer functions from noise.
|
||||
(WORD LIMIT) This model is then given a warm-start on a slightly-noisy nominal plant with which it will create a perturbed plant as it attempts to remove noise from the nominal plant.
|
||||
This generated plant can then be analyzed using analytical fitting of a transfer function to the bode plot, and evaluated whether or not it is within the set of allowed perturbations.
|
||||
Due to the ability of diffusion generative models to create novel samples, we can perform this process numerous times to collect a large number of perturbed plants.
|
||||
We can then perform our validation using this subset of the unstructured perturbed set.
|
||||
|
||||
This method of perturbation generation can be very powerful for robust controller validation. Using a diffusion generative model, we can generate a limitless number of perturbed plants. We can also add some structure to this model if we like, specifying uncertainty with the amount of noise introduced to different sections of the Bode plot.
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user