در طول استفاده از کوارتز، به مرور زمان تغییرات و اصلاحات مختلفی برای سفارشی‌سازی سایت انجام دادم. در این یادداشت فهرستی از این تغییرات را مستند کردم. این موارد نخست به عنوان یک مرجع شخصی عمل می‌کند تا در صورت نیاز بتوانم به راحتی تنظیمات قبلی را ویرایش کنم. از طرف دیگر می‌تواند راهنمای مفیدی باشد برای کسانی که تمایل دارند مشابه آن را روی سایت خود پیاده‌سازی کنند.



۱. متاداده‌های یادداشت

متاداده اطلاعات مربوط به یک یادداشت است که بعد از عنوان اصلی نمایش داده می شود. مثل تاریخ انتشار زمان تقریبی مطالعه. من ترجیح دادم تنظیمات زیر را به آن اضافه کنم:

زمان تقریبی مطالعه

بنا به دلایلی1 زمان تقریبی مطالعه را غیر فعال کردم. یک ویرگول هم برای جدا سازی این اطلاعات بین آن ها قرار می‌گرفت که آن را هم مخفی کردم. با اضافه کردن دستور زیر به فایل quartz.layout.ts نمایش این دو مورد غیرفعال می‌شود.

quartz.layout.ts

Component.ContentMeta({showReadingTime: false, showComma: false,})

تاریخ

به طور پیشفرض کوارتز فقط یک تاریخ را به کاربر نشان می‌دهد که نهایتا می توانید زمان آن را روی تاریخ انتشار یا تاریخ آخرین آپدیت تنظیم کنید. من تمایل داشتم علاوه بر تاریخ انتشار، تاریخ آخرین به‌روزرسانی هم برای کاربر قابل مشاهده باشد. با این دستور درصورتی که تاریخ انتشار و آخرین به‌روزرسانی یکسان نباشد علاوه بر تاریخ انتشار، تاریخ آخرین به‌روزرسانی هم به کاربر نمایش داده می‌شود.

برای تاریخ انتشار باید از پراپرتی date و برای تاریخ آخرین به روزرسانی باید از پراپرتی lastmod در یادداشت خود استفاده کنید.

quartz/components/ContentMeta.tsx

    if (text) {
      const segments: (string | JSX.Element)[] = []
 
      if (fileData.frontmatter) {
        const created = fileData.frontmatter.date ? formatDate(new Date(fileData.frontmatter.date), cfg.locale) : null
        const lastmod = fileData.frontmatter.lastmod ? formatDate(new Date(fileData.frontmatter.lastmod), cfg.locale) : null        
      
        if (created) {
          segments.push(` 📅 انتشار: ${created} `)
        }
        if (lastmod && created !== lastmod) {
          segments.push(` 🔄 به‌روزرسانی: ${lastmod} `)
        }
      }

این کد را از سایت کوانتوم گاردن گرفتم و کمی تغییر دادم. از اینجا میتوانید کدهای اصلی را مشاهده کنید.

وضعیت رشد

یکی از مواردی که در روش یادداشت برداری دیجیتال گاردن استفاده می‌شود مشخص کردن میزان رشد و پیشرفت یادداشت است. 🌱نهال، 🌿درختچه، 🌳همیشه‌سبز و 🍂زودگذر مواردی هستند که برای علامت گذاری وضعیت نوشته استفاده می‌کنم. در حالت عادی اگر پراپرتی خاصی را در یادداشت وارد کنید اتفاقی نمی افتد و چیزی در سایت نمایش داده نمی شود. پس ابتدا لازم است این پراپرتی را به کوارتز معرفی کنیم تا آن را شناسایی کرده و اطلاعات آن را نمایش دهد. با اضافه کردن کد زیر به فایل ContentMeta.tsx این تنظیم اعمال می شود.

ContentMeta.tsx

  const status = fileData.frontmatter?.status || "نامشخص";
  if (status !== "نامشخص") {
	segments.push(` ${status} `)
  }

این دستور نوشته‌ها را بررسی می‌کند اگر دارای پراپرتی با عنوان status باشند محتوای آن را نمایش می دهد.

---
status: 🌱نهال
---

استایل

برای این بخش یک بک‌گراند خاکستری روشن اضافه کردم که ظاهر بهتر و متمایزی داشته باشد.

quartz\styles\custom.scss

.content-meta>span {
    background-color: var(--lightgray);
    border-radius: 4px;
    padding: 1px 6px 1px 6px;
    font-size: 0.9em;
}
 
.content-meta {
    color: var(--darkgray);
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
}

حذف اطلاعات از صفحه اول

طبیعتا صفحه اول نیاز به نمایش این اطلاعات ندارد. برای مخفی کردن این موارد از صفحه اول باید کد زیر را به فایل ContentMeta.tsx اضافه کنید. باید بعد از const text = fileData.text قرار بگیرد.

ContentMeta.tsx

if (fileData.slug === "index") {
	return <></>
}

نمایش عنوان صفحه هم ضرورتی ندارد برای حذف آن کد بالا را به فایل quartz/components/ArticleTitle.tsx اضافه کنید. باید بعد از const title = fileData.frontmatter?.title قرار بگیرد. (+)


اضافه کردن تصویر شاخص

یکی از مواردی که معمولا در سایت ها استفاده می شود استفاده از تصویر شاخص برای یادداشت هاست. در کوارتز قابلیتی برای این مورد وجود ندارد. البته میتوان به صورت دستی یک تصویر را ابتدای یادداشت اضافه کرد. اما من بنا به دلایلی2 ترجیح دادم یک پراپرتی با عنوان image اضافه کنم و تصویر شاخص را در این پراپرتی وارد کنم.

با اضافه کردن کد زیر این قابلیت به کوارتز اضافه می شود. این کد باید بعد از const segmentsElements و قبل از } else { قرار بگیرد.

ContentMeta.tsx

      return (
        <>
          <p show-comma={options.showComma} class={classNames(displayClass, "content-meta")}>
            {segmentsElements}
          </p>
 
          {/* Display image */}
          {fileData.frontmatter?.image && (
            <div style={{ marginTop: "10px" }}>
              <img
                src={`/img/${fileData.frontmatter.image}`}
                alt="Note Image"
                style={{ maxWidth: "100%", height: "auto", display: "block" }}
              />
            </div>
          )}
        </>
      )

حالا در یادداشت خود یک پراپرتی با عنوان image اضافه کرده و اسم تصویر را وارد کنید به این شکل:

image: example.png



۲. صفحه بندی

چینش محتویات صفحه در فایل quartz.layout.ts انجام می شود. این خلاصه تغییراتی است که انجام دادم:

  • عنوان صفحه را سمت راست و گراف ویو را سمت چپ قرار دادم. (چون زبان سایت فارسی است این چینش بهتر است)
  • فهرست مطالب را در حالت موبایل قبل از body قرار دادم.
  • بک لینک را از سایدبار به afterBody منتقل کردم.
beforeBody: [
    Component.ArticleTitle(),
    Component.ContentMeta({showReadingTime: false, showComma: false,}),
    Component.MobileOnly(Component.TableOfContents()),
    
  ],
  left: [  
    Component.DesktopOnly(Component.Darkmode()),
    Component.Graph(),
    Component.Backlinks(),  
 
  ],
  right: [
    Component.PageTitle(),
    Component.MobileOnly(Component.Darkmode()),
    Component.Search(),    
    Component.DesktopOnly(Component.TableOfContents()),    
  ],

البته از آنجایی که شکست صفحه از سمت چپ انجام می شود در حالت موبایل یک مشکل ایجاد می شود. عنوان صفحه به انتهای صفحه منتقل شده و گراف ویو ابتدای صفحه قرار می گیرند. برای حل این مشکل کافیست در فایل custom.scss کد زیر را وارد کنیم:

// ریسپانسیو
    @media (max-width: 1510px) {
        .page>#quartz-body {
            flex-direction: column-reverse;
        }
 
        .page>#quartz-body .sidebar.left {
            align-items: normal;
            flex-direction: column;
        }
 
        .page>#quartz-body .sidebar.right>* {
            min-width: fit-content;
        }
 
        .page>#quartz-body .sidebar.right {
            align-items: center;
            flex-wrap: nowrap;
        }
    }
 
// جستجو
    .search {
        max-width: none;
    }
 
// دارک مود
    .darkmode {
        max-width: fit-content;
    }

البته راه حل دیگری هم وجود دارد. اینکه از هر مورد یک کپی بگیریم و با عبارت MobileOnly و DesktopOnly مشخص کنیم که در حالت موبایل یا دسکتاپ کدام سمت قرار بگیرد. منتها برای بعضی موارد مشکل ایجاد می شود. مثلا گراف ویو فقط در دستکتاپ کار میکند و در موبایل کار نمیکند، فقط باکس آن نمایش داده می شود و با کلیک روی آیکون آن گلوبال گراف هم باز نمی شود. برای جستجو هم همین اتفاق می‌افتاد.



۳. فارسی سازی

خوشبختانه کوارتز از زبان فارسی پشتیبانی می‌کند. و با ویرایش فایل fa-IR.ts می‌توان از معادل فارسی کلمات استفاده کرد. اما چند مورد وجود دارد که که فارسی سازی نشده:

کلمه Home در Breadcrumbs

کلمه home در Breadcrumbs (مسیر راهنمای سایت) به فارسی تبدیل نشده. با اضافه کردن دستور زیر به فایل quartz.layout.ts می توانید کلمه آن را تغییر دهید:

    Component.Breadcrumbs({rootName: "خانه",}),

نتیجه جستجو

اگر در باکس جستجو عبارتی را سرچ کنید و آن کلمه در سایت نباشد این متن را نمایش می دهد:

با ویرایش فایل search.inline.ts می‌توانید آن را اصلاح کنید.

quartz\components\scripts\search.inline.ts

if (finalResults.length === 0) {
  results.innerHTML = `<a class="result-card no-match">
	  <h3>نتیجه‌ای یافت نشد</h3>
	  <p>عبارت دیگری را امتحان کنید</p>
  </a>`
}

عنوان footnote

اگر از پاورقی استفاده کنید به طور پیشفرض یک عنوان با عبارت footnote به انتهای صفحه اضافه می‌شود. با قرار دادن کد زیر در custom.scss می توانید آن را با یک متن دیگر جایگزین کنید. (+)

h2#footnote-label{
	visibility: hidden;
}
 
h2#footnote-label::after{
	content: "پانوشت‌ها";
	visibility: visible;
	display: block;
	direction: rtl !important;
	font-size: 1.4rem;
	border-bottom: 1px solid var(--lightgray);     
	color: var(--darkgray);             
}



۴. جهت متن خودکار

برای راستچین شدن جهت متن لازم است فایل renderPage.tsx را ویرایش کنید. با اضافه کردن dir="rtl" به خط <html lang={lang}> جهت متن راستچین می شود.

<html dir="rtl" lang={lang}>

البته این روش همه متن‌ها حتی نوشته‌های انگلیسی را هم راستچین می‌کند. در بلاگ کریستالین یک ترفند برای حل این مشکل ارائه شده (اینجا. با اصلاحی که در فایل ofm.ts انجام شده جهت متن به صورت خودکار تنظیم می‌شود، یعنی متن فارسی راستچین و متن انگلیسی چپ‌چین می‌شود. از اینجا می‌توانید نتیجه این قابلیت را مشاهده کنید.

برای استفاده از این روش فایل ofm.ts را دانلود کرده و جاگزین فایل خود کنید: quartz/plugins/transformers/ofm.ts



۵. بازطراحی صفحه 404

صفحه 404 لینک بازگشتی به سایت ندارد و کاربر نمی‌تواند به سایت برگردد. با اضافه کردن کد زیر به فایل 404.tsx لینک بازگشت به صفحه اصلی در این صفحه نمایش داده می‌شود. این کد باید بعد از <p>{i18n(cfg.locale).pages.error.notFound}</p> قرار بگیرد.

quartz\components\pages\404.tsx

<p>بازگشت به <a href="/">صفحه اصلی</a></p>

اگر قابلیت کامنت را فعال کرده باشید برای این صفحات هم کامنت نمایش داده می شود. با اضافه کردن کد زیر به فایل Comments.tsx می توانید نمایش آن را غیرفعال کنید.

quartz/components/Comments.tsx

if (fileData.slug === "404" || !fileData.slug) { return <></> }



۶. تغییر فوتر

فقط کمی استایلش را عوض کردم. برای لینک ها هم ترجیح دادم به جای متن، از لوگو استفاده کنم. توجه داشته باشید که اگر کد زیر را جایگزین کنید دیگر ویرایش لینک ها از طریق فایل quartz.layout.ts امکان پذیر نیست و باید همین کد را تغییر دهید.

فایل svg لوگو را در مسیر quartz/static قرار دادم:

سپس در فایل Footer.tsx از کد زیر استفاده کردم:

<div class="footer2">
  <a href="https://instagram.com/ifard.ir/" title="اینستاگرام" class="footer-link"><img src="static/instagram.svg" alt="instagram" class="footer-svg" /></a>
  <a href="https://t.me/ifard_ir/" title="تلگرام" class="footer-link"><img src="/static/telegram.svg" alt="telegram" class="footer-svg" /></a>
  <a href="https://twitter.com/ifard_ir/" title="توییتر" class="footer-link"><img src="/static/twitter.svg" alt="twitter" class="footer-svg" /></a>
</div>

این استایل را هم در فایل custom.scss وارد کردم:

footer {
	opacity: 1;
	text-align: center;
}
 
.footer1 {
	font-size: smaller;
	opacity: 0.65;
}
 
.footer2 {
	display: flex;
	justify-content: center;
	gap: 20px;
}
 
.footer-link {
	display: inline-block;
	width: 1.3em;
	height: 1.3em;
}
 
.footer-svg {
	opacity: 0.65;
	filter: grayscale(100%);
	transition: filter 0.3s ease;
	transition: scale .3s ease;
}
 
.footer-svg:hover {
	filter: grayscale(0%);
	opacity: 1;
}



۷. ظاهر سایت

این تنظیمات مربوط به استایل ظاهری سایت است.

فهرست مطالب و بک لینک

برای تمایز بیشتر بوردر اضافه کردم. به اول هر سطر هم بولت پوینت اضافه کردم.

custom.scss

// فهرست مطالب
    .toc {
        border-radius: 5px;
        border: 1px solid var(--lightgray);
        padding: 12px; 
        font-size: 0.9rem;
    }
        
    #toc-content .depth-0 {
        list-style: disc;
        list-style-position: inside;
    }
    
    #toc-content .depth-1 {
        padding: 0px !important;
        padding-right: 1rem !important;
        list-style: circle;
        list-style-position: inside;
    }
    
    #toc-content .depth-2 {
        padding: 0px !important;
        padding-right: 2rem !important;
    }
    
    #toc-content .depth-3 {
        padding: 0px !important;
        padding-right: 3rem !important;
    }
 
    #toc-content ul>li>a {
        margin-right: -12px;
        opacity: .45;
    }
 
 
// بک لینک
    .backlinks>ul {
        border-radius: 5px;
        border: 1px solid var(--lightgray);
        list-style: disc;
        padding-right: 35px;
        padding-top: 10px;
        padding-left: 10px;
        font-size: 0.95rem;
    }
 
    ul.overflow:after,ol.overflow:after {
        display: none;
    }

بلوک کد

در حالت پیشفرض حتی اگر طول یک سطر کوتاه باشد باز هم اسکرول محور افقی نمایش داده می شود. با اضافه کردن کد overflow-x: auto اسکرول تنها در صورتی نمایش داده می‌شود که طول سطر طولانی بوده و خارج از بلوک کد باشد. بقیه تنظیمات مربوط به بک‌گراند، فونت و جهت قرار گفتن متن است.

custom.scss

pre {
	background: #afafaf1a;
}
 
pre>code {
	overflow-x: auto;
}
 
code {
	direction: ltr !important;
	font-family: var(--bodyFont);
}

دیاگرام

در کوارتز مانند ابسیدین امکان ساخت دیاگرام وجود دارد. به این شکل:

flowchart TD
A([عنوان اصلی])
A --- B([فرعی یک])
A --- C([فرعی دو])
A --- D([فرعی سه])

click A "/"

classDef default fill:#fff,stroke:gray, stroke-width:1px,color:#282828;
linkStyle default stroke:gray,stroke-width:1px;

در سایت mermaid تمامی دستورات برای استفاده از آن توضیح داده شده. کد زیر دیاگرام بالا را نشان می دهد:

```mermaid
flowchart TD
A([عنوان اصلی])
A --- B([فرعی یک])
A --- C([فرعی دو])
A --- D([فرعی سه])
 
click A "/"
 
classDef default fill:#fff,stroke:gray, stroke-width:1px,color:#282828;
linkStyle default stroke:gray,stroke-width:1px;
```

تنظیم استایل دیاگرام در خود آن امکان پذیر است. دو خط آخر کد بالا استایل این دیاگرام را تعریف می‌کند. علاوه براین تنظیمات دیگری هم به فایل custom.scss اضافه کردم:

  • دایرکشن را روی rtl گذاشتم، چون معمولا از فارسی استفاده می کنم.
  • بک‌گراند را شفاف کردم، چون بک‌گراند code رو خاکستری کرده بودم، دیاگرام هم خاکستری شده بود.
  • فونت را روی body font گذاشتم.
  • آیکون «کپی در کلیپ بورد» را مخفی کردم.

custom.scss

.mermaid {
	direction: rtl !important;
}
 
pre:has(>code.mermaid) {
	background-color: transparent;
}
 
.nodeLabel {
	font-family: var(--bodyFont);
}
 
pre:has(>code.mermaid) .clipboard-button {
	display: none;
}

دکمه

با استفاده از تگ </button> می توانید از دکمه استفاده کنید. به این شکل:

کد زیر دکمه بالا را تحویل می دهد:

<div style="text-align: center;">
<button 
	class="my-button"	onclick="window.open('https://www.example.com/', '_blank');">
     کلیک کنید
</button>
</div>

برای استایل هم این تنظیمات را اضافه کردم:

custom.scss

.my-button {
	background-color: var(--tertiary);
	color: white;
	padding: 8px 20px;
	border: none;
	border-radius: 10px;
	cursor: pointer;
	transition: background-color 0.3s ease;
	font-family: var(--bodyFont);
	font-size: 1em;
}  
 
.my-button:hover {
	background-color: var(--secondary);
}

تایپوگرافی

مقداری سایز متن بدنه و هدینگ ها را افزایش دادم. همینطور فاصله بین خطوط.

custom.scss

body {
	font-size: 1.1em;
}
 
p {
	line-height: 2rem;
} 
 
li {
	line-height: 2rem; 
}
 
sup {
vertical-align: middle; //اگه توی متن از پانوشت استفاده شده باشه باعث میشه فاصله بین خطوط به هم نریزه
}
 
h2 {
	font-size: 1.6rem;
	margin-top: 1rem;
	margin-bottom: -0.2rem;
	border-bottom: 1px solid var(--lightgray);
	padding-bottom: 0.2em;
}
 
h3 {
	font-size: 1.30rem;
	margin-top: 1rem;
	margin-bottom: -0.8rem;
}
 
h4,
h5,
h6 {
  font-size: 1.1rem;
  margin-top: 1rem;
  margin-bottom: -0.8rem;
}

چرخش آیکون >

این آیکون در قسمت های مختلف مثل فهرست، اکسپلور و کالوت استفاده شده. جهت این آیکون در حالت بسته باید سمت چپ باشد در حالی که به سمت راست است. برای چرخش آن باید مقدار rotateZ را در فایل های مربوط به هر کدام از منفی90 به مثبت90 تغییر دهید.(+)

فهرست: quartz/components/styles/toc.scss

اکسپلور: quartz/components/styles/explorer.scss

کالوت: quartz/quartz/styles/callouts.scss

به این شکل:

  &.collapsed .fold {
    transform: rotateZ(90deg);
  }

کالوت سفارشی

برای تغییر رنگ کالوت ها می توانید فایل quartz/quartz/styles/callouts.scss را ویرایش کنید. علاوه بر این میتوانید کالوت های جدید با آیکون های متفاوت اضافه کنید. مثلا من یک کالوت زرد رنگ با آیکون لامپ اضافه کردم:

لامپ

ابتدای فایل callouts.scss آیکون ها به این شکل --callout-icon مشخص شدند. یک خط را کپی کنید و اسم مورد نظر و کد SVG آیکون را جایگزین کنید. به این شکل:

  --callout-icon-ideas: url('data:image/svg+xml; utf8, <svg id="Layer_1" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="m17.994 2.286a9 9 0 0 0 -14.919 5.536 8.938 8.938 0 0 0 2.793 7.761 6.263 6.263 0 0 1 2.132 4.566v.161a3.694 3.694 0 0 0 3.69 3.69h.62a3.694 3.694 0 0 0 3.69-3.69v-.549a5.323 5.323 0 0 1 1.932-4 8.994 8.994 0 0 0 .062-13.477zm-5.684 19.714h-.62a1.692 1.692 0 0 1 -1.69-1.69s-.007-.26-.008-.31h4.008v.31a1.692 1.692 0 0 1 -1.69 1.69zm4.3-7.741a7.667 7.667 0 0 0 -2.364 3.741h-1.246v-7.184a3 3 0 0 0 2-2.816 1 1 0 0 0 -2 0 1 1 0 0 1 -2 0 1 1 0 0 0 -2 0 3 3 0 0 0 2 2.816v7.184h-1.322a8.634 8.634 0 0 0 -2.448-3.881 7 7 0 0 1 3.951-12.073 7.452 7.452 0 0 1 .828-.046 6.921 6.921 0 0 1 4.652 1.778 6.993 6.993 0 0 1 -.048 10.481z"/></svg>');

کمی پایین تر خود کالوت ها وارد شدند. یک مورد رو کپی کنید و عنوان کالوت و آیکون را تغییر بدهید. رنگ ها را هم میتوانید ویرایش کنید. به این شکل:

  &[data-callout="ideas"] {
    --color: #d98b19;
    --border: #cb9f1b47;
    --bg: #ffc8220f;
    --callout-icon: var(--callout-icon-ideas);
  }

حالا می توانید از این کالوت در نوشته خود استفاده کنید. به این شکل:

> [!ideas] لامپ
> 
> 

برای اینکه در خود ابسیدین هم این کالوت با آیکون و رنگ اختصاصی نمایش داده شود می توانید از پلاگین Admonition یا Callout Manager استفاده کنید.


اضافه کردن آیکون به عنوان سایت

یک آیکون به عنوان سایت اضافه کردم. کد SVG آیکون را به فایل PageTitle.tsx اضافه کردم.

quartz\components\PageTitle.tsx

  return (
    <h1 class={classNames(displayClass, "page-title")}>
      <a href={baseDir}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor" style="vertical-align: middle;"><path d="M512 32c0 113.6-84.6 207.5-194.2 222c-7.1-53.4-30.6-101.6-65.3-139.3C290.8 46.3 364 0 448 0l32 0c17.7 0 32 14.3 32 32zM0 96C0 78.3 14.3 64 32 64l32 0c123.7 0 224 100.3 224 224l0 32 0 160c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-160C100.3 320 0 219.7 0 96z"/></svg>
      &nbsp;{title}</a>
    </h1>
  )

نمای کارتی برای جدول‌

این تنظیم استایل جدول را به حالت کارت تبدیل می‌کند. مشابه حالتی که تم minimal برای جدول‌های dataview می‌سازد. یک حالت دیگر هم اضافه کردم که باعث می شود کارت‌ها فقط در یک ردیف نمایش داده شوند و باقی کارت‌ها با اسکرول کردن قابل مشاهده باشند. برای زمانی که تعداد کارت‌ها زیاد باشد این روش مناسب تر است.

برای استفاده از این روش ابتدا این کد را به فایل custom.scs اضافه کنید.

// نمای کارتی جدول - حالت گرید
    // استایل پایه
    .card-g {
        tr {
            display: flex;
            flex-direction: column;
            border: 1px solid #b5b5b526;
            background-color: #b5b5b518;
            border-radius: 8px;
            font-size: 0.9em;
            line-height: 1.5em;
            overflow: hidden;
            padding-bottom: 10px;
        }
 
        .table-container>table>* {
            display: grid;
            gap: 15px;
            grid-template-columns: repeat(4, 1fr);
        }
 
        .table-container>table {
            margin: 0;
            width: 100%;
        }
 
        td {
            text-align: center;
            padding: .2rem;
        }
 
 
        .table-container>table td img {
            margin: .7rem .7rem 0 .7rem;
            
        }
 
        .table-container>table>thead {
            display: none;
        }
 
    }
 
    // تعداد ستون
    .c-2 .table-container>table>* {grid-template-columns: repeat(2, 1fr);}
    .c-3 .table-container>table>* {grid-template-columns: repeat(3, 1fr);}
    .c-5 .table-container>table>* {grid-template-columns: repeat(5, 1fr);}
    .c-6 .table-container>table>* {grid-template-columns: repeat(6, 1fr);}
 
    // ستون ها در تبلت
    @media (max-width: 768px) {
        .card-g .table-container>table>* {
            grid-template-columns: repeat(3, 1fr);
        }
    }
 
    // ستون ها در موبایل        
    @media (max-width: 480px) {
        .card-g .table-container>table>* {
            grid-template-columns: repeat(2, 1fr);
        }
    }
 
 
// نمای کارتی جدول - حالت اسکرول
 
    // استایل پایه
    .card-s {
        tr {
            display: flex;
            flex-direction: column;
            border: 1px solid #b5b5b526;;
            background-color: #b5b5b518;
            border-radius: 8px;
            padding: 10px;
            font-size: 0.9em;
            line-height: 1.5em;
            overflow: hidden;
            width: 150px;
            justify-content: space-around;
        }
 
        .table-container>table>* {
            display: flex;
            gap: 15px;
            overflow-x: auto;
            padding: 10px 0 10px 0;
            margin: 0;
        }
 
        td {
            text-align: center;
            padding: 0;
        }
 
        .table-container>table td img {
            margin: 0;
        }
 
        .table-container>table>thead {
            display: none;
        }
    }
    
    // تنظیم عرض
    .w100 tr {width: 100px;}
    .w200 tr {width: 200px;}
    .w300 tr {width: 300px;}
 
 
 
// استایل های سفارشی مشترک: هم گرید هم اسکرول
 
    // نمایش متن در یک سطر و مخفی کردن کاراکترهای اضافی
    .nowarp td {
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }
 
    // نمایش متن در دو سطر و مخفی کردن کاراکترهای اضافی
    .nowarp2 td {
        display: -webkit-box;
        -webkit-box-orient: vertical;
        overflow: hidden;
        text-overflow: ellipsis;
        -webkit-line-clamp: 2;
    }
 
    // نسبت تصویر
    .c1-1 .table-container>table td img {
        aspect-ratio: 1 / 1;
        object-fit: contain;
        background-color: #a5a5a526;
    }
 
    .c16-9 .table-container>table td img {
        aspect-ratio: 16 / 9;
        object-fit: contain;
        background-color: #a5a5a526;
    }

برای اعمال این استایل می‌توانید از دو روش استفاده کنید:

اگر میخواهید روی همه جدول‌های موجود در یادداشت اعمال شود یک پراپرتی با عنوان cssclasses به یادداشت خود اضافه کنید و کلاس دلخواه را وارد کنید. به این شکل:

---
cssclasses: card-g c-3
---

اگر نمی خواهید این استایل روی همه جدول‌ها اعمال شود می توانید جدول خود را در تگ div قرار داده و کلاس مورد نظر را برای آن تعریف کنید. به این شکل:

<div class="card-g c-2">
| class  | description  |
| ------ | ------------ |
| card-g | grid style   |
| card-s | scroll style |
</div>

راهنمای کلاس‌ها

کلاس های مربوط به حالت گرید

card-gیک نمای کارتی با 4 ستون در ردیف‌های متعدد می‌سازد
c-2نمایش کارت ها در 2 ستون
c-3نمایش کارت ها در 3 ستون
c-5نمایش کارت ها در 5 ستون
c-6نمایش کارت ها در 6 ستون


کلاس های مربوط به حالت اسکرول

card-sیک نمای کارتی با عرض 150px در یک ردیف میسازد
w100تنظیم عرض کارت روی 100px
w200تنظیم عرض کارت روی 200px
w300تنظیم عرض کارت روی 300px


کلاس های مشترک

nowarpمتن را در یک خط نگه داشته و کاراکتر های اضافی را مخفی می‌کند
nowarp2متن را در دو خط نگه داشته و کاراکتر های اضافی را مخفی می‌کند
c1-1نسبت تصویر را 1:1 تنظیم می‌کند
c16-1نسبت تصویر را 16:9 تنظیم می‌کند



۸. دسته بندی و ساخت فهرست خودکار

دسته بندی در سایت باعث می شود کاربران مطالب را راحت تر پیدا کنند. کوارتز در حال حاضر قابلیتی برای دسته بندی ندارد و نهایتا میتوان از تگ یا فولدر استفاده کرد.

من ابتدا صفحات مستقلی را برای موضوعات ایجاد کردم و به صورت دستی لینک مطالب مرتبط را در آن وارد می کردم. اخیرا از پلاگین دیتاویو استفاده میکنم که به طور اتوماتیک این کار را انجام می دهد. البته خود پلاگین دیتاویو در کوارتز پشتیبانی نمی شود ولی میتوان از پلاگین Dataview Serializer استفاده کرد. چون جداول استخراج شده را به صورت HTML میسازد که به راحتی در کوارتز نمایش داده می شوند.

من ابتدا در یادداشت پراپرتی های زیر را وارد می کنم:

---
draft: false
image: cover-review.svg
parent: "[[نرم‌افزار ابسیدین]]"
order: "1"
---

سپس با دستور زیر آن ها را استخراج می کنم. اگر یادداشت تصویر داشته باشد همان را نمایش می دهد. اگر نداشته باشد یک تصویر خالی (noimage.svg) نشان می دهد.

<!-- QueryToSerialize: table without id choice(image=null, ![[noimage.svg]], embed(link(image))), "[[" + file.name + "|" + title + "]]" From note WHERE draft = false AND parent = [[نرم‌افزار ابسیدین]] SORT order ASC -->

از روش قبلی هم برای تبدیل جدول به نمای کارتی استفاده می‌کنم:

Info

البته اخیرا تصمیم گرفتم به تصاویر را از فهرست حذف کنم و فقط مطالب مرتبط را لیست کنم. در حال حاضر از این دستور استفاده میکنم:

<!-- QueryToSerialize: LIST without id "[[" + file.name + "|" + title + "]]" WHERE draft = false AND parent = [[نرم‌افزار ابسیدین]] SORT order ASC -->

کامنت را هم برای این صفحات غیرفعال کردم.

نمونه فهرست هایی که ایجاد کردم:

۹. غیرفعال کردن کامنت

در نسخه های قبلی کوارتز امکان غیرفعال کردن کامنت برای هر صفحه وجود نداشت. من از ترفند زیر برای غیرفعال سازی صفحات دلخواه استفاده میکردم:

<style>
	.giscus {
		display: none;
	}
</style>

در آپدیت جدید این امکان اضافه شده. کافیست یک پراپرتی با عنوان comments بسازید و از بخش property type حالت آن را روی checkbox بگذارید. حالا با کلیک روی چک باکس میتوانید کامنت را فعال یا غیر فعال کنید.

البته چون من کدهای سایت را دستکاری کردم نتوانستم از این آپدیت استفاده کنم. اما با اضافه کردن این کد به فایل زیر توانستم از این قابلیت استفاده کنم:

quartz\components\Comments.ts

export default ((opts: Options): QuartzComponentConstructor<Options> => {
  const Comments: QuartzComponent = ({ displayClass, fileData, cfg }: QuartzComponentProps) => {
    // بررسی وجود comments در فراداده و غیرفعال کردن کامنت‌ها در صورت نیاز
    const disableComment: boolean =
      typeof fileData.frontmatter?.comments !== "undefined" &&
      (!fileData.frontmatter?.comments || fileData.frontmatter?.comments === "false")
    if (disableComment) {
      return <></>
    }



۱۰. اسکرول به بالا و صفحه تصادفی

یک مورد جذاب هم در فوتر quartz.eilleeenz.com دیدم که با کلیک کردن روی Random Page یک صفحه تصادفی به کاربر نمایش می‌دهد. یک دکمه اسکرول به بالا هم دارد. اخیرا این دو مورد رو به صورت دکمه شناور گوشه پایین سایت قرار داده. از اینجا می توانید توضیحات خودش را مشاهده کنید. اما خلاصه میگویم که من چه طور آن را اضافه کردم.

ابتدا این فایل ها را به کوارتز اضافه کردم:

بعد این کد را به فوتر اضافه کردم:

<div class="floating-buttons">
          <div class="button-group">
            <button
              class="floating-button"
              onclick="location.href='#'"
            >
              <span class="floating-button-tooltip">scroll to top</span>
              <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <polyline points="18 15 12 9 6 15"></polyline>
              </svg>
            </button>
            <button
                class="floating-button"
                id="random-page-button"
              >
                <span class="floating-button-tooltip">Random page</span>
                <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M3 7H4.77985C6.93172 7 8.00766 7 8.87921 7.45631C9.25172 7.65134 9.59114 7.90388 9.88499 8.20464C10.5725 8.90832 10.8817 9.93888 11.5 12V12C12.1183 14.0611 12.4275 15.0917 13.115 15.7954C13.4089 16.0961 13.7483 16.3487 14.1208 16.5437C14.9923 17 16.0683 17 18.2202 17H21M21 17L18 14M21 17L18 20" stroke="currentColor"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M21.3536 6.64648L18.3536 3.64648L17.6464 4.35359L19.7929 6.50004H18.2202H18.1963C17.1406 6.50003 16.3153 6.50003 15.6464 6.55901C14.964 6.61918 14.405 6.74317 13.8889 7.01339C13.4698 7.2328 13.0879 7.51691 12.7574 7.85526C12.7386 7.87444 12.7202 7.8938 12.7019 7.91335C12.8289 8.16228 12.9399 8.41464 13.0406 8.66741C13.0782 8.7617 13.1154 8.85879 13.1523 8.95851C13.2519 8.80434 13.3571 8.6724 13.4727 8.5541C13.7298 8.29094 14.0268 8.06996 14.3527 7.89931C14.7081 7.71321 15.1228 7.60905 15.7343 7.55514C16.3542 7.50049 17.1355 7.50004 18.2202 7.50004H19.7929L17.6464 9.64648L18.3536 10.3536L21.3536 7.35359L21.7071 7.00004L21.3536 6.64648ZM10.2981 16.0867C10.1711 15.8378 10.0601 15.5854 9.95935 15.3327C9.92175 15.2384 9.88456 15.1413 9.84766 15.0416C9.74807 15.1957 9.64293 15.3277 9.52735 15.446C9.27024 15.7091 8.97324 15.9301 8.6473 16.1008C8.29185 16.2869 7.87716 16.391 7.26574 16.4449C6.64583 16.4996 5.86454 16.5 4.77985 16.5H3V17.5H4.77985H4.80369C5.85944 17.5 6.68467 17.5 7.35357 17.4411C8.03597 17.3809 8.59502 17.2569 9.11113 16.9867C9.5302 16.7673 9.91205 16.4832 10.2426 16.1448C10.2614 16.1256 10.2798 16.1063 10.2981 16.0867Z" fill="currentColor"></path> </g></svg>
              </button>

و این استایل را به فایل custom.scss:

.floating-buttons {
    position: fixed;
    bottom: 1.5rem;
    right: 1.5rem;
    display: flex;
    flex-direction: column;
    z-index: 100;
}
 
.button-group {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}
 
.floating-button {
    width: 36px;
    height: 36px;
    background: var(--light);
    border: 1px solid var(--lightgray);
    color: var(--dark);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
    padding: 0;
    position: relative;
    
    &:hover {
      background: var(--lightgray);
      color: var(--secondary);
    }
    
    svg {
      width: 18px;
      height: 18px;
    }
 
    .floating-button-tooltip {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      background: var(--dark);
      color: var(--light);
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
      opacity: 0;
      visibility: hidden;
      transition: all 0.2s ease;
      pointer-events: none;
      
      right: calc(100% + 10px);
      
      &::after {
        content: '';
        position: absolute;
        right: -4px;
        top: 50%;
        transform: translateY(-50%);
        border-left: 4px solid var(--dark);
        border-top: 4px solid transparent;
        border-bottom: 4px solid transparent;
      }
    }
 
    &:hover .floating-button-tooltip {
      opacity: 1;
      visibility: visible;
    }
  }
 
  @media (max-width: 600px) {
    .floating-buttons {
        bottom: 1rem;
        right: 1rem;
    }
  }

البته روش من چندان اصولی نیست و فقط یک ترفند سریع برای استفاده از این روش است. در اصل باید برای هرکدام یک کامپوننت با استایل مجزا بسازید. همانطوری که در سایت eilleeenz انجام شده.



۱۱. مشکل تنظیم slug

تنها مشکل من با کوارتز این است که نمی توانم slug را تنظیم کنم. در حالت عادی اسلاگ بر اساس اسم فایل انتخاب می شود. من قبلا از hugo استفاده می‌کردم و خیلی راحت با اضافه کردن پراپرتی slug می توانستم url مشخصی را وارد کنم اما اینجا امکانش وجود ندارد.

تنظیم اسلاگ بر اساس عنوان فایل روش بهینه ای نیست. چون عنوان یادداشت های من فارسی است و فارسی بودن اسلاگ باعث می شود هنگام اشتراک گذاری با یک لینک بلند حاوی علائمی چون عدد و درصد مواجه شوم.

علاوه بر این من مدام یادداشت‌ها را آپدیت می‌کنم و ممکن است عنوان فایل را هم دستکاری کنم. با اینکار اسلاگ هم تغییر می‌کند و لینک‌های قبلی که به اشتراک گذاشتم کار نمی‌کنند. همچنین صفحاتی که در گوگل ایندکس شدند نیز با مشکل مواجه می‌شوند. پس من نیاز دارم از یک اسلاگ ثابت و انگلیسی استفاده کنم که متاسفانه کوارتز در حالت عادی امکان آن را فراهم نکرده است و من مجبورم از روش پیچیده تری استفاده کنم. یعنی اسم فایل را انگلیسی وارد می‌کنم تا اسلاگ انگلیسی باشد. از پراپرتی title هم برای اضافه کردن عنوان فارسی به یادداشت استفاده می‌کنم. با این ترفند می توانم اسلاگ و عنوان یادداشت را جداگانه مدیریت کنم.

من از پوشه ها هم استفاده نمی کنم و بخش اکسپلور را از سایدبار مخفی کردم. چون مسیر پوشه‌ها در اسلاگ نیز وارد می شود که هم باعث طولانی شدن اسلاگ می شود هم با تغییر و جابه‌جایی فولدرها، اسلاگ نیز تغییر می‌کند و همان مشکل از کار افتادن لینک‌های قبلی به وجود می‌آید.

مطمئنا راه حلی برای این مشکل وجود دارد اما هنوز روش ساده و راحتی پیدا نکردم. اینجا هم در مورد این مشکل صحبت شده بود که ظاهرا راه حل آن چندان مناسب نیست.

Footnotes

  1. یکی از مشکلات محاسبه اشتباه مدت زمان تقریبی مطالعه بود که مقداری بیشتر از حالت معمولی آن را تخمین میزد. ظاهرا به خاطر زیاد بودن کلمات ربط در زبان فارسی نسبت به انگلیسی این اتفاق می افتد. مشکل دیگر تفاوت سرعت خواندن هرکس با یکدیگر است مضافا اینکه با توجه به دانش و سطح اطلاعات هر کس مدت زمان مطالعه نیز کم و زیاد می شود. به همین خاطر فکر میکنم نمایش مدت مطالعه یک فیلد اضافی است و کاربرد جدی ندارد.

  2. یک مورد به خاطر صفحه بندی خاصی است که برای موبایل در نظر گرفتم. من در فایل quartz.layout.ts فهرست مطالب را در حالت موبایل قبل از متن بدنه و بعد از ContentMeta قرار دادم. اگر به صورت دستی تصویر را اضافه می کردم فهرست مطالب قبل از تصویر نمایش داده میشد که جالب نبود. در حال حاضر تصویر در بخش ContentMeta قرار گرفته. یک مورد دیگر هم به خاطر شیوه خاصی است که برای صفحات فهرست در نظر گرفتم. با استفاده از پلاگین دیتاویو سریالیزر یک فهرست درست کردم که عنوان و تصویر یادداشت های مرتبط را نمایش می دهد. برای اینکار لازم بود از یک پراپرتی برای استخراج تصاویر استفاده کنم.