Added tag filtering and dropdown
This commit is contained in:
@@ -18,8 +18,8 @@ import nodeAdapter from '@astrojs/node'
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
adapter: nodeAdapter({
|
adapter: nodeAdapter({
|
||||||
mode: 'standalone',
|
mode: 'standalone'
|
||||||
}),// Set adapter for deployment
|
}), // Set adapter for deployment
|
||||||
site: themeConfig.site.website,
|
site: themeConfig.site.website,
|
||||||
image: {
|
image: {
|
||||||
service: {
|
service: {
|
||||||
|
|||||||
@@ -4,13 +4,128 @@ import type { PostListProps } from '@/types'
|
|||||||
import { themeConfig } from '@/config'
|
import { themeConfig } from '@/config'
|
||||||
|
|
||||||
const { posts } = Astro.props as PostListProps
|
const { posts } = Astro.props as PostListProps
|
||||||
|
|
||||||
|
// Read predefined tags and fallback from config
|
||||||
|
const predefinedTags: string[] = themeConfig.tags?.predefined ?? ['General']
|
||||||
|
const fallbackTag: string = themeConfig.tags?.default ?? themeConfig.tags?.fallback ?? 'General'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize and pick a canonical tag for a post.
|
||||||
|
* - Supports `tags`, `tag`, `topic` frontmatter shapes.
|
||||||
|
* - Accepts arrays, CSV strings, and nested/plugin-provided locations.
|
||||||
|
* - Matches case-insensitively against predefined tags; falls back to `fallbackTag`.
|
||||||
|
*/
|
||||||
|
function canonicalTag(post: any): string {
|
||||||
|
const fm = post?.data ?? {}
|
||||||
|
const candidates: string[] = []
|
||||||
|
|
||||||
|
const addCandidate = (v: unknown) => {
|
||||||
|
if (v == null) return
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
for (const x of v) addCandidate(x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
for (const part of v
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean)) {
|
||||||
|
candidates.push(part)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
try {
|
||||||
|
for (const val of Object.values(v as Record<string, unknown>)) addCandidate(val)
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// fallback: stringify anything else
|
||||||
|
candidates.push(String(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common frontmatter keys
|
||||||
|
addCandidate(fm.tags)
|
||||||
|
addCandidate(fm.tag)
|
||||||
|
addCandidate(fm.topic)
|
||||||
|
|
||||||
|
// Some plugins may attach frontmatter into nested fields
|
||||||
|
addCandidate(fm.remarkPluginFrontmatter?.tags ?? fm.remarkPluginFrontmatter?.tag)
|
||||||
|
addCandidate(fm.frontmatter?.tags ?? fm.frontmatter?.tag)
|
||||||
|
|
||||||
|
// Also accept top-level collection shapes such as post.tags
|
||||||
|
addCandidate(post?.tags)
|
||||||
|
addCandidate(post?.tag)
|
||||||
|
addCandidate(post?.topic)
|
||||||
|
|
||||||
|
const normalized = candidates.map((s) => String(s).trim()).filter(Boolean)
|
||||||
|
if (normalized.length === 0) return fallbackTag
|
||||||
|
|
||||||
|
// Exact case-insensitive match
|
||||||
|
for (const c of normalized) {
|
||||||
|
const match = predefinedTags.find((p) => p.toLowerCase() === c.toLowerCase())
|
||||||
|
if (match) return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loose substring match (helpful for hyphenated or compound tags)
|
||||||
|
for (const c of normalized) {
|
||||||
|
const match = predefinedTags.find(
|
||||||
|
(p) => c.toLowerCase().includes(p.toLowerCase()) || p.toLowerCase().includes(c.toLowerCase())
|
||||||
|
)
|
||||||
|
if (match) return match
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing matches, return fallback
|
||||||
|
return fallbackTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build posts with canonical tags and compute counts
|
||||||
|
const postsWithTag = posts.map((post) => ({ ...post, canonicalTag: canonicalTag(post) }))
|
||||||
|
|
||||||
|
const counts: Record<string, number> = {}
|
||||||
|
for (const t of predefinedTags) counts[t] = 0
|
||||||
|
// Ensure fallback counted even if not in predefined tags
|
||||||
|
if (!(fallbackTag in counts)) counts[fallbackTag] = 0
|
||||||
|
|
||||||
|
for (const p of postsWithTag) {
|
||||||
|
counts[p.canonicalTag] = (counts[p.canonicalTag] || 0) + 1
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<ul>
|
<div class="post-list-widget">
|
||||||
|
<div
|
||||||
|
class="post-list-controls"
|
||||||
|
style="display:flex;gap:0.5rem;align-items:center;margin-bottom:0.5rem;"
|
||||||
|
>
|
||||||
|
<label for="post-tag-filter" class="sr-only">Filter posts by tag</label>
|
||||||
|
<select
|
||||||
|
id="post-tag-filter"
|
||||||
|
aria-label="Filter posts by tag"
|
||||||
|
class="tag-select"
|
||||||
|
data-predefined={JSON.stringify(predefinedTags)}
|
||||||
|
data-fallback={fallbackTag}
|
||||||
|
>
|
||||||
|
<option value="">All ({postsWithTag.length})</option>
|
||||||
{
|
{
|
||||||
posts.map((post) => (
|
// Render all predefined tags in order
|
||||||
<li>
|
predefinedTags.map((t) => <option value={t}>{`${t} (${counts[t] ?? 0})`}</option>)
|
||||||
<a href={`/${post.id}/`}>
|
}
|
||||||
|
{
|
||||||
|
// If fallback is not already in predefined and has posts, include it
|
||||||
|
!predefinedTags.includes(fallbackTag) && counts[fallbackTag] > 0 && (
|
||||||
|
<option value={fallbackTag}>{`${fallbackTag} (${counts[fallbackTag]})`}</option>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="post-list" data-post-list>
|
||||||
|
{
|
||||||
|
postsWithTag.map((post) => (
|
||||||
|
<li class="post-list-item" data-tag={post.canonicalTag}>
|
||||||
|
<a href={`/${post.id}/`} class="post-link">
|
||||||
<div class={`post-item ${!themeConfig.date.dateOnRight ? 'date-left' : ''}`}>
|
<div class={`post-item ${!themeConfig.date.dateOnRight ? 'date-left' : ''}`}>
|
||||||
{!themeConfig.date.dateOnRight && (
|
{!themeConfig.date.dateOnRight && (
|
||||||
<p class="date font-features">
|
<p class="date font-features">
|
||||||
@@ -33,43 +148,92 @@ const { posts } = Astro.props as PostListProps
|
|||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
<div class="placeholder"></div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
ul {
|
.tag-select {
|
||||||
|
padding: 0.35rem 0.75rem 0.35rem 0.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
/* Use theme card/background variables rather than forcing white */
|
||||||
|
background-color: var(--card-background, var(--background, #0b0b0b));
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
padding-right: 2rem; /* space for custom arrow */
|
||||||
|
/* Ensure text color applies on webkit mobile */
|
||||||
|
-webkit-text-fill-color: var(--text-primary);
|
||||||
|
/* keep a simple caret SVG; color fallback chosen but field background now uses theme */
|
||||||
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23BBBBBB' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 0.6rem center;
|
||||||
|
background-size: 0.9rem;
|
||||||
|
transition:
|
||||||
|
box-shadow 0.12s ease,
|
||||||
|
border-color 0.12s ease,
|
||||||
|
background-color 0.12s ease;
|
||||||
|
}
|
||||||
|
/* Try to style option backgrounds where the browser allows it */
|
||||||
|
.tag-select option {
|
||||||
|
background-color: var(--card-background, var(--background, #0b0b0b));
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
/* Hide native arrow in IE/Edge */
|
||||||
|
.tag-select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tag-select:focus {
|
||||||
|
outline: 2px solid var(--focus-outline, rgba(50, 150, 250, 0.16));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.post-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
list-style-type: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
.post-list-item {
|
||||||
|
/* no default hiding; JS toggles inline style */
|
||||||
|
}
|
||||||
|
|
||||||
|
a.post-link {
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: opacity 0.15s ease-out;
|
transition: opacity 0.15s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Restore hover interactions for pointer devices */
|
||||||
@media (hover: hover) and (pointer: fine) {
|
@media (hover: hover) and (pointer: fine) {
|
||||||
ul:hover a {
|
/* When the list is hovered, de-emphasize non-hovered links */
|
||||||
|
ul.post-list:hover a.post-link {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul:hover a:hover {
|
/* Fully reveal hovered item */
|
||||||
|
ul.post-list:hover a.post-link:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul:hover a:hover .divider {
|
/* Improve divider and dotted-divider appearance on hovered item */
|
||||||
|
ul.post-list:hover a.post-link:hover .divider {
|
||||||
background-color: var(--text-tertiary);
|
background-color: var(--text-tertiary);
|
||||||
opacity: 0.75;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
ul:hover a:hover .dotted-divider {
|
ul.post-list:hover a.post-link:hover .dotted-divider {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
ul:hover a:hover .date {
|
|
||||||
|
/* Ensure date color increases contrast on hover */
|
||||||
|
ul.post-list:hover a.post-link:hover .date {
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -87,15 +251,6 @@ const { posts } = Astro.props as PostListProps
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.post-item.date-left .title {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.post-item.date-left .date {
|
|
||||||
margin-right: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
@@ -134,12 +289,44 @@ const { posts } = Astro.props as PostListProps
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dotted-divider::after {
|
.sr-only {
|
||||||
content: '·····························································································································································';
|
position: absolute !important;
|
||||||
pointer-events: none;
|
height: 1px;
|
||||||
}
|
width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
.placeholder {
|
clip: rect(1px, 1px, 1px, 1px);
|
||||||
height: 3rem;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
;(function () {
|
||||||
|
const selectEl = document.getElementById('post-tag-filter')
|
||||||
|
const list = document.querySelector('[data-post-list]')
|
||||||
|
if (!selectEl || !list) return
|
||||||
|
if (!(selectEl instanceof HTMLSelectElement)) return
|
||||||
|
const select = selectEl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a tag filter to the list.
|
||||||
|
* @param {string} val - Selected tag value (empty string means show all)
|
||||||
|
*/
|
||||||
|
const applyFilter = (val) => {
|
||||||
|
const items = list.querySelectorAll('li[data-tag]')
|
||||||
|
items.forEach((li) => {
|
||||||
|
if (!(li instanceof HTMLElement)) return
|
||||||
|
const tag = li.getAttribute('data-tag') || ''
|
||||||
|
if (!val) li.style.display = ''
|
||||||
|
else li.style.display = tag === val ? '' : 'none'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply initial filter (no-op by default)
|
||||||
|
applyFilter(select.value || '')
|
||||||
|
|
||||||
|
select.addEventListener('change', function () {
|
||||||
|
const v = this instanceof HTMLSelectElement ? this.value : ''
|
||||||
|
applyFilter(v)
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ export const themeConfig: ThemeConfig = {
|
|||||||
|
|
||||||
// GENERAL SETTINGS ////////////////////////////////////////////////////////////////////////////////////
|
// GENERAL SETTINGS ////////////////////////////////////////////////////////////////////////////////////
|
||||||
general: {
|
general: {
|
||||||
contentWidth: '40rem', // Content area width
|
contentWidth: '42rem', // Content area width
|
||||||
centeredLayout: true, // Use centered layout (false for left-aligned)
|
centeredLayout: true, // Use centered layout (false for left-aligned)
|
||||||
themeToggle: false, // Show theme toggle button (uses system theme by default)
|
themeToggle: false, // Show theme toggle button (uses system theme by default)
|
||||||
postListDottedDivider: true, // Show dotted divider in post list
|
postListDottedDivider: false, // Show dotted divider in post list
|
||||||
footer: true, // Show footer
|
footer: true, // Show footer
|
||||||
fadeAnimation: true // Enable fade animations
|
fadeAnimation: true // Enable fade animations
|
||||||
},
|
},
|
||||||
@@ -33,5 +33,15 @@ export const themeConfig: ThemeConfig = {
|
|||||||
toc: true, // Show the table of contents (when there is enough page width)
|
toc: true, // Show the table of contents (when there is enough page width)
|
||||||
imageViewer: true, // Enable image viewer
|
imageViewer: true, // Enable image viewer
|
||||||
copyCode: true // Enable copy button in code blocks
|
copyCode: true // Enable copy button in code blocks
|
||||||
|
},
|
||||||
|
|
||||||
|
// TAG SETTINGS //////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Predefined canonical tags for the site. This list is used by components
|
||||||
|
// (e.g., PostList) to render a consistent dropdown and to compute counts.
|
||||||
|
// Add or remove tags here to control what appears in the UI.
|
||||||
|
tags: {
|
||||||
|
predefined: ['General', 'Game Dev', 'Reverse Engineering', 'Study'],
|
||||||
|
// Fallback/default tag to use when a post does not have a recognized tag
|
||||||
|
default: 'General'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ const posts = defineCollection({
|
|||||||
title: z.string(),
|
title: z.string(),
|
||||||
// Transform string to Date object
|
// Transform string to Date object
|
||||||
pubDate: z.coerce.date(),
|
pubDate: z.coerce.date(),
|
||||||
image: z.string().optional()
|
image: z.string().optional(),
|
||||||
|
// Preserve tag fields so components can read them. Support string or array formats.
|
||||||
|
tags: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
tag: z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
topic: z.string().optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
58
src/content/posts/_relearning-programming-part1.md
Normal file
58
src/content/posts/_relearning-programming-part1.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
title: 'Relearning Programming - Variables and Initialization'
|
||||||
|
pubDate: '2025-11-22'
|
||||||
|
tags: 'Study'
|
||||||
|
---
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Foreword
|
||||||
|
Hey,
|
||||||
|
I suck at coding - and that's why I decided to relearn programming. My language of choice is C++. I've been working with this language for a long time now, and it just grew on me like no other language did.
|
||||||
|
|
||||||
|
|
||||||
|
# What Led Me Here
|
||||||
|
I've been struggling with my problem solving + the core concepts of C++ itself. Even though I am aware of the fact that it's totally common to make mistakes, I've had the overwhelming feeling of frustration and "imposter syndrome". Every piece of information was
|
||||||
|
floating around in my head with no structure. Toss in ADHD and boom. That's how you replicate the feeling.
|
||||||
|
|
||||||
|
I tolerated it for too long.
|
||||||
|
|
||||||
|
|
||||||
|
# My Plan And Goals
|
||||||
|
My goals with relearning programming and C++ is to start with the bare basics and thoroughly study and apply my knowledge with a modern approach to C++. During this journey I will make sure to deep dive into every concept where my skills are lacking. I will make sure to
|
||||||
|
fully understand each assignment, each function call, each class and each line of code.
|
||||||
|
|
||||||
|
I will make sure to create plenty of small exercises & projects. The exercises give me the freedom to experiment with certain features and
|
||||||
|
to puzzle together solutions, which then can be finally implemented into the projects. These projects can be small games, utility tools or
|
||||||
|
simply just code snippets. Old C++ code repels me, so I’m sticking to C++20 for now and moving to C++23 soon.
|
||||||
|
|
||||||
|
Nearly everything I learn and code will be talked about in this series of blog posts. This will make sure that I summarize and explain
|
||||||
|
the material in my own words. The length of the posts might vary. Sometimes I will talk about more than one concept in one post. Some
|
||||||
|
concepts are too short to write a 500 word post about.
|
||||||
|
|
||||||
|
|
||||||
|
# What This Series Will Look Like
|
||||||
|
- Notes from studying C++ / programming concepts in my own words
|
||||||
|
- Small exercises to experiment
|
||||||
|
- Projects to apply what I've learned (Games, utilities, code snippets)
|
||||||
|
- Mistakes I make along the way + solutions to them
|
||||||
|
- Occasional rants, schizophrenia and dog pictures
|
||||||
|
|
||||||
|
# Starting Point
|
||||||
|
Right now, I know a lot, but at the same time, I don't know anything. I struggle with the understanding of how everything is running
|
||||||
|
under the hood. This is a crucial point to be aware of, because each decision you take has influence on the logic and performance of
|
||||||
|
your program. Not to mention the cruciality of the architecture.
|
||||||
|
|
||||||
|
# Closing Thoughts
|
||||||
|
I feel motivated and nervous at the same time. This will be a long, but quick journey. Beside me mentioning that I suck at programming
|
||||||
|
and that "I don't know anything", I'm pretty sure that I'll profit of my past experience and knowledge.
|
||||||
|
|
||||||
|
I'm so hyped to show off my future projects and I'll try my best to entertain you via some text and images. It's quite hard to pull
|
||||||
|
off, if I am being honest.
|
||||||
|
|
||||||
|
To my future me ever reading this: Do not fucking skip anything.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*"Using no way as a way, having no limitation as limitation"</br>
|
||||||
|
~ Bruce Lee*
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: 'Relearning Programming - Introduction'
|
title: 'Relearning Programming - Introduction'
|
||||||
pubDate: '2025-11-21'
|
pubDate: '2025-11-21'
|
||||||
tag: 'study'
|
tag: 'Study'
|
||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'IT WORKED'
|
title: 'Welcome!!!'
|
||||||
pubDate: '2025-07-18'
|
pubDate: '2025-07-18'
|
||||||
topic: 'general'
|
topic: 'general'
|
||||||
---
|
---
|
||||||
@@ -74,7 +74,7 @@ html.light {
|
|||||||
|
|
||||||
/* Dark Mode (Explicit) */
|
/* Dark Mode (Explicit) */
|
||||||
html.dark {
|
html.dark {
|
||||||
--bg: #151414;
|
--bg: #0a0a0a;
|
||||||
--text-primary: rgba(197, 201, 197, 1);
|
--text-primary: rgba(197, 201, 197, 1);
|
||||||
--text-secondary: rgba(197, 201, 197, 0.4);
|
--text-secondary: rgba(197, 201, 197, 0.4);
|
||||||
--text-tertiary: rgba(197, 201, 197, 0.24);
|
--text-tertiary: rgba(197, 201, 197, 0.24);
|
||||||
|
|||||||
@@ -40,10 +40,20 @@ export interface PostSettings {
|
|||||||
copyCode: boolean
|
copyCode: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tag settings for predefined tags
|
||||||
|
export interface TagSettings {
|
||||||
|
predefined: string[]
|
||||||
|
// optional default/fallback keys allow flexibility in config naming
|
||||||
|
default?: string
|
||||||
|
fallback?: string
|
||||||
|
}
|
||||||
|
|
||||||
// Theme configuration type
|
// Theme configuration type
|
||||||
export interface ThemeConfig {
|
export interface ThemeConfig {
|
||||||
site: SiteInfo
|
site: SiteInfo
|
||||||
general: GeneralSettings
|
general: GeneralSettings
|
||||||
date: DateSettings
|
date: DateSettings
|
||||||
post: PostSettings
|
post: PostSettings
|
||||||
|
// optional tag settings
|
||||||
|
tags?: TagSettings
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user