mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-05-07 18:48:39 +00:00
Merge pull request #4757 from naturalcrit/fix-CM-bugs
Fix codemirror bugs 2 (they just keep coming)
This commit is contained in:
@@ -1,31 +1,134 @@
|
||||
// Icon fonts for emoji/autocomplete
|
||||
@import (less) "@themes/fonts/iconFonts/diceFont.less";
|
||||
@import (less) "@themes/fonts/iconFonts/elderberryInn.less";
|
||||
@import (less) "@themes/fonts/iconFonts/gameIcons.less";
|
||||
@import (less) "@themes/fonts/iconFonts/fontAwesome.less";
|
||||
@import (less) '@themes/fonts/iconFonts/diceFont.less';
|
||||
@import (less) '@themes/fonts/iconFonts/elderberryInn.less';
|
||||
@import (less) '@themes/fonts/iconFonts/gameIcons.less';
|
||||
@import (less) '@themes/fonts/iconFonts/fontAwesome.less';
|
||||
|
||||
@keyframes sourceMoveAnimation {
|
||||
50% {
|
||||
color: white;
|
||||
background-color: red;
|
||||
color : white;
|
||||
background-color : red;
|
||||
}
|
||||
100% {
|
||||
color: unset;
|
||||
background-color: unset;
|
||||
color : unset;
|
||||
background-color : unset;
|
||||
}
|
||||
}
|
||||
|
||||
:where(.codeEditor) {
|
||||
font-family: monospace;
|
||||
height: 100%;
|
||||
width:100%;
|
||||
|
||||
.cm-content {
|
||||
tab-size:2 !important;
|
||||
width : 100%;
|
||||
height : calc(100% - 25px);
|
||||
font-family : monospace;
|
||||
|
||||
.cm-editor {
|
||||
height : 100%;
|
||||
outline : none !important;
|
||||
}
|
||||
|
||||
@media screen and (pointer: coarse) {
|
||||
font-size: 16px;
|
||||
&.brewSnippets .cm-snippetLine,
|
||||
:where(&.brewText) .cm-pageLine {
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
}
|
||||
|
||||
&.brewSnippets {
|
||||
.cm-pageLine {
|
||||
color : #777777;
|
||||
background : #3E4E3E1B;
|
||||
border-top : #3399423B solid 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&:where(.brewText), &.brewSnippets {
|
||||
|
||||
|
||||
.cm-pageLine[data-page-number]::after {
|
||||
float : right;
|
||||
color : grey;
|
||||
content : attr(data-page-number);
|
||||
}
|
||||
.cm-columnSplit {
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
background-color : fade(#229999, 15%);
|
||||
border-bottom : #229999 solid 1px;
|
||||
}
|
||||
.cm-define {
|
||||
&:not(.term):not(.definition) {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
&.term { color : rgb(96, 117, 143); }
|
||||
&.definition { color : rgb(97, 57, 178); }
|
||||
}
|
||||
.cm-block:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : purple;
|
||||
}
|
||||
.cm-inline-block,
|
||||
.cm-define .cm-inline-block {
|
||||
font-weight : bold;
|
||||
color : red;
|
||||
span:not(.cm-comment) { color : inherit; }
|
||||
}
|
||||
.cm-injection:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : green;
|
||||
span { color : inherit; }
|
||||
}
|
||||
.cm-emoji:not(.cm-comment) {
|
||||
padding-bottom : 1px;
|
||||
margin-left : 2px;
|
||||
font-weight : bold;
|
||||
color : #360034;
|
||||
outline : solid 2px #FF96FC;
|
||||
outline-offset : -2px;
|
||||
background : #FFC8FF;
|
||||
border-radius : 6px;
|
||||
}
|
||||
.cm-superscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : super;
|
||||
color : goldenrod;
|
||||
}
|
||||
.cm-subscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : sub;
|
||||
color : rgb(123, 123, 15);
|
||||
}
|
||||
.cm-definitionList {
|
||||
.cm-definitionTerm { color : rgb(96, 117, 143); }
|
||||
.cm-definitionColon:not(:has(.cm-comment)) {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
.cm-definitionDesc { color : rgb(97, 57, 178); }
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
|
||||
li {
|
||||
display : flex;
|
||||
gap : 10px;
|
||||
align-items : center;
|
||||
justify-content : flex-start;
|
||||
|
||||
.cm-completionIcon { display : none; }
|
||||
.cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-content { tab-size : 2 !important; }
|
||||
|
||||
@media screen and (pointer : coarse) {
|
||||
font-size : 16px;
|
||||
}
|
||||
|
||||
.cm-gutterElement span {
|
||||
@@ -44,14 +147,14 @@
|
||||
|
||||
/* Flash animation for source moves */
|
||||
.cm-line.sourceMoveFlash {
|
||||
animation-name: sourceMoveAnimation;
|
||||
animation-duration: 0.4s;
|
||||
animation-name : sourceMoveAnimation;
|
||||
animation-duration : 0.4s;
|
||||
}
|
||||
|
||||
/* Search input */
|
||||
.cm-searchField {
|
||||
width: 25em !important;
|
||||
outline: 1px inset #00000055 !important;
|
||||
width : 25em !important;
|
||||
outline : 1px inset #00000055 !important;
|
||||
}
|
||||
|
||||
/* Tab character visualization (optional) */
|
||||
@@ -67,6 +170,6 @@
|
||||
|
||||
/* Emoji preview styling */
|
||||
.emojiPreview {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.2em;
|
||||
font-size : 1.5em;
|
||||
line-height : 1.2em;
|
||||
}
|
||||
|
||||
@@ -125,8 +125,6 @@ export function tokenizeCustomMarkdown(text) {
|
||||
from : offset,
|
||||
to : offset + desc.length,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// --- multiline def list ---
|
||||
|
||||
@@ -20,72 +20,142 @@ const indentLess = (view)=>{
|
||||
const makeBold = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('**') && selected.endsWith('**')
|
||||
? selected.slice(2, -2)
|
||||
: `**${selected}**`;
|
||||
|
||||
let text, cursor;
|
||||
|
||||
if(from === to) {
|
||||
text = '****';
|
||||
cursor = from + 2;
|
||||
} else if(selected.startsWith('**') && selected.endsWith('**')) {
|
||||
text = selected.slice(2, -2);
|
||||
cursor = from + text.length;
|
||||
} else {
|
||||
text = `**${selected}**`;
|
||||
cursor = from + text.length;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to, insert: text },
|
||||
selection : { anchor: from + text.length },
|
||||
selection : { anchor: cursor },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeItalic = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('*') && selected.endsWith('*')
|
||||
? selected.slice(1, -1)
|
||||
: `*${selected}*`;
|
||||
|
||||
let text, cursor;
|
||||
|
||||
if(from === to) {
|
||||
text = '**';
|
||||
cursor = from + 1;
|
||||
} else if(selected.startsWith('*') && selected.endsWith('*')) {
|
||||
text = selected.slice(2, -2);
|
||||
cursor = from + text.length;
|
||||
} else {
|
||||
text = `*${selected}*`;
|
||||
cursor = from + text.length;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to, insert: text },
|
||||
selection : { anchor: from + text.length },
|
||||
selection : { anchor: cursor },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeUnderline = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('<u>') && selected.endsWith('</u>')
|
||||
? selected.slice(3, -4)
|
||||
: `<u>${selected}</u>`;
|
||||
|
||||
let text, cursor;
|
||||
|
||||
if(from === to) {
|
||||
text = '<u></u>';
|
||||
cursor = from + 3;
|
||||
} else if(selected.startsWith('<u>') && selected.endsWith('</u>')) {
|
||||
text = selected.slice(3, -4);
|
||||
cursor = from + text.length;
|
||||
} else {
|
||||
text = `<u>${selected}</u>`;
|
||||
cursor = from + text.length;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to, insert: text },
|
||||
selection : { anchor: from + text.length },
|
||||
selection : { anchor: cursor },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeSuper = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('^') && selected.endsWith('^')
|
||||
? selected.slice(1, -1)
|
||||
: `^${selected}^`;
|
||||
|
||||
let text, cursor;
|
||||
|
||||
if(from === to) {
|
||||
text = '^^';
|
||||
cursor = from + 1;
|
||||
} else if(selected.startsWith('^') && selected.endsWith('^')) {
|
||||
text = selected.slice(1, -1);
|
||||
cursor = from + text.length;
|
||||
} else {
|
||||
text = `^${selected}^`;
|
||||
cursor = from + text.length;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to, insert: text },
|
||||
selection : { anchor: from + text.length },
|
||||
selection : { anchor: cursor },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeSub = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
const selected = view.state.doc.sliceString(from, to);
|
||||
const text = selected.startsWith('^^') && selected.endsWith('^^')
|
||||
? selected.slice(2, -2)
|
||||
: `^^${selected}^^`;
|
||||
|
||||
let text, cursor;
|
||||
|
||||
if(from === to) {
|
||||
text = '^^^^';
|
||||
cursor = from + 2;
|
||||
} else if(selected.startsWith('^^') && selected.endsWith('^^')) {
|
||||
text = selected.slice(2, -2);
|
||||
cursor = from + text.length;
|
||||
} else {
|
||||
text = `^^${selected}^^`;
|
||||
cursor = from + text.length;
|
||||
}
|
||||
|
||||
view.dispatch({
|
||||
changes : { from, to, insert: text },
|
||||
selection : { anchor: from + text.length },
|
||||
selection : { anchor: cursor },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeNbsp = (view)=>{
|
||||
const { from, to } = view.state.selection.main;
|
||||
view.dispatch({ changes: { from, to, insert: ' ' } });
|
||||
return true;
|
||||
const makeNbsp = (view) => {
|
||||
const { from } = view.state.selection.main;
|
||||
|
||||
const prev2 = from >= 2
|
||||
? view.state.doc.sliceString(from - 2, from)
|
||||
: '';
|
||||
|
||||
const insert = (prev2 === ':>' || prev2 === '>>') ? '>' : ':>';
|
||||
|
||||
view.dispatch({
|
||||
changes: { from, to: from, insert },
|
||||
selection: { anchor: from + insert.length },
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const makeSpace = (view)=>{
|
||||
@@ -188,13 +258,13 @@ const newPage = (view)=>{
|
||||
return true;
|
||||
};
|
||||
|
||||
export const generalKeymap = keymap.of([
|
||||
export const generalKeymap = Prec.high(keymap.of([
|
||||
{ key: 'Tab', run: indentMore },
|
||||
{ key: 'Mod-z', run: undo }, //i think it may be unnecessary
|
||||
{ key: 'Mod-Shift-z', run: redo },
|
||||
{ key: 'Mod-y', run: redo },
|
||||
{ key: 'Mod-d', run: deleteLine},
|
||||
]);
|
||||
{ key: 'Mod-d', run: deleteLine },
|
||||
]));
|
||||
|
||||
export const markdownKeymap = Prec.highest(keymap.of([
|
||||
//{ key: 'Shift-Tab', run: indentMore },
|
||||
|
||||
@@ -6,116 +6,6 @@
|
||||
height : 100%;
|
||||
container : editor / inline-size;
|
||||
background : white;
|
||||
:where(.codeEditor) {
|
||||
height : calc(100% - 25px);
|
||||
.cm-editor { height : 100%;
|
||||
outline:none !important;
|
||||
}
|
||||
&.brewSnippets .cm-snippetLine {
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
}
|
||||
|
||||
:where(&.brewText) .cm-pageLine {
|
||||
background : #33333328;
|
||||
border-top : #333399 solid 1px;
|
||||
}
|
||||
|
||||
&.brewSnippets {
|
||||
.cm-pageLine {
|
||||
background : #3e4e3e1b;
|
||||
border-top : #3399423b solid 1px;
|
||||
color:#777;
|
||||
}
|
||||
}
|
||||
|
||||
&:where(.brewText), &.brewSnippets {
|
||||
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
|
||||
li {
|
||||
display : flex;
|
||||
gap : 10px;
|
||||
align-items : center;
|
||||
justify-content : flex-start;
|
||||
|
||||
.cm-completionIcon { display : none; }
|
||||
.cm-tooltip-autocomplete .cm-completionLabel { translate : 0 -2px; }
|
||||
}
|
||||
}
|
||||
|
||||
.cm-pageLine[data-page-number]::after {
|
||||
content:attr(data-page-number);
|
||||
float:right;
|
||||
color : grey;
|
||||
}
|
||||
.cm-columnSplit {
|
||||
font-style : italic;
|
||||
color : grey;
|
||||
background-color : fade(#229999, 15%);
|
||||
border-bottom : #229999 solid 1px;
|
||||
}
|
||||
.cm-define {
|
||||
&:not(.term):not(.definition) {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
&.term { color : rgb(96, 117, 143); }
|
||||
&.definition { color : rgb(97, 57, 178); }
|
||||
}
|
||||
.cm-block:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : purple;
|
||||
}
|
||||
.cm-inline-block:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : red ;
|
||||
span { color : inherit }
|
||||
}
|
||||
.cm-injection:not(.cm-comment) {
|
||||
font-weight : bold;
|
||||
color : green;
|
||||
span { color : inherit }
|
||||
}
|
||||
.cm-emoji:not(.cm-comment) {
|
||||
padding-bottom : 1px;
|
||||
margin-left : 2px;
|
||||
font-weight : bold;
|
||||
color : #360034;
|
||||
outline : solid 2px #FF96FC;
|
||||
outline-offset : -2px;
|
||||
background : #FFC8FF;
|
||||
border-radius : 6px;
|
||||
}
|
||||
.cm-superscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : super;
|
||||
color : goldenrod;
|
||||
}
|
||||
.cm-subscript:not(.cm-comment) {
|
||||
font-size : 0.9em;
|
||||
font-weight : bold;
|
||||
vertical-align : sub;
|
||||
color : rgb(123, 123, 15);
|
||||
}
|
||||
.cm-definitionList {
|
||||
.cm-definitionTerm { color : rgb(96, 117, 143); }
|
||||
.cm-definitionColon {
|
||||
font-weight : bold;
|
||||
color : #949494;
|
||||
background : #E5E5E5;
|
||||
border-radius : 3px;
|
||||
}
|
||||
.cm-definitionDesc { color : rgb(97, 57, 178); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.brewJump {
|
||||
position : absolute;
|
||||
|
||||
@@ -29,7 +29,7 @@ import cm5Themes from 'codemirror-5-themes';
|
||||
|
||||
const themes = { default: defaultCM5Theme, ...cm5Themes, darkbrewery };
|
||||
|
||||
const EditorThemes = Object.entries(themes)
|
||||
const themeNames = Object.entries(themes)
|
||||
.filter(([name, value]) =>
|
||||
Array.isArray(value) &&
|
||||
!name.endsWith('Init') &&
|
||||
@@ -37,6 +37,13 @@ const EditorThemes = Object.entries(themes)
|
||||
)
|
||||
.map(([name]) => name);
|
||||
|
||||
const EditorThemes = [
|
||||
'default',
|
||||
...themeNames
|
||||
.filter(name => name !== 'default')
|
||||
.sort((a, b) => a.localeCompare(b))
|
||||
];
|
||||
|
||||
const execute = function(val, props){
|
||||
if(_.isFunction(val)) return val(props);
|
||||
return val;
|
||||
|
||||
Generated
+4
-4
@@ -33,7 +33,7 @@
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"classnames": "^2.5.1",
|
||||
"codemirror-5-themes": "^1.3.0",
|
||||
"codemirror-5-themes": "^1.4.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"core-js": "^3.49.0",
|
||||
"cors": "^2.8.5",
|
||||
@@ -6094,9 +6094,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/codemirror-5-themes": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-5-themes/-/codemirror-5-themes-1.3.0.tgz",
|
||||
"integrity": "sha512-FO8HG4m4GdcphVtJFcj8wcx9nhktb4UMnSu8ia5yCNd3G89pBGqrIxw2UAEluJP4D2gLZWQOwdPaU8Ik3Bqt2w==",
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-5-themes/-/codemirror-5-themes-1.4.0.tgz",
|
||||
"integrity": "sha512-3WKAmpTLqcE1MXFLHdNtwQYaxlWZXS69MY79UiNsFpW9KedMkf/vBYBjUAmacDXPKAJjdPSNMGmCPIZi21yoXA==",
|
||||
"dependencies": {
|
||||
"@codemirror/view": "^6.41.1"
|
||||
}
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@
|
||||
"@vitejs/plugin-react": "^5.1.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"classnames": "^2.5.1",
|
||||
"codemirror-5-themes": "^1.3.0",
|
||||
"codemirror-5-themes": "^1.4.0",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"core-js": "^3.49.0",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
/*This document is old, from back when Codemirror was version 5,
|
||||
if someone wants to update it, feel free, it needs to be like default.js or darkbrewery.js
|
||||
Then imported in snippetbar.jsx and codeEditor.jsx.
|
||||
*/
|
||||
|
||||
.CodeMirror {
|
||||
background: #0C0C0C;
|
||||
color: #B9BDB6;
|
||||
}
|
||||
|
||||
/* Brew BG */
|
||||
.brewRenderer {
|
||||
background-color: #0C0C0C;
|
||||
}
|
||||
|
||||
.cm-s-darkvision {
|
||||
/* Blinking cursor and selection */
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid #B9BDB6;
|
||||
}
|
||||
.CodeMirror-selected {
|
||||
background: #E0E8FF40;
|
||||
}
|
||||
|
||||
/* Line number stuff */
|
||||
.cm-gutter-elt {
|
||||
color: #81969A;
|
||||
}
|
||||
.CodeMirror-linenumber {
|
||||
background-color: #0C0C0C;
|
||||
}
|
||||
.cm-gutter {
|
||||
background-color: #0C0C0C;
|
||||
}
|
||||
|
||||
/* column splits */
|
||||
.editor .codeEditor .columnSplit {
|
||||
font-style: italic;
|
||||
color: inherit;
|
||||
background-color:#1F5763;
|
||||
border-bottom: #299 solid 1px;
|
||||
}
|
||||
|
||||
/* # headings */
|
||||
.cm-header {
|
||||
color: #C51B1B;
|
||||
-webkit-text-stroke-width: 0.1px;
|
||||
}
|
||||
/* bold points */
|
||||
.cm-strong {
|
||||
font-weight: bold;
|
||||
color: #309DD2;
|
||||
}
|
||||
/* Link headings */
|
||||
.cm-link {
|
||||
color: #DD6300;
|
||||
}
|
||||
/* links */
|
||||
.cm-string {
|
||||
color: #5CE638;
|
||||
}
|
||||
/*@import*/
|
||||
.cm-def {
|
||||
color: #2986CC;
|
||||
}
|
||||
/* Bullets and such */
|
||||
.cm-variable-2 {
|
||||
color: #3CBF30;
|
||||
}
|
||||
|
||||
/* Tags (divs) */
|
||||
.cm-tag {
|
||||
color: #E3FF00;
|
||||
}
|
||||
.cm-attribute {
|
||||
color: #E3FF00;
|
||||
}
|
||||
.cm-atom {
|
||||
color: #CF7EA9;
|
||||
}
|
||||
.cm-qualifier {
|
||||
color: #EE1919;
|
||||
}
|
||||
.cm-comment {
|
||||
color: #BBC700;
|
||||
}
|
||||
.cm-keyword {
|
||||
color: #CC66FF;
|
||||
}
|
||||
.cm-property {
|
||||
color: aqua;
|
||||
}
|
||||
.cm-error {
|
||||
color: #C50202;
|
||||
}
|
||||
.CodeMirror-foldmarker {
|
||||
color: #F0FF00;
|
||||
}
|
||||
/* New page */
|
||||
.cm-builtin {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.editor .codeEditor {
|
||||
/* blocks */
|
||||
.block:not(.cm-comment) {
|
||||
color: magenta;
|
||||
}
|
||||
/* definition lists */
|
||||
.define.definition {
|
||||
color: #FFAA3E;
|
||||
}
|
||||
.define.term {
|
||||
color: #7290d9;
|
||||
}
|
||||
.define:not(.term):not(.definition) {
|
||||
background: #333;
|
||||
}
|
||||
/* New page */
|
||||
.pageLine {
|
||||
background: #000;
|
||||
color: #000;
|
||||
border-bottom: 1px solid #FFF;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user