mirror of
https://git.pumpin.online/funkpower/menologion.git
synced 2026-10-22 18:57:14 +00:00
horologion was the wrong book
The Horologion is the Book of Hours: the fixed portions of the services themselves. This shows the day's commemorations, its fast and its season, which is a Menologion. Naming it for a book whose contents it does not carry was a real error and worth the churn to correct. typikon already uses the word internally, for menologion.py and the data file it loads. That is not a collision: typikon provides a menologion, this displays one. Renamed throughout, not just the repo: the two scripts, the QML component and its three qmldir modules, MENOLOGION_COLORS, ~/.config/menologion/colors.json, the eww widgets and scss variables, the waybar class. Verified standalone and embedded in a foreign shell, and the smoke suite still passes. Also drops the em dashes from the prose.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# horologion
|
||||
# menologion
|
||||
|
||||
Desktop front ends for [`typikon`](https://git.pumpin.online/funkpower/typikon)
|
||||
— the Orthodox liturgical day on your desktop, in Hyprland (Quickshell),
|
||||
waybar or eww.
|
||||
Desktop front ends for [`typikon`](https://git.pumpin.online/funkpower/typikon).
|
||||
The Orthodox liturgical day on your desktop, in Hyprland (Quickshell), waybar
|
||||
or eww.
|
||||
|
||||
Everything is computed locally by `typikon`, so none of this touches the
|
||||
network, and the widget re-reads only when the date rolls over — the answer
|
||||
network, and the widget re-reads only when the date rolls over. The answer
|
||||
changes at midnight and at no other moment.
|
||||
|
||||

|
||||
@@ -22,8 +22,8 @@ $ typikon --set-default rocor # or antiochian, oca, greece, ...
|
||||
Then:
|
||||
|
||||
```console
|
||||
$ git clone https://git.pumpin.online/funkpower/horologion
|
||||
$ cd horologion && ./bin/horologion-install
|
||||
$ git clone https://git.pumpin.online/funkpower/menologion
|
||||
$ cd menologion && ./bin/menologion-install
|
||||
```
|
||||
|
||||
That links rather than copies, so `git pull` updates what you are running. Pass
|
||||
@@ -34,19 +34,19 @@ That links rather than copies, so `git pull` updates what you are running. Pass
|
||||
A desktop widget for Hyprland and anything else Quickshell runs on.
|
||||
|
||||
```console
|
||||
$ quickshell -p ~/.config/quickshell/horologion/shell.qml
|
||||
$ quickshell -p ~/.config/quickshell/menologion/shell.qml
|
||||
```
|
||||
|
||||
It sits on the background layer, so it behaves like part of the wallpaper —
|
||||
It sits on the background layer, so it behaves like part of the wallpaper:
|
||||
always under your windows, never stealing a click.
|
||||
|
||||
To drop it into a shell you already have, link this directory in beside your
|
||||
own and import it by path:
|
||||
|
||||
```qml
|
||||
import "horologion/modules"
|
||||
import "menologion/modules"
|
||||
|
||||
Horologion {}
|
||||
Menologion {}
|
||||
```
|
||||
|
||||
The imports inside are relative, so the widget always resolves its own
|
||||
@@ -57,16 +57,16 @@ Three pieces, each usable on its own:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| `services/Typikon.qml` | a singleton wrapping the CLI — bind to it from anything |
|
||||
| `services/Typikon.qml` | a singleton wrapping the CLI; bind to it from anything |
|
||||
| `services/Theme.qml` | colours, from JSON, with defaults for every key |
|
||||
| `modules/Horologion.qml` | the widget |
|
||||
| `modules/Menologion.qml` | the widget |
|
||||
|
||||
Set a jurisdiction per-widget if you want one different from your default:
|
||||
|
||||
```qml
|
||||
import "horologion/services" // for Typikon
|
||||
import "menologion/services" // for Typikon
|
||||
|
||||
Horologion {
|
||||
Menologion {
|
||||
Component.onCompleted: Typikon.jurisdiction = "moscow"
|
||||
}
|
||||
```
|
||||
@@ -76,25 +76,24 @@ you look at what Great Week will ask of you before it arrives.
|
||||
|
||||
### Colours
|
||||
|
||||
`Theme.qml` reads `~/.config/horologion/colors.json`, or whatever
|
||||
`HOROLOGION_COLORS` points at, and watches it for changes. Every key has a
|
||||
`Theme.qml` reads `~/.config/menologion/colors.json`, or whatever
|
||||
`MENOLOGION_COLORS` points at, and watches it for changes. Every key has a
|
||||
default, so the widget is legible with no file at all. See
|
||||
`quickshell/colors.example.json` — the flat shape most theme generators
|
||||
`quickshell/colors.example.json`, the flat shape most theme generators
|
||||
already emit.
|
||||
|
||||
## waybar
|
||||
|
||||
```jsonc
|
||||
"custom/typikon": {
|
||||
"exec": "horologion-waybar",
|
||||
"exec": "menologion-waybar",
|
||||
"return-type": "json",
|
||||
"interval": 1800,
|
||||
"on-click": "typikon --days 7"
|
||||
}
|
||||
```
|
||||
|
||||
The module's CSS class is the fast level — `.xerophagy`, `.wine_oil`, `.fish`
|
||||
— so a day's discipline reads off the colour without reading the text.
|
||||
The module's CSS class is the fast level (`.xerophagy`, `.wine_oil`, `.fish`), so a day's discipline reads off the colour without reading the text.
|
||||
`.obligatory` is added only where a particular law actually binds, which under
|
||||
the Typikon is never: the Orthodox Church keeps the fast as tradition, not as
|
||||
precept. `waybar/style.css` has a starting palette.
|
||||
@@ -102,18 +101,18 @@ precept. `waybar/style.css` has a starting palette.
|
||||
Change what the text says with `--format`:
|
||||
|
||||
```console
|
||||
$ horologion-waybar --format '{emoji} {fastLabel}'
|
||||
$ horologion-waybar -j moscow --format '{emoji} {churchDate}'
|
||||
$ menologion-waybar --format '{emoji} {fastLabel}'
|
||||
$ menologion-waybar -j moscow --format '{emoji} {churchDate}'
|
||||
```
|
||||
|
||||
## eww
|
||||
|
||||
```lisp
|
||||
(include "horologion/horologion.yuck")
|
||||
(include "menologion/menologion.yuck")
|
||||
```
|
||||
|
||||
Then `(horologion)` for the full panel or `(horologion-bar)` for one line.
|
||||
`horologion.scss` carries the styling; its colours are `!default` variables, so
|
||||
Then `(menologion)` for the full panel or `(menologion-bar)` for one line.
|
||||
`menologion.scss` carries the styling; its colours are `!default` variables, so
|
||||
set your own before the `@import`.
|
||||
|
||||
## What it shows
|
||||
@@ -123,7 +122,7 @@ the week ahead, and Pascha.
|
||||
|
||||
**The church date is always in the corner.** On the new calendar it echoes the
|
||||
wall calendar; on the old it is the answer you came for. Below, ROCOR on 7
|
||||
January — which is 25 December, and the Nativity.
|
||||
January, which is 25 December, and the Nativity.
|
||||
|
||||

|
||||
|
||||
@@ -133,12 +132,12 @@ days at a time, and a widget that leads with "Post-festive Day of the
|
||||
Exaltation" for a week has told you nothing.
|
||||
|
||||
**The legend is built from the week in view**, not hardcoded, because the
|
||||
levels a tradition uses are its own — six under the Typikon, three under
|
||||
Ruthenian particular law — and the words for them differ too. Great Week uses
|
||||
levels a tradition uses are its own: six under the Typikon, three under
|
||||
Ruthenian particular law, and the words for them differ too. Great Week uses
|
||||
nearly all of them:
|
||||
|
||||

|
||||
|
||||
## Licence
|
||||
|
||||
GPL-2.0-only — see [LICENSE](LICENSE).
|
||||
GPL-2.0-only. See [LICENSE](LICENSE).
|
||||
|
||||
@@ -9,12 +9,12 @@ binhome="${HOME}/.local/bin"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
horologion-install [quickshell|waybar|eww|bin|all]
|
||||
menologion-install [quickshell|waybar|eww|bin|all]
|
||||
|
||||
quickshell link quickshell/ into a quickshell config as horologion/
|
||||
quickshell link quickshell/ into a quickshell config as menologion/
|
||||
waybar link the bar script and print the module to merge
|
||||
eww link eww/ into your eww config
|
||||
bin link horologion-waybar into ~/.local/bin
|
||||
bin link menologion-waybar into ~/.local/bin
|
||||
all bin + whichever of the three are already configured
|
||||
|
||||
USAGE
|
||||
@@ -37,7 +37,7 @@ link() {
|
||||
}
|
||||
|
||||
do_bin() {
|
||||
link "$src/bin/horologion-waybar" "$binhome/horologion-waybar"
|
||||
link "$src/bin/menologion-waybar" "$binhome/menologion-waybar"
|
||||
case ":$PATH:" in
|
||||
*":$binhome:"*) ;;
|
||||
*) echo " note: $binhome is not on your PATH" >&2 ;;
|
||||
@@ -45,43 +45,43 @@ do_bin() {
|
||||
}
|
||||
|
||||
do_quickshell() {
|
||||
link "$src/quickshell" "$config/quickshell/horologion"
|
||||
link "$src/quickshell" "$config/quickshell/menologion"
|
||||
cat <<'NOTE'
|
||||
|
||||
Add to your shell.qml:
|
||||
|
||||
import qs.horologion.modules
|
||||
import qs.menologion.modules
|
||||
...
|
||||
Horologion {}
|
||||
Menologion {}
|
||||
|
||||
Or run it on its own:
|
||||
|
||||
quickshell -p ~/.config/quickshell/horologion/shell.qml
|
||||
quickshell -p ~/.config/quickshell/menologion/shell.qml
|
||||
NOTE
|
||||
}
|
||||
|
||||
do_waybar() {
|
||||
link "$src/waybar/style.css" "$config/waybar/horologion.css"
|
||||
link "$src/waybar/style.css" "$config/waybar/menologion.css"
|
||||
cat <<'NOTE'
|
||||
|
||||
Add "custom/typikon" to a modules list, merge waybar/config.jsonc into
|
||||
your config, and @import "horologion.css" from your style.css.
|
||||
your config, and @import "menologion.css" from your style.css.
|
||||
NOTE
|
||||
}
|
||||
|
||||
do_eww() {
|
||||
link "$src/eww" "$config/eww/horologion"
|
||||
link "$src/eww" "$config/eww/menologion"
|
||||
cat <<'NOTE'
|
||||
|
||||
Add to your eww.yuck: (include "horologion/horologion.yuck")
|
||||
Add to your eww.scss: @import "horologion/horologion.scss";
|
||||
Then put (horologion) in a window, or (horologion-bar) in a bar.
|
||||
Add to your eww.yuck: (include "menologion/menologion.yuck")
|
||||
Add to your eww.scss: @import "menologion/menologion.scss";
|
||||
Then put (menologion) in a window, or (menologion-bar) in a bar.
|
||||
NOTE
|
||||
}
|
||||
|
||||
require_typikon() {
|
||||
command -v typikon >/dev/null 2>&1 && return 0
|
||||
echo "horologion: typikon is not on PATH." >&2
|
||||
echo "menologion: typikon is not on PATH." >&2
|
||||
echo " pipx install git+https://git.pumpin.online/funkpower/typikon" >&2
|
||||
exit 1
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"""waybar custom module — the liturgical day in the bar.
|
||||
|
||||
"custom/typikon": {
|
||||
"exec": "horologion-waybar",
|
||||
"exec": "menologion-waybar",
|
||||
"return-type": "json",
|
||||
"interval": 1800,
|
||||
"on-click": "typikon --days 7"
|
||||
@@ -1,62 +0,0 @@
|
||||
// horologion — colours are variables so you can point them at your own theme.
|
||||
// Defaults are a neutral dark set; override them before the @import.
|
||||
|
||||
$horologion-fg: #ebdbb2 !default;
|
||||
$horologion-fg-dim: #a89984 !default;
|
||||
$horologion-accent: #d3869b !default;
|
||||
$horologion-red: #fb4934 !default;
|
||||
$horologion-yellow: #fabd2f !default;
|
||||
$horologion-surface: #282828 !default;
|
||||
$horologion-overlay: #504945 !default;
|
||||
|
||||
.horologion {
|
||||
background-color: rgba(40, 40, 40, 0.82);
|
||||
border: 1px solid $horologion-overlay;
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
color: $horologion-fg;
|
||||
min-width: 400px;
|
||||
|
||||
.glyph-big { font-size: 28px; }
|
||||
.glyph { font-size: 15px; }
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
&.feast { color: $horologion-accent; }
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 10px;
|
||||
color: $horologion-yellow;
|
||||
&.obligatory { color: $horologion-red; font-weight: bold; }
|
||||
}
|
||||
|
||||
.cycle {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
color: rgba(168, 153, 132, 0.8);
|
||||
}
|
||||
|
||||
.fast { font-size: 11px; color: $horologion-fg-dim; }
|
||||
|
||||
.rule {
|
||||
background-color: rgba(80, 73, 69, 0.6);
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.weekday { font-size: 11px; color: $horologion-yellow; }
|
||||
|
||||
.day {
|
||||
font-size: 10px;
|
||||
color: $horologion-fg-dim;
|
||||
&.feast { color: $horologion-accent; }
|
||||
}
|
||||
|
||||
.pascha { font-size: 10px; color: $horologion-fg-dim; }
|
||||
}
|
||||
|
||||
.horologion-bar {
|
||||
.glyph { font-size: 14px; }
|
||||
.title { font-size: 12px; }
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// menologion — colours are variables so you can point them at your own theme.
|
||||
// Defaults are a neutral dark set; override them before the @import.
|
||||
|
||||
$menologion-fg: #ebdbb2 !default;
|
||||
$menologion-fg-dim: #a89984 !default;
|
||||
$menologion-accent: #d3869b !default;
|
||||
$menologion-red: #fb4934 !default;
|
||||
$menologion-yellow: #fabd2f !default;
|
||||
$menologion-surface: #282828 !default;
|
||||
$menologion-overlay: #504945 !default;
|
||||
|
||||
.menologion {
|
||||
background-color: rgba(40, 40, 40, 0.82);
|
||||
border: 1px solid $menologion-overlay;
|
||||
border-radius: 14px;
|
||||
padding: 14px;
|
||||
color: $menologion-fg;
|
||||
min-width: 400px;
|
||||
|
||||
.glyph-big { font-size: 28px; }
|
||||
.glyph { font-size: 15px; }
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
&.feast { color: $menologion-accent; }
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 10px;
|
||||
color: $menologion-yellow;
|
||||
&.obligatory { color: $menologion-red; font-weight: bold; }
|
||||
}
|
||||
|
||||
.cycle {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
color: rgba(168, 153, 132, 0.8);
|
||||
}
|
||||
|
||||
.fast { font-size: 11px; color: $menologion-fg-dim; }
|
||||
|
||||
.rule {
|
||||
background-color: rgba(80, 73, 69, 0.6);
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.weekday { font-size: 11px; color: $menologion-yellow; }
|
||||
|
||||
.day {
|
||||
font-size: 10px;
|
||||
color: $menologion-fg-dim;
|
||||
&.feast { color: $menologion-accent; }
|
||||
}
|
||||
|
||||
.pascha { font-size: 10px; color: $menologion-fg-dim; }
|
||||
}
|
||||
|
||||
.menologion-bar {
|
||||
.glyph { font-size: 14px; }
|
||||
.title { font-size: 12px; }
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
;; horologion — the liturgical day for eww
|
||||
;; menologion — the liturgical day for eww
|
||||
;;
|
||||
;; Include this from your own eww.yuck:
|
||||
;;
|
||||
;; (include "horologion/horologion.yuck")
|
||||
;; (include "menologion/menologion.yuck")
|
||||
;;
|
||||
;; and put (horologion) in a window, or (horologion-bar) in a bar. Set a
|
||||
;; and put (menologion) in a window, or (menologion-bar) in a bar. Set a
|
||||
;; jurisdiction by adding `--jurisdiction <name>` to both defpoll commands
|
||||
;; below; leaving them as they are follows whatever `typikon --set-default`
|
||||
;; wrote.
|
||||
@@ -22,8 +22,8 @@
|
||||
`typikon --json --days 6 --date $(date -d tomorrow +%F)`)
|
||||
|
||||
;; ── the one-line version, for a bar
|
||||
(defwidget horologion-bar []
|
||||
(box :class "horologion-bar"
|
||||
(defwidget menologion-bar []
|
||||
(box :class "menologion-bar"
|
||||
:space-evenly false
|
||||
:spacing 6
|
||||
(label :class "glyph" :text {typikon-day.emoji ?: ""})
|
||||
@@ -32,8 +32,8 @@
|
||||
:text {typikon-day.title ?: "typikon?"})))
|
||||
|
||||
;; ── the full panel
|
||||
(defwidget horologion []
|
||||
(box :class "horologion"
|
||||
(defwidget menologion []
|
||||
(box :class "menologion"
|
||||
:orientation "v"
|
||||
:space-evenly false
|
||||
:spacing 8
|
||||
@@ -1,3 +1,3 @@
|
||||
module horologion.modules
|
||||
module menologion.modules
|
||||
Panel 1.0 Panel.qml
|
||||
Horologion 1.0 Horologion.qml
|
||||
Menologion 1.0 Menologion.qml
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
module horologion
|
||||
module menologion
|
||||
singleton Config 1.0 Config.qml
|
||||
|
||||
@@ -8,16 +8,16 @@ import Quickshell.Io
|
||||
// rest of your desktop.
|
||||
//
|
||||
// Every key has a default, so the widget is legible with no file at all. Point
|
||||
// HOROLOGION_COLORS at your own, or drop one at the path below. The shape is
|
||||
// MENOLOGION_COLORS at your own, or drop one at the path below. The shape is
|
||||
// the flat sixteen-colour kind most theme generators already emit.
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string path: {
|
||||
const env = Quickshell.env("HOROLOGION_COLORS");
|
||||
const env = Quickshell.env("MENOLOGION_COLORS");
|
||||
return env && env !== ""
|
||||
? env
|
||||
: Quickshell.env("HOME") + "/.config/horologion/colors.json";
|
||||
: Quickshell.env("HOME") + "/.config/menologion/colors.json";
|
||||
}
|
||||
|
||||
property var c: ({})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module horologion.services
|
||||
module menologion.services
|
||||
singleton Typikon 1.0 Typikon.qml
|
||||
singleton Theme 1.0 Theme.qml
|
||||
|
||||
@@ -11,7 +11,7 @@ import "services"
|
||||
// A runnable widget on its own — `quickshell -p quickshell/shell.qml`.
|
||||
//
|
||||
// It sits on the background layer, so it behaves like part of the wallpaper:
|
||||
// always under your windows, never stealing a click. Drop the Horologion
|
||||
// always under your windows, never stealing a click. Drop the Menologion
|
||||
// component into an existing shell instead if you already have one.
|
||||
ShellRoot {
|
||||
Variants {
|
||||
@@ -22,7 +22,7 @@ ShellRoot {
|
||||
screen: modelData
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Background
|
||||
WlrLayershell.namespace: "horologion"
|
||||
WlrLayershell.namespace: "menologion"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
color: "transparent"
|
||||
|
||||
@@ -36,7 +36,7 @@ ShellRoot {
|
||||
id: content
|
||||
anchors.fill: parent
|
||||
|
||||
Horologion {
|
||||
Menologion {
|
||||
// Leave empty to follow `typikon --set-default`, or name a
|
||||
// jurisdiction here: "rocor", "antiochian", "oca", ...
|
||||
Component.onCompleted: Typikon.jurisdiction = ""
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ ShellRoot {
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.namespace: "horologion-shot"
|
||||
WlrLayershell.namespace: "menologion-shot"
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
color: "transparent"
|
||||
|
||||
@@ -49,7 +49,7 @@ ShellRoot {
|
||||
anchors.topMargin: 34
|
||||
width: Config.width
|
||||
|
||||
Horologion {
|
||||
Menologion {
|
||||
Component.onCompleted: {
|
||||
const j = Quickshell.env("SHOT_JURISDICTION");
|
||||
const d = Quickshell.env("SHOT_DATE");
|
||||
|
||||
+4
-4
@@ -9,8 +9,8 @@
|
||||
set -uo pipefail
|
||||
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
waybar="$root/bin/horologion-waybar"
|
||||
install="$root/bin/horologion-install"
|
||||
waybar="$root/bin/menologion-waybar"
|
||||
install="$root/bin/menologion-install"
|
||||
pass=0 fail=0
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
@@ -63,9 +63,9 @@ check "--help without typikon" 0 env PATH=/usr/bin:/bin "$install" --
|
||||
check "an unknown subcommand" 2 "$install" nonsense
|
||||
mkdir -p "$tmp/a/.config/quickshell" "$tmp/a/.config/waybar"
|
||||
check "a clean run with eww absent" 0 env HOME="$tmp/a" XDG_CONFIG_HOME="$tmp/a/.config" "$install" all
|
||||
mkdir -p "$tmp/b/.local/bin" "$tmp/b/.config"; : > "$tmp/b/.local/bin/horologion-waybar"
|
||||
mkdir -p "$tmp/b/.local/bin" "$tmp/b/.config"; : > "$tmp/b/.local/bin/menologion-waybar"
|
||||
check "a real file at the target" 1 env HOME="$tmp/b" XDG_CONFIG_HOME="$tmp/b/.config" "$install" all
|
||||
[ -L "$tmp/a/.local/bin/horologion-waybar" ] \
|
||||
[ -L "$tmp/a/.local/bin/menologion-waybar" ] \
|
||||
&& ok "the clean run actually linked" || bad "the clean run actually linked"
|
||||
|
||||
echo
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
// "custom/typikon", and merge the block below.
|
||||
{
|
||||
"custom/typikon": {
|
||||
"exec": "horologion-waybar",
|
||||
// Add -j to pin a jurisdiction, e.g. "horologion-waybar -j rocor".
|
||||
"exec": "menologion-waybar",
|
||||
// Add -j to pin a jurisdiction, e.g. "menologion-waybar -j rocor".
|
||||
// Without it, whatever `typikon --set-default` wrote is used.
|
||||
"return-type": "json",
|
||||
// The answer changes at midnight and at no other moment, so this is only
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* horologion — waybar styling for the custom/typikon module.
|
||||
/* menologion — waybar styling for the custom/typikon module.
|
||||
*
|
||||
* The module's class is the fast level, so a day's discipline can be read off
|
||||
* the colour without reading the text. `.obligatory` is added only where a
|
||||
|
||||
Reference in New Issue
Block a user