Merge pull request #397 from vkbo/more_cleanup
More Code Cleanup (flake8)
This commit is contained in:
@@ -24,4 +24,4 @@ jobs:
|
||||
- name: Check for Syntax Error on Tests
|
||||
run: flake8 tests --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
- name: Check for Code Style on novelWriter
|
||||
run: flake8 nw --count --max-line-length=99 --select E1,E231,E27,E4,E5,E7,E9,W,F --show-source --statistics
|
||||
run: flake8 nw --count --max-line-length=99 --ignore E203,E221,E226,E241,E251,E261,E266,E302,E305 --show-source --statistics
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via the
|
||||
issue tracker with the owner of this repository before making a change. If you just want to make a
|
||||
minor correction, like fix a typo or similar, feel free to just make a pull request directly.
|
||||
|
||||
There is a code of conduct. Please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Make sure your code passes all tests and conforms to the style guide. You can check that the code
|
||||
conforms by running `flake8` from the root of the project folder.
|
||||
2. Please provide a complete description of the changes in the pull request, and a summary that can
|
||||
be copied into the [CHANGELOG](CHANGELOG.md). Remember to reference any issue related by
|
||||
providing the issue number.
|
||||
3. Do not change the version number unless asked to do so. Version numbers are bumped in separate
|
||||
release pull requests by the maintainer.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers
|
||||
pledge to making participation in our project and our community a harassment-free experience for
|
||||
everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level
|
||||
of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
Please see the [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) file for the full text.
|
||||
|
||||
## Code Style Guide
|
||||
|
||||
The source code of novelWriter broadly follows the [PEP8](https://www.python.org/dev/peps/pep-0008/)
|
||||
style guide , but with a few modifications and exceptions.
|
||||
|
||||
### Linting with flake8
|
||||
|
||||
An excellent tool for checking Python code for errors and coding style is `flake8`.
|
||||
The documentation is available [here](https://flake8.pycqa.org/en/latest/).
|
||||
|
||||
The `setup.cfg` file in the root of this project has the following settings:
|
||||
```conf
|
||||
[flake8]
|
||||
ignore = E203,E221,E226,E241,E251,E261,E266,E302,E305
|
||||
max-line-length = 99
|
||||
exclude = docs/*
|
||||
```
|
||||
|
||||
The command line equivalent, with reporting, is:
|
||||
```bash
|
||||
flake8 . --count --ignore E203,E221,E226,E241,E251,E261,E266,E302,E305 --max-line-length=99 --show-source --statistics
|
||||
```
|
||||
|
||||
Passing this check is required before contributuions are merged into the `main` branch. This is
|
||||
checked automatically when you make a pull request. You can run the `flake8` command locally to
|
||||
check beforehand. The full command will give you a detailed description of the code lines that do
|
||||
not conform to the standard.
|
||||
|
||||
## Ignored Errors
|
||||
|
||||
Some errors are ignored in novelWriter, for various reasons. In addition, novelWriter sues camelCase
|
||||
function and variable names due to this being the standard for the Qt libraries, and also because of
|
||||
the author's personal preferences.
|
||||
|
||||
The reason behind the other ignored error codes are listed below. Many of them are due to PEP8 not
|
||||
permitting column alignment as opposed to many other coding styles. I find them useful in regions of
|
||||
bulk value assignments. There's a reason why tables are more readable than lists. They should be
|
||||
used sparingly though.
|
||||
|
||||
The ignored errors are all `pycodestyle` errors, and they are documented
|
||||
[here](https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes).
|
||||
|
||||
**E203:** whitespace before ‘:’
|
||||
**Reason:** Column alignment. It is natural to align dictionary columns along the `:` character,
|
||||
|
||||
**E221:** multiple spaces before operator
|
||||
**Reason:** Column alignment.
|
||||
|
||||
**E226:** missing whitespace around arithmetic operator
|
||||
**Reason:** This doesn't actually follow the PEP8 recommendation of grouping longer equations by
|
||||
operator precedence like `2*a + 3*b` instead of `a * a + 3 * b`. Generally, don't use spaces around
|
||||
`*`, `/` and `**`, but do use spaces around `+` and `-`. For appending strings, the spaces can be
|
||||
dropped. Don't use the `+` operator for appending multiple strings. Use formatting instead.
|
||||
|
||||
**E241:** multiple spaces after ‘,’
|
||||
**Reason:** Column alignment.
|
||||
|
||||
**E251:** unexpected spaces around keyword / parameter equals
|
||||
**Reason:** Column alignment.
|
||||
|
||||
**E261:** at least two spaces before inline comment
|
||||
**Reason:** With syntax highlighting, this one is meaningless and wastes an extra space.
|
||||
|
||||
**E266:** too many leading ‘#’ for block comment
|
||||
**Reason:** In the source multiple `#`s to indicate importance levels is sometimes used, like
|
||||
markdown headers.
|
||||
|
||||
**E302:** expected 2 blank lines, found 0
|
||||
**Reason:** Applies to classes. Instead, end classes with a comment like `# END Class ClassName` to
|
||||
make it easier to se which class just ended. The double line break is then redundant.
|
||||
|
||||
**E305:** expected 2 blank lines after end of function or class
|
||||
**Reason:** Instead, _always_ end a function with a `return`, preferrably indented at function
|
||||
level. The end of the function is then clear.
|
||||
@@ -20,6 +20,8 @@ is primarily saved in JSON files.
|
||||
|
||||
The full documentation is available at [novelwriter.readthedocs.io](https://novelwriter.readthedocs.io/).
|
||||
|
||||
The contributing guide is available in [CONTRIBUTING](CONTRIBUTING.md).
|
||||
|
||||
|
||||
### Note on the Default Branch
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Code Style Guide
|
||||
|
||||
The source code of novelWriter broadly follows the style guide [PEP8](https://www.python.org/dev/peps/pep-0008/), but with a few modifications and exceptions.
|
||||
|
||||
### Source Code Exceptions
|
||||
|
||||
* Methods are camelCase, not underscore based.
|
||||
The reason is partially because of the maintainers personal preference, and partially because that is what Qt5 and PyQt5 uses.
|
||||
The maintainer generally, across multiple programming languages, uses underscores for defining namespaces.
|
||||
* The maximum length of a code line is 99 characters, not 79.
|
||||
The reason for this is that novelWriter is almost entirely made up of classes, meaning nearly all lines of code already have 8 leading spaces.
|
||||
A 79 character limitation is too strict, and causes too many wrapped lines.
|
||||
99 characters is suitable for GitHub diff readability, and therefore the preferred limit.
|
||||
It is also permitted under PEP8 as the maximum.
|
||||
Comments and docstrings should comply with the 72 character limit.
|
||||
* Aligning code with additional spaces is acceptable in those cases where it improves readability.
|
||||
Otherwise, the PEP8 standard should be applied.
|
||||
|
||||
### Documentation
|
||||
|
||||
The documentation does not adhere to the 80 character limit either.
|
||||
The standard used in documentation is one line break after each sentence.
|
||||
This is an alternative style that greatly improves readability of diffs as re-wrapping text is not needed when inserting new text in paragraphs.
|
||||
Instead, the diff will show changes to each sentence.
|
||||
@@ -76,6 +76,7 @@ logging.addLevelName(VERBOSE, "VERBOSE")
|
||||
def logVerbose(self, message, *args, **kws):
|
||||
if self.isEnabledFor(VERBOSE):
|
||||
self._log(VERBOSE, message, args, **kws)
|
||||
|
||||
logging.Logger.verbose = logVerbose
|
||||
|
||||
# Initiating logging
|
||||
|
||||
+26
-26
@@ -68,32 +68,32 @@ class nwItemLayout(Enum):
|
||||
|
||||
class nwDocAction(Enum):
|
||||
|
||||
NO_ACTION = 0
|
||||
UNDO = 1
|
||||
REDO = 2
|
||||
CUT = 3
|
||||
COPY = 4
|
||||
PASTE = 5
|
||||
EMPH = 6
|
||||
STRONG = 7
|
||||
STRIKE = 8
|
||||
S_QUOTE = 9
|
||||
D_QUOTE = 10
|
||||
SEL_ALL = 11
|
||||
SEL_PARA = 12
|
||||
FIND = 13
|
||||
REPLACE = 14
|
||||
GO_NEXT = 15
|
||||
GO_PREV = 16
|
||||
REPL_NEXT = 17
|
||||
BLOCK_H1 = 18
|
||||
BLOCK_H2 = 19
|
||||
BLOCK_H3 = 20
|
||||
BLOCK_H4 = 21
|
||||
BLOCK_COM = 22
|
||||
BLOCK_TXT = 23
|
||||
REPL_SNG = 24
|
||||
REPL_DBL = 25
|
||||
NO_ACTION = 0
|
||||
UNDO = 1
|
||||
REDO = 2
|
||||
CUT = 3
|
||||
COPY = 4
|
||||
PASTE = 5
|
||||
EMPH = 6
|
||||
STRONG = 7
|
||||
STRIKE = 8
|
||||
S_QUOTE = 9
|
||||
D_QUOTE = 10
|
||||
SEL_ALL = 11
|
||||
SEL_PARA = 12
|
||||
FIND = 13
|
||||
REPLACE = 14
|
||||
GO_NEXT = 15
|
||||
GO_PREV = 16
|
||||
REPL_NEXT = 17
|
||||
BLOCK_H1 = 18
|
||||
BLOCK_H2 = 19
|
||||
BLOCK_H3 = 20
|
||||
BLOCK_H4 = 21
|
||||
BLOCK_COM = 22
|
||||
BLOCK_TXT = 23
|
||||
REPL_SNG = 24
|
||||
REPL_DBL = 25
|
||||
|
||||
# END Enum nwDocAction
|
||||
|
||||
|
||||
+8
-7
@@ -206,14 +206,14 @@ class NWProject():
|
||||
self.autoOutline = True
|
||||
self.statusItems = NWStatus()
|
||||
self.statusItems.addEntry("New", (100, 100, 100))
|
||||
self.statusItems.addEntry("Note", (200, 50, 0))
|
||||
self.statusItems.addEntry("Draft", (200, 150, 0))
|
||||
self.statusItems.addEntry("Finished", ( 50, 200, 0))
|
||||
self.statusItems.addEntry("Note", (200, 50, 0))
|
||||
self.statusItems.addEntry("Draft", (200, 150, 0))
|
||||
self.statusItems.addEntry("Finished", (50, 200, 0))
|
||||
self.importItems = NWStatus()
|
||||
self.importItems.addEntry("New", (100, 100, 100))
|
||||
self.importItems.addEntry("Minor", (200, 50, 0))
|
||||
self.importItems.addEntry("Major", (200, 150, 0))
|
||||
self.importItems.addEntry("Main", ( 50, 200, 0))
|
||||
self.importItems.addEntry("Minor", (200, 50, 0))
|
||||
self.importItems.addEntry("Major", (200, 150, 0))
|
||||
self.importItems.addEntry("Main", (50, 200, 0))
|
||||
self.lastEdited = None
|
||||
self.lastViewed = None
|
||||
self.lastWCount = 0
|
||||
@@ -1402,7 +1402,8 @@ class NWProject():
|
||||
try:
|
||||
rmdir(theData)
|
||||
logger.info("Removed folder: %s" % theFolder)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
errList.append("Failed to remove: %s" % theFolder)
|
||||
|
||||
return errList
|
||||
|
||||
+23
-23
@@ -40,34 +40,34 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Tokenizer():
|
||||
|
||||
FMT_B_B = 1 # Begin bold
|
||||
FMT_B_E = 2 # End bold
|
||||
FMT_I_B = 3 # Begin italics
|
||||
FMT_I_E = 4 # End italics
|
||||
FMT_D_B = 5 # Begin strikeout
|
||||
FMT_D_E = 6 # End strikeout
|
||||
FMT_B_B = 1 # Begin bold
|
||||
FMT_B_E = 2 # End bold
|
||||
FMT_I_B = 3 # Begin italics
|
||||
FMT_I_E = 4 # End italics
|
||||
FMT_D_B = 5 # Begin strikeout
|
||||
FMT_D_E = 6 # End strikeout
|
||||
|
||||
T_EMPTY = 1 # Empty line (new paragraph)
|
||||
T_SYNOPSIS = 2 # Synopsis comment
|
||||
T_COMMENT = 3 # Comment line
|
||||
T_KEYWORD = 4 # Command line
|
||||
T_TITLE = 5 # Title
|
||||
T_HEAD1 = 6 # Header 1
|
||||
T_HEAD2 = 7 # Header 2
|
||||
T_HEAD3 = 8 # Header 3
|
||||
T_HEAD4 = 9 # Header 4
|
||||
T_EMPTY = 1 # Empty line (new paragraph)
|
||||
T_SYNOPSIS = 2 # Synopsis comment
|
||||
T_COMMENT = 3 # Comment line
|
||||
T_KEYWORD = 4 # Command line
|
||||
T_TITLE = 5 # Title
|
||||
T_HEAD1 = 6 # Header 1
|
||||
T_HEAD2 = 7 # Header 2
|
||||
T_HEAD3 = 8 # Header 3
|
||||
T_HEAD4 = 9 # Header 4
|
||||
T_TEXT = 10 # Text line
|
||||
T_SEP = 11 # Scene separator
|
||||
T_SKIP = 12 # Paragraph break
|
||||
|
||||
A_NONE = 0 # No special style
|
||||
A_LEFT = 1 # Left aligned
|
||||
A_RIGHT = 2 # Right aligned
|
||||
A_CENTRE = 4 # Centred
|
||||
A_JUSTIFY = 8 # Justified
|
||||
A_PBB = 16 # Page break before always
|
||||
A_PBB_AV = 32 # Page break before avoid
|
||||
A_PBB_NO = 64 # Page break before never
|
||||
A_NONE = 0 # No special style
|
||||
A_LEFT = 1 # Left aligned
|
||||
A_RIGHT = 2 # Right aligned
|
||||
A_CENTRE = 4 # Centred
|
||||
A_JUSTIFY = 8 # Justified
|
||||
A_PBB = 16 # Page break before always
|
||||
A_PBB_AV = 32 # Page break before avoid
|
||||
A_PBB_NO = 64 # Page break before never
|
||||
A_PBA = 128 # Page break after always
|
||||
A_PBA_AV = 256 # Page break after avoid
|
||||
A_PBA_NO = 512 # Page break after avoid
|
||||
|
||||
+2
-2
@@ -153,7 +153,7 @@ class NWTree():
|
||||
outFile.write(" Table of Contents\n")
|
||||
outFile.write("===================\n")
|
||||
outFile.write("\n")
|
||||
outFile.write(" %-25s %-9s %s\n" %("File Name", "Class", "Document Label"))
|
||||
outFile.write(" %-25s %-9s %s\n" % ("File Name", "Class", "Document Label"))
|
||||
outFile.write("-"*80+"\n")
|
||||
for tHandle in sorted(self._treeOrder):
|
||||
tItem = self.__getitem__(tHandle)
|
||||
@@ -161,7 +161,7 @@ class NWTree():
|
||||
continue
|
||||
tFile = tHandle+".nwd"
|
||||
if path.isfile(path.join(self.theProject.projContent, tFile)):
|
||||
outFile.write(" %-25s %-9s %s\n" %(
|
||||
outFile.write(" %-25s %-9s %s\n" % (
|
||||
path.join("content", tFile),
|
||||
tItem.itemClass.name,
|
||||
tItem.itemName,
|
||||
|
||||
@@ -1068,7 +1068,6 @@ class GuiBuildNovelDocView(QTextBrowser):
|
||||
strBuildTime = "Unknown"
|
||||
self.theTitle.setText("<b>Build Time:</b> %s" % strBuildTime)
|
||||
|
||||
|
||||
def _updateDocMargins(self):
|
||||
"""Automatically adjust the header to fill the top of the
|
||||
document within the viewport.
|
||||
|
||||
@@ -101,17 +101,17 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.colEmph = None
|
||||
|
||||
self.hStyles = {
|
||||
"header1" : self._makeFormat(self.colHead, "bold", 1.8),
|
||||
"header2" : self._makeFormat(self.colHead, "bold", 1.6),
|
||||
"header3" : self._makeFormat(self.colHead, "bold", 1.4),
|
||||
"header4" : self._makeFormat(self.colHead, "bold", 1.2),
|
||||
"header1" : self._makeFormat(self.colHead, "bold", 1.8),
|
||||
"header2" : self._makeFormat(self.colHead, "bold", 1.6),
|
||||
"header3" : self._makeFormat(self.colHead, "bold", 1.4),
|
||||
"header4" : self._makeFormat(self.colHead, "bold", 1.2),
|
||||
"header1h" : self._makeFormat(self.colHeadH, "bold", 1.8),
|
||||
"header2h" : self._makeFormat(self.colHeadH, "bold", 1.6),
|
||||
"header3h" : self._makeFormat(self.colHeadH, "bold", 1.4),
|
||||
"header4h" : self._makeFormat(self.colHeadH, "bold", 1.2),
|
||||
"bold" : self._makeFormat(self.colEmph, "bold"),
|
||||
"italic" : self._makeFormat(self.colEmph, "italic"),
|
||||
"strike" : self._makeFormat(self.colEmph, "strike"),
|
||||
"bold" : self._makeFormat(self.colEmph, "bold"),
|
||||
"italic" : self._makeFormat(self.colEmph, "italic"),
|
||||
"strike" : self._makeFormat(self.colEmph, "strike"),
|
||||
"trailing" : self._makeFormat(self.colTrail, "background"),
|
||||
"nobreak" : self._makeFormat(self.colTrail, "background"),
|
||||
"dialogue1" : self._makeFormat(self.colDialN),
|
||||
@@ -121,7 +121,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"hidden" : self._makeFormat(self.colComm),
|
||||
"keyword" : self._makeFormat(self.colKey),
|
||||
"modifier" : self._makeFormat(self.colMod),
|
||||
"value" : self._makeFormat(self.colVal, "underline"),
|
||||
"value" : self._makeFormat(self.colVal, "underline"),
|
||||
}
|
||||
|
||||
self.hRules = []
|
||||
|
||||
+5
-5
@@ -43,12 +43,12 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
DEF_WIDTH = {
|
||||
nwOutline.TITLE : 200,
|
||||
nwOutline.LEVEL : 40,
|
||||
nwOutline.LEVEL : 40,
|
||||
nwOutline.LABEL : 150,
|
||||
nwOutline.LINE : 40,
|
||||
nwOutline.CCOUNT : 50,
|
||||
nwOutline.WCOUNT : 50,
|
||||
nwOutline.PCOUNT : 50,
|
||||
nwOutline.LINE : 40,
|
||||
nwOutline.CCOUNT : 50,
|
||||
nwOutline.WCOUNT : 50,
|
||||
nwOutline.PCOUNT : 50,
|
||||
nwOutline.POV : 100,
|
||||
nwOutline.CHAR : 100,
|
||||
nwOutline.PLOT : 100,
|
||||
|
||||
+27
-27
@@ -77,11 +77,11 @@ class GuiTheme:
|
||||
self.themeLicenseUrl = ""
|
||||
|
||||
## GUI
|
||||
self.treeWCount = [ 0, 0, 0]
|
||||
self.treeWCount = [0, 0, 0]
|
||||
self.statNone = [120, 120, 120]
|
||||
self.statUnsaved = [120, 120, 40]
|
||||
self.statSaved = [ 40, 120, 0]
|
||||
self.helpText = [ 0, 0, 0]
|
||||
self.statUnsaved = [120, 120, 40]
|
||||
self.statSaved = [40, 120, 0]
|
||||
self.helpText = [0, 0, 0]
|
||||
|
||||
# Loaded Syntax Settings
|
||||
|
||||
@@ -96,21 +96,21 @@ class GuiTheme:
|
||||
|
||||
## Colours
|
||||
self.colBack = [255, 255, 255]
|
||||
self.colText = [ 0, 0, 0]
|
||||
self.colLink = [ 0, 0, 0]
|
||||
self.colHead = [ 0, 0, 0]
|
||||
self.colHeadH = [ 0, 0, 0]
|
||||
self.colEmph = [ 0, 0, 0]
|
||||
self.colDialN = [ 0, 0, 0]
|
||||
self.colDialD = [ 0, 0, 0]
|
||||
self.colDialS = [ 0, 0, 0]
|
||||
self.colComm = [ 0, 0, 0]
|
||||
self.colKey = [ 0, 0, 0]
|
||||
self.colVal = [ 0, 0, 0]
|
||||
self.colSpell = [ 0, 0, 0]
|
||||
self.colTagErr = [ 0, 0, 0]
|
||||
self.colRepTag = [ 0, 0, 0]
|
||||
self.colMod = [ 0, 0, 0]
|
||||
self.colText = [0, 0, 0]
|
||||
self.colLink = [0, 0, 0]
|
||||
self.colHead = [0, 0, 0]
|
||||
self.colHeadH = [0, 0, 0]
|
||||
self.colEmph = [0, 0, 0]
|
||||
self.colDialN = [0, 0, 0]
|
||||
self.colDialD = [0, 0, 0]
|
||||
self.colDialS = [0, 0, 0]
|
||||
self.colComm = [0, 0, 0]
|
||||
self.colKey = [0, 0, 0]
|
||||
self.colVal = [0, 0, 0]
|
||||
self.colSpell = [0, 0, 0]
|
||||
self.colTagErr = [0, 0, 0]
|
||||
self.colRepTag = [0, 0, 0]
|
||||
self.colMod = [0, 0, 0]
|
||||
|
||||
# Changeable Settings
|
||||
self.guiTheme = None
|
||||
@@ -146,7 +146,7 @@ class GuiTheme:
|
||||
self.fontPixelSize = int(round(qMetric.height()))
|
||||
self.baseIconSize = int(round(qMetric.ascent()))
|
||||
self.textNHeight = qMetric.boundingRect("N").height()
|
||||
self.textNWidth= qMetric.boundingRect("N").width()
|
||||
self.textNWidth = qMetric.boundingRect("N").width()
|
||||
|
||||
logger.verbose("GUI Font Family: %s" % self.guiFont.family())
|
||||
logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize)
|
||||
@@ -276,13 +276,13 @@ class GuiTheme:
|
||||
## Main
|
||||
cnfSec = "Main"
|
||||
if confParser.has_section(cnfSec):
|
||||
self.themeName = self._parseLine( confParser, cnfSec, "name", "")
|
||||
self.themeDescription = self._parseLine( confParser, cnfSec, "description", "")
|
||||
self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "")
|
||||
self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "")
|
||||
self.themeUrl = self._parseLine( confParser, cnfSec, "url", "")
|
||||
self.themeLicense = self._parseLine( confParser, cnfSec, "license", "")
|
||||
self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "")
|
||||
self.themeName = self._parseLine(confParser, cnfSec, "name", "")
|
||||
self.themeDescription = self._parseLine(confParser, cnfSec, "description", "")
|
||||
self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "")
|
||||
self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "")
|
||||
self.themeUrl = self._parseLine(confParser, cnfSec, "url", "")
|
||||
self.themeLicense = self._parseLine(confParser, cnfSec, "license", "")
|
||||
self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "")
|
||||
|
||||
## Palette
|
||||
cnfSec = "Palette"
|
||||
|
||||
Reference in New Issue
Block a user