Compare commits
2 Commits
5d36def32c
...
46f7424bc9
Author | SHA1 | Date | |
---|---|---|---|
46f7424bc9 | |||
dc3b8ea5d8 |
162
extras/figures.css
Normal file
162
extras/figures.css
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
|
||||||
|
figure[data-lang] {
|
||||||
|
margin-block: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure a.view-svg {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang] svg {
|
||||||
|
display: block;
|
||||||
|
margin-inline: auto;
|
||||||
|
margin-block: 2rem;
|
||||||
|
|
||||||
|
/* The auto-scaling font-size from typography/*.css does bad things to a lot of SVGs.
|
||||||
|
* The SVGs themselves are inherently scalable, so there is no need for it here anyway. */
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-size='medium']:has(svg) {
|
||||||
|
margin-block: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-size='large'] {
|
||||||
|
margin-block: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-size='small'] svg {
|
||||||
|
max-width: 40rem;
|
||||||
|
max-height: min(20rem, 50vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-size='medium'] svg {
|
||||||
|
max-width: 60rem;
|
||||||
|
max-height: min(40rem, 50vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-size='large'] svg {
|
||||||
|
max-height: min(60rem, 80vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* figure[data-lang].big {
|
||||||
|
background: var(--theme-bg-main);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
border: 0.25rem var(--theme-line) solid;
|
||||||
|
margin: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang].big svg {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
} */
|
||||||
|
|
||||||
|
figure:is([data-lang='pikchr'], [data-lang='nomnoml'], [data-lang='bytefield']) svg text {
|
||||||
|
fill: var(--theme-text-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure:is([data-lang='pikchr'], [data-lang='nomnoml'], [data-lang='bytefield']) svg text:not([font-family~='Courier']) {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-lang='bash:samp'] samp {
|
||||||
|
display: block;
|
||||||
|
margin-block-start: 0.5rem;
|
||||||
|
padding-block-start: 0.5rem;
|
||||||
|
border-block-start: 0.1rem solid var(--theme-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === KaTeX === */
|
||||||
|
|
||||||
|
.katex-display {
|
||||||
|
color: var(--theme-text-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.katex-display .katex {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* using "body" here to add specificity, to override styles from katex.min.css */
|
||||||
|
body .katex-display {
|
||||||
|
margin: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body figure.align-left .katex-display > .katex {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
body figure.align-right .katex-display > .katex {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(.katex-display) > .katex {
|
||||||
|
font-size: inherit;
|
||||||
|
margin-inline: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.katex span[style~='color:transparent;'] {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Pikchr */
|
||||||
|
|
||||||
|
/* boxes */
|
||||||
|
figure[data-lang='pikchr'] svg path[style*='fill:none;'] {
|
||||||
|
fill: var(--theme-bg-light) !important;
|
||||||
|
transition: fill linear .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lines and boxes */
|
||||||
|
figure[data-lang='pikchr'] svg path[style*='stroke:rgb(0,0,0);'] {
|
||||||
|
stroke: var(--theme-text-body) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* circles */
|
||||||
|
figure[data-lang='pikchr'] svg circle[style*='stroke:rgb(0,0,0);'] {
|
||||||
|
stroke: var(--theme-text-body) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* arrow heads */
|
||||||
|
figure[data-lang='pikchr'] svg polygon[style='fill:rgb(0,0,0)'] {
|
||||||
|
fill: var(--theme-text-body) !important;
|
||||||
|
transition: fill linear .5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Bytefield === */
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg line[stroke-width='1'] {
|
||||||
|
stroke-width: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-size='11'] {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-size='18'] {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-family~='Courier'] {
|
||||||
|
font-family: var(--font-monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg :is(text, tspan)[font-family~='Times'] {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg line[stroke-dasharray='1,1'] {
|
||||||
|
stroke-dasharray: 4px, 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure[data-lang='clojure:bytefield'] svg line[stroke-dasharray='1,3'] {
|
||||||
|
stroke-dasharray: 2px, 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -76,3 +76,26 @@ dd + dt {
|
|||||||
figcaption {
|
figcaption {
|
||||||
margin-block-start: 1.5rem;
|
margin-block-start: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Pre-formatted Blocks ===== */
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin-block: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Note Blocks ===== */
|
||||||
|
|
||||||
|
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :first-child {
|
||||||
|
margin-block-start: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :last-child {
|
||||||
|
margin-block-end: 0.25rem;
|
||||||
|
}
|
||||||
|
@ -80,6 +80,14 @@ p {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Inline Styles ===== */
|
||||||
|
|
||||||
|
del {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===== Links ===== */
|
/* ===== Links ===== */
|
||||||
|
|
||||||
@ -111,6 +119,10 @@ a.icon-link svg.icon {
|
|||||||
--icon-size: 1rem;
|
--icon-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
del a {
|
||||||
|
text-decoration: line-through underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -255,24 +267,28 @@ table dl {
|
|||||||
pre {
|
pre {
|
||||||
color: var(--theme-code-normal);
|
color: var(--theme-code-normal);
|
||||||
font-family: var(--font-monospace);
|
font-family: var(--font-monospace);
|
||||||
margin-block: 3rem;
|
margin-inline-start: 1rem;
|
||||||
margin-inline: 2rem;
|
margin-inline-end: 5rem;
|
||||||
padding-block: 0.5rem;
|
|
||||||
padding-inline: 1rem;
|
|
||||||
border: 0.1rem solid var(--theme-line);
|
border: 0.1rem solid var(--theme-line);
|
||||||
border-radius: 1rem;
|
border-radius: 0.5rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: var(--theme-bg-light);
|
background: var(--theme-bg-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 60rem) {
|
||||||
|
pre {
|
||||||
|
margin-inline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===== Code / Sample Output ===== */
|
/* ===== Code / Sample Output ===== */
|
||||||
|
|
||||||
code, samp {
|
code, samp {
|
||||||
font-size: 1rem;
|
font-size: inherit;
|
||||||
color: var(--theme-code-normal);
|
color: var(--theme-code-normal);
|
||||||
font-family: var(--font-monospace);
|
font-family: var(--font-monospace);
|
||||||
}
|
}
|
||||||
@ -280,8 +296,11 @@ code, samp {
|
|||||||
:not(pre) > :is(code, samp) {
|
:not(pre) > :is(code, samp) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background: var(--theme-bg-light);
|
background: var(--theme-bg-light);
|
||||||
|
margin-inline: 0.15rem;
|
||||||
|
padding-block: 0.1rem;
|
||||||
padding-inline: 0.25rem;
|
padding-inline: 0.25rem;
|
||||||
border: 0.1rem solid var(--theme-line);
|
border: 0.1rem solid var(--theme-line);
|
||||||
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,6 +45,20 @@ p {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Note Blocks ===== */
|
||||||
|
|
||||||
|
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :first-child {
|
||||||
|
margin-block-start: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(aside, section):is([role='note'], .info, .highlight, .warning, .problem) > :last-child {
|
||||||
|
margin-block-end: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===== Lists ===== */
|
/* ===== Lists ===== */
|
||||||
|
|
||||||
@ -75,3 +89,13 @@ dd + dt {
|
|||||||
figcaption {
|
figcaption {
|
||||||
margin-block-start: 2rem;
|
margin-block-start: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ===== Pre-formatted Blocks ===== */
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-block: 2rem;
|
||||||
|
padding-block: 0.5rem;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
31
package-lock.json
generated
31
package-lock.json
generated
@ -8,9 +8,9 @@
|
|||||||
"name": "@doc-utils/docs2website",
|
"name": "@doc-utils/docs2website",
|
||||||
"version": "0.1.6",
|
"version": "0.1.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@doc-utils/color-themes": "^0.1.14",
|
"@doc-utils/color-themes": "^0.1.15",
|
||||||
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
||||||
"@doc-utils/markdown2html": "^0.3.2",
|
"@doc-utils/markdown2html": "^0.3.4",
|
||||||
"glob": "^10.2.3",
|
"glob": "^10.2.3",
|
||||||
"ical": "^0.8.0",
|
"ical": "^0.8.0",
|
||||||
"ical-generator": "^4.1.0",
|
"ical-generator": "^4.1.0",
|
||||||
@ -33,9 +33,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@doc-utils/color-themes": {
|
"node_modules/@doc-utils/color-themes": {
|
||||||
"version": "0.1.14",
|
"version": "0.1.15",
|
||||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.14/color-themes-0.1.14.tgz",
|
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.15/color-themes-0.1.15.tgz",
|
||||||
"integrity": "sha512-j0U8v8Y+9zAm9D7pbCheTQYGEKt9FSpKSZQNGsogxWl95S9Z7QMjtmJns6QPgdOsSDss7sjMLFS5Gm50GCMzNA=="
|
"integrity": "sha512-P0oIlq4Z0cUOf7P4T2OUfhT/Cn/msl3oHTenVWHxKMDPlIYueR6AhFdRCHTI0A+DxyH+0EIi6CnMq3JYnILI0w=="
|
||||||
},
|
},
|
||||||
"node_modules/@doc-utils/jsonschema2markdown": {
|
"node_modules/@doc-utils/jsonschema2markdown": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@ -51,9 +51,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@doc-utils/markdown2html": {
|
"node_modules/@doc-utils/markdown2html": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.4",
|
||||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.2/markdown2html-0.3.2.tgz",
|
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.4/markdown2html-0.3.4.tgz",
|
||||||
"integrity": "sha512-aRPlxA25gowmWmzPID4xyLCTVs7VgQJvlmYn3amnFAsUC55JzFXZQ9MR40AKeQYv9VSXIpxjUr7A86hgOzRFFw==",
|
"integrity": "sha512-zL3kay/zbrBJWGKIxGCyF+EXsafSxY1aN+miG+GOyml1neoOwd67vNIiE96KuEucf6rvuICPiYTjm3uJxkjd/g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bytefield-svg": "^1.6.1",
|
"bytefield-svg": "^1.6.1",
|
||||||
"dompurify": "^2.3.6",
|
"dompurify": "^2.3.6",
|
||||||
@ -66,6 +66,9 @@
|
|||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"vega": "^5.22.1",
|
"vega": "^5.22.1",
|
||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"markdown2html": "bin/markdown2html"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
@ -2674,9 +2677,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@doc-utils/color-themes": {
|
"@doc-utils/color-themes": {
|
||||||
"version": "0.1.14",
|
"version": "0.1.15",
|
||||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.14/color-themes-0.1.14.tgz",
|
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fcolor-themes/-/0.1.15/color-themes-0.1.15.tgz",
|
||||||
"integrity": "sha512-j0U8v8Y+9zAm9D7pbCheTQYGEKt9FSpKSZQNGsogxWl95S9Z7QMjtmJns6QPgdOsSDss7sjMLFS5Gm50GCMzNA=="
|
"integrity": "sha512-P0oIlq4Z0cUOf7P4T2OUfhT/Cn/msl3oHTenVWHxKMDPlIYueR6AhFdRCHTI0A+DxyH+0EIi6CnMq3JYnILI0w=="
|
||||||
},
|
},
|
||||||
"@doc-utils/jsonschema2markdown": {
|
"@doc-utils/jsonschema2markdown": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
@ -2692,9 +2695,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@doc-utils/markdown2html": {
|
"@doc-utils/markdown2html": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.4",
|
||||||
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.2/markdown2html-0.3.2.tgz",
|
"resolved": "https://gitea.jbrumond.me/api/packages/doc-utils/npm/%40doc-utils%2Fmarkdown2html/-/0.3.4/markdown2html-0.3.4.tgz",
|
||||||
"integrity": "sha512-aRPlxA25gowmWmzPID4xyLCTVs7VgQJvlmYn3amnFAsUC55JzFXZQ9MR40AKeQYv9VSXIpxjUr7A86hgOzRFFw==",
|
"integrity": "sha512-zL3kay/zbrBJWGKIxGCyF+EXsafSxY1aN+miG+GOyml1neoOwd67vNIiE96KuEucf6rvuICPiYTjm3uJxkjd/g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bytefield-svg": "^1.6.1",
|
"bytefield-svg": "^1.6.1",
|
||||||
"dompurify": "^2.3.6",
|
"dompurify": "^2.3.6",
|
||||||
|
@ -22,9 +22,9 @@
|
|||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@doc-utils/color-themes": "^0.1.14",
|
"@doc-utils/color-themes": "^0.1.15",
|
||||||
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
"@doc-utils/jsonschema2markdown": "^0.1.1",
|
||||||
"@doc-utils/markdown2html": "^0.3.2",
|
"@doc-utils/markdown2html": "^0.3.4",
|
||||||
"glob": "^10.2.3",
|
"glob": "^10.2.3",
|
||||||
"ical": "^0.8.0",
|
"ical": "^0.8.0",
|
||||||
"ical-generator": "^4.1.0",
|
"ical-generator": "^4.1.0",
|
||||||
|
@ -6,6 +6,7 @@ import { write_text } from '../fs';
|
|||||||
import { AuthorConfig, app_version } from '../conf';
|
import { AuthorConfig, app_version } from '../conf';
|
||||||
import { map_output_file_to_url } from './helpers';
|
import { map_output_file_to_url } from './helpers';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { FileMetadata } from '../metadata';
|
||||||
|
|
||||||
export interface RSSEntry {
|
export interface RSSEntry {
|
||||||
url: string;
|
url: string;
|
||||||
@ -84,11 +85,21 @@ export async function write_rss_if_needed(state: BuildState) {
|
|||||||
// channel.ele('image').txt('');
|
// channel.ele('image').txt('');
|
||||||
// channel.ele('skipHours').txt('');
|
// channel.ele('skipHours').txt('');
|
||||||
// channel.ele('skipDays').txt('');
|
// channel.ele('skipDays').txt('');
|
||||||
|
|
||||||
|
let entries: (RSSEntry & { metadata: FileMetadata, first_seen: number })[] = [ ];
|
||||||
|
|
||||||
for (const entry of state.rss[index] || [ ]) {
|
for (const entry of state.rss[index] || [ ]) {
|
||||||
const item = channel.ele('item');
|
|
||||||
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
const in_file = entry.in_file.slice(state.conf.input.root.length);
|
||||||
const metadata = state.new_metadata.files[in_file];
|
const metadata = state.new_metadata.files[in_file];
|
||||||
|
entries.push({ ...entry, metadata, first_seen: DateTime.fromISO(metadata.first_seen_time).toUnixInteger() });
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = entries.sort((a, b) => {
|
||||||
|
return b.first_seen - a.first_seen;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const item = channel.ele('item');
|
||||||
|
|
||||||
item.ele('link').txt(entry.url);
|
item.ele('link').txt(entry.url);
|
||||||
|
|
||||||
@ -111,7 +122,7 @@ export async function write_rss_if_needed(state: BuildState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.ele('guid').txt(entry.url);
|
item.ele('guid').txt(entry.url);
|
||||||
item.ele('pubDate').txt(DateTime.fromISO(metadata.first_seen_time).toRFC2822());
|
item.ele('pubDate').txt(DateTime.fromISO(entry.metadata.first_seen_time).toRFC2822());
|
||||||
|
|
||||||
if (entry.html_content) {
|
if (entry.html_content) {
|
||||||
item.ele('content:encoded').ele({ $: entry.html_content });
|
item.ele('content:encoded').ele({ $: entry.html_content });
|
||||||
|
@ -116,6 +116,7 @@ export async function load_extras() {
|
|||||||
'forms-inputs/spacious.css',
|
'forms-inputs/spacious.css',
|
||||||
'forms-inputs/compact.css',
|
'forms-inputs/compact.css',
|
||||||
'forms-inputs/general.css',
|
'forms-inputs/general.css',
|
||||||
|
'figures.css',
|
||||||
];
|
];
|
||||||
|
|
||||||
const promises = extras_files.map((file) => load_from_dir(extras_dir, file));
|
const promises = extras_files.map((file) => load_from_dir(extras_dir, file));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user