Git Product home page Git Product logo

fresh_blog_plugin's Introduction

๐Ÿšง Work in Progress! This is still an early-stage project. Engage with it or contribute at your own risk.

Fresh Blog Plugin

A ๐Ÿ‹ Fresh plugin designed to add blog functionalities with markdown files powered by MyST.

Core Features

  • Robust MyST Markdown Syntax: Expands authoring capabilities with a syntax that caters to technical and scientific authoring as well as any other blogging needs. Fully compatible with CommonMark, the most known and used Markdown specification.

  • Code & Math: Integrates Shiki for code highlighting and Katex for Tex Math, delivering out-of-the-box functionality for technical content.

  • Feed Syndication: Automatically generates feeds for JSON, Atom, and RSS, making content easily accessible and ensuring wide reach.

  • Modular Components: Offers minimal, reusable components for custom route and layout design, providing unmatched flexibility in blog presentation.

How It Works

This plugin exposes multiple configurable routes:

  • Blog Index Route (default /blog): listing all posts sorted by their date
  • Blog Post Route (default /blog/:slug): showing the detail of a post identified by the :slug which is inferred from the markdown file name
  • Atom 1.0 Feed Route (default /blog/atom)
  • JSON Feed 1.0 Feed Route (default /blog/json)
  • RSS 2.0 Feed Route (default /blog/rss)
  • Provide an optional and minimal CSS (default /freshblog.css)

This plugin is meant to integrate into any existing Fresh website, it doesn't make any assumption about your styles, layouts (i.e _layout.tsx) or your App Wrapper (i.e routes/_app.tsx).

For customization of the default routes path and other options, please refer to the "Options" section below or browse the ./example/ directory.

Getting Started

Quick Setup

Incorporate the plugin with a simple update to fresh.config.ts. Starting a blog is a matter of adding a few lines of code to an existing Fresh project.

import { defineConfig } from '$fresh/server.ts'
import blogPlugin from 'https://deno.land/x/fresh_blog_plugin/mod.ts'

export default defineConfig({
  plugins: [blogPlugin()],
})

โœจ That's it, by default your blog is now accessible on the /blog path and markdown files can be added to the ./posts/ directory.

Include Default Styles (Optional)

Minimal CSS is included with this plugin which you're free to adopt or use as the basis for your own CSS.

This include styles for various components and the code syntax highlighting.

<!-- Add to your App Wrapper `routes/_app.tsx` -->
<link rel="stylesheet" href="/freshblog.css" />

The /freshblog.css file is served during development and automatically exported to your static files at build time. You can inspect its content in ./styles.css.

TailwindCSS: Configuration & Gotchas (Optional)

[TailwinCSS)(https://fresh.deno.dev/docs/examples/migrating-to-tailwind) and in particular its @tailwindcss/typography plugin offer a straighforward path to visually style the content of your posts.

The ./example/ folder demonstrate the usage of TailwindCSS and the .prose CSS utilities classes; size variants, color scale or modifiers.

Example of a tailwind.config.ts:

import { type Config } from 'tailwindcss'
import typographyPlugin from 'npm:@tailwindcss/typography'

export default {
  plugins: [typographyPlugin],
  content: [
    '{routes,islands,components}/**/*.{ts,tsx}',
    'posts/*.md', // this must match `options.contentDir`
  ],
} satisfies Config

Gotcha: It's advisable to align the last rule with your *options.contentDir(s) to ensure any TailwindCSS utilities used within your markdown files don't get pruned.

Options

export interface BlogOptions {
/**
* Title of the blog
* @default { 'Blog' }
*/
title?: string
/**
* Description of the blog content, this is used in the syndicate feeds
* @default { 'This is a Fresh Blog' }
*/
description?: string
/**
* Language code BCP 47, currently used to format date and time format
* @default { 'en' }
*/
language?: string
/**
* Path to folder containing the markdown (*.md) files
* @default { './posts' }
*/
contentDir?: string
/**
* URL path of the blog index page
* @default { '/blog' }
*/
path?: string
/**
* URL path prefix for the blog feeds (rss, atom, json), default will use the same as `path` but it can be set to empty i.e `''` to have feeds on `/rss`, `/atom`, `/json`
*/
feedPathPrefix?: string
/**
* Path of the favicon, this is used in the feeds
* @default { '/favicon.ico' }
*/
favicon?: string
/**
* Copyright text, `{{year}}` and `{{url}}` can be used for automatic replacement
* @default { 'Copryright {{year}} {{url}}' }
*/
copyright?: string
/**
* A string used in RSS 2.0 feed to indicate the program used to generate the channel.
* @default { 'Feed (https://github.com/jpmonette/feed) for Deno' }
*/
generator?: string
/**
* Configuration passed to the code highlighter Shiki where `light` and `dark`
* themes can be set. See https://shiki.style/themes for supported themes.
* @default { { themes: { light: 'material-theme-lighter', dark: 'material-theme-darker' } } }
*/
highlighter?: {
themes: {
light: string
dark: string
}
}
/**
* Name of the CSS file which will be exported by the plugin for the blog
* @default { 'freshblog.css' }
*/
cssFilename?: string

API

The API documentation is available on https://nrako.github.io/fresh_blog_plugin/

Example

Explore the ./example/ directory to understand how blogPlugin() can be implemented. The example demonstrates that a Fresh website can host multiple blogs.

You can also run the example with deno task example.

Provisional License Statement

Current License

This project is licensed under the GNU Lesser General Public License (LGPLv3), selected for its framework that encourages open contributions to the project while facilitating the use of this library/project in both open source and proprietary software. This licensing approach aims to foster a collaborative development environment and maximize the library's/project's applicability and impact across diverse software ecosystems.

Future Licensing

Upon reaching maturity and stability milestones, consideration will be given to adopting a more permissive license, such as the ISC License. Such change would aim to eliminate obligations and extend permissions, fully unleashing the project's potential. Meanwhile, contributors are encouraged to work within the current LGPLv3 licensing framework, ensuring that enhancements and modifications continue to serve the greater good.

fresh_blog_plugin's People

Contributors

nrako avatar

Watchers

 avatar  avatar

fresh_blog_plugin's Issues

Figure out a "pagination" logic for the "index" page of the blog...

Currently, there is no limit on the number of posts listed on index pages, nor is there a limit on how many posts the getPosts function attempts to read from the file system.

I'm just dropping some ideas here as several approaches could be used for a solution:

  • Pagination
  • Capped listing (count-based). Posts beyond that cap would be listed in an "archives" section, which would then need some sort of pagination.
  • Pagination combined with a logic of using subfolder to do some "organization". Through periodic "chores", the blog owner(s) could move any .md file into a subfolder (e.g archives/), which would act as a high-level "filter"; removing posts located in any subfolder(s) from the main "index" listing. However, the posts should still be served on their :slug (derived from their filename) on the "root route" of the blog (permalink โ€“ we don't want redirect, canonical link, here the unique "state" is the file system). However, this might also collide with the concept of subject which should be supported through frontmatter (see #3). The subfolder(s) could be anything, "archives", an arbitrary "category" (which might overlap with subject?), or something like a full year number or any other logic allowing a more fine-grained archiving logic. The subfolders could have a certain level of nesting depths too.
  • ...?

One might want to use a hands-on approach for the "index" route of his blog. Building his page with various components which would highlight the posts of his choice, etc. In any case some kind of "pagination" might needs to exist at some levels (e.g for filters results of posts by tags, subject, etc)

Note: this is a different problem than #7 and this is likely to require a different solution, while the same underlying data method getPosts might be involved.

This problem and any potential solution will need more shaping... I feel there are more rabbit-holes to this than I'd like.

Support MyST frontmatter specs

https://mystmd.org/guide/frontmatter

  • title
    • max 500 chars limit
  • short_title โ€“ 3197db6
  • name โ€“ not sure if this is relevant
  • description
    • max 500 chars limit
  • tags
  • thumbnail
  • subtitle
  • date
  • authors
  • affiliations
  • doi
  • arxiv
  • open_accessโ€“ open_access: not sure if this has any merit in this context
  • license
  • funding
  • github
  • binder โ€“ what is this?
  • subject (max 40 chars)
  • venue
  • biblio โ€“ not clear what's the purpose, how this is used
  • math โ€“ add some challenges in processor
  • abbreviations
  • parts โ€“ what is this?
  • options โ€“ not sure if this will be ever needed in this context

Fix the odd processed results coming out of current usage of MyST

See the following tests

// TODO fix thix test, right now the link is not automatically formatted
// In the myst-cli this is handled here https://github.com/executablebooks/mystmd/blob/4d4116c59479f717bff456f5e3117584df9e1553/packages/myst-cli/src/transforms/dois.ts#L19-L32
Deno.test('transforms doi links for citations', { ignore: true }, async () => {
const html = await processor('[](doi:10.5281/zenodo.6476040)')
assertStringIncludes(
html,
'<a href="https://doi.org/10.5281/zenodo.6476040" rel="noopener">Cockett (2022)</a>',
)
})
// TODO fix thix test, right now the admonition-title appear twice for unclear reason
Deno.test('transforms callouts (admonition) without double titles', {
ignore: true,
}, async () => {
const src = `:::{tip}
Test Callout
:::`
const html = await processor(src)
assertStringIncludes(
html,
'<aside class="admonition tip"><p class="admonition-title">Tip</p><p>Test Callout</p></aside>',
)
})
// TODO fix thix test, right now the input is wrapped in an undesired paragraph block
Deno.test('transforms task list', { ignore: true }, async () => {
const src = `- [ ] foo`
const html = await processor(src)
assertStringIncludes(
html,
'<ul class="contains-task-list"><li class="task-list-item"><input type="checkbox" disabled="">foo</li></ul>',
)
})
// TODO fix thix test, AFAICT this should be supported by markdownit which is used by MyST
Deno.test('transforms GFM striketrough', { ignore: true }, async () => {
const src = `~~Hi~~ Hello, ~there~ world!`
const html = await processor(src)
assertStringIncludes(
html,
'<p><del>Hi</del> Hello, <del>there</del> world!</p>',
)

Can't use relative paths in the TailwindCSS config `content` in Deno

Deno dependencies aren't located in any local "vendors" folder (e.g node_modules), thus using relative paths in the tailwind.config.ts file won't work.

The instruction in the README or in the example folder are misleading.

// For Fresh Blog Plugin
'node_modules/.deno/fresh_blog_plugin/components/**/*.tsx',
'node_modules/.deno/fresh_blog_plugin/routes/**/*.tsx',

'../src/{routes,islands,components}/**/*.{ts,tsx}',

I'm not quite sure how to solve this right now...

Pagination for syndication feeds

This might be a ๐Ÿ‡๐Ÿ•ณ๏ธ

The current library used to create the feeds do not support pagination: jpmonette/feed#84

This is also a gray area in RSS 2.0, in other ATOM & JSON this is well supported.

RSS 2.0

RSS 2.0 itself does not inherently support pagination. The RSS 2.0 specification, as originally released, focuses on providing a feed format for delivering updates to users, including items such as news articles, blog posts, and other content. The specification includes elements for describing the feed and its items but does not include built-in features for pagination, which would allow clients to fetch a subset of items based on a specific page number or limit.

However, pagination can still be implemented with RSS 2.0 feeds through various approaches, depending on the requirements of the publisher and the capabilities of the consuming application. Here are a few common methods:

  1. Custom Elements: Feed publishers can add custom elements to their RSS feeds to indicate pagination controls or information. These might include elements specifying the current page, total number of items, total pages, and links to next or previous pages. Consuming applications that understand these custom elements can then use them to navigate the feed in a paginated manner.

  2. Feed Segmentation: Publishers can segment their feeds into multiple, smaller feeds based on criteria such as date ranges, categories, or arbitrary pagination. Each segmented feed would represent a "page" of content. This approach requires the consumer to know how to access these segmented feeds, typically through different URLs.

  3. Query Parameters: Some feed providers allow clients to request paginated feeds by appending query parameters to the feed URL (e.g., ?page=2&limit=10). This approach is controlled by the server-side implementation and requires the feed consumer to modify the feed URL according to the pagination scheme supported by the provider.

  4. Link Elements: Although not a standard part of RSS 2.0, some feeds include <link> elements (not to be confused with the standard <link> element within an item) or other mechanisms in the feed metadata to provide URLs for pagination. These links can point to the next, previous, first, and last pages of content.

It's important to note that since these methods are not standardized within the RSS 2.0 specification, their support and implementation can vary widely. Consumers of RSS feeds need to rely on documentation provided by the feed publishers or inspect the feeds to determine if and how pagination is implemented.

Atom 1.0

Atom 1.0, unlike RSS 2.0, includes built-in support for pagination, which is a significant advantage for managing large feeds or for applications needing to process feed data in chunks. Pagination in Atom feeds is commonly handled through the use of link elements with rel attributes indicating their relationship to the current feed document. Here's how it works:

Pagination Elements in Atom 1.0

  • <link rel="next">: This link points to the next "page" of entries in the feed. It indicates that there is more content available beyond what is currently being viewed or processed.
  • <link rel="previous"> or <link rel="prev">: Similar to the "next" link but in the opposite direction. It points to the previous page of entries, allowing navigation to earlier content in the feed.
  • <link rel="first">: Points to the first page of entries in the feed. This can be useful for users or applications that want to navigate to the beginning of the feed content.
  • <link rel="last">: Points to the last page of entries. This allows users or applications to jump directly to the most recent content, regardless of their current position in the feed.

Example

In an Atom feed, pagination links might appear as follows:

<link rel="first" href="http://example.com/feed?page=1" />
<link rel="prev" href="http://example.com/feed?page=2" />
<link rel="next" href="http://example.com/feed?page=4" />
<link rel="last" href="http://example.com/feed?page=10" />

Implementation Notes

  • Dynamic Content: Since Atom feeds often represent dynamic content (e.g., blog posts, news articles), the pagination links can change as new content is added. Clients consuming the feed should be prepared to handle these changes.
  • Server-Side Support: Effective pagination requires support from the server generating the Atom feed. The server needs to calculate and insert the appropriate pagination links based on the total content available and the current position within that content.
  • Client-Side Processing: Clients consuming Atom feeds with pagination support should be able to recognize these link elements and use them to navigate through the feed content efficiently, fetching additional pages of entries as needed.

Pagination in Atom 1.0 feeds provides a standardized way to navigate through large sets of feed entries, making it easier for both publishers and consumers to manage and consume feed content effectively.

JSON Feed 1.0

JSON Feed 1.0 supports pagination in a straightforward and flexible manner, similar to Atom 1.0, by utilizing link objects within the feed. These link objects can specify URLs for the next page of content, previous page, and other navigational links, depending on the publisher's implementation. Here's how it works:

Pagination in JSON Feed 1.0

  • next_url: This is a key in the JSON Feed object that points to the URL of the next page of items. When present, it indicates that there are more items to fetch beyond what is included in the current feed document.
  • _prev_url: Though not officially part of the JSON Feed specification, some implementations might include a similar key for the previous page URL, following the naming convention (_prev_url) or a similar one, to maintain consistency with next_url. However, because this is not standardized, its support and naming could vary.

Example

A JSON Feed with pagination might look something like this:

{
  "version": "https://jsonfeed.org/version/1",
  "title": "My Example Feed",
  "home_page_url": "http://example.com/",
  "feed_url": "http://example.com/feed.json",
  "items": [
    {
      "id": "2",
      "content_text": "This is a second item.",
      "url": "http://example.com/second-item"
    },
    {
      "id": "1",
      "content_text": "This is a first item.",
      "url": "http://example.com/first-item"
    }
  ],
  "next_url": "http://example.com/feed.json?page=2"
}

Implementation Notes

  • Flexibility: JSON Feed allows for a flexible implementation of pagination, with the primary method being through the next_url. Publishers can also include custom keys for additional navigational links if they choose, but such implementations should be documented for consumers of the feed.
  • Dynamic Content Management: As with Atom, managing dynamic content via pagination in JSON Feed requires the publisher to dynamically generate the next_url (and potentially other navigational links) as content is added or as the feed is requested.
  • Client-Side Handling: Clients consuming JSON Feeds should be prepared to handle pagination by detecting the presence of next_url and making additional requests as needed to fetch the entire content of the feed.

Pagination support in JSON Feed 1.0 makes it easier for both publishers and consumers to manage and navigate large sets of data in a feed, providing a simple yet effective mechanism for content discovery and consumption.

Push content to feed items

Push content to feed items

posts.map((post) => {
const item: FeedItem = {
id: `${origin}/${post.title}`,
title: post.title,
description: post.description,
date: post.date,
link: `${origin}${options.path}/${post.slug}`,
copyright,
published: post.date,
}
feed.addItem(item)
})

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.