quadrats-module

Quadrats CMS NestJS Module (Quadrats 富文本 CMS 模組)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "quadrats-module" with this command: npx skills add rytass/utils/rytass-utils-quadrats-module

Quadrats CMS NestJS Module (Quadrats 富文本 CMS 模組)

Overview

@rytass/quadrats-nestjs 提供 Quadrats CMS 的 NestJS 整合模組,支援富文本內容管理、文章版本控制、多語言內容和圖片上傳。

Quick Start

安裝

npm install @rytass/quadrats-nestjs @quadrats/core

基本設定

import { QuadratsModule } from '@rytass/quadrats-nestjs';

@Module({ imports: [ QuadratsModule.forRoot({ accessKey: 'your-access-key', secret: 'your-secret', // host: 'https://api.quadrats.io', // 預設值 }), ], }) export class AppModule {}

非同步設定

import { QuadratsModule } from '@rytass/quadrats-nestjs'; import { ConfigService } from '@nestjs/config';

@Module({ imports: [ QuadratsModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ accessKey: config.get('QUADRATS_ACCESS_KEY'), secret: config.get('QUADRATS_SECRET'), }), }), ], }) export class AppModule {}

Services

QuadratsArticleService

文章 CRUD 與版本管理:

import { QuadratsArticleService, Language } from '@rytass/quadrats-nestjs'; import { Paragraph } from '@quadrats/core';

@Injectable() export class ContentService { constructor(private readonly articleService: QuadratsArticleService) {}

// 建立文章 async createArticle() { return this.articleService.create({ title: '文章標題', categoryIds: ['category-id'], tags: ['tag1', 'tag2'], contents: [ Paragraph.create({ children: [{ text: '文章內容' }] }), ], language: Language.ZH_TW, releasedAt: new Date(), }); }

// 多語言文章 async createMultiLangArticle() { return this.articleService.create({ title: '多語言文章', categoryIds: ['category-id'], tags: ['multilang'], languageContents: [ { language: Language.ZH_TW, elements: [Paragraph.create({ children: [{ text: '繁體中文內容' }] })], }, { language: Language.EN, elements: [Paragraph.create({ children: [{ text: 'English content' }] })], }, ], }); }

// 取得文章 async getArticle(id: string, versionId?: string) { return this.articleService.get(id, versionId); }

// 取得文章 ID 列表 async getArticleIds() { return this.articleService.getIds({ limit: 10, offset: 0, categoryIds: ['category-id'], tags: ['featured'], }); }

// 新增版本 async addVersion(id: string) { return this.articleService.addVersion({ id, title: '更新標題', categoryIds: ['category-id'], tags: ['updated'], contents: [ Paragraph.create({ children: [{ text: '新版本內容' }] }), ], }); }

// 刪除文章 async removeArticle(id: string) { return this.articleService.remove(id); } }

QuadratsArticleCategoryService

分類管理:

import { QuadratsArticleCategoryService } from '@rytass/quadrats-nestjs';

@Injectable() export class CategoryManagementService { constructor(private readonly categoryService: QuadratsArticleCategoryService) {}

// 取得所有分類 async getAllCategories() { return this.categoryService.getAll(); }

// 取得單一分類 async getCategory(id: string) { return this.categoryService.get(id); }

// 建立分類 async createCategory(name: string, parentId?: string) { return this.categoryService.create(name, parentId); }

// 重新命名分類 async renameCategory(id: string, newName: string) { return this.categoryService.rename(id, newName); } }

QuadratsArticleTagService

標籤查詢:

import { QuadratsArticleTagService } from '@rytass/quadrats-nestjs';

@Injectable() export class TagService { constructor(private readonly tagService: QuadratsArticleTagService) {}

// 取得所有標籤 async getAllTags() { return this.tagService.getAll({ limit: 50, offset: 0, searchTerm: 'tech', // 可選搜尋詞 }); } }

QuadratsArticleImageService

圖片上傳:

import { QuadratsArticleImageService, ImageDetailURL } from '@rytass/quadrats-nestjs'; import { Readable } from 'stream';

@Injectable() export class ImageUploadService { constructor(private readonly imageService: QuadratsArticleImageService) {}

// 上傳圖片 (urlMode = false 或省略,回傳詳細 URL 資訊) async uploadImageWithDetails(buffer: Buffer): Promise<ImageDetailURL> { return this.imageService.uploadImage(buffer, false); // 回傳: ImageDetailURL { id, preload, thumbnails, public, full } }

// 上傳圖片 (不帶 urlMode 時預設為 false,回傳詳細 URL 資訊) async uploadImageDefault(buffer: Buffer): Promise<ImageDetailURL> { return this.imageService.uploadImage(buffer); // 回傳: ImageDetailURL { id, preload, thumbnails, public, full } }

// 上傳圖片 (urlMode = true,回傳簡單 URL 字串) async uploadImageUrl(buffer: Buffer): Promise<string> { return this.imageService.uploadImage(buffer, true); // 回傳: string (URL) }

// 支援 Stream 上傳 async uploadFromStream(stream: Readable): Promise<ImageDetailURL> { return this.imageService.uploadImage(stream); } }

ImageDetailURL

interface ImageDetailURL {
  id: string;         // 檔案 ID
  preload: string;    // 模糊預載 URL
  thumbnails: string; // 小尺寸 URL
  public: string;     // 標準尺寸 URL
  full: string;       // 大尺寸 URL
}

Language Enum

支援的語言(含別名):

import { Language } from '@rytass/quadrats-nestjs';

enum Language {
  DEFAULT = 'DEFAULT',
  ZH = 'TRADITIONAL_CHINESE',       // 繁體中文(別名)
  ZH_TW = 'TRADITIONAL_CHINESE',    // 繁體中文
  ZH_CN = 'SIMPLIFIED_CHINESE',     // 簡體中文
  EN_US = 'ENGLISH_UNITED_STATES',  // 美式英文(別名)
  EN = 'ENGLISH_UNITED_STATES',     // 美式英文
  EN_GB = 'ENGLISH_UNITED_KINGDOM', // 英式英文
  JA_JP = 'JAPANESE',               // 日文
  JP = 'JAPANESE',                  // 日文(別名)
  KO_KR = 'KOREAN',                 // 韓文
  KR = 'KOREAN',                    // 韓文(別名)
  ES = 'SPANISH_SPAIN',             // 西班牙文
  PT = 'PORTUGUESE_PORTUGAL',       // 葡萄牙文
  DE = 'GERMANY_GERMAN',            // 德文
  IT = 'ITALIAN_ITALY',             // 義大利文
  FR = 'FRENCH_FRANCE',             // 法文
}

提示: ZH
/ZH_TW
、EN
/EN_US
、JP
/JA_JP
、KR
/KO_KR
 為相同值的別名,可依習慣選用。

Data Types

QuadratsArticle

interface QuadratsArticle {
  id: string;
  versionId: string;
  title: string;
  categories: QuadratsArticleCategory[];
  tags: string[];
  releasedAt: Date | null;
  contents: QuadratsArticleContentItem[];
}

QuadratsArticleCategory

interface QuadratsArticleCategory {
  id: string;
  name: string;
}

QuadratsArticleContentItem

import type { QuadratsElement } from '@quadrats/core';

interface QuadratsArticleContentItem {
  language: string;
  elements: QuadratsElement[];
}

Complete Example

import { Module, Injectable } from '@nestjs/common';
import {
  QuadratsModule,
  QuadratsArticleService,
  QuadratsArticleCategoryService,
  QuadratsArticleImageService,
  Language,
} from '@rytass/quadrats-nestjs';
import { Paragraph, createEditor } from '@quadrats/core';

// Module 設定
@Module({
  imports: [
    QuadratsModule.forRoot({
      accessKey: process.env.QUADRATS_ACCESS_KEY!,
      secret: process.env.QUADRATS_SECRET!,
    }),
  ],
  providers: [BlogService],
})
export class BlogModule {}

// 部落格服務
@Injectable()
export class BlogService {
  constructor(
    private readonly articleService: QuadratsArticleService,
    private readonly categoryService: QuadratsArticleCategoryService,
    private readonly imageService: QuadratsArticleImageService,
  ) {}

  // 發佈文章(含圖片)
  async publishPost(data: {
    title: string;
    content: string;
    categoryId: string;
    tags: string[];
    coverImage?: Buffer;
  }) {
    // 上傳封面圖(不帶 urlMode 或 urlMode=false 時回傳 ImageDetailURL)
    let coverUrl: string | undefined;
    if (data.coverImage) {
      const imageResult = await this.imageService.uploadImage(data.coverImage);
      coverUrl = imageResult.public;
    }

    // 建立文章內容
    const elements = [
      Paragraph.create({ children: [{ text: data.content }] }),
    ];

    // 建立文章
    return this.articleService.create({
      title: data.title,
      categoryIds: [data.categoryId],
      tags: data.tags,
      contents: elements,
      language: Language.ZH_TW,
      releasedAt: new Date(),
    });
  }

  // 取得文章列表
  async getPosts(categoryId?: string, page = 1, pageSize = 10) {
    const ids = await this.articleService.getIds({
      categoryIds: categoryId ? [categoryId] : undefined,
      limit: pageSize,
      offset: (page - 1) * pageSize,
    });

    return Promise.all(ids.map(id => this.articleService.get(id)));
  }

  // 更新文章(建立新版本)
  async updatePost(id: string, updates: { title: string; content: string }) {
    return this.articleService.addVersion({
      id,
      title: updates.title,
      categoryIds: [],
      tags: [],
      contents: [
        Paragraph.create({ children: [{ text: updates.content }] }),
      ],
    });
  }

  // 分類管理
  async setupCategories() {
    const tech = await this.categoryService.create('技術');
    const frontend = await this.categoryService.create('前端', tech.id);
    const backend = await this.categoryService.create('後端', tech.id);

    return { tech, frontend, backend };
  }
}

Configuration Options

interface QuadratsModuleOptions {
  accessKey: string;   // Quadrats API Access Key
  secret: string;      // Quadrats API Secret
  host?: string;       // API Host (預設: https://api.quadrats.io)
}

interface QuadratsModuleAsyncOptions extends Pick&#x3C;ModuleMetadata, 'imports'> {
  name?: string;
  useFactory: (...args: any[]) => Promise&#x3C;QuadratsModuleOptions> | QuadratsModuleOptions;
  inject?: (InjectionToken | OptionalFactoryDependency)[];
}

Dependencies

Required:

- @quadrats/core
 ^1.1.5 (富文本元素定義)

Peer Dependencies:

- @nestjs/common
 ^9.4.2

Troubleshooting

文章建立失敗

確保 contents
 或 languageContents
 其中之一有設定:

// 正確: 使用 contents
await articleService.create({
  title: '標題',
  categoryIds: [],
  tags: [],
  contents: [Paragraph.create({ children: [{ text: '內容' }] })],
});

// 正確: 使用 languageContents
await articleService.create({
  title: '標題',
  categoryIds: [],
  tags: [],
  languageContents: [
    { language: Language.ZH_TW, elements: [...] },
  ],
});

// 錯誤: 兩者都沒設定
await articleService.create({
  title: '標題',
  categoryIds: [],
  tags: [],
  // 會拋出錯誤: `contents` or `languageContents` should be set
});

圖片上傳失敗

確認圖片格式和大小符合要求,並使用正確的 Buffer 或 Stream:

import * as fs from 'fs';

// 從檔案讀取
const buffer = fs.readFileSync('image.jpg');
await imageService.uploadImage(buffer);

// 從 Stream 讀取
const stream = fs.createReadStream('image.jpg');
await imageService.uploadImage(stream);

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

wms-module

No summary provided by upstream source.

Repository SourceNeeds Review
General

payment-adapters

No summary provided by upstream source.

Repository SourceNeeds Review
General

invoice-adapters

No summary provided by upstream source.

Repository SourceNeeds Review
General

wms-react-components

No summary provided by upstream source.

Repository SourceNeeds Review