Compare commits
No commits in common. "319b886803e05d3c915d83254226108379311a15" and "d144d1f9897b4c4532897e2a61825bfea0b02d21" have entirely different histories.
319b886803
...
d144d1f989
@ -11,5 +11,3 @@ node_modules
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
/result
|
|
||||||
bun.lockb
|
|
||||||
|
@ -13,5 +13,3 @@ node_modules
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
/result
|
|
||||||
bun.lockb
|
|
||||||
|
@ -14,7 +14,7 @@ A production build is also available at `packages.x86_64-linux.musikspider`.
|
|||||||
# TODOs
|
# TODOs
|
||||||
|
|
||||||
- [x] basic music playing and controls
|
- [x] basic music playing and controls
|
||||||
- [ ] implement playlists (local and remote)
|
- [ ] implement playlists (local)
|
||||||
- [ ] implement album and artist viewing
|
- [ ] implement album and artist viewing
|
||||||
- [ ] implement persistence of music data, playlists and such
|
- [ ] implement persistence of music data, playlists and such
|
||||||
- [ ] implement scrobbling (last.fm, etc)
|
- [ ] implement scrobbling (last.fm, etc)
|
||||||
|
188
flake.lock
188
flake.lock
@ -1,96 +1,96 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"naked-shell": {
|
"naked-shell": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681286841,
|
"lastModified": 1681286841,
|
||||||
"narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
|
"narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
|
||||||
"owner": "yusdacra",
|
"owner": "yusdacra",
|
||||||
"repo": "mk-naked-shell",
|
"repo": "mk-naked-shell",
|
||||||
"rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
|
"rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "yusdacra",
|
"owner": "yusdacra",
|
||||||
"repo": "mk-naked-shell",
|
"repo": "mk-naked-shell",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697059129,
|
"lastModified": 1681126633,
|
||||||
"narHash": "sha256-9NJcFF9CEYPvHJ5ckE8kvINvI84SZZ87PvqMbH6pro0=",
|
"narHash": "sha256-evQ3Ct/yJDSHej16Hiq+JfxRjgm9FXu/2LBxsyorGdE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5e4c2ada4fcd54b99d56d7bd62f384511a7e2593",
|
"rev": "db24d86dd8a4769c50d6b7295e81aa280cd93f35",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs-lib": {
|
"nixpkgs-lib": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"dir": "lib",
|
"dir": "lib",
|
||||||
"lastModified": 1696019113,
|
"lastModified": 1680213900,
|
||||||
"narHash": "sha256-X3+DKYWJm93DRSdC5M6K5hLqzSya9BjibtBsuARoPco=",
|
"narHash": "sha256-cIDr5WZIj3EkKyCgj/6j3HBH4Jj1W296z7HTcWj1aMA=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "f5892ddac112a1e9b3612c39af1b72987ee5783a",
|
"rev": "e3652e0735fbec227f342712f180f4f21f0594f2",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"dir": "lib",
|
"dir": "lib",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"parts": {
|
"parts": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": "nixpkgs-lib"
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696343447,
|
"lastModified": 1680392223,
|
||||||
"narHash": "sha256-B2xAZKLkkeRFG5XcHHSXXcP7To9Xzr59KXeZiRf4vdQ=",
|
"narHash": "sha256-n3g7QFr85lDODKt250rkZj2IFS3i4/8HBU2yKHO3tqw=",
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"rev": "c9afaba3dfa4085dbd2ccb38dfade5141e33d9d4",
|
"rev": "dcc36e45d054d7bb554c9cdab69093debd91a0b5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "hercules-ci",
|
||||||
"repo": "flake-parts",
|
"repo": "flake-parts",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"naked-shell": "naked-shell",
|
"naked-shell": "naked-shell",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"parts": "parts",
|
"parts": "parts",
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1680978846,
|
"lastModified": 1680978846,
|
||||||
"narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
|
"narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "x86_64-linux",
|
"repo": "x86_64-linux",
|
||||||
"rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
|
"rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "x86_64-linux",
|
"repo": "x86_64-linux",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
"version": 7
|
"version": 7
|
||||||
}
|
}
|
||||||
|
19
flake.nix
19
flake.nix
@ -20,19 +20,16 @@
|
|||||||
}: {
|
}: {
|
||||||
devShells.default = config.mk-naked-shell.lib.mkNakedShell {
|
devShells.default = config.mk-naked-shell.lib.mkNakedShell {
|
||||||
name = "musikspider-devshell";
|
name = "musikspider-devshell";
|
||||||
packages = with pkgs; [nodejs_20 bun];
|
packages = with pkgs; [nodejs yarn deno];
|
||||||
shellHook = ''
|
|
||||||
export PATH="$PATH:$PWD/node_modules/.bin"
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
# packages.musikspider = pkgs.mkYarnPackage {
|
packages.musikspider = pkgs.mkYarnPackage {
|
||||||
# src = ./.;
|
src = ./.;
|
||||||
|
|
||||||
# buildPhase = "HOME=$TMPDIR yarn --offline build";
|
buildPhase = "HOME=$TMPDIR yarn --offline build";
|
||||||
# distPhase = "true";
|
distPhase = "true";
|
||||||
# installPhase = "mv deps/musikspider/build $out";
|
installPhase = "mv deps/musikspider/build $out";
|
||||||
# };
|
};
|
||||||
# packages.default = config.packages.musikspider;
|
packages.default = config.packages.musikspider;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
44
package.json
44
package.json
@ -12,32 +12,30 @@
|
|||||||
"format": "prettier --plugin-search-dir . --write ."
|
"format": "prettier --plugin-search-dir . --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/line-md": "^1.1.32",
|
"@iconify-json/line-md": "^1.1.24",
|
||||||
"@iconify-json/mdi": "^1.1.54",
|
"@iconify-json/mdi": "^1.1.50",
|
||||||
"@skeletonlabs/skeleton": "^2.3.0",
|
"@skeletonlabs/skeleton": "^1.2.5",
|
||||||
"@skeletonlabs/tw-plugin": "^0.2.2",
|
"@sveltejs/kit": "^1.15.9",
|
||||||
"@sveltejs/kit": "^1.25.2",
|
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||||
"@types/node": "^20.8.5",
|
"@typescript-eslint/parser": "^5.59.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
"autoprefixer": "^10.4.14",
|
||||||
"@typescript-eslint/parser": "^6.7.5",
|
"eslint": "^8.39.0",
|
||||||
"autoprefixer": "^10.4.16",
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"eslint": "^8.51.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-svelte3": "^4.0.0",
|
"eslint-plugin-svelte3": "^4.0.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.23",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^2.8.8",
|
||||||
"prettier-plugin-svelte": "^3.0.3",
|
"prettier-plugin-svelte": "^2.10.0",
|
||||||
"svelte": "^4.2.1",
|
"svelte": "^3.58.0",
|
||||||
"svelte-adapter-bun": "^0.5.0",
|
"svelte-adapter-deno": "^0.9.0",
|
||||||
"svelte-check": "^3.5.2",
|
"svelte-check": "^3.2.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.2",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.5.0",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.0.4",
|
||||||
"unplugin-icons": "^0.17.0",
|
"unplugin-icons": "^0.16.1",
|
||||||
"vite": "^4.4.11"
|
"vite": "^4.3.3"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"svelte-tiny-virtual-list": "^2.0.5"
|
"svelte-tiny-virtual-list": "^2.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +0,0 @@
|
|||||||
const tailwindcss = require('tailwindcss');
|
|
||||||
const autoprefixer = require('autoprefixer');
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
plugins: [
|
|
||||||
//Some plugins, like tailwindcss/nesting, need to run before Tailwind,
|
|
||||||
tailwindcss(),
|
|
||||||
//But others, like autoprefixer, need to run after,
|
|
||||||
autoprefixer
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = config;
|
|
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {}
|
||||||
|
}
|
||||||
|
};
|
2
src/app.d.ts
vendored
2
src/app.d.ts
vendored
@ -11,4 +11,4 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export { };
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html class="dark" lang="en">
|
<html class="dark" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@apply h-full overflow-hidden;
|
@apply h-full overflow-hidden;
|
||||||
|
@ -9,11 +9,9 @@
|
|||||||
import Spinnny from '~icons/line-md/loading-loop';
|
import Spinnny from '~icons/line-md/loading-loop';
|
||||||
import IconPlay from '~icons/mdi/play';
|
import IconPlay from '~icons/mdi/play';
|
||||||
import IconMusic from '~icons/mdi/music';
|
import IconMusic from '~icons/mdi/music';
|
||||||
import { getToastStore } from '@skeletonlabs/skeleton';
|
import { toastStore } from '@skeletonlabs/skeleton';
|
||||||
import { getAudioElement, makeShareUrl } from '../utils';
|
import { getAudioElement, makeShareUrl } from '../utils';
|
||||||
|
|
||||||
const toastStore = getToastStore();
|
|
||||||
|
|
||||||
export let track_with_id: TrackWithId;
|
export let track_with_id: TrackWithId;
|
||||||
let track = track_with_id.track;
|
let track = track_with_id.track;
|
||||||
let track_id = track_with_id.id;
|
let track_id = track_with_id.id;
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Your selected Skeleton theme:
|
||||||
|
import '@skeletonlabs/skeleton/themes/theme-crimson.css';
|
||||||
|
// This contains the bulk of Skeletons required styles:
|
||||||
|
import '@skeletonlabs/skeleton/styles/all.css';
|
||||||
import '../../app.postcss';
|
import '../../app.postcss';
|
||||||
|
|
||||||
import { AppShell, Toast, getToastStore, initializeStores } from '@skeletonlabs/skeleton';
|
import { AppShell, Toast, toastStore } from '@skeletonlabs/skeleton';
|
||||||
import type { ToastSettings, ToastStore } from '@skeletonlabs/skeleton';
|
|
||||||
import {
|
import {
|
||||||
address,
|
address,
|
||||||
changeLoop,
|
changeLoop,
|
||||||
@ -21,9 +24,6 @@
|
|||||||
import LoopButton from '../../components/loopButton.svelte';
|
import LoopButton from '../../components/loopButton.svelte';
|
||||||
import { getAudioElement, interceptKeys } from '../../utils';
|
import { getAudioElement, interceptKeys } from '../../utils';
|
||||||
|
|
||||||
initializeStores();
|
|
||||||
const toastStore = getToastStore();
|
|
||||||
|
|
||||||
$: title = $currentTrack !== null ? `${$currentTrack.track.title} - musikspider` : `musikspider`;
|
$: title = $currentTrack !== null ? `${$currentTrack.track.title} - musikspider` : `musikspider`;
|
||||||
|
|
||||||
comm.setCallbacks({
|
comm.setCallbacks({
|
||||||
@ -94,7 +94,6 @@
|
|||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<Toast position="tr" />
|
|
||||||
<AppShell>
|
<AppShell>
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<div class="flex w-screen place-content-end max-sm:place-content-center">
|
<div class="flex w-screen place-content-end max-sm:place-content-center">
|
||||||
@ -110,3 +109,4 @@
|
|||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<slot />
|
<slot />
|
||||||
</AppShell>
|
</AppShell>
|
||||||
|
<Toast position="tr" />
|
||||||
|
@ -3,29 +3,29 @@ import { LOCAL_MUSIKQUAD_SERVER } from '$env/static/private';
|
|||||||
import { scheme } from '../../../utils';
|
import { scheme } from '../../../utils';
|
||||||
|
|
||||||
interface MusicInfo {
|
interface MusicInfo {
|
||||||
title: string;
|
title: string;
|
||||||
album: string;
|
album: string;
|
||||||
artist: string;
|
artist: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function load({ params }) {
|
export async function load({ params }) {
|
||||||
const token = params.token;
|
const token = params.token;
|
||||||
let color = '#222222';
|
let color = '#222222';
|
||||||
|
|
||||||
const resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/info/${token}`);
|
const resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/info/${token}`);
|
||||||
const info: MusicInfo = await resp.json();
|
const info: MusicInfo = await resp.json();
|
||||||
|
|
||||||
/*const thumb_resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/thumbnail/${token}`);
|
/*const thumb_resp = await fetch(`${LOCAL_MUSIKQUAD_SERVER}/share/thumbnail/${token}`);
|
||||||
if (thumb_resp.ok) {
|
if (thumb_resp.ok) {
|
||||||
const thumb = await thumb_resp.blob();
|
const thumb = await thumb_resp.blob();
|
||||||
const rawColor = await getColor(thumb);
|
const rawColor = await getColor(thumb);
|
||||||
color = rgbToHex(rawColor[0], rawColor[1], rawColor[2]);
|
color = rgbToHex(rawColor[0], rawColor[1], rawColor[2]);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
info,
|
info,
|
||||||
color,
|
color,
|
||||||
thumbnail_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/thumbnail/${token}`,
|
thumbnail_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/thumbnail/${token}`,
|
||||||
audio_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/audio/${token}`
|
audio_url: `${scheme}://${PUBLIC_MUSIKQUAD_SERVER}/share/audio/${token}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// Your selected Skeleton theme:
|
||||||
|
import '@skeletonlabs/skeleton/themes/theme-crimson.css';
|
||||||
|
// This contains the bulk of Skeletons required styles:
|
||||||
|
import '@skeletonlabs/skeleton/styles/all.css';
|
||||||
import '../../../app.postcss';
|
import '../../../app.postcss';
|
||||||
|
|
||||||
import IconMusic from '~icons/mdi/music';
|
import IconMusic from '~icons/mdi/music';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import adapter from 'svelte-adapter-bun';
|
import adapter from 'svelte-adapter-deno';
|
||||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
preprocess: [vitePreprocess({})],
|
preprocess: vitePreprocess(),
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: adapter()
|
adapter: adapter()
|
||||||
|
12
tailwind.config.js
Normal file
12
tailwind.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
'./src/**/*.{html,js,svelte,ts}',
|
||||||
|
require('path').join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: [...require('@skeletonlabs/skeleton/tailwind/skeleton.cjs')()],
|
||||||
|
darkMode: 'class'
|
||||||
|
};
|
@ -1,22 +0,0 @@
|
|||||||
import { join } from 'path';
|
|
||||||
import type { Config } from 'tailwindcss';
|
|
||||||
|
|
||||||
import { skeleton } from '@skeletonlabs/tw-plugin';
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
darkMode: 'class',
|
|
||||||
content: [
|
|
||||||
'./src/**/*.{html,js,svelte,ts}',
|
|
||||||
join(require.resolve('@skeletonlabs/skeleton'), '../**/*.{html,js,svelte,ts}')
|
|
||||||
],
|
|
||||||
theme: {
|
|
||||||
extend: {}
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
skeleton({
|
|
||||||
themes: { preset: ['crimson'] }
|
|
||||||
})
|
|
||||||
]
|
|
||||||
} satisfies Config;
|
|
||||||
|
|
||||||
export default config;
|
|
Loading…
Reference in New Issue
Block a user