mirror of
https://github.com/wahyd4/docsify.git
synced 2026-08-09 05:16:06 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a77b8a7f3 | ||
|
|
00b13c5e86 | ||
|
|
82b7b8226d | ||
|
|
c9739e71af | ||
|
|
bda1b76ba5 | ||
|
|
11f77cd434 | ||
|
|
6d42c6b04a | ||
|
|
46967e73bf | ||
|
|
ccdc3c855a | ||
|
|
7c4b0b7c74 | ||
|
|
98d1b19bea | ||
|
|
0709239c77 | ||
|
|
73cc43c6bd | ||
|
|
9d096b03a4 | ||
|
|
2af035ff45 | ||
|
|
3979f5cd3f | ||
|
|
9abae495ac | ||
|
|
d179ade7df | ||
|
|
d79c7655cd | ||
|
|
af5d2934e4 | ||
|
|
129bd4d3a0 | ||
|
|
abaf5c0849 | ||
|
|
336efb30cb | ||
|
|
fb8bf89d73 |
@@ -1,3 +1,37 @@
|
||||
## 1.10.2
|
||||
### Bug fixes
|
||||
- Fix render emojis again
|
||||
|
||||
## 1.10.1
|
||||
### Bug fixes
|
||||
- Fix render emojis
|
||||
|
||||
## 1.10.0
|
||||
### Features
|
||||
- Support emoji :laughing:
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### Bug fixes
|
||||
- Destroys the vue instance when the route is changed
|
||||
|
||||
### Features
|
||||
- Add `!>` and `?>` doc helper.
|
||||
|
||||
### Break change
|
||||
- Remove `!` doc helper.
|
||||
|
||||
## 1.8.0
|
||||
### Bug fixes
|
||||
- Using `v-pre` skip compilation.
|
||||
|
||||
### Features
|
||||
- Execute script when vue exists.
|
||||
|
||||
## 1.7.4
|
||||
### Bug fixes
|
||||
- Fix bugs caused by the previous version
|
||||
|
||||
## 1.7.3
|
||||
### Bug fixes
|
||||
- Add `hr` style
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 cinwell.li
|
||||
Copyright (c) 2016 - 2017 cinwell.li
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
- Simple and lightweight (~13kB gzipped)
|
||||
- Multiple themes
|
||||
- Not build static html files
|
||||
- Support emoji :laughing:
|
||||
|
||||
## Quick start
|
||||
Create a `index.html` and using `hash router`.
|
||||
@@ -53,6 +54,8 @@ These open-source projects are using docsify to generate their sites. Pull reque
|
||||
- [Snipaste](https://docs.snipaste.com/) - A new way to boost your productivity.
|
||||
- [puck](https://puck.zz173.com/) - A small & magical php framework.
|
||||
- [Samaritan](http://samaritan.stockdb.org) - An Algorithmic Trading Framework for Digital Currency.
|
||||
- [Vudash](http://vudash.github.io/vudash/) - Powerful, Flexible, Open Source dashboards for anything
|
||||
- [Trilogy](http://trilogy.js.org) - No-hassle SQLite with a Promise-based, document store style API.
|
||||
|
||||
## Similar projects
|
||||
- [docute](https://github.com/egoist/docute) - 📜 Effortlessly documentation done right
|
||||
|
||||
+42
-3
@@ -160,10 +160,10 @@ window.$docsify = {
|
||||
### Doc Helpers
|
||||
#### p.tip
|
||||
|
||||
'! ' add your content will rendered as `<p class="tip">content</p>`
|
||||
`!> ` add your content will rendered as `<p class="tip">content</p>`
|
||||
|
||||
```markdown
|
||||
! Important **information**
|
||||
!> Important **information**
|
||||
```
|
||||
|
||||
It will be rendered
|
||||
@@ -174,9 +174,48 @@ It will be rendered
|
||||
|
||||
e.g.
|
||||
|
||||
! Important **information**
|
||||
!> Important **information**
|
||||
|
||||
|
||||
#### p.warn
|
||||
|
||||
```markdown
|
||||
?> todo info
|
||||
```
|
||||
|
||||
?> todo info
|
||||
|
||||
### Combining Vue
|
||||
We can write the Vue syntax directly in the markdown file, when the Vue library is loaded into `index.html`. Default will automatically initialize a Vue instance, of course, we can also manually.
|
||||
|
||||
index.html
|
||||
```html
|
||||
<script src="//unpkg.com/vue"></script>
|
||||
<script src="//unpkg.com/docsify" data-router></script>
|
||||
```
|
||||
|
||||
```markdown
|
||||
<ul>
|
||||
<li v-for="i in 10">{{ i }}</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Manual initialization
|
||||
|
||||
```markdown
|
||||
<div>
|
||||
<input type="text" v-model="msg">
|
||||
<p>Hello, {{ msg }}</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: 'main',
|
||||
data: { msg: 'Docsify' }
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
You can add configurations in the script tag attributes or with `window.$docsify`.
|
||||
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||

|
||||
|
||||
# docsify <small>1.7.3</small>
|
||||
# docsify <small>1.10.2</small>
|
||||
|
||||
> A magical documentation site generator.
|
||||
|
||||
- Simple and lightweight (~13kB gzipped)
|
||||
- Multiple themes
|
||||
- Not build static html files
|
||||
- Support emoji :laughing:
|
||||
- Multiple themes
|
||||
|
||||
|
||||
[GitHub](https://github.com/QingWei-Li/docsify/)
|
||||
|
||||
+47
-3
@@ -166,10 +166,10 @@ window.$docsify = {
|
||||
### 文档助手
|
||||
#### 内置「提示」语法
|
||||
|
||||
感叹号加空格,后面接内容,会渲染成带 tip 类名的段落。
|
||||
`!>`后面接内容,会渲染成带 tip 类名的段落。
|
||||
|
||||
```markdown
|
||||
! 提示信息,**支持其他 markdown 语法**
|
||||
!> 提示信息,**支持其他 markdown 语法**
|
||||
```
|
||||
|
||||
将被渲染成
|
||||
@@ -180,7 +180,51 @@ window.$docsify = {
|
||||
|
||||
效果
|
||||
|
||||
! 适合显示醒目的内容
|
||||
!> 适合显示醒目的内容
|
||||
|
||||
#### 内置「警示」语法
|
||||
|
||||
`?>`后面接内容,会渲染成带 warn 类名的段落。
|
||||
|
||||
```markdown
|
||||
?> 警示内容样式
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
?> 警示内容样式
|
||||
|
||||
### 结合 Vue
|
||||
|
||||
`index.html` 内引入 Vue 后,可以在文档里直接写 Vue 语法。默认会自己初始化一个 Vue 示例,当然我们也可以手动初始化一个实例。
|
||||
|
||||
index.html
|
||||
```html
|
||||
<script src="//unpkg.com/vue"></script>
|
||||
<script src="//unpkg.com/docsify" data-router></script>
|
||||
```
|
||||
|
||||
```markdown
|
||||
<ul>
|
||||
<li v-for="i in 10">{{ i }}</li>
|
||||
</ul>
|
||||
```
|
||||
|
||||
手动初始化示例
|
||||
|
||||
```markdown
|
||||
<div>
|
||||
<input type="text" v-model="msg">
|
||||
<p>Hello, {{ msg }}</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: 'main',
|
||||
data: { msg: 'Docsify' }
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
## 配置参数
|
||||
|
||||
|
||||
+43
-16
@@ -168,6 +168,12 @@ var merge = Object.assign || function (to) {
|
||||
return to
|
||||
};
|
||||
|
||||
function emojify (text) {
|
||||
return text
|
||||
.replace(/:(\w*?):/ig, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
|
||||
.replace(/__colon__/g, ':')
|
||||
}
|
||||
|
||||
/**
|
||||
* Active sidebar when scroll
|
||||
* @link https://buble.surge.sh/
|
||||
@@ -2463,11 +2469,14 @@ function tree (toc, tpl) {
|
||||
return tpl
|
||||
}
|
||||
|
||||
function helper (className, content) {
|
||||
return ("<p class=\"" + className + "\">" + (content.slice(5).trim()) + "</p>")
|
||||
}
|
||||
|
||||
var OPTIONS$1 = {};
|
||||
var markdown = marked;
|
||||
var toc = [];
|
||||
var CACHE = {};
|
||||
var TIP_RE = /^!\s/;
|
||||
|
||||
var renderTo = function (dom, content) {
|
||||
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
|
||||
@@ -2505,10 +2514,9 @@ function init (options) {
|
||||
renderer.code = function (code, lang) {
|
||||
if ( lang === void 0 ) lang = '';
|
||||
|
||||
var hl = prism.highlight(code, prism.languages[lang] || prism.languages.markup)
|
||||
.replace(/{{/g, '<span>{{</span>');
|
||||
var hl = prism.highlight(code, prism.languages[lang] || prism.languages.markup);
|
||||
|
||||
return ("<pre data-lang=\"" + lang + "\"><code class=\"lang-" + lang + "\">" + hl + "</code></pre>")
|
||||
return ("<pre v-pre data-lang=\"" + lang + "\"><code class=\"lang-" + lang + "\">" + (hl.replace(/:/g, '__colon__')) + "</code></pre>")
|
||||
};
|
||||
renderer.link = function (href, title, text) {
|
||||
if (OPTIONS$1.router && !/:/.test(href)) {
|
||||
@@ -2518,8 +2526,12 @@ function init (options) {
|
||||
return ("<a href=\"" + href + "\" title=\"" + (title || '') + "\">" + text + "</a>")
|
||||
};
|
||||
renderer.paragraph = function (text) {
|
||||
var isTip = TIP_RE.test(text);
|
||||
return isTip ? ("<p class=\"tip\">" + (text.replace(TIP_RE, '')) + "</p>") : ("<p>" + text + "</p>")
|
||||
if (/^!>/.test(text)) {
|
||||
return helper('tip', text)
|
||||
} else if (/^\?>/.test(text)) {
|
||||
return helper('warn', text)
|
||||
}
|
||||
return ("<p>" + text + "</p>")
|
||||
};
|
||||
|
||||
if (typeof OPTIONS$1.markdown === 'function') {
|
||||
@@ -2528,6 +2540,10 @@ function init (options) {
|
||||
} else {
|
||||
markdown.setOptions(merge({ renderer: renderer }, OPTIONS$1.markdown));
|
||||
}
|
||||
|
||||
var md = markdown;
|
||||
|
||||
markdown = function (text) { return emojify(md(text)); };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2560,9 +2576,15 @@ function renderArticle (content) {
|
||||
renderTo('article', content ? markdown(content) : 'not found');
|
||||
if (!OPTIONS$1.sidebar && !OPTIONS$1.loadSidebar) { renderSidebar(); }
|
||||
|
||||
if (content && typeof Vue !== 'undefined' && typeof Vuep !== 'undefined') {
|
||||
var vm = new Vue({ el: 'main' }); // eslint-disable-line
|
||||
vm.$nextTick(function (_) { return scrollActiveSidebar(); });
|
||||
if (content && typeof Vue !== 'undefined') {
|
||||
var script = content.match('<script[^>]*?>([^<]+)</script>');
|
||||
|
||||
script && document.body.querySelector('article script').remove();
|
||||
CACHE.vm && CACHE.vm.$destroy();
|
||||
CACHE.vm = script
|
||||
? new Function(("return " + (script[1].trim())))()
|
||||
: new Vue({ el: 'main' }); // eslint-disable-line
|
||||
CACHE.vm && CACHE.vm.$nextTick(function (_) { return scrollActiveSidebar(); });
|
||||
}
|
||||
if (OPTIONS$1.auto2top) { setTimeout(function () { return scroll2Top(OPTIONS$1.auto2top); }, 0); }
|
||||
}
|
||||
@@ -2621,9 +2643,12 @@ function renderCover (content) {
|
||||
if (renderCover.rendered) { return sticky() }
|
||||
|
||||
// render cover
|
||||
var cacheToc = toc.slice();
|
||||
var html = markdown(content);
|
||||
var match = html.trim().match('<p><img[^s]+src="(.*?)"[^a]+alt="(.*?)">([^<]*?)</p>$');
|
||||
|
||||
toc = cacheToc.slice();
|
||||
|
||||
// render background
|
||||
if (match) {
|
||||
var coverEl = document.querySelector('section.cover');
|
||||
@@ -2703,11 +2728,13 @@ var script = document.currentScript || [].slice.call(document.getElementsByTagNa
|
||||
if (script) {
|
||||
for (var prop in OPTIONS) {
|
||||
var val = script.getAttribute('data-' + camel2kebab(prop));
|
||||
OPTIONS[prop] = isNil(val) ? OPTIONS[prop] : (typeof OPTIONS[prop] === 'string' ? val : true);
|
||||
OPTIONS[prop] = isNil(val) ? OPTIONS[prop] : (val || true);
|
||||
}
|
||||
if (OPTIONS.loadSidebar === true) { OPTIONS.loadSidebar = '_sidebar.md'; }
|
||||
if (OPTIONS.loadNavbar === true) { OPTIONS.loadNavbar = '_navbar.md'; }
|
||||
if (OPTIONS.coverpage === true) { OPTIONS.coverpage = '_coverpage.md'; }
|
||||
if (OPTIONS.repo === true) { OPTIONS.repo = ''; }
|
||||
if (OPTIONS.name === true) { OPTIONS.name = ''; }
|
||||
if (OPTIONS.sidebar) { OPTIONS.sidebar = window[OPTIONS.sidebar]; }
|
||||
}
|
||||
|
||||
@@ -2752,11 +2779,10 @@ var mainRender = function (cb) {
|
||||
if (OPTIONS.coverpage && page !== OPTIONS.homepage) { renderCover(); }
|
||||
// render sidebar
|
||||
if (OPTIONS.loadSidebar) {
|
||||
load(basePath + OPTIONS.loadSidebar)
|
||||
.then(function (result) {
|
||||
renderSidebar(result);
|
||||
cb();
|
||||
});
|
||||
var renderSidebar$$1 = function (result) { renderSidebar(result); cb(); };
|
||||
|
||||
load(basePath + OPTIONS.loadSidebar).then(renderSidebar$$1,
|
||||
function (_) { return load(OPTIONS.loadSidebar).then(renderSidebar$$1); });
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
@@ -2764,7 +2790,8 @@ var mainRender = function (cb) {
|
||||
|
||||
// Render navbar
|
||||
if (OPTIONS.loadNavbar) {
|
||||
load(basePath + OPTIONS.loadNavbar).then(renderNavbar);
|
||||
load(basePath + OPTIONS.loadNavbar).then(renderNavbar,
|
||||
function (_) { return load(OPTIONS.loadNavbar).then(renderNavbar); });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "docsify",
|
||||
"version": "1.7.3",
|
||||
"version": "1.10.2",
|
||||
"description": "A magical documentation generator.",
|
||||
"main": "lib/docsify.js",
|
||||
"files": [
|
||||
|
||||
+9
-7
@@ -25,11 +25,13 @@ const script = document.currentScript || [].slice.call(document.getElementsByTag
|
||||
if (script) {
|
||||
for (const prop in OPTIONS) {
|
||||
const val = script.getAttribute('data-' + camel2kebab(prop))
|
||||
OPTIONS[prop] = isNil(val) ? OPTIONS[prop] : (typeof OPTIONS[prop] === 'string' ? val : true)
|
||||
OPTIONS[prop] = isNil(val) ? OPTIONS[prop] : (val || true)
|
||||
}
|
||||
if (OPTIONS.loadSidebar === true) OPTIONS.loadSidebar = '_sidebar.md'
|
||||
if (OPTIONS.loadNavbar === true) OPTIONS.loadNavbar = '_navbar.md'
|
||||
if (OPTIONS.coverpage === true) OPTIONS.coverpage = '_coverpage.md'
|
||||
if (OPTIONS.repo === true) OPTIONS.repo = ''
|
||||
if (OPTIONS.name === true) OPTIONS.name = ''
|
||||
if (OPTIONS.sidebar) OPTIONS.sidebar = window[OPTIONS.sidebar]
|
||||
}
|
||||
|
||||
@@ -74,11 +76,10 @@ const mainRender = function (cb) {
|
||||
if (OPTIONS.coverpage && page !== OPTIONS.homepage) render.renderCover()
|
||||
// render sidebar
|
||||
if (OPTIONS.loadSidebar) {
|
||||
load(basePath + OPTIONS.loadSidebar)
|
||||
.then(result => {
|
||||
render.renderSidebar(result)
|
||||
cb()
|
||||
})
|
||||
const renderSidebar = result => { render.renderSidebar(result); cb() }
|
||||
|
||||
load(basePath + OPTIONS.loadSidebar).then(renderSidebar,
|
||||
_ => load(OPTIONS.loadSidebar).then(renderSidebar))
|
||||
} else {
|
||||
cb()
|
||||
}
|
||||
@@ -86,7 +87,8 @@ const mainRender = function (cb) {
|
||||
|
||||
// Render navbar
|
||||
if (OPTIONS.loadNavbar) {
|
||||
load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar)
|
||||
load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar,
|
||||
_ => load(OPTIONS.loadNavbar).then(render.renderNavbar))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+24
-9
@@ -2,13 +2,12 @@ import marked from 'marked'
|
||||
import Prism from 'prismjs'
|
||||
import * as tpl from './tpl'
|
||||
import * as event from './event'
|
||||
import { genTree, getRoute, isMobile, slugify, merge } from './util'
|
||||
import { genTree, getRoute, isMobile, slugify, merge, emojify } from './util'
|
||||
|
||||
let OPTIONS = {}
|
||||
let markdown = marked
|
||||
let toc = []
|
||||
const CACHE = {}
|
||||
const TIP_RE = /^!\s/
|
||||
|
||||
const renderTo = function (dom, content) {
|
||||
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
|
||||
@@ -45,9 +44,8 @@ export function init (options) {
|
||||
// highlight code
|
||||
renderer.code = function (code, lang = '') {
|
||||
const hl = Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup)
|
||||
.replace(/{{/g, '<span>{{</span>')
|
||||
|
||||
return `<pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>`
|
||||
return `<pre v-pre data-lang="${lang}"><code class="lang-${lang}">${hl.replace(/:/g, '__colon__')}</code></pre>`
|
||||
}
|
||||
renderer.link = function (href, title, text) {
|
||||
if (OPTIONS.router && !/:/.test(href)) {
|
||||
@@ -57,8 +55,12 @@ export function init (options) {
|
||||
return `<a href="${href}" title="${title || ''}">${text}</a>`
|
||||
}
|
||||
renderer.paragraph = function (text) {
|
||||
const isTip = TIP_RE.test(text)
|
||||
return isTip ? `<p class="tip">${text.replace(TIP_RE, '')}</p>` : `<p>${text}</p>`
|
||||
if (/^!>/.test(text)) {
|
||||
return tpl.helper('tip', text)
|
||||
} else if (/^\?>/.test(text)) {
|
||||
return tpl.helper('warn', text)
|
||||
}
|
||||
return `<p>${text}</p>`
|
||||
}
|
||||
|
||||
if (typeof OPTIONS.markdown === 'function') {
|
||||
@@ -67,6 +69,10 @@ export function init (options) {
|
||||
} else {
|
||||
markdown.setOptions(merge({ renderer }, OPTIONS.markdown))
|
||||
}
|
||||
|
||||
const md = markdown
|
||||
|
||||
markdown = text => emojify(md(text))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +105,15 @@ export function renderArticle (content) {
|
||||
renderTo('article', content ? markdown(content) : 'not found')
|
||||
if (!OPTIONS.sidebar && !OPTIONS.loadSidebar) renderSidebar()
|
||||
|
||||
if (content && typeof Vue !== 'undefined' && typeof Vuep !== 'undefined') {
|
||||
const vm = new Vue({ el: 'main' }) // eslint-disable-line
|
||||
vm.$nextTick(_ => event.scrollActiveSidebar())
|
||||
if (content && typeof Vue !== 'undefined') {
|
||||
const script = content.match('<script[^>]*?>([^<]+)</script>')
|
||||
|
||||
script && document.body.querySelector('article script').remove()
|
||||
CACHE.vm && CACHE.vm.$destroy()
|
||||
CACHE.vm = script
|
||||
? new Function(`return ${script[1].trim()}`)()
|
||||
: new Vue({ el: 'main' }) // eslint-disable-line
|
||||
CACHE.vm && CACHE.vm.$nextTick(_ => event.scrollActiveSidebar())
|
||||
}
|
||||
if (OPTIONS.auto2top) setTimeout(() => event.scroll2Top(OPTIONS.auto2top), 0)
|
||||
}
|
||||
@@ -160,9 +172,12 @@ export function renderCover (content) {
|
||||
if (renderCover.rendered) return event.sticky()
|
||||
|
||||
// render cover
|
||||
const cacheToc = toc.slice()
|
||||
let html = markdown(content)
|
||||
const match = html.trim().match('<p><img[^s]+src="(.*?)"[^a]+alt="(.*?)">([^<]*?)</p>$')
|
||||
|
||||
toc = cacheToc.slice()
|
||||
|
||||
// render background
|
||||
if (match) {
|
||||
const coverEl = document.querySelector('section.cover')
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
height: 1.2em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: $color-primary;
|
||||
height: 2px;
|
||||
@@ -354,6 +359,12 @@ body.sticky {
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-section p.warn {
|
||||
padding: 1em;
|
||||
background: rgba($color-primary, 0.1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
body.close {
|
||||
.sidebar {
|
||||
transform: translateX(-$sidebar-width);
|
||||
|
||||
@@ -72,3 +72,6 @@ export function tree (toc, tpl = '') {
|
||||
return tpl
|
||||
}
|
||||
|
||||
export function helper (className, content) {
|
||||
return `<p class="${className}">${content.slice(5).trim()}</p>`
|
||||
}
|
||||
|
||||
@@ -157,3 +157,10 @@ export const merge = Object.assign || function (to) {
|
||||
|
||||
return to
|
||||
}
|
||||
|
||||
export function emojify (text) {
|
||||
return text
|
||||
.replace(/:(\w*?):/ig, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
|
||||
.replace(/__colon__/g, ':')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user