diff --git a/docs/tutorials/blogs/basic.md b/docs/tutorials/blogs/basic.md new file mode 100644 index 000000000..16bf7c46b --- /dev/null +++ b/docs/tutorials/blogs/basic.md @@ -0,0 +1,375 @@ +# Basic blogs + +Blogs are a great way to engage with your audience. Software developers can use +a blog to announce new features, demonstrate their usage and provide background +information. You can demonstrate competence by commenting on the state of the +art or document your own work as best practice. Posts on current topics can help +draw in visitors for your main website and can keep your audience engaged. Of +course, you can blog about any topics close to your heart. + +The [blog plugin] makes running a blog alongside your other content easy but you +can also configure it to run a stand-alone blog if posts are the only kind +of content you need. + +After a brief overview of the basic concepts of a blog, this tutorial guides you +through the process of configuring the [blog plugin], setting up your blog, +creating posts, and defining post metadata. + +[blog plugin]: ../../plugins/blog.md + +__Time required:__ typically 20 minutes + +## Key concepts + +**Post, excerpt**: a blog consists of a number of self-contained _posts_ (often called +articles) and an index page that shows the posts in reverse chronological order, with +the most recent post at the top. The index page usually shows only a short _excerpt_ and a +link that the user can click to navigate to the full post. + +**Metadata**: both the index page and the post itself list information such as +when you published the post, when you updated it, who the author is, and what the +expected reading time is. + +**Slug**: since the blog posts are primarily arranged by time and not into a hierarchy, +their URLs do not reflect such a structure. Instead, each post's URL +contains a shortened description, the _slug_, which is usually derived from +the first heading in the post. + +**Navigation**: the main navigation structure is the timeline, which you can +subdivide into _categories_. The main index page shows the more recent posts +while an _archive_ section allows access to older ones, organized by year. +In addition, posts can be _tagged_ and _tag index pages_ provide an additional +navigation structure based on content. + +You can see all these elements on the [Material for MkDocs blog]. + +[Material for MkDocs blog]: https://squidfunk.github.io/mkdocs-material/blog/ + +## Setting up your blog + +The blog plugin is part of Material for MkDocs but you need to configure it +in the `mkdocs.yml`. + +!!! example "Set up a blog" + + If you have not done so already, create a project for your blog, + then edit the `mkdocs.yml` file to make sure if has the following content: + + ```yaml + site_name: Blog Tutorial + site_description: an example blog set up following the tutorial + site_url: http://www.example.com + + theme: + name: material + + plugins: + - search + - blog + ``` + + The blog plugin will create a directory structure for your blog posts if it + does not exist, so simply run `mkdocs serve` to get: + + ``` + docs + ├── blog + │   ├── index.md + │   └── posts + └── index.md + ``` + +Now create your first blog post in `docs/blog/posts`. You can use any +naming convention and directory structure you like for your posts, as long as +they are inside `docs/blog/posts`. + +Each post _must_ have a page header, which appears at the top of the Markdown +code between lines with three dashes. Within this header, you need to have at +least a `date` entry but you can add other data, as you will see below. +Following the header comes the page content. Note that it is important +to have a level one heading as the plugin uses it to produce the _slug_. Also, +by adding `` to the page, you can define where the excerpt will end +that the index page shows. + +!!! example "Write your first post" + + Create a file `docs/blog/posts/myfirst.md` with the following contents: + + ``` + --- + date: + created: 2023-12-31 + --- + + # Happy new years eve! + + We hope you are all having fun and wish you all the best for the new year! + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. + ``` + + Then, run `mkdocs serve` and point your web browser at + `http://localhost:8000/blog`. + + The blog plugin automatically creates navigation elements for + the blog. The index page shows only the extract. When you select the + "Continue reading" link, you will get to the full blog post. Note how it + has a URL generated from the first-level heading. + +!!! tip "Navigation" + + We also have a [tutorial on navigation] that shows you how to change the + automatically created navigation and integrate the blog into your existing + navigation structure. It shows how to create secondary nagigation, produce + author pages, and control pagination. + +[tutorial on navigation]: navigation.md + +## Post metadata + +In addition to the date, you can provide other metadata and give the plugin +instructions, such as to treat a post as a draft or to pin it. + +### Drafts + +You may want to produce a draft of a blog post and work with it locally but +exclude it from the build that you publish. Simply add a field to the page +header to indicate that a post is still in draft form. + +!!! example "Create a draft" + + Create a second blog post in `docs/blogs/posts/draft.md` with the following + contents: + + ```hl_lines="3" + --- + date: + created: 2024-01-01 + draft: true + --- + + # Happy new year! + + Happy 2024 to everyone. Wishing you all the best! + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. + ``` + + Now, note how the draft appears on the index page but with a label that + indicates that it is a draft. When you run `mkdocs build`, the draft will + _not_ appear in the output: + + ``` + $ mkdocs build + $ ls site/blog + site/blog + ├── 2023 + │   └── 12 + │   └── 31 + │   └── happy-new-years-eve + │   └── index.html + ... + ``` + + The first blog post for 2024 is not there yet because it is still in draft + stage. Remember to remove the `draft` setting in the header when it is time + to publish it. + +If you are using the [Insiders Edition], you can also create +a folder to keep your drafts in and use the [Meta plugin] to add the +`draft` header setting to all the posts in that folder. This has the advantage +that it is easier to see which posts are still in draft form. We will cover the +Meta plugin later on. + +[Meta plugin]: ../../plugins/meta.md + +### Edits + +Sometimes, bloggers need to update a post. This might happen when you make +a mistake or when something changes that you need to reflect in the post. To +indicate you have edited a post, you can include an `updated` date in the page +header. + +!!! example "Editing a post" + + Make a change to your first blog post, then add an edit date to the header: + + ```hl_lines="3 4" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + --- + ``` + +The Metadata section of the blog post itself will contain the edit date, +though the index page omits this detail by default. + +### Reading time + +To give the reader some idea of how long it might take them to read a post, +a read time is automatically calculated. If you want to override this, you can +do so in the page header by specifying the number of minutes you estimate +your readers will take the read the post. + +!!! example "Overriding the reading time" + + Add a reading time override to your first blog post: + + ```hl_lines="5" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + readtime: 15 + --- + ``` + +### Pinning + +Sometimes, blog authors want to 'pin' a specific post so that it will always +appear at the top of the index page, no matter what else gets published. If you +are using the [Insiders Edition], you can achieve this by adding the `pin` +attribute in the page header: + +!!! example "Pin a post " + + Add the `pin` attribute to your first blog post: + + ```hl_lines="5" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + readtime: 15 + pin: true + --- + ``` + + Observe how this makes the post appear on top of the index page even though + its publication date is prior to other posts. A small pin icon shows that the + post has been pinned. + +### Related links + +When your blog is part of a wider site such as technical documentation, you +will want to provide links from blog posts into your other content. One way you +can do this is to have a related links section. In the [Insiders Edition], the +blog plugin can create one for you if you provide link targets in your page +header: + +!!! example "Add a related links section " + + Add the following to a blog post: + + ``` hl_lines="5-7" + --- + date: + created: 2023-12-31 + ... + links: + - index.md + - blog/index.md + --- + ``` + + The related links appear underneath the Metadata section. + +The nice thing here is that you do not need to provide a page title. The plugin +will deduce the link text by applying the same logic that MkDocs uses for the +main navigation. In fact, the syntax is the same as that of the `nav` section +in the `mkdocs.yml`, so you can override the title if you want and even define +subsections: + +!!! example "Override the page titles" + + Change the link section to override the page titles: + + ```hl_lines="6-9" + --- + date: + created: 2023-12-31 + ... + links: + - Homepage: index.md + - Blog index: blog/index.md + - External links: + - Material documentation: https://squidfunk.github.io/mkdocs-material + --- + ``` + +The plugin renders related links in the left sidebar on screens that are wide +enough and at the bottom of the post on narrow screens. Change the size of your +browser window to see this in action. + +## Meta plugin + +The Meta plugin is available in the [Insiders Edition]. +It helps simplify the management of metadata that is common to a group of +files in the same subdirectory. Instead of having to repeat the +same metadata in the page headers of a number of files, you can add a +`.meta.yml` file in the directory and the Meta plugin will merge its contents +into the headers of all the pages contained. +Settings from the page header take precedence, so you can always override +settings by adding them to a post's header. + +For example, you may want to manage drafts by keeping them in a directory +together so that they are not only flagged as drafts but also easier to find. +(Otherwise, you would need to inspect the page headers or trace back from the +output to the files to figure out which posts are drafts.) + +!!! example "Drafts using the Meta plugin " + + You first need to activate the plugin in your `mkdocs.yaml`: + + ```yaml hl_lines="4" + plugins: + - search + - blog + - meta + ``` + + Now create the folder for the drafts: + + === "MacOS/Linux" + + ```bash + $ mkdir docs/blog/posts/drafts + ``` + + === "Windows" + TODO + + Now, within this folder, crate a file `.meta.yml` that contains: + + ```yaml + draft: true + ``` + + Add another blog post and store it in `docs/blog/posts/drafts`. When you + look at it locally, you will see the label that identifies it as a draft, + while in the version built for publication it does not appear. To move a + post from draft status to published, simply move it outside `drafts/`. + +[Insiders Edition]: ../../insiders/index.md + +## What's next? + +You should now have a working blog. However, as it accumulates content, you +may want to make sure that people can find posts they are interested in, so +you may want to add secondary navigation with tags and categories. You may +have more than one author and want to attribute posts to them as well as +generate author pages for them. We have a [tutorial on navigation, pagination, +and authors] that covers these topics. + +[tutorial on navigation, pagination, and authors]: navigation.md + +You may want to increase engagement with your blog by allowing people to +subscribe to an RSS feed or by setting up a comment system. The [engagement +and dissemination tutorial] walks you through setting these up. + +[engagement and dissemination tutorial]: engage.md diff --git a/docs/tutorials/blogs/engage.md b/docs/tutorials/blogs/engage.md new file mode 100644 index 000000000..8f23b48c3 --- /dev/null +++ b/docs/tutorials/blogs/engage.md @@ -0,0 +1,369 @@ +# Engagement and dissemination + +You can foster reader engagement and improve the dissemination of content +on your blog by providing an RSS feed that people can subscribe to and by +integrating a discussion system. To learn more about who is or is not reading +your posts, you may want to integrate an analytics system. You may also want +to post on social media when you public a new blog post. This tutorial gives +you a leg up on all of these topics. + +__Time required:__ typically 30 minutes + +## RSS feeds + +An _RSS feed_ allows users to subscribe to a blog so that they get notified when +you publish new posts. RSS Feed readers are often used to access blogs that a +user follows. They usually support downloading the blog content for offline +consumption. + +An easy way to create an RSS feed for your blog is to use the +[MkDocs RSS Plugin], which is well integrated with Material for MkDocs. +Since it is a third-party plugin, you need to install it before using it. + +[MkDocs RSS Plugin]: https://guts.github.io/mkdocs-rss-plugin + + +!!! example "Add an RSS feed" + + Install the RSS plugin into your project: + + ``` + $ pip install mkdocs-rss-plugin + ``` + + It is important that have the `site_name`, `site_description` and + `site_url` settings configured as [instructed in the basic blog tutorial]. + The RSS plugin makes use of this information to construct the feed, so make + sure you have configured them. + + [instructed in the basic blog tutorial]: basic.md#setting-up-your-blog + + Now, configure the plugin in the `mkdocs.yml`. The options provided restrict + the pages that RSS entries are created for to the blog posts, which is + probably what you want. Also note the configuration of the date fields to + match the format that Material for MkDocs uses to accommodate both a + creation date and a date for updates. + + ```yaml hl_lines="9" + plugins: + - ... + - rss: + match_path: "blog/posts/.*" + date_from_meta: + as_creation: date.created + as_update: date.updated + ``` + + Have a look at http://localhost:8000/feed_rss_created.xml to see the RSS + feed in all its XML glory. You can use a browser like Firefox or Chrome that + can display the raw RSS feed or use `curl` to get the feed and `xmllint` to + format it. (You may need to install these tools.) + + ``` + curl -s http://localhost:8000/feed_rss_created.xml | xmllint --format - + ``` + + You may also want to try your feed with a feed reader. There are various desktop + and mobile apps as well as online services. Of course, to use the latter you + will need to deploy your project somewhere that is accessible to them. + +This minimal configuration should work well if you have not made any changes +to the default configuration of the blog plugin. For more information on adapting +the feed to your needs, see [the RSS plugin's documentation]. + +[the RSS plugin's documentation]: https://guts.github.io/mkdocs-rss-plugin/ + +## Social media buttons + +Social media buttons can serve two purposes: to allow your readers to navigate +to your social media profiles or to share content you have published via their +own accounts. + +### Profile links + +Links to social media profiles a usually provided in the footer of pages and +Material for MkDocs makes this easy. All you need to do is to provide the +necessary links and define the icons to use. + +!!! example "Adding social media profile links" + + Add an `extra` section to your `mkdocs.yml` and, within it, a `social` + section to contain a list of link definitions. These consist of the logo + to use and the link to the profile. + + ```yaml + extra: + social: + - icon: fontawesome/brands/mastodon + name: squidfunk on Mastodon + link: https://fosstodon.org/@squidfunk + ``` + + For the `icon`, you can choose any valid path to an icon bundled with the + theme. The `name` will be used as the title attribute for the icon and + including this improves accessibility. + For popular social media systems, the link needs to be absolute and + needs to include the scheme, most likely `https://`. + + You can also use other schemes. For example, to cerate an icon that allows + people to create an email, add this: + + ```yaml + extra: + social: + - icon: /fontawesome/regular/envelope + name: send me an email + link: mailto: + ``` + + Finally, you can specify a URL within your site, such as to your contact + page. It is possible to specify only the path to the page: + + ```yaml + extra: + social: + - icon: /material/mailbox + name: contact us + link: /contact + ``` + +### Share and like buttons + +Adding buttons that let people share your content on social media is a bit +more involved, which is why there are companies offering components for this. + + +!!! tip "Data Protection" + + "Share" and "Like" buttons that use integrations provided by social media + companies often leave copious data traces even when the user does not + interact with these buttons. If you choose to integate such feature on + your site please be aware of the data protection implications and your + duties as a provider to ensure that processing occurs only once the user + has granted consent. + +This implementation of share buttons deliberately does not use third party code. +It supports sharing to Twitter/X and Facebook without causing a data flow to +these companies whenever someone views the pages. Only when someone clicks a +share button will there be interactions with those companies' servers. + +!!! example "Add share buttons" + + In order to add the share buttons, you can add a hook that appends buttons + for sharing the current page. + + Create a directory `hooks` in your project root and configure it + in your `mkdocs.yml`: + + ```yaml + hooks: + - hooks/socialmedia.py + ``` + + Add the file `hooks/socialmedia.py` with the following Python code: + + ```python + from textwrap import dedent + import urllib.parse + import re + + x_intent = "https://twitter.com/intent/tweet" + fb_sharer = "https://www.facebook.com/sharer/sharer.php" + include = re.compile(r"blog/[1-9].*") + + def on_page_markdown(markdown, **kwargs): + page = kwargs['page'] + config = kwargs['config'] + if not include.match(page.url): + return markdown + + page_url = config.site_url+page.url + page_title = urllib.parse.quote(page.title+'\n') + + return markdown + dedent(f""" + [Share on :simple-x:]({x_intent}?text={page_title}&url={page_url}){{ .md-button }} + [Share on :simple-facebook:]({fb_sharer}?u={page_url}){{ .md-button }} + """) + ``` + + The hook first checks if the current page is a blog post and then appends + Markdown code for the share buttons. The buttons use icons, so you also need + to configure the following markdown extensions: + + ```yaml + markdown_extensions: + - attr_list + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + ``` + + +## Add a discussion system + +Allowing your readers to comment on your posts is a great way of receiving +feedback, learning something, as well as giving readers the opportunity to +discuss the content and the topic it is about. + +There are plenty of discussion system out there and you will need to consider +your audience when choosing one appropriate for your blog. Likewise, you will +also need to consider existing commitments to communication channels. If you +are a heavy user Slack, for example, you may have a string preference for this +system. Consider that when you add a communication channel, you will need to +be prepared to use it regularly and to moderate discussions. + +### Giscus integration + +In this tutorial, we will be using [Giscus] because it is free, open source, +and uses [GitHub Discussions] as a backend. Because a lot of users of Material +for MkDocs use GitHub, this seems like an obvious choice. + +[Giscus]: https://giscus.app/ +[GitHub Discussions]: https://docs.github.com/en/discussions + +To add Giscuss to your blog you will need to go through a number of steps: + +1. Create a GitHub repository if there is not already one +2. Turn on discussions and install the [Giscus app] +3. Configure the code needed to embed Giscus into your blog +4. Add the code to your MkDocs project + +[Giscus app]: https://github.com/apps/giscus + +You may want to create a test repository for this tutorial that you can +scrap later on. The instructions below assume that you are user "example" +and that you create a repository "giscus-test." The repository will need +to be public for people to be able to use the discussions. + +In the instructions given below, you will need to replace at least the username +but also the repository name if you chose another name such as when you +want to work directly on an existing repository. + +!!! example "Turn on discussions and install the Giscus app" + + Once the repository is set up, go to its settings page and find + `Features` in the `General` section. Tick the checkbox for `Discussions`. + You will see that `Discussions` appears in the top navigation for the + repository. If you are using a live repository then you may want to add some + minimal content to the dicussions section at this point and come back to the + tutorial. + + Next, you need to install the [Giscus app] by following the link in this + sentence, and choosing `Install`, then following the instructions to choose + where the Giscus app is to be installed: + + 1. Choose the account or organization for the repository you want to use. + 2. Choose to install only on select repositories and select the one you + want to use. Note that you can choose more than one repository here. + 3. Select `Install` at the end. You may need to authenticate to give + permission for this to happen. + 4. You will end up on the `Applications` page in your settings, where you + can control the Gicsus app and uninstall it if so desired. + +That is all the preparation you will need for the repository. Next, it is time +to generate a piece of code that embeds Giscuss in your site. The resulting code +snippet will look something like this: + +```html + +``` + +!!! example "Configure the code needed to embed Giscus into your blog" + + Go to the [Giscus homepage] and configure the embedding code. There are a + number of settings: + + 1. Choose the language + 2. Enter the username / organization name and repository name + 3. Choose how the discussions are to be mapped to the page on your blog. + Because for a blog post the title is the basis of the URL, it makes + sense to use the `Discussion title contains page ` option. + 4. Under `Discussion Category` choose `Announcements` to limit the creation + of new discussions to Giscuss and people with maintainer or admin + permissions. + 5. Under `Features`, select the following: + 1. Enable reactions for the main post + 2. Emit discussion metadata + 3. Place the comment box above the comments + 6. Under `Theme`, select `Preferred color scheme` so that Giscus matches + the color scheme selected by the user for your site. + +[Giscus homepage]: https://giscus.app/ + +With these settings in place, you now need to integrate the code into your +site. There is a partial `partials/comments.html` that exists for this purpose +and is empty be default. It is included by the `content.html` partial, so will +be included for every page on your site. You may or may not want this. In this +tutorial, you will limit the Giscus integration to only blog posts but it is +easy enough to leave out the code that achieves this if you want to have Giscus +discussions active for every page. + +!!! example "Add Giscus integration code" + + First, you need to create an `overrides` directory that will contain the + templates and partials you want to override. + + ``` + mkdir -p overrides/partials + ``` + + You need to declare it in your `mkdocs.yaml`: + + ```yaml hl_lines="3" + theme: + name: material + custom_dir: overrides + ``` + + Now add a file `overrides/partials/comments.html` and paste in the code + snippet you obtained from the Giscus homepage. Look at the result locally + and you will see that the integration is active on all pages of the site. + If you want to restrict it to your blog posts, you need to add a conditional + around the Giscus script that tests if comments should be included. A simple + way of doing this is to test for a metadata flag: + + ```html + {% if page.meta.comments %} + <script>...</script> + {% endif %} + ``` + + The disadvantage is that you now need to manually turn on comments for each + blog post - unless you want to turn them off on some. To get the comments + section on all blog posts, use code like this: + + ```html + {% if page.file.src_uri.startswith('blog/posts') %} + <script>...</script> + {% endif %} + ``` + + You should see now that the Giscus comments are added at the bottom of your + blog posts but not on other pages. + +## What's next? + +This is the end of the blog tutorial. We hope you have enjoyed it and manage to +set up your blog the way you like it. There are numerous other features and +options that we have not been able to cover here. The [blog plugin reference] +provides comprehensive documentation for the plugin. You may also want to +look at the [social plugin tutorial] to generate social cards for your blog +posts that get displayed when you post links to social media systems. + +[blog plugin reference]: https://squidfunk.github.io/mkdocs-material/plugins/blog/ +[social plugin tutorial]: ../social/basic.md diff --git a/docs/tutorials/blogs/navigation.md b/docs/tutorials/blogs/navigation.md new file mode 100644 index 000000000..4634ca250 --- /dev/null +++ b/docs/tutorials/blogs/navigation.md @@ -0,0 +1,525 @@ +# Navigation, authors, and pagination + +The Blog plugin provides blog-style navigation with a reverse-chronological +index page and an archive organized by year by default. This tutorial shows +how you can configure details of the default navigation, configure authors, and +add more navigation options using categories and the [Tags plugin]. + +[Tags plugin]: ../../plugins/tags.md + +__Time required:__ typically 30 minutes + +## Integrating navigation + +So far, you have let the Blog plugin and MkDocs worry about navigation. For some +use cases, this might be enough and it is simply sufficient to not declare a +`nav` section in the `mkdocs.yml`. + +However, you may want to integrate a blog with other content and a navigation +structure that you have defined in the `nav` section of the configuration. +In such cases, you need to provide a place where the Blog plugin should +attach the blog navigation to the rest of the navigation structure. + +!!! example "Integrate with site navigation" + + Add the following to your `mkdocs.yml` to see how the Blog plugin can + integrate the blog navigation with the overall navigation structure. + Note that the only thing you need to specify at this point is the + index page for the blog and its path must match the `blog_dir` setting, + which is `blog` by default: + + ```yaml hl_lines="5 6" + nav: + - Home: index.md + - Install: install.md + - Usage: usage.md + - Blog: + - blog/index.md + ``` + + You will notice that "Blog" is duplicated in the navigation structure. To + avoid this, you can use the `navigation.indexes` feature to make the blog + index the seciton index page for the blog: + + ```yaml hl_lines="3 4" + theme: + name: material + features: + - navigation.indexes + ``` + +!!! tip "Stand-alone blog" + + If what you need is a stand-alone blog instead of one that is integrated with + a larger site, this can be done by using the `blog_dir` configuration option. + To see how this is done, see [setting up a blog]. + The rest of the tutorial assumes that you are integrating the blog with + a wider site. + +[Setting up a blog]: ../../setup/setting-up-a-blog.md#blog-only + +!!! tip "Adding pages" + + You can add additional pages to the blog section by putting them into + `docs/blog` (and adding them to the navigation). The blog archive will be + added to the navigation after these pages. + +## Configuring the archive + +By default, the blog archive lists posts by year only. If you want to add +listings by month, you can configure the date format for the archive. + +!!! example "Organize posts by month" + + Add the following to your `mkdocs.yml` to get a listing with the month + name (in the language selected in the theme options): + + ```yaml hl_lines="2" + - blog: + archive_date_format: MMMM yyyy + ``` + + If you do not want the full month name, you can make the the date + configuration `MM/yyyy`, for example. + + If you want to add the day, you can add a placeholder for them. + For example, to get an American-style output, make it `MM/dd/yyyy`. + For the plugin to sort the blog posts by the full date, you will + also need to set the `archive_url_date_format` to include the month + and day, so make it `MM/dd/yyyy` as well. + +## Using categories + +Categories are a way to make blog posts accessible by topic while retaining +the navigation structure based on chronology within each category listing. +Use them when there is a limited set of non-overlapping categories that +you can sort your posts into. + +Categories appear in the main navigation, so are directly accessible from there. +This implies that there are relatively few categories as otherwise the +`categories` section in your main navigation will become too crowded. + + +!!! example "Add a category" + + Add a category to your first blog post by adding it to the page header: + + ``` hl_lines="4 5"" + --- + date: 2023-12-31 + updated: 2024-01-02 + categories: + - Holidays + --- + ``` + + Now that the blog post has been categorised, `Holidays` appears under + `Categories` in the main navigation and the blog post appears in the + index page for this category. + + +!!! tip "Single or multiple categories?" + + While it is traditionally the case that a blog post would belong to only + one category, Material for MkDocs actually allows you to assign more + than one. While this gives you a degree of freedom, you should + probably not use this too much, not least because you can use tags to + deal with multiple classifications. We will cover them in the next step. + +Material allows you to control which categories blog authors can use. You +declare them in the `mkdocs.yml`. This way you can make sure everyone sticks +to agreed categories and that the plugin detects typos. + +!!! example "Control your categories" + + Add a `categories_allowed` entry to the configuration of the Blog plugin + with the entries "Holidays" and "News": + + ```yaml hl_lines="5-7" + plugins: + - search + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + ``` + + Now, when you add a category to a blog post that does not match one of these + two, you should get a build error. + +## Using tags + +The [Tags plugin] provides another way to classify blog posts and to make +them accessible independently of the main navigation structure. Tags are useful +for making related content easily discoverable even if it is in different parts +of the navigation hierarchy. + +[Tags plugin]: https://squidfunk.github.io/mkdocs-material/plugins/tags/ + +You may have a tutorial like this one as well as a more comprehensive setup guide +and reference documentation. Adding the same tag to all three shows that they +are related. As you will see, it is possible to navigate from a tagged page to +the tag index and, from there, to other pages that carry the same tag. + +!!! example "Enable the plugin and add tags" + + First, you need to add the plugin to your `mkdocs.yml`: + + ```yaml hl_lines="8" + plugins: + - search + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + - tags + ``` + + Once this is done, you can add tags to posts in the page header: + + ``` hl_lines="9-12"" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + authors: + - material + categories: + - Holidays + tags: + - new year + - hogmanay + - festive season + --- + ``` + +You should see the tags that you defined at the top of the post. However, at the +moment that is it. While the blog plugin automatically creates an index page for +categories, the tags plugin does not do the same for tags. This is because the +tags plugin is not specific for blogs. You can use it for any site content, so +it is not obvious were the tag index should go. + +You can configure a basic tag index using the public version of Material for +MkDocs. The Insider Edition supports this as well, of course, but also provides +an alternative index mechanism that allows for an arbitrary number of tag +indexes, scoped listings, shadow tags, nested tags, and much more. + +!!! example "Adding a tags index" + === "Basic tag index" + + To configure a tag index using the public version, add a `tags_file` entry + to your configuration of the tags plugin and configure it in your `nav` + section. Remember to add a colon at the end of the existing `tags` entry. + + ```yaml hl_lines="8-9 17" + plugins: + - search + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + - tags: + tags_file: blog/tags.md + + nav: + - Home: index.md + - Install: install.md + - Usage: usage.md + - Blog: + - blog/index.md + Tags: blog/tags.md + ``` + + The tag index will be appended to the configured page, which you should + now create at the location specified. + + Note that you can put the tag index page anywhere in your primary + navigation, so if you are using tags elsewhere instead of just in your + blog then you may want to have the tag index outside the blog section + of the navigation. + + + === "Insider Edition" + + To add a tag index, you add a placeholder in a Markdown file to tell + the plugin to insert an index at that point. This means that you + can add content before and after the index. Crucially, you can add + placeholders in multiple pages, each with a configuration of what + subset of tags should be displayed in the index. + + The simplest index page looks like this. Create it under `docs/tags.md`. + + ```markdown + # Tag index + <!-- material/tags --> + ``` + + Now, you may want to keep the tags for your blog separate from tags + you use in the rest of your page. You can achieve this by assigning + the tag index a scope. Put the following under `docs/blog/tags.md`: + + ```markdown + # Tag index for the blog + <!-- material/tags { scope: true } --> + ``` + + You now have two index pages: one covers the whole site and one + covers only the blog. Add both to the navigation: + + ```yaml + nav: + - Home: index.md + - Tags: tags.md + - Blog: + - blog/index.md + - blog/tags.md + ``` + + The tags plugin in the Insider Edition is an incredibly powerful tool + and we can only scratch the surface of what is possible with it. If you + want to explore more after you have worked for this part of the tutorial, + have a look at the [tags plugin reference]. + +[tags plugin reference]: ../../plugins/tags.md + +## Defining authors + +If your blog has more than one author then you may want to identify the author +for each blog post. The blog plugin allows you to create a file that contains +the author information and to then reference the authors of a particular post in +the page header. + +!!! example "Create author info" + + Create a file `docs/blog/.authors.yml` with this content: + + ```yaml + authors: + team: + name: Team + description: Creator + avatar: https://simpleicons.org/icons/materialformkdocs.svg + squidfunk: + name: Martin Donath + description: Creator + avatar: https://github.com/squidfunk.png + ``` + + and then add a line to the header of the first post: + + + ```hl_lines="5-6" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + authors: + - team + --- + ``` + + Note that `authors` is a list, so you can specify multiple authors. + +With the Insiders edition, you can create custom author index pages that +can highlight the contributions of an author as well as provide additional +information about them. + +!!! example "Add author page <!-- md:sponsors -->" + + First, you need to enable author profiles in the `mkdocs.yml`: + + ```yaml hl_lines="8" + plugins: + - search + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + authors_profiles: true + ``` + + Check your blog to see that there is now an extra entry in the main + navigation next to `archive` and `categories` that lists the authors and + their contributions. + + To customize the author page, you can create a page that overrides the one + generated by default. First, create the `author` directory that the profile + pages will live in: + + ```hl_lines="3" + docs + ├── blog + │   ├── author + │   ├── index.md + │   └── posts + │   ├── draft.md + │   └── myfirst.md + └── index.md + ``` + + Then create a page `docs/blog/author/team.md`: + + ``` + # The Material Team + + A small group of people dedicated to making writing documentation easy, if + not outright fun! Here are some of the things we have blogged about: + ``` + + As you can see, the author index gets appended to the content you have + written in the Markdown file. + +## Pagination + +Once your blog starts growing, you may not want to pay attention to the number +of posts displayed per page. By default, the plugin displays up to 10 posts on +the index pages. You can change this number separately for the main index, +the archive index pages, and the category index pages. + +!!! example "Changing pagination" + + Add five more blog posts, then set the pagination setting to show five per + page only: + + ```yaml hl_lines="7" + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + authors_profiles: true + pagination_per_page: 5 + ``` + + You will see that the pagination setting for archive and category pages + are inherited from the setting you added. If you want to have different + settings for the different index pages, you can specify each setting + separately: + + ```yaml + - blog: + archive_date_format: MMMM yyyy + categories_allowed: + - Holidays + - News + authors_profiles: true + pagination_per_page: 5 + archive_pagination_per_page: 10 + categories_pagination_per_page: 10 + ``` + +## Blog table of contents + +Another thing you may want to do once you have a large enough number of posts +is to turn on the function that produces a table of contents for the blog +index pages, giving your readers the opportunity to quickly scan the content +of each page for something that interests them without having to scroll +(assuming that the number of post per page is not too big). + +!!! example "Turn on the table of contents feature" + + To produce a table of contents for the blog index pages, add the following + to the configuration of the blog plugin: + + ```yaml hl_lines="2" + - blog: + blog_toc: true + archive_date_format: MMMM yyyy + # ... + ``` + +## Custom slugs + +If, for some reason, you are not happy with the way that Material for MkDocs +turns headings into slugs, you can create your own slugify function or you +can manually define a slug for a specific post. + +!!! example "Slugify function" + + To define your own slugify function, you need to write a Python function + that converts text into a slug given additional arguments from the + configuration. You also need to write a function that returns that + function. + + Say you want to define two slugify functions that you can switch between. + The first one returns a slug similar to what the default slugify function + produces. The second one cuts the result of that up into words and returns + a slug based on a maximum of five of them: + + ```python + import re, functools, unicodedata + + RE_HTML_TAGS = re.compile(r'</?[^>]*>', re.UNICODE) + RE_INVALID_SLUG_CHAR = re.compile(r'[^\w\- ]', re.UNICODE) + RE_WHITESPACE = re.compile(r'\s', re.UNICODE) + + def _make_slug(text, sep, **kwargs): + slug = unicodedata.normalize('NFC', text) + slug = RE_HTML_TAGS.sub('', slug) + slug = RE_INVALID_SLUG_CHAR.sub('', slug) + slug = slug.strip().lower() + slug = RE_WHITESPACE.sub(sep, slug) + return slug + + def _make_slug_short(text, sep, **kwargs): + words = _make_slug(text, sep, **kwargs).split(sep) + return sep.join(words[:5]) + + def slugify(**kwargs): + if 'short' in kwargs and kwargs['short']: + return functools.partial(_make_slug_short, **kwargs) + return functools.partial(_make_slug, **kwargs) + ``` + Save this code in `ext/slugs.py` and also add an (empty) `__init__.py` + file to indicate that the directory is a module. Now you can configure + your custom slugify code like this: + + ```yaml hl_lines="4-6" + plugins: + - blog: + # other entries omitted + post_slugify: !!python/object/apply:ext.slugs.slugify + kwds: + short: true + ``` + + Change the heading of a blog post to be longer than five words and observe + how the slugify function shortens the URL. Change the `short` attribute to + `false` and you can turn this off again. + +If you want to influence the slug only for a single blog post, you can define +it manually by specifying it in the header of the post. Note that this is meant +as a last resort option. Specifying a custom slug manually for every post would +be tedious. + +!!! example "Manually define slug" + + If, for example, you wanted the slug to be 'ny-eve' instead of the somewhat + lengthy 'happy-new-years-eve', you could add the following: + + ```hl_lines="7" + --- + date: + created: 2023-12-31 + updated: 2024-01-02 + readtime: 15 + pin: true + slug: ny-eve + --- + ``` + + The URL for this post should now be + `http://localhost:8000/blog/2023/01/31/ny-eve/`. + +## What's next? + +You may want to increase engagement with your blog by allowing people to +subscribe to an RSS feed, by providing links to your social media profiles, by +providing share and like buttons, or by setting up a comment system. +The [engagement and dissemination tutorial] walks you through setting these up. + +[engagement and dissemination tutorial]: engage.md \ No newline at end of file diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md new file mode 100644 index 000000000..2d52aae06 --- /dev/null +++ b/docs/tutorials/index.md @@ -0,0 +1,56 @@ +# Tutorials + +In addition to the basic getting started guides, we offer tutorials that aim to +show how you can use Material for MkDocs in different use cases. In contrast to +the getting started guides or the reference documentation, the tutorials show +the breadth of features available in Material for MkDocs but also within +the wider MkDocs ecosystem. + +The tutorials guide you through worked examples, so by following them you should +gain not only an understanding of how to use Material for MkDocs, but also +a template for your own projects. For convenience, these templates are also +available as template repositories on GitHub. + +The tutorials assume that you have installed either the [public version] or the +[Insiders edition] of Material for MkDocs and that you have worked through the +[creating your site] setup guide. + +Note that where the features we use require the Insiders edition, we mark these +with the heart icon: <!-- md:sponsors --> If you are using the public version +then you can skip these steps. Sometimes there will be ways of achieving the +same goal that differ between the public version and the Insider edition. In +that case, we will show them in a tabbed view so you can see one or the other. + +[public version]: ../getting-started.md +[Insiders edition]: ../insiders/getting-started.md +[creating your site]: ../creating-your-site.md + +!!! note "Feedback wanted!" + The tutorials are a recent addition to our documentation and we are still + working out what shape they should have in the end. Please contact us if you + want to provide feedback. <!--- TODO: how? --> + + Note, however, that suggestions should be specific and feasible. We want to + focus on creating more content at the moment, instead of developing a + specific styling or behaviour for the tutorials. If there are worthwhile + improvements that we can make through simple customization then we are happy + to consider those. + +## Blogs +* [Basics](blogs/basic.md) (20 min) <br/> + covers the basics of setting up a blog, including post metadata. +* [Navigation, pagination, multiple authors](blogs/navigation.md) (30 min)<br/> + describes how to make it easier for your readers to find content. +* [Engagement and dissemination](blogs/engage.md) (30 min)<br/> + walks you through ways of increasing engagement with your content. + +[:octicons-repo-template-24: Template Repository](https://github.com/mkdocs-material/create-blog) + +## Social cards +* [Basics](social/basic.md) (20 min) <br/> + shows you how to configure Material for MkDocs to create social cards for + your content. +* [Custom cards](social/custom.md) (15 min)<br/> + shows you how to design your own custom social cards. + +[:octicons-repo-template-24: Template Repository](https://github.com/mkdocs-material/create-social-cards) diff --git a/docs/tutorials/social/basic.md b/docs/tutorials/social/basic.md new file mode 100644 index 000000000..57b832787 --- /dev/null +++ b/docs/tutorials/social/basic.md @@ -0,0 +1,203 @@ +# Basic social cards + +Social cards are images that other systems such as social media can display as +a preview for content linked to. It is easy to get started with the social +plugin, true to the motto of Material with MkDocs: "batteries included." + +## Basics + +Before you start, there are just a couple of [dependencies to install]. These +are libraries for image processing that the plugin needs to produce the social +cards, as well as their Python bindings. + +[dependencies to install]: https://squidfunk.github.io/mkdocs-material/plugins/requirements/image-processing/ + +With those prerequisites met, it is simply a matter of activating the plugin, +which will: + +* produce the social cards as PNG images for each page in your site; +* create meta data in the headers of your site's pages that will provide + social media systems with key information and tell them how to find the + social card image. + +!!! example "Add social cards" + + Simply add the social plugin to your list of plugins: + + ```yaml hl_lines="3" + plugins: + - search + - social + - ... + ``` + +Now, then you run `mkdocs build` and look at the `site` directory, you will +see that it contains subfolders under `assets/images/social` that reflect +the structure of your Markdown files. Each page has a corresponding PNG file +that contains the social card image. + +Have a look at the generated HTML and you will see the metadata produced in +the `head` element, including one entry that points to the image. + +## Background color + +The social plugin has configuration options for changing aspects such as colors, +images, fonts, logos, the title, even the description. You can configure them +for all social cards in the `mkdocs.yml` and, in the Insiders Edition, they can +be overridden in the page header for individual pages. + +!!! example "Change the background color" + + To change the background color to an attention-grabbing hot pink, + you might add: + + ```yaml hl_lines="4-5" + plugins: + ... + - social: + cards_layout_options: + background_color: "#ff1493" + ``` + +## Logos + +By default, the plugin uses the logo that you set for the whole site, either +through the `theme.logo` or the `theme.icon.logo` setting. The difference +between the two is that the `theme.icon.logo` version will directly embed the +logo's SVG code into the HTML, allowing it to inherit CSS color settings. When +you use `theme.logo`, the Material includes the logo as an image. + +You can set your own logo specific for the social cards as well. The path used +is relative to your project root and needs to point to an SVG file or a pixel +image. It should be rectangular and have a transparent background. + +!!! example "Set your own logo" + + ```yaml hl_lines="3-4" + plugins: + - social: + cards_layout_options: + logo: docs/assets/images/ourlogo.png + ``` + +## Background images + +In addition to adding your own logo, the most impactful way to personalize your +social cards is to add a background image instead of the default solid color +background. Make sure you choose one that will contrast well with the other +elements of the card. + +Also, the background color gets rendered *on top of* the background image, +allowing you to use a transparent color to tint an image. To use just the image, +use the color value `transparent`. + +!!! example "Add background image" + + ```yaml hl_lines="4 5" + plugins: + - social: + cards_layout_options: + background_image: layouts/background.png + background_color: transparent + ``` + +The path to the background image is resolved from the root of your project, +so this is where you should make the `layouts` directory and place the +background image. The default site of the social cards included with the plugin +is 1200x630 pixels, so choose an image that size or one that scales well to it. + +## Additional layouts and styles <!-- md:sponsors --> + +The Insiders Edition provides additional layouts as well as the option to +configure different styles for different (kinds of) pages. + +The Insiders Edition comes with a number of additional layouts for the social +cards. For example, the `default/variant` layout adds a page icon to the card. +You can use this to distinguish social cards visually, depending on what kind +of page you are sharing. + +For example, imagine you have a set of pages that advertise events and you want +to include a calendar icon as a visual indication that a card advertises an +event. In the following, you will set up a directory for event pages and use +the meta plugin to assign them a calendar icon. + +!!! example "Social cards for event pages" + + First, create a directory in your `docs` directory to hold the event pages: + + ``` + $ mkdir docs/events + ``` + + Then, add a file `.meta.yml` inside this new directory with settings for + the page icon and a hot pink background color that will stand out on + social media. Note that you can override the background image by setting it + to `null` here since it is not visible anyway because of the opaque coloring. + + ```yaml + --- + icon: material/calendar-plus + social: + cards_layout_options: + background_image: null + background_color: "#ff1493" + --- + ``` + + Now add a page within the `docs/events` directoy. It does not need to have + any special content, just a top level header. + + To turn on the `default/variant` layout in `mkdocs.yml`, add the + `cards_layout` option and also add the meta plugin: + + ```yaml + plugins: + - meta + - social: + cards_layout: default/variant + ``` + + After running `mkdocs build`, you can see that the social card at + `site/assets/images/social/events/index.png` features the page icon. + +Note that the icon will also appear next to the navigation element for the +page. If that is not what you want then you will need to modify the social +card template to gets its icons from another source. You can learn how to +do this in the [custom social cards tutorial](custom.md). + +## Per-page settings <!-- md:sponsors --> + +With the Insiders Edition, you can customize the card layout for each +page by adding settings to the page header. You have effectively done this +in the previous exercise, but using the meta plugin to affect a whole set of +pages. + +Say that in addition to regular events you also have the odd webinar and +for this you want to set a different icon and also set the description to +indicate that the event is part of the webinar series. + +!!! example "Override card style in page header" + + Add the following to the top of the page in `docs/events` or create a new + one: + + ```yaml + --- + icon: material/web + social: + cards_layout_options: + description: Our Webinar Series + --- + ``` + +## What's next? + +With the Insiders Edition, you can also define custom layouts if the +configuration options introduced above as not enough to meet your needs. +Continue to the [custom social cards tutorial](custom.md) if you want to +find out how to do this. + +Social cards are particularly useful for blog posts. If you have a blog, +you need to do nothing more than to turn on both plugins to create social cards +to advertise your latest blog posts. If you do not have one yet but would like +to, why not check out the [blog tutorials](../index.md#blogs)? diff --git a/docs/tutorials/social/custom.md b/docs/tutorials/social/custom.md new file mode 100644 index 000000000..fdc08d26f --- /dev/null +++ b/docs/tutorials/social/custom.md @@ -0,0 +1,142 @@ +# Custom cards + +The Insiders Edition allows you to define custom layouts for your social cards +to suit your specific needs if the configuration options are not enough. +For example, you may want to define a social card to advertise a new release +of your product. It should have an icon indicating a launch announcement as +well as the version number of the release on the card. + +## Setup + +You can either design a custom layout from scratch or use an existing layout +as the basis that you add to or otherwise modify. In this tutorial, you will +use the default layout as the basis. + +!!! example "Copy default layout to customize <!-- md:sponsors -->" + + Copy the default social card layout from your installation of Material + for MkDocs to a new directory `layouts`. The instructions below assume you + are in your project root and have a virtual environment within this. The + path on your machine, of course, may differ. + + ``` + $ mkdir layouts + $ cp venv/lib/python3.12/site-packages/material/plugins/social/templates/default/variant.yml \ + layouts/release.yml + ``` + + Before customizing the social cards, you need to tell the plugin where to + find them as well as tell MkDocs to watch for any changes. Add the following + to the plugin configuration in your `mkdocs.yml`: + + ``` yaml hl_lines="2-6" + plugins: + - social: + cards_layout_dir: layouts + + watch: + - layouts + ``` + +Have a look at the contents of `release.yml`. You will see that there are: + +* a number of definitions of content pulled from the site, +* definitions of tags that end up in the `meta` elements in the page header + of each page, +* a specification that consists of a number of layers that the social plugin + applies on top of each other in the order in which they are defined. + +## Define page metadata + +In the following, you will add a version number to the social card. This +assumes you have a changelog page with information about each release. +Add the version number of the latest version to the page header (so it does +not need to be parsed out of the Markdown content): + +!!! example "Defining the release data <!-- md:sponsors -->" + + Create a page `docs/changelog.md` with the following content: + + ```yaml + --- + icon: material/rocket-launch-outline + social: + cards_layout: release + cards_layout_options: + title: New release! + latest: 1.2.3 + --- + + # Releases + ``` + +## Extract page metadata + +With the data defined in the page header, you can now add code to the layout +that pulls it out and makes it available to render later on. This is to separate +the data manipulation from the actual layout instructions and so make the +layout file easier to read. + +!!! example "Adding data definitions" + + Add the following at the top of the layout file: + + ```yaml hl_lines="2-9" + definitions: + - &latest >- + {%- if 'latest' in page.meta %} + {{ page.meta['latest']}} + {%- else -%} + No release version data defined! + {%- endif -%} + ``` + +The code presented here checks whether the page header contains the necessary +entries and spits out a message to the social card if not. Unfortunately, there +is no straightforward way to raise an exception or log an error, so the messages +merely appear in the social card produced. + +## Add release version layer + +The next step is to use these data definitions in a new layer and add it to the +ones already present. + +!!! example "Render release version" + + Finally, add the following to end of the custom layout: + + ```yaml + - size: { width: 990, height: 50 } + offset: { x: 50, y: 360 } + typography: + content: *latest + align: start + color: *color + ``` + +You should now see the social plugin use the custom layout on the changelog page +you set up. + +## Adjust layout + +Finally, the rocket icon used for the changelog page is not quite in the right +position. Find the please where the `page_icon` variable is used to create the +page icon layer and adjust the horizontal position to 600 instead of 800. + +!!! tip "Debugging layout files" + + Should you find that your layouts are causing your MkDocs build to fail, + there are a number of things you can do: + + 1. Run Mkdocs with the `--verbose` option to get more detailed reporting. + 2. Comment out things you recently added or that you suspect are the cause + 3. Install the `jinja2` command-line tool with `pip install Jinja2` and + run it over your layout file, for example: `jinja2 event.yml`. + +## What's next? + +If you do not have a blog yet, why not check out the +[blog tutorials](../index.md#blogs) and learn how to set one up? The social +plugin will help you draw attention to your posts on social media. + +Check out the [other tutorials](../index.md) we have prepared for you. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index d79f2669e..7dacbf7b8 100755 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -174,6 +174,9 @@ markdown_extensions: custom_checkbox: true - pymdownx.tilde +not_in_nav: | + /tutorials/**/*.md + # Page tree nav: - Home: index.md @@ -188,6 +191,15 @@ nav: - Philosophy: philosophy.md - Alternatives: alternatives.md - License: license.md + - Tutorials: + - tutorials/index.md + - "Blogs": + - tutorials/blogs/basic.md + - tutorials/blogs/navigation.md + - tutorials/blogs/engage.md + - "Social cards": + - tutorials/social/basic.md + - tutorials/social/custom.md - Changelog: - changelog/index.md - How to upgrade: upgrade.md