├── .npmrc
├── docs
├── showcase
│ └── index.md
├── favicon-180x180.png
├── changelogs
│ ├── nacara.md
│ ├── nacara-core.md
│ ├── nacara-apigen.md
│ └── nacara-layout-standard.md
├── assets
│ └── changelog
│ │ └── 0_2_0
│ │ ├── touch_preview.gif
│ │ └── desktop_preview.png
├── nacara-layout-standard
│ ├── menu.json
│ ├── introduction.md
│ ├── partials.md
│ ├── layouts.md
│ ├── scss-sass-variables.md
│ └── markdown-features.md
├── nacara
│ ├── advanced
│ │ ├── layout-from-scratch.md
│ │ └── custom-layout-fsharp.md
│ ├── guides
│ │ ├── create-a-section.md
│ │ ├── deploy-your-site.md
│ │ ├── literate-files.md
│ │ ├── customize-the-style.md
│ │ ├── create-a-page.md
│ │ └── section-menu.md
│ ├── introduction.md
│ ├── menu.json
│ ├── partials.md
│ ├── cli-usage.md
│ └── directory-structure.md
├── _partials
│ └── footer.jsx
├── nacara-apigen
│ └── introduction.md
├── style.scss
└── index.md
├── babel.config.json
├── global.json
├── src
├── Nacara.Create
│ ├── templates
│ │ ├── babel.config.json
│ │ ├── gitignore
│ │ ├── docs
│ │ │ ├── documentation
│ │ │ │ ├── introduction.md
│ │ │ │ ├── menu.json
│ │ │ │ └── guides
│ │ │ │ │ ├── deploy-your-site.md
│ │ │ │ │ ├── customize-the-style.md
│ │ │ │ │ ├── create-a-page.md
│ │ │ │ │ └── create-a-section.md
│ │ │ ├── _partials
│ │ │ │ └── footer.jsx
│ │ │ ├── index.md
│ │ │ └── style.scss
│ │ ├── package.json
│ │ └── style.scss
│ ├── package.json
│ ├── CHANGELOG.md
│ └── index.js
├── Nacara.Layout.Standard
│ ├── scss
│ │ ├── adapters
│ │ │ ├── _all.scss
│ │ │ └── bulma
│ │ │ │ ├── button.scss
│ │ │ │ └── content.scss
│ │ ├── components
│ │ │ ├── _all.scss
│ │ │ ├── page-header.scss
│ │ │ ├── copy-buttons.scss
│ │ │ ├── textual-steps.scss
│ │ │ ├── anchors.scss
│ │ │ ├── mobile-menu.scss
│ │ │ ├── navigation-button.scss
│ │ │ ├── menu.scss
│ │ │ └── changelog.scss
│ │ ├── nacara.scss
│ │ └── nacara-api.scss
│ ├── Source
│ │ ├── Dependencies.fs
│ │ ├── paket.references
│ │ ├── Nacara.Layouts.Standard.fsproj
│ │ ├── Helpers.fs
│ │ ├── TableOfContentParser.fs
│ │ └── Export.fs
│ ├── package.json
│ ├── js
│ │ └── remark-block-container.js
│ └── CHANGELOG.md
├── Nacara.ApiGen
│ ├── Tests
│ │ ├── paket.references
│ │ ├── Main.fs
│ │ └── Nacara.ApiGen.Tests.fsproj
│ ├── Source
│ │ ├── paket.references
│ │ ├── File.fs
│ │ ├── StringBuilder.fs
│ │ ├── Helpers.fs
│ │ ├── Nacara.ApiGen.fsproj
│ │ ├── Generate.fs
│ │ └── Main.fs
│ └── CHANGELOG.md
├── Nacara
│ ├── js
│ │ ├── afterClean-options.js
│ │ ├── version.js
│ │ └── nodemon-watch.js
│ ├── Source
│ │ ├── paket.references
│ │ ├── Clean.fs
│ │ ├── Version.fs
│ │ ├── Serve.fs
│ │ ├── Nacara.fsproj
│ │ ├── Layout.fs
│ │ ├── Partial.fs
│ │ ├── FsharpFileParser.fs
│ │ ├── Write.fs
│ │ └── Build.fs
│ ├── package.json
│ ├── cli.js
│ └── scripts
│ │ └── live-reload.js
└── Nacara.Core
│ ├── paket.references
│ ├── Bindings
│ ├── Semver.fs
│ ├── FrontMatter.fs
│ ├── Sass.fs
│ └── Chokidar.fs
│ ├── Array.Extensions.fs
│ ├── List.Extensions.fs
│ ├── Nacara.Core.fsproj
│ ├── Interop.fs
│ ├── Log.fs
│ ├── Navbar.fs
│ ├── Menu.fs
│ └── CHANGELOG.md
├── .docker
└── Dockerfile
├── NuGet.Config
├── .config
└── dotnet-tools.json
├── .editorconfig
├── .gitpod.yml
├── LICENSE.txt
├── .github
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitattributes
├── package.json
├── BACKERS.md
├── paket.dependencies
├── .vscode
├── tasks.json
└── launch.json
├── scripts
├── release-npm.js
├── release-nuget.js
└── release-core.js
├── Directory.Build.props
├── README.md
├── Makefile
├── nacara.config.json
├── .gitignore
└── Nacara.sln
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/docs/showcase/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: navbar-only
3 | ---
4 |
5 | TODO showcase
6 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/docs/favicon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangelMaxime/Nacara/HEAD/docs/favicon-180x180.png
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "6.0.0",
4 | "rollForward": "latestMinor"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/docs/changelogs/nacara.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | title: Nacara
4 | changelog_path: ./../../src/Nacara/CHANGELOG.md
5 | ---
6 |
--------------------------------------------------------------------------------
/docs/assets/changelog/0_2_0/touch_preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangelMaxime/Nacara/HEAD/docs/assets/changelog/0_2_0/touch_preview.gif
--------------------------------------------------------------------------------
/docs/changelogs/nacara-core.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | title: Nacara.Core
4 | changelog_path: ./../../src/Nacara.Core/CHANGELOG.md
5 | ---
6 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/adapters/_all.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @import "./bulma/button.scss";
4 | @import "./bulma/content.scss";
5 |
--------------------------------------------------------------------------------
/docs/assets/changelog/0_2_0/desktop_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MangelMaxime/Nacara/HEAD/docs/assets/changelog/0_2_0/desktop_preview.png
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Tests/paket.references:
--------------------------------------------------------------------------------
1 | group ApiGen
2 | FSharp.Core
3 | FSharp.Formatting
4 | FSharp.Compiler.Service
5 | Expecto
6 |
--------------------------------------------------------------------------------
/docs/changelogs/nacara-apigen.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | title: Nacara.ApiGen
4 | changelog_path: ./../../src/Nacara.ApiGen/CHANGELOG.md
5 | ---
6 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/Dependencies.fs:
--------------------------------------------------------------------------------
1 | module Dependencies
2 |
3 | []
4 | let menu = "resources/nacara-standard-layouts/scripts/menu.js"
5 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/paket.references:
--------------------------------------------------------------------------------
1 | group ApiGen
2 | FSharp.Core
3 | FSharp.Formatting
4 | FSharp.Compiler.Service
5 | Argu
6 | FSlugify
7 |
--------------------------------------------------------------------------------
/docs/changelogs/nacara-layout-standard.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | title: Nacara.Standard.Layout
4 | changelog_path: ./../../src/Nacara.Layout.Standard/CHANGELOG.md
5 | ---
6 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | docs_deploy/
6 | .nacara/
7 |
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
--------------------------------------------------------------------------------
/src/Nacara/js/afterClean-options.js:
--------------------------------------------------------------------------------
1 | export default {
2 | description: "Command to run after Nacara cleaned the output folder. Please, make sure to wrap the command between quotes",
3 | type: "string"
4 | }
5 |
--------------------------------------------------------------------------------
/src/Nacara.Core/paket.references:
--------------------------------------------------------------------------------
1 | Fable.Core
2 | Fable.FontAwesome
3 | Fable.FontAwesome.Free
4 | Fable.Node
5 | Fable.Promise
6 | Glutinum.Chalk
7 | Thoth.Json
8 | FSharp.Core
9 | Microsoft.SourceLink.GitHub
10 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/adapters/bulma/button.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | button {
4 | &.is-copy-button {
5 | position: absolute;
6 | top: 1rem;
7 | right: 1rem;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Nacara/Source/paket.references:
--------------------------------------------------------------------------------
1 | Fable.Core
2 | Fable.Elmish
3 | Fable.FontAwesome
4 | Fable.FontAwesome.Free
5 | Fable.Node
6 | Fable.Promise
7 | Glutinum.Chalk
8 | Glutinum.Express
9 | Thoth.Json
10 | FSharp.Core
11 |
--------------------------------------------------------------------------------
/src/Nacara/js/version.js:
--------------------------------------------------------------------------------
1 | import { readFile } from 'fs/promises'
2 |
3 | export default async function() {
4 | const pkg = JSON.parse(await readFile(new URL('./../package.json', import.meta.url)));
5 |
6 | return pkg.version;
7 | }
8 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/paket.references:
--------------------------------------------------------------------------------
1 | Fable.Core
2 | Fable.Date
3 | Fable.FontAwesome
4 | Fable.FontAwesome.Free
5 | Fable.Node
6 | Fable.Promise
7 | Feliz
8 | Feliz.Bulma
9 | Glutinum.Chalk
10 | Thoth.Json
11 | FSharp.Core
12 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Bindings/Semver.fs:
--------------------------------------------------------------------------------
1 | module rec Semver
2 |
3 | open Fable.Core
4 | open Fable.Core.JsInterop
5 |
6 | let semver : IExport = import "default" "semver"
7 |
8 | type IExport =
9 | abstract gte : v1 : string * v2 : string -> bool
10 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Tests/Main.fs:
--------------------------------------------------------------------------------
1 | module Main
2 |
3 | open Expecto
4 |
5 | let tests =
6 | testList "All" [
7 | Tests.CommentFormatter.tests
8 | ]
9 |
10 | []
11 | let main args =
12 | runTestsWithCLIArgs [] args tests
13 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | "nacara-layout-standard/introduction",
3 | "nacara-layout-standard/layouts",
4 | "nacara-layout-standard/partials",
5 | "nacara-layout-standard/markdown-features",
6 | "nacara-layout-standard/scss-sass-variables"
7 | ]
8 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Clean.fs:
--------------------------------------------------------------------------------
1 | module Clean
2 |
3 | open Nacara.Core.Types
4 | open Node
5 |
6 | let clean (config : Config) =
7 | promise {
8 | do! Directory.rmdir (path.join(config.WorkingDirectory, ".nacara"))
9 | do! Directory.rmdir config.DestinationFolder
10 | }
11 |
--------------------------------------------------------------------------------
/.docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM gitpod/workspace-dotnet:latest
2 |
3 | USER gitpod
4 |
5 | # Install & use custom Node.js version
6 | ENV NODE_VERSION=14
7 | RUN bash -c ". .nvm/nvm.sh && \
8 | nvm install ${NODE_VERSION} && \
9 | nvm alias default ${NODE_VERSION}"
10 |
11 | RUN npm i -g npm@7
12 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "fable": {
6 | "version": "3.4.9",
7 | "commands": [
8 | "fable"
9 | ]
10 | },
11 | "paket": {
12 | "version": "6.1.3",
13 | "commands": [
14 | "paket"
15 | ]
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/src/Nacara/Source/Version.fs:
--------------------------------------------------------------------------------
1 | module Version
2 |
3 | open Fable.Core
4 | open Fable.Core.JsInterop
5 |
6 | let getVersion () : JS.Promise =
7 | import "default" "./../js/version.js"
8 |
9 | let version () =
10 | promise {
11 | let! version = getVersion ()
12 |
13 | Log.log $"Nacara version: {version}"
14 | }
15 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | layout: standard
4 | ---
5 |
6 | This is a quick tutorial on how to use Nacara to build your site.
7 |
8 | ## Live edit
9 |
10 | Open `docs/documentation/introduction.md` and edit some lines.
11 |
12 | You should see the site reload automatically with your changes applied
13 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/_all.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @import './changelog.scss';
4 | @import './textual-steps.scss';
5 | @import './navigation-button.scss';
6 | @import './nacara-navbar.scss';
7 | @import './anchors.scss';
8 | @import './mobile-menu.scss';
9 | @import './copy-buttons.scss';
10 | @import './menu.scss';
11 | @import './page-header.scss';
12 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Serve.fs:
--------------------------------------------------------------------------------
1 | module Serve
2 |
3 | open Nacara.Core.Types
4 |
5 | let serve (config : Config) =
6 | promise {
7 | let server = Server.create config
8 |
9 | server.listen(config.ServerPort, fun () ->
10 | Log.success $"Serving {config.SourceFolder} at: http://localhost:%i{config.ServerPort}"
11 | )
12 | |> ignore
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | insert_final_newline = true
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | indent_style = space
12 | indent_size = 4
13 |
14 | [Makefile]
15 | indent_style = tab
16 |
17 | [*.yml]
18 | indent_size = 2
--------------------------------------------------------------------------------
/src/Nacara.Create/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-nacara",
3 | "version": "1.1.0",
4 | "description": "",
5 | "main": "index.js",
6 | "bin": "index.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "chalk": "^4.1.2",
14 | "enquirer": "^2.3.6",
15 | "shelljs": "^0.8.4"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/_partials/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const year = new Date().getFullYear();
4 |
5 | export default (
6 |
7 |
8 | Copyright © {year} - Built with Nacara
9 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.0",
3 | "private": true,
4 | "type": "module",
5 | "engines": {
6 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0",
7 | "npm": ">=7.0.0"
8 | },
9 | "scripts": {
10 | "build": "nacara build",
11 | "clean": "nacara clean",
12 | "watch": "nacara watch",
13 | "serve": "nacara serve"
14 | },
15 | "dependencies": {}
16 | }
17 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/page-header.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | .page-header {
4 | min-height: 1em;
5 | margin-bottom: 1em;
6 |
7 | h1 {
8 | margin-top: 0 !important;
9 | margin-bottom: 1em;
10 | }
11 |
12 | .breadcrumb {
13 | ul {
14 | margin-left: 0;
15 | margin-top: 0;
16 | }
17 |
18 | li {
19 | height: $navbar-height;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/File.fs:
--------------------------------------------------------------------------------
1 | module File
2 |
3 | open System.IO
4 | open System.Text
5 |
6 | let write
7 | (docsRoot : string)
8 | (fileName : string)
9 | (sb : StringBuilder) =
10 |
11 | let filePath = Path.Combine(docsRoot, fileName)
12 |
13 | // Ensure that the directory exists
14 | Directory.CreateDirectory(Path.GetDirectoryName(filePath))
15 | |> ignore
16 |
17 | use file = new StreamWriter(filePath)
18 | file.Write(sb.ToString())
19 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ### Changed
10 |
11 | * Upgrade FSharp.Formatting to latest version
12 |
13 | ## 1.0.0-beta-001 - 2021-10-28
14 |
15 | ### Added
16 |
17 | * Initial release
18 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Bindings/FrontMatter.fs:
--------------------------------------------------------------------------------
1 | module FrontMatter
2 |
3 | open Fable.Core
4 |
5 | type [] FrontMatterResult<'T> =
6 | abstract attributes: 'T
7 | abstract body: string
8 | abstract frontmatter: string option
9 |
10 | type [] FM =
11 | [] abstract Invoke<'T> : file: string -> FrontMatterResult<'T>
12 | abstract test: file: string -> bool
13 |
14 | let [] fm: FM = jsNative
15 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | image:
2 | file: .docker/Dockerfile
3 |
4 | ports:
5 | - port: 8080
6 |
7 | tasks:
8 | - init: npm install
9 |
10 | github:
11 | prebuilds:
12 | # enable for pull requests coming from forks (defaults to false)
13 | pullRequestsFromForks: true
14 | # add a "Review in Gitpod" button as a comment to pull requests (defaults to false)
15 | addComment: true
16 |
17 | vscode:
18 | extensions:
19 | - christian-kohler.path-intellisense
20 | - mrmlnc.vscode-scss
21 | - Ionide.Ionide-fsharp
22 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | layout: standard
4 | ---
5 |
6 | ## Installation
7 |
8 | `nacara-layout-standard` is the package providing the standard layout when working with Nacara.
9 |
10 | ```
11 | npm install nacara-layout-standard
12 | ```
13 |
14 | ## Quick-view
15 |
16 | Here is the list of all the layout available:
17 |
18 | - `nacara-standard`: standard documentation page
19 | - `nacara-navbar-only`: a navbar with a blank canvas
20 | - `nacara-changelog`: for displaying your changelog on your website
21 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Tests/Nacara.ApiGen.Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | Exe
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/nacara/advanced/layout-from-scratch.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Overview
3 | layout: standard
4 | ---
5 |
6 | Nacara is extensible, you can write your own layout or re-use existing layout to adapt them to your needs.
7 |
8 | You can use both **F#** and **JavaScript** to do so.
9 |
10 | In general, you should prefer **JavaScript** when adapting an existing layout as it is easier to **interop with the NPM packages**.
11 |
12 | If you want to write a complex layout, you should prefer **F#** as Nacara comes with `Nacara.Core` nuget, which includes all the **types definition** and some **helpers**.
13 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2021 Mangel Maxime
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | pull_request:
4 | branches: [ master ]
5 | workflow_dispatch:
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-dotnet@v1
12 | with:
13 | dotnet-version: '6.0.x'
14 | - uses: actions/setup-node@v2
15 | with:
16 | node-version: '14'
17 | - name: Install and use custom npm version
18 | run: npm i -g npm@7
19 | - name: Setup workspace
20 | run: make setup-dev
21 | - name: Build site
22 | run: make generate-docs
23 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "section",
4 | "label": "Overview",
5 | "items": [
6 | "documentation/introduction"
7 | ]
8 | },
9 | {
10 | "type": "section",
11 | "label": "Guides",
12 | "items": [
13 | "documentation/guides/create-a-page",
14 | "documentation/guides/customize-the-style",
15 | "documentation/guides/create-a-section",
16 | "documentation/guides/custom-layout",
17 | "documentation/guides/deploy-your-site"
18 | ]
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # These files are text and should be normalized (convert crlf to lf)
2 | *.config text eol=lf crlf=input
3 | *.cs text eol=lf crlf=input
4 | *.fs text eol=lf crlf=input
5 | *.fsx text eol=lf crlf=input
6 | *.md text eol=lf crlf=input
7 | *.yml text eol=lf crlf=input
8 | *.sh text eol=lf crlf=input
9 | *.cmd text eol=lf crlf=input
10 | *.csproj text eol=lf crlf=input
11 | *.fsproj text eol=lf crlf=input
12 | *.sln text eol=lf crlf=input
13 | *.js text eol=lf crlf=input
14 |
15 | # These files should be treated as binary
16 | *.png binary
17 | *.jpg binary
18 | *.jpeg binary
19 | *.gif binary
20 | *.ai binary
21 | *.psd binary
22 | $.exe binary
23 |
--------------------------------------------------------------------------------
/docs/nacara/guides/create-a-section.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Create a Section
3 | layout: standard
4 | ---
5 |
6 | A section in Nacara helps you organize your website. For example, you can have the following sections:
7 |
8 | - **Documentation**: a place to host your project documentation
9 | - **Blog**: a place to publish blog posts
10 |
11 | ## Create a new section
12 |
13 | A section is defined by creating a folder right under your source folder (default is `docs`).
14 |
15 | If your project has this structure:
16 |
17 | ```
18 | docs
19 | ├── blog
20 | └── documentation
21 | ```
22 |
23 | This means that you have two sections called `documentation` and `blog`.
24 |
--------------------------------------------------------------------------------
/src/Nacara/js/nodemon-watch.js:
--------------------------------------------------------------------------------
1 | import { runWatch } from '../dist/Main.js';
2 | import yargs from 'yargs';
3 | import { hideBin } from "yargs/helpers";
4 | import afterCleanOptions from './afterClean-options.js';
5 |
6 | // Yargs to parse the arguments
7 | // We don't need to be complete or strict here because
8 | // arguments have already been check by the cli.js
9 | // Here we re-parse the arguments because we are in a new instance of Nacara
10 | // started from Nodemon
11 | const res =
12 | yargs(hideBin(process.argv))
13 | .option(
14 | "afterClean",
15 | afterCleanOptions
16 | )
17 | .argv
18 |
19 | runWatch(res);
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nacara-root",
3 | "private": "true",
4 | "license": "Apache-2.0",
5 | "type": "module",
6 | "devDependencies": {
7 | "chalk": "^4.1.2",
8 | "changelog-parser": "^2.8.0",
9 | "concurrently": "^6.2.2",
10 | "nodemon": "^2.0.20",
11 | "shelljs": "^0.8.5"
12 | },
13 | "engines": {
14 | "node": ">=14.0.0",
15 | "npm": ">=7.0.0"
16 | },
17 | "dependencies": {
18 | "@babel/preset-react": "^7.14.5",
19 | "bulma": "^0.9.3",
20 | "remark-github": "^11.1.0",
21 | "vscode-theme-onelight": "github:akamud/vscode-theme-onelight"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/adapters/bulma/content.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | .content {
4 | .breadcrumb > ul > li:last-child {
5 | font-weight: $weight-bold;
6 | }
7 |
8 | & li + li {
9 | margin-top: 0;
10 | }
11 |
12 | // Use the same font-size for the table font as for the rest
13 | // I don't know if it is a bug from Bulma or Firefox
14 | .table {
15 | font-size: $body-size;
16 | }
17 |
18 | @include mobile {
19 | th, td {
20 | word-break: break-all;
21 | }
22 | }
23 |
24 | pre {
25 | word-break: break-all;
26 | white-space: break-spaces;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: navbar-only
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 | My Site
11 |
12 |
13 | Nacara is a static site generator focused on simplicity
14 |
15 |
16 | Quick tutorial
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/copy-buttons.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | // Style relative to the snippet and copy button
4 | pre {
5 | position: relative; // Make the pre relative so the copy button and positioned itself relative to it
6 | padding-top: $size-4 !important; // This padding is to display correctly the button even if the snippet has only 1 line of code
7 | padding-bottom: $size-4 !important; // This padding is to display correctly the button even if the snippet has only 1 line of code
8 |
9 | button.is-copy-button {
10 | visibility: hidden;
11 | }
12 |
13 | &:hover {
14 | button.is-copy-button {
15 | visibility: visible;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/BACKERS.md:
--------------------------------------------------------------------------------
1 | Sponsors & Backers
2 |
3 | The development of this project is made possible thanks to the support of these awesome sponsors & backers.
4 |
5 | [Become a backer or sponsor on Patreon](https://www.patreon.com/MangelMaxime)
6 |
7 | If you prefer one time donation, you can do it via [Paypal](https://www.paypal.me/MangelMaxime)
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | *If you found a typo or error in the backers list please open an issue on [https://github.com/MangelMaxime/sponsors](https://github.com/MangelMaxime/sponsors)*
16 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | version 6.1.3
2 | source https://www.nuget.org/api/v2
3 |
4 | storage: none
5 |
6 | nuget FSharp.Core ~> 5 redirects: force
7 | nuget Fable.Core
8 | nuget Fable.Date
9 | nuget Fable.Elmish
10 | nuget Fable.FontAwesome
11 | nuget Fable.FontAwesome.Free
12 | nuget Fable.Node
13 | nuget Fable.Promise
14 | nuget Feliz
15 | nuget Feliz.Bulma
16 | nuget Glutinum.Chalk
17 | nuget Glutinum.Express
18 | nuget Thoth.Json
19 | nuget Microsoft.SourceLink.GitHub copy_local: true
20 |
21 | group ApiGen
22 | source https://api.nuget.org/v3/index.json
23 | framework: net6.0
24 |
25 | nuget FSharp.Formatting
26 | nuget FSharp.Core
27 | nuget FSharp.Compiler.Service 40.0.0
28 | nuget Expecto
29 | nuget Argu
30 | nuget FSlugify
31 |
--------------------------------------------------------------------------------
/docs/nacara/guides/deploy-your-site.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Deploy your site
3 | layout: standard
4 | ---
5 |
6 | Nacara is a static site generator, meaning that your website is only static HTML, JavaScript and CSS files.
7 |
8 | ## Build your site
9 |
10 | You can build your side for **production** by running:
11 |
12 | ```
13 | npx nacara build
14 | ```
15 |
16 | The static files generated are located in the `build` folder.
17 |
18 | ## Deploy your site
19 |
20 | You can test your production website locally by running:
21 |
22 | ```
23 | npx nacara serve
24 | ```
25 |
26 | You can now deploy the `docs` folder almost anywhere.
27 |
28 | For example, on Github you can choose to push the `docs` and serve your website using [Github pages](https://pages.github.com/).
29 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/partials.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Partials
3 | layout: standard
4 | ---
5 |
6 | The following partials are supported:
7 |
8 |
9 |
10 |
11 | | Property |
12 | Required |
13 | Description |
14 |
15 |
16 |
17 |
18 |
19 | footer
20 | |
21 | |
22 |
23 | A footer to include at the bottom of all pages.
24 | |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/docs/_partials/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CopyrightScript = () => (
4 |
10 | )
11 |
12 | export default (
13 |
14 |
15 | The content of this website is copyright © 2021-
16 |
17 | under the terms of the MIT License
18 |
19 |
20 |
21 | )
22 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/StringBuilder.fs:
--------------------------------------------------------------------------------
1 | module StringBuilder.Extensions
2 |
3 | open System.Text
4 |
5 | type StringBuilder with
6 | member this.Write(text : string) =
7 | this.Append(text)
8 | |> ignore
9 |
10 | member this.WriteLine(text : string) =
11 | this.AppendLine(text)
12 | |> ignore
13 |
14 | member this.NewLine() =
15 | this.AppendLine()
16 | |> ignore
17 |
18 | member this.Indent(?factor : int) =
19 | let factor = defaultArg factor 1
20 |
21 | this.Space(factor * 4)
22 | |> ignore
23 |
24 | member this.Space(?factor : int) =
25 | let factor = defaultArg factor 1
26 |
27 | let text = String.replicate factor " "
28 |
29 | this.Append(text)
30 | |> ignore
31 |
--------------------------------------------------------------------------------
/docs/nacara/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | layout: standard
4 | ---
5 |
6 | Nacara is a static site generator. You write markdown files and let Nacara generate your website for you.
7 |
8 | You can easily change how the site looks and feels, and if needed write your own layouts.
9 |
10 | ## Prerequisites
11 |
12 | Nacara requires you to have at least Node.js 12.20, 14.14, or 16.0 because it is using ESM modules.
13 |
14 | ## Quick start
15 |
16 |
17 |
18 | -
19 |
20 | Initialize your site from the template
21 |
22 | ```
23 | npm init nacara
24 | ```
25 |
26 |
27 |
28 | -
29 |
30 | ```
31 | cd my-site
32 | ```
33 |
34 |
35 |
36 | -
37 |
38 | Run your site
39 |
40 | ```
41 | npm run watch
42 | ```
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/textual-steps.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | $textual-steps-color: $grey-lighter !default;
4 |
5 | ul.textual-steps {
6 | list-style: none;
7 | padding-left: 0;
8 | counter-reset: textual-steps;
9 | position: relative;
10 |
11 | >li {
12 | position: relative;
13 | padding-left: 4em;
14 | margin-bottom: 2em;
15 | padding-top: 1em;
16 |
17 | &::before {
18 | content: counter(textual-steps, decimal-leading-zero) ".";
19 | counter-increment: textual-steps;
20 | position: absolute;
21 | top: 0;
22 | left: 0;
23 | color: $textual-steps-color;
24 | font-size: $size-3;
25 | font-weight: $weight-bold;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | branches:
5 | - master
6 | workflow_dispatch:
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: actions/setup-dotnet@v1
13 | with:
14 | dotnet-version: '6.0.x'
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: '14'
18 | - name: Install and use custom npm version
19 | run: npm i -g npm@7
20 | - name: Setup workspace
21 | run: make setup-dev
22 | - name: Build site
23 | run: make generate-docs
24 | - name: Deploy site
25 | uses: peaceiris/actions-gh-pages@v3
26 | with:
27 | personal_token: ${{ secrets.GITHUB_TOKEN }}
28 | publish_dir: ./docs_deploy
29 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Array.Extensions.fs:
--------------------------------------------------------------------------------
1 | /// Additional operations on Array
2 | module Array
3 |
4 | // Copied from F# plus
5 | //https://github.com/fsprojects/FSharpPlus/blob/327cdfcff9d7a209bf934218a5067301ef44e35d/src/FSharpPlus/Extensions/Array.fs#L100-100
6 |
7 | ///
8 | /// Creates two arrays by applying the mapper function to each element in the array
9 | /// and classifies the transformed values depending on whether they were wrapped with Choice1Of2 or Choice2Of2.
10 | ///
11 | ///
12 | /// A tuple with both resulting arrays.
13 | ///
14 | let partitionMap (mapper: 'T -> Choice<'T1,'T2>) (source: array<'T>) =
15 | let (x, y) = ResizeArray (), ResizeArray ()
16 | Array.iter (mapper >> function Choice1Of2 e -> x.Add e | Choice2Of2 e -> y.Add e) source
17 | x.ToArray (), y.ToArray ()
18 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/Helpers.fs:
--------------------------------------------------------------------------------
1 | module Helpers
2 |
3 | module String =
4 |
5 | let normalizeEndOfLine (text : string)=
6 | text.Replace("\r\n", "\n")
7 |
8 | let splitBy (c : char) (text : string) =
9 | text.Split(c)
10 |
11 | let splitLines (text : string) =
12 | text
13 | |> normalizeEndOfLine
14 | |> splitBy '\n'
15 |
16 | let toLower (text : string) =
17 | text.ToLower()
18 |
19 | let replace (oldValue : string) (newValue : string) (text : string) =
20 | text.Replace(oldValue, newValue)
21 |
22 | let append (value : string) (text : string) =
23 | text + value
24 |
25 | module List =
26 |
27 | let intersperse (element : 'T) (source : 'T list) =
28 | [
29 | for item in source do
30 | item
31 | element
32 | ]
33 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/Nacara.ApiGen.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.0.0-beta-001
5 | net6.0
6 | Exe
7 | true
8 | nacara-apigen
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Nacara.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/Nacara.Layouts.Standard.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "command": "dotnet",
9 | "type": "shell",
10 | "args": [
11 | "build",
12 | "src/Nacara.ApiGen/Source",
13 | // Ask dotnet build to generate full paths for file names.
14 | "/property:GenerateFullPaths=true",
15 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel
16 | "/consoleloggerparameters:NoSummary"
17 | ],
18 | "group": "build",
19 | "presentation": {
20 | "reveal": "silent"
21 | },
22 | "problemMatcher": "$msCompile"
23 | }
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/anchors.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | // Anchor behavior
4 | h1, h2, h3, h4, h5, h6 {
5 | a span.anchor {
6 | visibility: hidden;
7 | margin-left: 0.5rem;
8 | }
9 |
10 | &:hover {
11 |
12 | a span.anchor {
13 | visibility: visible;
14 | }
15 | }
16 |
17 | // Make sure that when jumping to an anchor, that anchor is displayed just below the navbar and visible
18 |
19 | // On desktop and above we only have the navbar to take into account
20 | @include desktop {
21 | &[id] {
22 | scroll-margin-top: $computed-navbar-height + 1rem;
23 | }
24 | }
25 |
26 | // On touch screen we need to take into account the navbar and the breadcrumb
27 | @include touch {
28 | &[id] {
29 | scroll-margin-top: $computed-navbar-height + $navbar-height + 1rem;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Nacara.Core/List.Extensions.fs:
--------------------------------------------------------------------------------
1 | /// Additional operations on List
2 | module List
3 |
4 | // Copied from F# plus
5 | // https://github.com/fsprojects/FSharpPlus/blob/327cdfcff9d7a209bf934218a5067301ef44e35d/src/FSharpPlus/Extensions/List.fs#L133-133
6 |
7 | ///
8 | /// Creates two lists by applying the mapping function to each element in the list
9 | /// and classifying the transformed values depending on whether they were wrapped with Choice1Of2 or Choice2Of2.
10 | ///
11 | ///
12 | /// A tuple with both resulting lists.
13 | ///
14 | let partitionMap (mapping: 'T -> Choice<'T1,'T2>) (source: list<'T>) =
15 | let rec loop ((acc1, acc2) as acc) = function
16 | | [] -> acc
17 | | x::xs ->
18 | match mapping x with
19 | | Choice1Of2 x -> loop (x::acc1, acc2) xs
20 | | Choice2Of2 x -> loop (acc1, x::acc2) xs
21 | loop ([], []) (List.rev source)
22 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/guides/deploy-your-site.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Deploy your site
3 | layout: standard
4 | ---
5 |
6 | Nacara is a static site generator, meaning that your website is only static HTML, JavaScript and CSS files.
7 |
8 | ## Build your site
9 |
10 | You can build your side for **production** by running:
11 |
12 | ```
13 | npm run build
14 | ```
15 |
16 | The static files generated are located in the `build` folder.
17 |
18 | ## Deploy your site
19 |
20 | :::warning{title="Important"}
21 |
22 | Don't forget to adapt the `siteMetadata` properties to your environnement before deploying the website.
23 |
24 | More information available [here](https://mangelmaxime.github.io/Nacara/nacara/configuration.html#sitemetadata)
25 |
26 | :::
27 |
28 | You can test your production website locally by running:
29 |
30 | ```
31 | npm run serve
32 | ```
33 |
34 | You can now deploy the `docs` folder almost anywhere.
35 |
36 | For example, on Github you can choose to push the `docs` and serve your
37 |
--------------------------------------------------------------------------------
/scripts/release-npm.js:
--------------------------------------------------------------------------------
1 | import path from "node:path"
2 | import chalk from "chalk"
3 | import shell from "shelljs"
4 |
5 | const log = console.log
6 |
7 | import { release } from "./release-core.js"
8 |
9 | // Check that we have enough arguments
10 | if (process.argv.length < 3) {
11 | log(chalk.red("Missing the path arguments"))
12 | process.exit(1)
13 | }
14 |
15 | const cwd = process.cwd()
16 | const baseDirectory = path.resolve(cwd, process.argv[2])
17 |
18 | release({
19 | baseDirectory: baseDirectory,
20 | projectFileName: "package.json",
21 | versionRegex: /(^\s*"version":\s*")(.+)(",\s*$)/gmi,
22 | publishFn: async () => {
23 | const publishResult =
24 | shell.exec(
25 | "npm publish",
26 | {
27 | cwd: baseDirectory
28 | }
29 | )
30 |
31 | // If published failed revert the file change
32 | if (publishResult.code !== 0) {
33 | throw "Npm publish failed"
34 | }
35 | }
36 | })
37 |
--------------------------------------------------------------------------------
/docs/nacara/menu.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "section",
4 | "label": "Overview",
5 | "items": [
6 | "nacara/introduction",
7 | "nacara/configuration",
8 | "nacara/cli-usage",
9 | "nacara/directory-structure",
10 | "nacara/partials"
11 | ]
12 | },
13 | {
14 | "type": "section",
15 | "label": "Guides",
16 | "items": [
17 | "nacara/guides/create-a-page",
18 | "nacara/guides/customize-the-style",
19 | "nacara/guides/create-a-section",
20 | "nacara/guides/section-menu",
21 | "nacara/guides/deploy-your-site",
22 | "nacara/guides/literate-files"
23 | ]
24 | },
25 | {
26 | "type": "section",
27 | "label": "Custom Layouts",
28 | "items": [
29 | "nacara/advanced/layout-from-scratch",
30 | "nacara/advanced/custom-layout-js",
31 | "nacara/advanced/custom-layout-fsharp"
32 | ]
33 | }
34 | ]
35 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nacara-layout-standard",
3 | "version": "1.8.0",
4 | "description": "",
5 | "exports": {
6 | ".": "./dist/Export.js",
7 | "./*": "./*"
8 | },
9 | "type": "module",
10 | "engines": {
11 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0",
12 | "npm": ">=7.0.0"
13 | },
14 | "files": [
15 | "dist/**/*.*",
16 | "scss",
17 | "scripts",
18 | "js"
19 | ],
20 | "scripts": {
21 | "test": "echo \"Error: no test specified\" && exit 1"
22 | },
23 | "author": "Maxime Mangel ",
24 | "license": "Apache-2.0",
25 | "peerDependencies": {
26 | "bulma": "^0.9.3",
27 | "react": "^18.0.0",
28 | "react-dom": "^18.0.0"
29 | },
30 | "dependencies": {
31 | "hastscript": "^7.0.2",
32 | "mdast-util-to-hast": "^11.3.0",
33 | "remark-directive": "^2.0.0",
34 | "slugify": "^1.6.0",
35 | "unist-util-visit": "^4.1.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Nacara.Core.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.2.1
5 | netstandard2.0
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/mobile-menu.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @include desktop {
4 | .mobile-menu {
5 | display: none;
6 | }
7 | }
8 |
9 | @include touch {
10 | .mobile-menu {
11 | display: flex;
12 | flex-direction: revert;
13 | align-items: center;
14 | height: $navbar-height;
15 | background: $white-ter;
16 | position: sticky;
17 | top: $computed-navbar-height;
18 | z-index: 1;
19 | overflow-x: auto;
20 |
21 | .menu-trigger {
22 | @include hamburger($navbar-height);
23 |
24 | .icon {
25 | margin-right: 0;
26 | }
27 | }
28 |
29 | .breadcrumb {
30 | // Prevent the breadcrumb from being wrapped on small screens
31 | //
32 | flex-shrink: 0;
33 | ul {
34 | margin-left: 0;
35 | margin-top: 0;
36 | }
37 |
38 | li {
39 | height: $navbar-height;
40 | }
41 | }
42 |
43 | .breadcrumb>ul>li:last-child {
44 | font-weight: $weight-bold;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/style.scss:
--------------------------------------------------------------------------------
1 | @import "./../node_modules/bulma/sass/utilities/initial-variables";
2 |
3 | // Customize bulma base variables
4 | $primary: #44387a;
5 | $text: #2b2b2b;
6 |
7 | @import "./../node_modules/bulma/sass/utilities/derived-variables";
8 |
9 | // Customize others bulma variables
10 |
11 | $navbar-item-color: $white;
12 | $navbar-item-active-color: $white-bis;
13 | $navbar-item-active-background-color: lighten($primary, 8%);
14 | $navbar-background-color: $primary;
15 | $navbar-item-hover-color: $white;
16 | $navbar-item-hover-background-color: lighten($primary, 8%);
17 |
18 | $menu-item-active-background-color: $primary;
19 | $menu-item-active-color: $white;
20 | $menu-item-hover-color: $primary;
21 | $menu-item-hover-background-color: transparent;
22 | $menu-label-font-size: $size-6;
23 | $menu-item-radius: $radius-large $radius-large;
24 |
25 | $link: $primary;
26 |
27 | $body-size: 14px;
28 |
29 | @import "../node_modules/bulma/sass/utilities/_all.sass";
30 | @import './../node_modules/bulma/bulma.sass';
31 |
32 | // Customize nacara variables
33 | // $textual-steps-color: red;
34 |
35 | @import './../node_modules/nacara-layout-standard/scss/nacara.scss';
36 |
37 | @import './scss//my-style.scss';
38 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/navigation-button.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | .bd-docs-pagination.bd-pagination-links {
4 | border-top: 2px solid #f5f5f5;
5 | margin-top: 3rem;
6 | display: flex;
7 | flex-wrap: wrap;
8 |
9 | .bd-fat-button {
10 | font-weight: $weight-medium;
11 | height: auto;
12 | padding: .75em 1.25em;
13 | font-size: $size-5;
14 |
15 | &.bd-pagination-next {
16 | margin-left: auto;
17 |
18 | span {
19 | margin-right: 1em;
20 | }
21 | }
22 |
23 | &.bd-pagination-prev {
24 | span {
25 | margin-left: 1em;
26 | }
27 | }
28 |
29 | span {
30 | text-align: left;
31 | line-height: 1.25;
32 |
33 | em {
34 | display: block;
35 | font-size: $size-6;
36 | font-style: normal;
37 | font-weight: $weight-normal;
38 | opacity: .5;
39 | text-transform: capitalize;
40 | }
41 |
42 | strong {
43 | font-weight: $weight-medium;
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Interop.fs:
--------------------------------------------------------------------------------
1 | /// Addition interop functions
2 | module Interop
3 |
4 | open Fable.Core.JsInterop
5 | open Node
6 |
7 | ///
8 | /// Dynamically load a module from an absolute path or relative path or an NPM package.
9 | ///
10 | /// The function will take care of transforming the importPath if needed
11 | ///
12 | /// Current working directory to use when transforming a relative path
13 | /// Path to import, it can be absolute, relative or an NPM package
14 | ///
15 | /// A promise loading the provided module
16 | let importDynamic (cwd : string) (importPath : string) =
17 | let importPath =
18 | // This is an absolute path, we need to prefix it with "file://" for Windows
19 | if path.isAbsolute importPath then
20 | path.join("file://", importPath)
21 | // This is a relative path, we need to compute the absolute path and prefix it with "file://" for Windows
22 | else if importPath.StartsWith("./") then
23 | path.join("file://", cwd, importPath)
24 | // This is a package path, nothing to do
25 | else
26 | importPath
27 |
28 | importDynamic importPath
29 |
--------------------------------------------------------------------------------
/docs/nacara/partials.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Partials
3 | layout: standard
4 | ---
5 |
6 | Partials are small components which are used by the layout to give you control over a certain part of the page.
7 |
8 | The most common usage for a partial is to add a footer to your website.
9 |
10 | Look at **your layout documentation** to know which partials are **available**.
11 |
12 | :::info
13 | If you are a layout creator, learn more about how to access the partial by [clicking here](nacara/partials).
14 | :::
15 |
16 | ## Structure
17 |
18 | To create a partial you need to create a file under the `docs/_partials` folder.
19 |
20 | For example, with the following structure there are two partials available:
21 |
22 | - `footer`
23 | - `dropdown-1`
24 |
25 | ```
26 | docs
27 | └── _partials
28 | ├── dropdown-1.jsx
29 | └── footer.jsx
30 | ```
31 |
32 | ## Writing a partial
33 |
34 | When writing a partial you can use raw JavaScript or JSX. If you use JSX, you will need to [setup babel](https://babeljs.io/docs/en/config-files).
35 |
36 | The easiest way to do it is by installing `@babel/preset-react` and creating a `babel.config.json` file near your `package.json` containing:
37 |
38 | ```json
39 | {
40 | "presets": [
41 | "@babel/preset-react"
42 | ]
43 | }
44 | ```
45 |
--------------------------------------------------------------------------------
/src/Nacara.Create/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## 1.1.0 - 2021-11-09
10 |
11 | ### Fixed
12 |
13 | * Fix #130: Install `bulma` when initializing the project
14 |
15 | ## 1.0.2 - 2021-10-27
16 |
17 | ### Changed
18 |
19 | * Ask user confirmation before deleting the destination folder
20 |
21 | ## 1.0.1 - 2021-10-27
22 |
23 | ### Added
24 |
25 | * Add instruction usage at the end of setup
26 |
27 | ## 1.0.0 - 2021-10-26
28 |
29 | ### Changed
30 |
31 | * Use ESM syntax in custom-layout.md file
32 |
33 | ## 1.0.0-beta-004 - 2021-10-26
34 |
35 | ### Fixed
36 |
37 | * Add missing code lang information
38 | * Try fix missing `.gitignore`
39 |
40 | ## 1.0.0-beta-003 - 2021-10-26
41 |
42 | ### Added
43 |
44 | * Add basic footer
45 |
46 | ## 1.0.0-beta-002 - 2021-10-26
47 |
48 | ### Added
49 |
50 | * Release for RC
51 |
52 | ## 1.0.0-beta-001 - 2021-08-25
53 |
54 | ### Added
55 |
56 | * Initial release, because the docs is being publish has work is done on it and people could want to try Nacara as it is.
57 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 5
5 |
6 |
7 |
8 | https://mangelmaxime.github.io/Nacara/
9 | https://github.com/MangelMaxime/Nacara
10 | LICENSE.txt
11 | true
12 | Maxime Mangel
13 |
14 |
15 |
16 | true
17 | true
18 | true
19 | true
20 | snupkg
21 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/docs/nacara/guides/literate-files.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: standard
3 | title: Literate files
4 | ---
5 |
6 | A literate file is a file ending with `.fs` or `.fsx` extension and which start with a front-matter block like this one:
7 |
8 | ```fs
9 | (**
10 | ---
11 | layout: the-layout-name
12 | ---
13 | **)
14 | ```
15 |
16 | Literate files are converted into markdown files before being processed by the Nacara as a normal markdown file.
17 |
18 | They are really handy because:
19 |
20 | - You can use your code editor to get intellisense when writting the snippets
21 | - Compile or run the files to check for errors
22 |
23 | :::info
24 | To keep, the implementation simple, Nacara **doesn't** test the literate files against the F# compiler.
25 |
26 | So if there is a syntax error Nacara will still transform the file.
27 | :::
28 |
29 | ## Commands
30 |
31 | You can decide to hide a code portion by using `(*** hide ***)`
32 |
33 | ```fs
34 | (*** hide ***)
35 |
36 | // This code is hidden
37 | let answer = 42
38 |
39 | (**
40 | From here the code is visible
41 | *)
42 | ```
43 |
44 | ## Markdown blocks
45 |
46 | Markdown blocks are defined by using `(** ... *)`. Anything between these tags is going to be treated as markdown.
47 |
48 | ```fs
49 | (**
50 | ### This line will be converted to a header
51 |
52 | This text is **strong** and this one is in *italic*
53 | *)
54 | ```
55 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Log.fs:
--------------------------------------------------------------------------------
1 | /// Module contains logging functionality
2 | module Log
3 |
4 | open Fable.Core
5 | open Glutinum.Chalk
6 |
7 |
8 | ///
9 | /// Log a message in the console using a bright blue color
10 | ///
11 | ///
12 | ///
13 | let info (text : string) =
14 | JS.console.log(chalk.blueBright.Invoke text)
15 |
16 |
17 | ///
18 | /// Log a warning in the console using a yellow color
19 | ///
20 | /// Message to log
21 | ///
22 | let warn (text : string) =
23 | JS.console.warn(chalk.yellow.Invoke text)
24 |
25 | ///
26 | /// Log a message in the console using a green color
27 | ///
28 | /// Message to log
29 | ///
30 | let success (text : string) =
31 | JS.console.log(chalk.green.Invoke text)
32 |
33 | ///
34 | /// Log an error in the console using a red color
35 | ///
36 | /// Message to log
37 | ///
38 | let error (text : string) =
39 | JS.console.error(chalk.red.Invoke text)
40 |
41 |
42 | ///
43 | /// Alias to JS.console.log
44 | ///
45 | /// Message to log
46 | ///
47 | let log (text : string) =
48 | JS.console.log text
49 |
--------------------------------------------------------------------------------
/docs/nacara/cli-usage.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Command line usage
3 | layout: standard
4 | ---
5 |
6 | ## Usage
7 |
8 | Nacara has several commands available.
9 |
10 | Here is a list of the commands and their usage:
11 |
12 | - `build` : Build the website
13 | - `clean` : Clean up the generated files
14 | - `serve` : Serve the website locally
15 | - `watch` : Start the development server
16 |
17 | You can use `--help` to get more information about a specific command.
18 |
19 | ## Hooks
20 |
21 | ### afterClean
22 |
23 | Both `build` and `watch` support the `--afterClean` hooks which takes a command and will spawn it after the initial clean is done.
24 |
25 | This is useful if you are not using SASS/SCSS to generates your website style.
26 |
27 | Indeed, if you use [TailwindCSS](https://tailwindcss.com/) you can do:
28 |
29 | `nacara build --afterClean 'npx postcss style/main.css -o docs_deploy/style.css'`
30 |
31 | This will spawn `postcss` once the initial clean is done and then generates your website.
32 |
33 | :::warning
34 | It is important that you place your command **between** quotes or singles quotes.
35 | :::
36 |
37 | ## Watcher improvements
38 |
39 | If you see an error like `EMFILE: too many open files`, you can try setting the `CHOKIDAR_USEPOLLING` environment variable to `true`.
40 |
41 | Example: `CHOKIDAR_USEPOLLING=true nacara watch`
42 |
43 | See [Chokidar documentation](https://github.com/paulmillr/chokidar#performance) to learn more about it.
44 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/layouts.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Layouts
3 | layout: standard
4 | ---
5 |
6 | Here are all the layouts include in this package:
7 |
8 | - `nacara-standard`: standard documentation page
9 | - `nacara-navbar-only`: a navbar with a blank canvas
10 | - `nacara-changelog`: for displaying your changelog on your website
11 |
12 | ## nacara-standard
13 |
14 | This layout is mostly used for documentation page.
15 |
16 | This layout include:
17 |
18 | - An optional menu
19 | - A table of content
20 | - Navigation buttons
21 |
22 | ### Front matter
23 |
24 | #### `toc`
25 |
26 | Optional
27 | Type: `Object | Boolean`
28 |
29 | Options:
30 |
31 | - `from` - Default: `2`
32 | The level of the heading to start the table of content
33 |
34 | - `to` - Default: `2`
35 | The level of the heading to end the table of content
36 |
37 | **Example**
38 |
39 | ```yml
40 | # Change the configuration of the table of content
41 | toc:
42 | from: 2
43 | to: 3
44 |
45 | # Disable the table of content
46 | toc: false
47 | ```
48 |
49 | ## nacara-navbar-only
50 |
51 | This layout will generate a navbar and your content underneath it.
52 |
53 | In general, you will want to use this layout for your index page so you have total freedom to style it using HTML tags.
54 |
55 | ## nacara-changelog
56 |
57 | This layout will parse your changelog file and generate a page based on it.
58 |
59 | ### Front matter
60 |
61 | #### `changelog_path`
62 |
63 | Required
64 | Type: `String`
65 |
66 | Relative path to the changelog to display
67 |
68 | **Example**
69 |
70 | ```yml
71 | changelog_path: ./../../src/Nacara/CHANGELOG.md
72 | ```
73 |
--------------------------------------------------------------------------------
/docs/nacara/guides/customize-the-style.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Style your application
3 | layout: standard
4 | ---
5 |
6 | Nacara allows you to customize your application style to match your design.
7 |
8 | ## Main file
9 |
10 | The main file for styling your application is `docs/style.scss` or `docs/style.sass`.
11 |
12 | Try editing this file and see the site being updated.
13 |
14 | ## Special folder
15 |
16 | When your website becomes bigger, you will want to split your `style` file into smaller files.
17 |
18 | Nacara has special folders to deal with that:
19 |
20 | - `docs/scss`: use this folder if you are using SCSS to write your style
21 | - `docs/sass`: use this folder if you are using SASS to write your style
22 |
23 | When one of the files of these folders changes, Nacara will recompile your `docs/style.scss` or `docs/style.sass`.
24 |
25 | Let's try it out, we are going to assume you use SCSS but the process is the same when using SASS.
26 |
27 |
28 |
29 | -
30 |
31 | Add the following code to the page you created earlier `docs/tutorial/my-page`
32 |
33 | ```html
34 |
35 |
36 | This text should be bold and red.
37 |
38 |
39 | ```
40 |
41 |
42 |
43 | -
44 |
45 | Create a file `docs/scss/my-style.scss`
46 |
47 | ```scss
48 | .my-text {
49 | color: $danger;
50 | font-weight: $weight-bold;
51 | }
52 | ```
53 |
54 | Because default layout of Nacara use Bulma, you have access to [Bulma variables](https://bulma.io/documentation/customize/variables/).
55 |
56 |
57 |
58 | -
59 |
60 | Include your file in `style.scss`
61 |
62 | ```scss
63 | @import './scss/my-style.scss';
64 | ```
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/Nacara/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nacara",
3 | "version": "1.8.0",
4 | "license": "MIT",
5 | "bin": {
6 | "nacara": "./cli.js"
7 | },
8 | "files": [
9 | "dist/**/*.*",
10 | "js",
11 | "scripts"
12 | ],
13 | "type": "module",
14 | "scripts": {},
15 | "engines": {
16 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0",
17 | "npm": ">=7.0.0"
18 | },
19 | "dependencies": {
20 | "@babel/core": "^7.15.5",
21 | "chalk": "^4.1.2",
22 | "chokidar": "^3.5.2",
23 | "express": "^4.21.2",
24 | "front-matter": "^4.0.2",
25 | "nodemon": "^2.0.20",
26 | "react": "^18.0.0",
27 | "react-dom": "^18.0.0",
28 | "rehype-autolink-headings": "^6.1.0",
29 | "rehype-format": "^4.0.0",
30 | "rehype-preset-minify": "^6.0.0",
31 | "rehype-raw": "^6.1.0",
32 | "rehype-slug": "^5.0.0",
33 | "rehype-stringify": "^9.0.2",
34 | "remark-parse": "^10.0.0",
35 | "remark-rehype": "^9.1.0",
36 | "remark-slug": "^7.0.0",
37 | "sass": "1.42.1",
38 | "semver": "^7.5.2",
39 | "unified": "^10.1.0",
40 | "ws": "^8.17.1",
41 | "yargs": "^17.2.1"
42 | },
43 | "comments": "I don't know why but if we include gatsby-remark-vscode to the root package.json then there is a bug when building the documentation with the linked version of Nacara... Adding it to this package.json as a devDependencies helps mitigates the issues. I checked and the problem doesn't happen when using Nacara from the NPM package as a user would",
44 | "devDependencies": {
45 | "gatsby-remark-vscode": "^3.3.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/guides/customize-the-style.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Style your application
3 | layout: standard
4 | ---
5 |
6 | Nacara allows you to customize your application style to match your design.
7 |
8 | ## Main file
9 |
10 | The main file for styling your application is `docs/style.scss`.
11 |
12 | Modify the `$primary` color to `#44387a` and save the file.
13 |
14 | You should see the site update and use the new color.
15 |
16 | ## Special folder
17 |
18 | When you website become bigger you will want to split your `style.scss` file into smaller files.
19 |
20 | Nacara has specials folders to deal with that:
21 |
22 | - `docs/scss`: use this folder is you are using SCSS to write your style
23 | - `docs/sass`: use this folder is you are using SASS to write your style
24 |
25 | When one of the files of these folders changes, Nacara will recompile your `docs/style.scss`.
26 |
27 | Let's try it out
28 |
29 | :::info
30 | The template is configured to use SCSS, if you prefer to use SASS you can adapt the template.
31 | :::
32 |
33 |
34 |
35 | -
36 |
37 | Add the following code to the page you created earlier `docs/tutorial/my-page`
38 |
39 | ```md
40 |
41 |
42 | This text should be bold and red.
43 |
44 |
45 | ```
46 |
47 |
48 |
49 | -
50 |
51 | Create a file `docs/scss/my-style.scss`
52 |
53 | ```scss
54 | .my-text {
55 | color: $danger;
56 | font-weight: $weight-bold;
57 | }
58 | ```
59 |
60 | Because default layout of Nacara use Bulma, you have access to [Bulma variables](https://bulma.io/documentation/customize/variables/).
61 |
62 |
63 |
64 | -
65 |
66 | Include your file in `style.scss`
67 |
68 | ```scss
69 | @import './scss/my-style.scss';
70 | ```
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nacara
2 |
3 | Nacara is a simple static documentation generator extensible using F# via [Fable](https://fable.io) or JavaScript.
4 |
5 | ## Development
6 |
7 | ### Requirements
8 |
9 | Make sure to have these programs installed before proceeding.
10 |
11 | - Node v14+
12 | - Yarn v1.22+
13 | - .NET SDK 5.0.202+
14 | - Make 4.3
15 |
16 | If you can't or don't want to install the requirements on your computer, you can use Gitpod which provide a ready to use environment directly in your browser.
17 |
18 | **For Linux**
19 |
20 | If on Linux, you should install the following packages on your system before proceeding:
21 |
22 | ```bash
23 | # Debian based
24 | sudo apt-get install make gcc g++
25 | ```
26 |
27 | **For Windows**
28 |
29 | You need to have make installed on your system. The easiest way to install make is by using [chocolatey](https://chocolatey.org/).
30 |
31 | 1. Install [chocolatey](https://chocolatey.org/install)
32 | 2. Install `make` via `choco install make`
33 |
34 | It is planned to add a `make.bat` file in the future but for now it will do.
35 |
36 | ### Makefile
37 |
38 | This project use a Makefile to handle the build automation here are the main targets
39 |
40 | - `setup-dev`:
41 |
42 | Install of the dependencies and configure the npm link.
43 |
44 | Useful, when you want to test a local build of Nacara in another project or to develop it.
45 |
46 | Note: You should run this target each time you updates the NPM dependencies
47 |
48 | - `unsetup-dev`:
49 |
50 | Revert the npm link
51 |
52 | - `watch`:
53 |
54 | Watch files changes for compilations and run a local version of Nacara for development
55 |
56 | - `release`:
57 |
58 | Release the different NPM and NuGet packages
59 |
60 | - `publish`:
61 |
62 | Execute release and publish a new version of the docs site
63 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/guides/create-a-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Create a Page
3 | layout: standard
4 | ---
5 |
6 | A page in Nacara is a **Markdown** file composed of two things:
7 |
8 | - **Front Matter**: configure how the page should rendered, for example it is here that you specify which layout to applied
9 | - **Content**: The markdown content to include in the page
10 |
11 | ## Create your first page
12 |
13 | Create a file `docs/documentation/guides/my-page.md`:
14 |
15 | ```
16 | ---
17 | title: My page
18 | layout: standard
19 | ---
20 |
21 | This is a new page created for the tutorial.
22 | ```
23 |
24 | The new page is available at [http://localhost:8080/documentation/guides/my-page.html](http://localhost:8080/documentation/guides/my-page.html)
25 |
26 | ## Add your page to the menu
27 |
28 | If you look on the menu in the left, you will see that your page is missing from it.
29 |
30 | This is because you need to provide some information to Nacara via the `menu.json` file.
31 |
32 | Edit the file `docs/documentation/menu.json` to add `"documentation/guides/my-page"` to it.
33 |
34 | The file should looks like:
35 |
36 | ```json
37 | [
38 | {
39 | "type": "section",
40 | "label": "Overview",
41 | "items": [
42 | "documentation/introduction"
43 | ]
44 | },
45 | {
46 | "type": "section",
47 | "label": "Guides",
48 | "items": [
49 | "documentation/guides/create-a-page",
50 | "documentation/guides/customize-the-style",
51 | "documentation/guides/create-a-section",
52 | "documentation/guides/custom-layout",
53 | "documentation/guides/deploy-your-site"
54 | ]
55 | },
56 | "documentation/guides/my-page"
57 | ]
58 |
59 | ```
60 |
61 | You should now see `My page` at the bottom of the menu.
62 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Navbar.fs:
--------------------------------------------------------------------------------
1 | /// Helpers making it easier to work with navbar
2 | module Navbar
3 |
4 | open Nacara.Core.Types
5 |
6 | let tryFindWebsiteSectionLabelForPage
7 | (navbar : NavbarConfig)
8 | (pageContext : PageContext) =
9 | let rec tryFindFromDropdown (dropdownItems : DropdownItem list) =
10 | match dropdownItems with
11 | | head :: tail ->
12 | match head with
13 | | DropdownItem.Divider ->
14 | tryFindFromDropdown tail
15 |
16 | | DropdownItem.Link { Section = Some itemSection; Label = itemLabel } ->
17 | if itemSection = pageContext.Section then
18 | Some itemLabel
19 | else
20 | tryFindFromDropdown tail
21 |
22 | | DropdownItem.Link _ ->
23 | tryFindFromDropdown tail
24 |
25 | | [] ->
26 | None
27 |
28 | let rec tryFind (navbarItems : StartNavbarItem list) =
29 | match navbarItems with
30 | | head :: tail ->
31 | match head with
32 | | StartNavbarItem.LabelLink { Section = Some itemSection; Label = itemLabel } ->
33 | if itemSection = pageContext.Section then
34 | Some [ itemLabel ]
35 | else
36 | tryFind tail
37 |
38 | | StartNavbarItem.Dropdown dropdownInfo ->
39 | match tryFindFromDropdown dropdownInfo.Items with
40 | | Some label ->
41 | Some [ dropdownInfo.Label; label ]
42 |
43 | | None ->
44 | None
45 |
46 | | _ ->
47 | tryFind tail
48 |
49 | | [] ->
50 | None
51 |
52 | tryFind navbar.Start
53 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Layout.fs:
--------------------------------------------------------------------------------
1 | module Layout
2 |
3 | #nowarn "52"
4 |
5 | open Nacara.Core.Types
6 | open Fable.Core
7 | open Fable.Core.JsInterop
8 | open Node
9 |
10 | let private babel : obj =
11 | import "default" "@babel/core"
12 |
13 | let load (config : Config) (layoutPath : string) : JS.Promise =
14 | promise {
15 | // The path is relative, so load it relatively from the CWD
16 | if layoutPath.StartsWith("./") then
17 | let fullPath =
18 | path.join(config.WorkingDirectory, layoutPath)
19 |
20 | // Cache busting is useful for development, but not for production.
21 | let cacheBusting =
22 | System.DateTime.UtcNow.ToString("O")
23 |
24 | match layoutPath with
25 | | Js ->
26 | return! Interop.importDynamic config.WorkingDirectory (fullPath + "?" + cacheBusting)
27 |
28 | | Jsx ->
29 | let! res = babel?transformFileAsync(fullPath)
30 |
31 | let destination =
32 | path.join(config.WorkingDirectory, ".nacara", layoutPath)
33 | |> File.changeExtension "js"
34 |
35 | do! File.write destination res?code
36 |
37 | return! Interop.importDynamic config.WorkingDirectory (destination + "?" + cacheBusting)
38 |
39 | | Other _ ->
40 | Log.error $"Local layouts scripts must be JavaScript or JSX files: %s{layoutPath}"
41 | ``process``.exit(ExitCode.INVALID_LAYOUT_SCRIPT)
42 | return failwith "Make the compiler happy, but should already have existed"
43 |
44 | // The path is not relative, require it as an npm module
45 | else
46 | return! importDynamic layoutPath
47 | }
48 | |> Promise.map (fun (layoutModule : LayoutInterface) ->
49 | layoutModule.``default``
50 | )
51 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/js/remark-block-container.js:
--------------------------------------------------------------------------------
1 | import { visit } from "unist-util-visit";
2 | import { h } from 'hastscript';
3 | import {toHast} from 'mdast-util-to-hast';
4 |
5 | const renderMessageHeader = (title) => {
6 | return h(
7 | "div",
8 | {
9 | className: "message-header"
10 | },
11 | h(
12 | "p",
13 | null,
14 | title
15 | )
16 | )
17 | }
18 |
19 | export default function () {
20 | return (tree) => {
21 | visit(tree, (node) => {
22 | if (
23 | node.type === 'containerDirective'
24 | ) {
25 | if (
26 | node.name === "primary"
27 | || node.name === "info"
28 | || node.name === "success"
29 | || node.name === "warning"
30 | || node.name === "danger"
31 | ) {
32 | const data = node.data || (node.data = {})
33 |
34 | const hast =
35 | h(
36 | "article",
37 | {
38 | className: `message is-${node.name}`
39 | },
40 | (node.attributes.title) ? renderMessageHeader(node.attributes.title) : null,
41 | h(
42 | "div",
43 | {
44 | className: "message-body"
45 | },
46 | toHast(node)
47 | )
48 | )
49 |
50 | data.hName = hast.tagName
51 | data.hProperties = hast.properties
52 | data.hChildren = hast.children
53 | }
54 |
55 | }
56 | })
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Partial.fs:
--------------------------------------------------------------------------------
1 | module Partial
2 |
3 | #nowarn "52"
4 |
5 | open Nacara.Core.Types
6 | open Fable.Core.JsInterop
7 | open Node
8 |
9 | let babel : obj =
10 | import "default" "@babel/core"
11 |
12 | let loadFromJavaScript (config : Config) (partialPath : string) =
13 | promise {
14 | // Cache busting is useful for development, but not for production.
15 | let cacheBusting =
16 | System.DateTime.UtcNow.ToString("O")
17 |
18 | let fullPath =
19 | path.join(config.WorkingDirectory, config.SourceFolder, partialPath)
20 |
21 | let bustedPath =
22 | fullPath + "?" + cacheBusting
23 |
24 | let! m =
25 | Interop.importDynamic config.WorkingDirectory bustedPath
26 |
27 | let res : Partial =
28 | {
29 | Id = getPartialId partialPath
30 | Module = m
31 | }
32 |
33 | return res
34 | }
35 |
36 | let loadPartialFromJsx (config : Config) (partialPath : string) =
37 | promise {
38 | let fullPath =
39 | path.join(config.WorkingDirectory, config.SourceFolder, partialPath)
40 |
41 | let! res = babel?transformFileAsync(fullPath)
42 |
43 | let destination =
44 | path.join(config.WorkingDirectory, ".nacara", partialPath)
45 | |> File.changeExtension "js"
46 |
47 | do! File.write destination res?code
48 |
49 | // Cache busting is useful for development, but not for production.
50 | let cacheBusting =
51 | System.DateTime.UtcNow.ToString("O")
52 |
53 | let bustedPath =
54 | destination + "?" + cacheBusting
55 |
56 | let! m =
57 | Interop.importDynamic config.WorkingDirectory bustedPath
58 |
59 | let res : Partial =
60 | {
61 | Id = getPartialId partialPath
62 | Module = m
63 | }
64 |
65 | return res
66 | }
67 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/Helpers.fs:
--------------------------------------------------------------------------------
1 | module Helpers
2 |
3 | open Feliz
4 | open Nacara.Core.Types
5 |
6 | let getMenuLabel (pageContext : PageContext) (itemInfo : MenuItemPage) =
7 | match pageContext.Title, itemInfo.Label with
8 | | Some label, Some _
9 | | None, Some label
10 | | Some label, None -> label
11 | | None, None ->
12 | failwith $"Missing label information for '%s{itemInfo.PageId}'. You can set it in the markdown page using the 'title' property or directly in the menu.json via the 'label' property"
13 |
14 |
15 | let rec tryFindTitlePathToCurrentPage
16 | (pageContext : PageContext)
17 | (acc : string list)
18 | (menu : Menu) =
19 |
20 | match menu with
21 | | head :: tail ->
22 | match head with
23 | // Skip this item as it doesn't represent a page
24 | | MenuItem.Link _ ->
25 | tryFindTitlePathToCurrentPage pageContext acc tail
26 |
27 | | MenuItem.List info ->
28 | match tryFindTitlePathToCurrentPage pageContext (acc @ [ info.Label ]) info.Items with
29 | | Some res ->
30 | Some res
31 |
32 | | None ->
33 | tryFindTitlePathToCurrentPage pageContext acc tail
34 |
35 | | MenuItem.Page info ->
36 | if info.PageId = pageContext.PageId then
37 | let menuLabel =
38 | getMenuLabel pageContext info
39 |
40 | Some (acc @ [ menuLabel ])
41 | else
42 | tryFindTitlePathToCurrentPage pageContext acc tail
43 |
44 | | [ ] ->
45 | None
46 |
47 |
48 | let renderBreadcrumbItems (items : string list) =
49 | items
50 | |> List.map (fun item ->
51 | Html.li [
52 | // Make the item active to make it not clickable
53 | prop.className "is-active"
54 |
55 | prop.children [
56 | Html.a [
57 | prop.text item
58 | ]
59 | ]
60 | ]
61 | )
62 |
--------------------------------------------------------------------------------
/scripts/release-nuget.js:
--------------------------------------------------------------------------------
1 | import path from "node:path"
2 | import chalk from "chalk"
3 | import shell from "shelljs"
4 |
5 | const log = console.log
6 |
7 | import { release } from "./release-core.js"
8 |
9 | const getEnvVariable = function (varName) {
10 | const value = process.env[varName];
11 | if (value === undefined) {
12 | log(chalk.red(`Missing environnement variable ${varName}`))
13 | process.exit(1)
14 | } else {
15 | return value;
16 | }
17 | }
18 |
19 | // Check that we have enough arguments
20 | if (process.argv.length < 4) {
21 | log(chalk.red("Missing arguments"))
22 | process.exit(1)
23 | }
24 |
25 | const cwd = process.cwd()
26 |
27 | const relativePathToFsproj = process.argv[3]
28 | const baseDirectory = path.resolve(cwd, process.argv[2])
29 | const fullPathToFsproj = path.resolve(baseDirectory, relativePathToFsproj)
30 | const fsprojDirectory = path.dirname(fullPathToFsproj)
31 | const projectName = path.basename(fullPathToFsproj, ".fsproj")
32 |
33 | const NUGET_KEY = getEnvVariable("NUGET_KEY")
34 |
35 | release({
36 | baseDirectory: baseDirectory,
37 | projectFileName: relativePathToFsproj,
38 | versionRegex: /(^\s*)(.*)(<\/Version>\s*$)/gmi,
39 | publishFn: async (versionInfo) => {
40 |
41 | const packResult =
42 | shell.exec(
43 | "dotnet pack -c Release",
44 | {
45 | cwd: fsprojDirectory
46 | }
47 | )
48 |
49 | if (packResult.code !== 0) {
50 | throw "Dotnet pack failed"
51 | }
52 |
53 | const pushNugetResult =
54 | shell.exec(
55 | `dotnet nuget push bin/Release/${projectName}.${versionInfo.version}.nupkg -s nuget.org -k ${NUGET_KEY}`,
56 | {
57 | cwd: fsprojDirectory
58 | }
59 | )
60 |
61 | if (pushNugetResult.code !== 0) {
62 | throw "Dotnet push failed"
63 | }
64 | }
65 | })
66 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/menu.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | $table-of-content-item-padding-left: 0.75rem !default;
4 |
5 | .menu-container {
6 | // Make the menu container "static" and always displayed at the same place
7 | // The menu container has its own scrollbar if needed
8 | // 3.25rem is the navbar height but it is not yet available at this point of the SCSS compilation
9 | position: sticky;
10 | top: $computed-navbar-height;
11 | // margin: 3.25rem 0 0;
12 | overflow-y: auto;
13 | overscroll-behavior: contain;
14 | max-height: calc(100vh - #{$computed-navbar-height});
15 |
16 | @include touch {
17 | height: calc(100vh - #{$computed-navbar-height});
18 | }
19 |
20 | .menu {
21 | margin-top: $navbar-height;
22 | padding-right: 1rem;
23 | padding-left: 1rem;
24 |
25 | @include desktop {
26 | margin-bottom: $navbar-height;
27 | }
28 |
29 | @include touch {
30 | margin-bottom: 10rem;
31 | }
32 |
33 | .menu-external-link {
34 | position: relative;
35 |
36 | &::after {
37 | content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAQElEQVR42qXKwQkAIAxDUUdxtO6/RBQkQZvSi8I/pL4BoGw/XPkh4XigPmsUgh0626AjRsgxHTkUThsG2T/sIlzdTsp52kSS1wAAAABJRU5ErkJggg==);
38 | position: absolute;
39 | top: calc(50% - 5px);
40 | padding-left: 0.325rem;
41 | }
42 | }
43 |
44 | .table-of-content {
45 | @for $rank from 3 through 6 {
46 | [data-toc-rank="#{$rank}"] {
47 | padding-left: calc($table-of-content-item-padding-left * ($rank - 2));
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | .column.is-menu-column {
55 | &.force-show {
56 | display: block !important;
57 | padding: 0 !important;
58 |
59 | .menu-container {
60 | position: fixed;
61 | left: 0;
62 | background: $white-bis;
63 | width: 100%;
64 | z-index: 2;
65 | margin-top: $navbar-height;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/docs/nacara-apigen/introduction.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | layout: standard
4 | ---
5 |
6 | :::info{title="Information"}
7 |
8 | Nacara.ApiGen is in early beta.
9 |
10 | It can already be used to produce API documentation, but changes in the generated files are expected in future version and it is possible that not every F# types are supported yet.
11 |
12 | :::
13 |
14 | ## Installation
15 |
16 | Nacara.ApiGen is a tool for generating API documentation from your code.
17 |
18 | `nacara-layout-standard` is the package providing the standard layout when working with Nacara.
19 |
20 | ```bash
21 | dotnet new tool-manifest # if you are setting up this repo
22 | dotnet tool install Nacara.ApiGen
23 | ```
24 |
25 | ## Usage
26 |
27 | `nacara-apigen` generateds markdonw files that can be consume by `nacara` to generate the `.html` files.
28 |
29 | **Example**
30 |
31 | ```bash
32 | # First prepare the DLLs and XML files
33 | dotnet publish
34 |
35 | # Run nacara-apigen
36 | dotnet nacara-apigen \
37 | --project MyProject \
38 | -lib src/bin/Debug/netstandard2.0/publish \
39 | --output docs \
40 | --base-url /my-site/
41 | ```
42 |
43 | :::info
44 | Remember to have set `true` in your fsproj file to have the XML file generated.
45 | :::
46 |
47 | ### CLI Arguments
48 |
49 | Righ now `nacara-apigen` doesn't know yet how to read your `nacara.config.json` so you need to pass everything via CLI arguments:
50 |
51 | - `--project` or `-p`: The name of your project
52 | - `--lib-directory` or `-lib`: The source dierectory where your dlls and xml files are located
53 | - `--base-url`: Base URL for your site. The same as you configured in your `nacara.config.json` file
54 | - `--output` or `-o`: The output directory
55 |
56 | In order to works, `nacara-apigen` needs to have access to all your project dlls and xml files., the easiest way to get them is to run `dotnet publish` before invoking `dotnet nacara-apigen` using the generated directory as source.
57 |
58 | ### SCSS variables
59 |
60 | `$nacara-api-keyword-color`
61 |
62 | Default: `#a626a4`
63 |
64 | Control the color of the keywords.
65 |
66 | ---
67 |
68 | `$nacara-api-type-color`
69 |
70 | Default: `#c18401`
71 |
72 | Control the color of the types.
73 |
74 | ---
75 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/scss-sass-variables.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: SCSS/SASS variables
3 | layout: standard
4 | ---
5 |
6 | ## Computed
7 |
8 | ---
9 |
10 | `$computed-navbar-height`
11 |
12 | Easy way to access the navbar height, handy if you need to place element based on the navbar position.
13 |
14 | ---
15 |
16 | ## Font awesome
17 |
18 | ---
19 |
20 | `$font-awesome-url`
21 |
22 | Default: `https://use.fontawesome.com/releases/v5.15.4/css/all.css`
23 |
24 | Control which version of Font Awesome is loaded
25 |
26 | ---
27 |
28 | ## Navbar
29 |
30 | ---
31 |
32 | `$nacara-navbar-menu-border`
33 |
34 | Default: `1px solid $navbar-item-active-background-color`
35 |
36 | Control the border of the menu items displayed when on mobile
37 |
38 | ---
39 |
40 | ## Dropdown
41 |
42 | ---
43 |
44 | `$nacara-navbar-dropdown-item-color`
45 |
46 | Default: `$text`
47 |
48 | Control the text color of the dropdown items
49 |
50 | ---
51 |
52 | `$nacara-navbar-dropdown-boxed-shadow`
53 |
54 | Default: `$navbar-dropdown-boxed-shadow`
55 |
56 | Control the shadow displayed when the dropdown is open
57 |
58 | ---
59 |
60 | `$nacara-navbar-dropdown-z-index`
61 |
62 | Default: `$navbar-z`
63 |
64 | Control the z-index of the dropdown when open
65 |
66 | ---
67 |
68 | `$nacara-grey-overlay-z`
69 |
70 | Default: `20`
71 |
72 | Control the z-index of the grey overlay which is displayed when the dropdown is open
73 |
74 | ---
75 |
76 | `$nacara-grey-overlay-background-color`
77 |
78 | Default: `bulmaRgba($scheme-invert, 0.1)`
79 |
80 | Control the color of the grey overlay which is displayed when the dropdown is open
81 |
82 | ---
83 |
84 | `$nacara-navbar-dropdown-floating-max-width`
85 |
86 | Default: `300px`
87 |
88 | Control the max width of the dropdown when floating
89 |
90 | ## Textual steps
91 |
92 | ---
93 |
94 | `$textual-steps-color`
95 |
96 | Default: `$grey-lighter`
97 |
98 | Control the text color of the generated number for the textual-steps
99 |
100 | ---
101 |
102 | ## Table of content
103 |
104 | ---
105 |
106 | `$table-of-content-item-padding-left`
107 |
108 | Default: `0.75rem`
109 |
110 | Control the padding step used for the table of content items, at each level of indentation we increase the padding by the provided value
111 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Menu.fs:
--------------------------------------------------------------------------------
1 | /// Helpers making it easier to work with menus
2 | module Menu
3 |
4 | open Nacara.Core.Types
5 |
6 |
7 | ///
8 | /// Transform the given menu into a flat list of MenuItem.Page and MenuItem.Link
9 | ///
10 | /// This function is mostly used by toFlatMenu function.
11 | ///
12 | /// Menu to flatten
13 | /// A flatten representation of the menu
14 | let rec flatten (menu : Menu) =
15 | menu
16 | |> List.collect (fun menuItem ->
17 | match menuItem with
18 | | MenuItem.Page _
19 | | MenuItem.Link _ -> [ menuItem ]
20 | | MenuItem.List info ->
21 | flatten info.Items
22 | )
23 |
24 |
25 | ///
26 | /// Transform a menu into a FlatMenu representation
27 | ///
28 | /// This function is handy when generating menu or the navigation button between pages.
29 | ///
30 | /// Menu to transform
31 | /// Converted menu to FlatMenu type
32 | let toFlatMenu (menu : Menu) =
33 | flatten menu
34 | |> List.map (fun menuItem ->
35 | match menuItem with
36 | | MenuItem.Page info -> FlatMenu.Page info
37 | | MenuItem.Link info -> FlatMenu.Link info
38 | | MenuItem.List _ ->
39 | failwith "Should not happen because all the MenuItem.List should have been flattened"
40 | )
41 |
42 | ///
43 | /// It returns a MenuItem.List if it finds a page with the given pageId under a MenuItem.List.
44 | ///
45 | /// Used for obtaining the section name for navigation buttons.
46 | ///
47 | /// Menu where to look for a page ID.
48 | /// ID of the page to look for.
49 | /// MenuItem.List or MenuItem.Page or None.
50 | let rec tryFindSection (menu : Menu) pageId =
51 | menu
52 | |> List.tryFind (fun menuItem ->
53 | match menuItem with
54 | | MenuItem.Page page -> page.PageId = pageId
55 | | MenuItem.Link _ -> false
56 | | MenuItem.List list ->
57 | match list.Items with
58 | | [] -> false
59 | | items ->
60 | match tryFindSection items pageId with
61 | | None -> false
62 | | Some _ -> true
63 | )
64 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/nacara.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | $computed-navbar-height: $navbar-height + $navbar-padding-vertical * 2;
4 |
5 | @import "./adapters/_all.scss";
6 | @import "./components/_all.scss";
7 | @import "./nacara-api.scss";
8 |
9 | $font-awesome-url: "https://use.fontawesome.com/releases/v5.15.4/css/all.css" !default;
10 |
11 | @import url($font-awesome-url);
12 |
13 | // Fix bug: On some browser the body is not placed at the right place
14 | // This code should fix the issue
15 | html {
16 | &.has-navbar-fixed-top {
17 | padding-top: 0;
18 |
19 | body {
20 | padding-top: 0;
21 | margin-top: $computed-navbar-height;
22 | }
23 | }
24 | }
25 |
26 | // If a footer is added via the footer partial
27 | // We setup some specific rules allowing us to have
28 | // the footer stick to the bottom when there are not a lot of content
29 | // and otherwise it will be displayed under the page content as normal
30 | // Adapted from: https://css-tricks.com/couple-takes-sticky-footer/#there-is-flexbox
31 | body.has-footer {
32 |
33 | display: flex;
34 | flex-direction: column;
35 |
36 | @include tablet {
37 | height: calc(100vh - #{$computed-navbar-height});
38 | }
39 |
40 | @include mobile {
41 | height: calc(100vh - #{$computed-navbar-height + $navbar-height});
42 | }
43 |
44 | .nacara-content {
45 | flex: 1 0 auto;
46 | }
47 |
48 | .footer {
49 | flex-shrink: 0;
50 | }
51 |
52 | }
53 |
54 | // Remove bottom margin of the name of the project in the navbar otherwise a the navbar is distorted
55 | .navbar-item.title.is-4 {
56 | margin-bottom: 0;
57 | }
58 |
59 | // Allow to add `is-active` class to navbar div item
60 | .navbar-brand {
61 | div.navbar-item,
62 | a.navbar-item {
63 | &.is-active,
64 | &:hover {
65 | background-color: $navbar-item-hover-background-color;
66 | color: $navbar-item-hover-color;
67 | }
68 | }
69 | }
70 |
71 | // Add a space between an icon and the text
72 | .icon + span {
73 | margin-left: 0.5rem;
74 | }
75 |
76 | span + .icon {
77 | margin-left: 0.5rem;
78 | }
79 |
80 | // Force the margin-left when using span+.icon in a button element
81 | // otherwise the margin seems to small to me
82 | .button span + .icon {
83 | margin-left: 0.5rem !important;
84 | }
85 |
--------------------------------------------------------------------------------
/src/Nacara/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import nodemon from 'nodemon';
4 | import { runBuild, runClean, runServe } from './dist/Main.js';
5 |
6 | import { fileURLToPath } from 'node:url'
7 | import path from 'node:path';
8 | import { info } from './dist/Nacara.Core/Log.js';
9 | import yargs from 'yargs';
10 | import { hideBin } from "yargs/helpers";
11 | import afterCleanOptions from './js/afterClean-options.js';
12 |
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = path.dirname(__filename);
15 |
16 | yargs(hideBin(process.argv))
17 | .completion()
18 | .strict()
19 | .help()
20 | .scriptName("nacara")
21 | .alias("help", "h")
22 | .version()
23 | .command(
24 | "watch",
25 | "Start the development server",
26 | (argv) => {
27 | argv
28 | .option(
29 | "afterClean",
30 | afterCleanOptions
31 | )
32 | },
33 | async (argv) => {
34 | nodemon({
35 | script: path.join(__dirname, "./js/nodemon-watch.js"),
36 | args: process.argv.slice(2),
37 | watch: [
38 | "nacara.config.json",
39 | "nacara.config.js"
40 | ],
41 | delay: 200
42 | })
43 | // Inform the user that the application is restarting
44 | .on("restart", (x) => {
45 | info("Nacara configuration file changed, restarting Nacara...")
46 | })
47 | // Allow the user to kill the application with a single CTRL+C
48 | // Without this the user need to do it twice
49 | .once('quit', function () {
50 | process.exit();
51 | });
52 | }
53 | )
54 | .command(
55 | "clean",
56 | "Clean up generated files",
57 | () => {},
58 | runClean
59 | )
60 | .command(
61 | "serve",
62 | "Serve the website locally",
63 | () => {},
64 | runServe
65 | )
66 | .command(
67 | [ "$0", "build" ],
68 | "Build the website",
69 | (argv) => {
70 | argv
71 | .option(
72 | "afterClean",
73 | afterCleanOptions
74 | )
75 | },
76 | runBuild
77 | )
78 | .argv
79 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/style.scss:
--------------------------------------------------------------------------------
1 | @import "./../node_modules/bulma/sass/utilities/initial-variables";
2 |
3 | // Color palette
4 | // https://lospec.com/palette-list/fluffy8
5 |
6 | /////////////////////////////////
7 | /// Customize Bulma
8 | /////////////////////////////////
9 | $primary: #7679db;
10 | $text: #2b2b2b;
11 | $danger: #c43636;
12 |
13 | @import "./../node_modules/bulma/sass/utilities/derived-variables";
14 |
15 | /////////////////////////////////
16 | /// nacara-layout-standard customizations
17 | /// Do not touch unless you know what you are doing
18 | /////////////////////////////////
19 | $navbar-breakpoint: 0px;
20 | $navbar-padding-vertical: 0.5rem;
21 | $navbar-padding-horizontal: 1rem;
22 | /////////////////////////////////
23 |
24 | // Specific to gatsby-remark-vscode usage
25 | $content-pre-padding: unset;
26 |
27 | /////////////////////////////////
28 | /// Customize Bulma
29 | /////////////////////////////////
30 |
31 | $navbar-item-color: $white;
32 | $navbar-background-color: $primary;
33 | $navbar-item-active-color: $white;
34 | $navbar-item-active-background-color: lighten($primary, 4%);
35 | $navbar-item-hover-color: $white;
36 | $navbar-item-hover-background-color: lighten($primary, 4%);
37 | $navbar-dropdown-item-active-background-color: lighten($primary, 4%);
38 | $navbar-dropdown-item-hover-background-color: lighten($primary, 4%);
39 | $navbar-dropdown-item-hover-color: $white;
40 | $navbar-dropdown-item-active-color: $white;
41 |
42 | $menu-item-active-background-color: $primary;
43 | $menu-item-active-color: $white;
44 | $menu-item-hover-color: $primary;
45 | $menu-item-hover-background-color: transparent;
46 | $menu-label-font-size: $size-6;
47 | $menu-item-radius: $radius-large $radius-large;
48 |
49 | $footer-background-color: $primary;
50 | $footer-color: $white;
51 |
52 | $link: darken($primary, 4%);
53 | $code: $red;
54 |
55 | $body-size: 14px;
56 |
57 | // Customize nacara variables
58 | // $textual-steps-color: red;
59 |
60 | @import "../node_modules/bulma/sass/utilities/_all.sass";
61 | @import "./../node_modules/bulma/bulma.sass";
62 | @import "./../node_modules/nacara-layout-standard/scss/nacara.scss";
63 |
64 | // Begin gatsby-remark-vscode specific
65 | :root {
66 | --grvsc-padding-v: 1.25rem;
67 | }
68 |
69 | // Make the code use the full width for when user use line highlighting
70 | .content {
71 | pre > code {
72 | width: 100%;
73 | }
74 | }
75 | // End gatsby-remark-vscode specific
76 |
--------------------------------------------------------------------------------
/src/Nacara.Create/templates/docs/documentation/guides/create-a-section.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Create a Section
3 | layout: standard
4 | ---
5 |
6 | A section in Nacara helps you organize your website. For example, you can have the following sections:
7 |
8 | - **Documentation**: a place to host your project documentation
9 | - **Blog**: a place to publish blog posts
10 |
11 | ## Create a new section
12 |
13 | A section, is defined by creating a folder right under your source folder.
14 |
15 | Currently, you have this structure:
16 |
17 | ```
18 | docsrc
19 | └── documentation
20 | ```
21 |
22 | This means that we only have a single section called `documentation`
23 |
24 |
25 |
26 | -
27 |
28 | Create a new folder `docs/blog`, to get
29 |
30 | ```
31 | docsrc
32 | ├── blog
33 | └── documentation
34 | ```
35 |
36 | You now have 2 sections one for your documentation and another one for hosting some blog post.
37 |
38 |
39 |
40 | -
41 |
42 | Create a file `docs/blog/index.md`:
43 |
44 | ```
45 | ---
46 | title: Blog
47 | layout: standard
48 | ---
49 |
50 | List of posts:
51 |
52 | * [Welcome](/blog/2021/welcome.html)
53 | ```
54 |
55 | This file will serve as the index of your blog
56 |
57 | Create a file `docs/blog/2021/welcome.md`:
58 |
59 | ```
60 | ---
61 | title: Welcome
62 | layout: standard
63 | ---
64 |
65 | Welcome to my blog
66 | ```
67 |
68 |
69 |
70 | -
71 |
72 | We now have our blog pages created but no way to access them.
73 |
74 | Let's add a new **section** to the navbar.
75 |
76 | Add these lines to the `navbar.start` property of `nacara.config.json`:
77 |
78 | ```json
79 | {
80 | "section": "blog",
81 | "url": "/blog/index.html",
82 | "label": "Blog"
83 | }
84 | ```
85 |
86 | It should looks similar to that:
87 |
88 | ```json
89 | "navbar": {
90 | "start": [
91 | {
92 | "section": "documentation",
93 | "url": "/documentation/index.html",
94 | "label": "Documentation"
95 | },
96 | {
97 | "section": "blog",
98 | "url": "/blog/index.html",
99 | "label": "Blog"
100 | }
101 | ],
102 | ```
103 |
104 | Nacara will re-start itself, to take your changes into consideration.
105 |
106 | You should see a `Blog` button in the navbar, click on it and see your blog index page.
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Bindings/Sass.fs:
--------------------------------------------------------------------------------
1 | // ts2fable 0.6.1
2 | module Sass
3 |
4 | open Fable.Core
5 | open System
6 |
7 | type ImporterReturnFile =
8 | { file : string }
9 |
10 | type ImporterReturnContents =
11 | { contents : string }
12 |
13 | type ImporterReturnType =
14 | U3 option
15 |
16 | type [] Importer =
17 | []
18 | abstract Invoke: url: string * prev: string * ``done``: (ImporterReturnType -> unit) -> ImporterReturnType
19 | []
20 | abstract InvokeVoid: url: string * prev: string * ``done``: (ImporterReturnType -> unit) -> unit
21 |
22 | []
23 | []
24 | type OutputStyle =
25 | | Compact
26 | | Compressed
27 | | Expanded
28 | | Nested
29 |
30 | type [] Options =
31 | abstract file: string with get, set
32 | abstract data: string with get, set
33 | abstract importer: U2 with get, set
34 | abstract functions: obj with get, set
35 | abstract includePaths: string array with get, set
36 | abstract indentedSyntax: bool with get, set
37 | abstract indentType: string with get, set
38 | abstract indentWidth: float with get, set
39 | abstract linefeed: string with get, set
40 | abstract omitSourceMapUrl: bool with get, set
41 | abstract outFile: string with get, set
42 | abstract outputStyle: OutputStyle with get, set
43 | abstract precision: float with get, set
44 | abstract sourceComments: bool with get, set
45 | abstract sourceMap: U2 with get, set
46 | abstract sourceMapContents: bool with get, set
47 | abstract sourceMapEmbed: bool with get, set
48 | abstract sourceMapRoot: string with get, set
49 |
50 | type [] SassError(msg) =
51 | inherit Exception(msg)
52 | abstract message: string with get, set
53 | abstract line: float with get, set
54 | abstract column: float with get, set
55 | abstract status: float with get, set
56 | abstract file: string with get, set
57 |
58 | type [] Result =
59 | abstract css: Node.Buffer.Buffer with get, set
60 | abstract map: Node.Buffer.Buffer with get, set
61 | abstract stats: obj with get, set
62 |
63 | type [] IExports =
64 | abstract render: options: Options * callback: (SassError -> Result -> obj) -> unit
65 | abstract renderSync: options: Options -> Result
66 |
67 | []
68 | let sass : IExports = jsNative
69 |
--------------------------------------------------------------------------------
/docs/nacara/guides/create-a-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Create a page
3 | layout: standard
4 | ---
5 |
6 | A page in Nacara is a **Markdown** file composed of two things:
7 |
8 | - **Front Matter**: configure how the page should rendered, for example it is here that you specify which layout to applied
9 | - **Content**: The markdown content to include in the page
10 |
11 | ## Create your first page
12 |
13 | Create a file `docs/documentation/tutorial/my-page.md`:
14 |
15 | ```
16 | ---
17 | title: My page
18 | layout: standard
19 | ---
20 |
21 | This is a new page created for the tutorial.
22 | ```
23 |
24 | The new page is available at [http://localhost:8080/documentation/tutorial/my-page.html](http://localhost:8080//documentation/tutorial/my-page.html)
25 |
26 | ## Add your page to the menu
27 |
28 | If you look on the menu in the left, you will see that your page is missing from it.
29 |
30 | This is because you need to provide some information to Nacara via the `menu.json` file.
31 |
32 | Edit the file `docs/documentation/menu.json` to add `"documentation/tutorial/my-page"` to it.
33 |
34 | The file should look like:
35 |
36 | ```json
37 | [
38 | "documentation/introduction",
39 | {
40 | "type": "category",
41 | "label": "API",
42 | "items": [
43 | "documentation/guides/create-a-page",
44 | "documentation/guides/create-a-section",
45 | "documentation/guides/customize-the-style"
46 | ]
47 | },
48 | "documentation/tutorial/my-page"
49 | ]
50 | ```
51 |
52 | You should now see `My page` in the menu.
53 |
54 | ## Front matter
55 |
56 |
57 |
58 |
59 | | Name |
60 | Required |
61 | Description |
62 |
63 |
64 |
65 |
66 |
67 | layout
68 | |
69 |
70 | X
71 | |
72 | Name of the layout used to render the page |
73 |
74 |
75 |
76 | title
77 | |
78 |
79 | |
80 | Optional, title of the page |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/Nacara.Core/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## 1.2.1 - 2021-11-08
10 |
11 | ### Fixed
12 |
13 | * Detect the Node.JS version and use `fs.rm` or `fs.rmdir` depending on the version.
14 |
15 | This avoid warning about `fs.rmdir` being deprecated in the future.
16 |
17 | ## 1.2.0 - 2021-11-04
18 |
19 | ### Changed
20 |
21 | * Lower FSharp.Core requirements
22 |
23 | ## 1.1.0 - 2021-11-04
24 |
25 | ### Changed
26 |
27 | * Provide the `relativePath` to `unified`.
28 |
29 | This is required for `remark-code-import` plugins to work.
30 |
31 | ## 1.0.0 - 2021-10-28
32 |
33 | ### Added
34 |
35 | * Release v1.0.0
36 |
37 | ## 1.0.0-beta-007 - 2021-10-28
38 |
39 | ### Fixed
40 |
41 | * Check if a directory exist before executing `Directory.rmdir`. Since Node.js v16 it generate an error if the directory doesn't exist
42 |
43 | ## 1.0.0-beta-006 - 2021-10-26
44 |
45 | ### Added
46 |
47 | * Add lot of XML comments
48 |
49 | ## 1.0.0-beta-005 - 2021-09-30
50 |
51 | ### Added
52 |
53 | * Add `Interop.importDynamic` which abstract dynamic loading of package/file.
54 |
55 | This function takes care of prefix the path if needed for the import to works on Windows too
56 |
57 | ### Fixed
58 |
59 | * Fix dynamic import for Windows
60 |
61 | ## 1.0.0-beta-04 - 2021-09-30
62 |
63 | ### Added
64 |
65 | * Add support for the `Partial` property to the `DropdownInfo`
66 |
67 | ## 1.0.0-beta-003 - 2021-09-28
68 |
69 | ### Fixed
70 |
71 | * Attach `MarkdownToHtml` to `RendererContext` class
72 |
73 | ## 1.0.0-beta-002 - 2021-09-26
74 |
75 | ### Changed
76 |
77 | * Fix #44: Move the "site metadata info" into a siteMetadata property in `nacara.config.json` (by @mabasic)
78 | * Add `remarkPlugins` property to `nacara.config.json`
79 | * Add `rehypePlugins` property to `nacara.config.json`
80 |
81 | ### Added
82 |
83 | * `Navbar.tryFindWebsiteSectionLabelForPage` function which return the label of the navbar item corresponding to the given page
84 | * Rework the `NavbarConfig`
85 | - The start section of the navbar now support both `LabelLink` and `Dropdown`
86 | - The end section of the navbar can now only contains links with label/icon intended for Github, Twitter, etc links.
87 | * Fix #96: Add partials support
88 |
89 | ### Removed
90 |
91 | * Remove `lightner` property from `nacara.config.json`
92 |
93 | ## 1.0.0-beta-001 - 2021-07-29
94 |
95 | ### Added
96 |
97 | * Initial release
98 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/TableOfContentParser.fs:
--------------------------------------------------------------------------------
1 | []
2 | module TableOfContentParser
3 |
4 | open System.Text.RegularExpressions
5 |
6 | type HeaderInfo =
7 | {
8 | Title : string
9 | Link : string
10 | }
11 |
12 | type Header =
13 | | Header2 of HeaderInfo
14 | | Header3 of HeaderInfo
15 | | Header4 of HeaderInfo
16 | | Header5 of HeaderInfo
17 | | Header6 of HeaderInfo
18 |
19 | type TableOfContent = Header list
20 |
21 | let private isNotNull (o : 'T) =
22 | not (isNull o)
23 |
24 | let private genHeaderMatcher
25 | (rank : int)
26 | (mapper : HeaderInfo -> Header)
27 | (m : Match) =
28 |
29 | if isNotNull m.Groups.[$"header_%i{rank}"] then
30 | {
31 | Title = m.Groups.[$"h%i{rank}_text"].Value
32 | Link = m.Groups.[$"h%i{rank}_link"].Value
33 | }
34 | |> mapper
35 | |> Some
36 | else
37 | None
38 |
39 | let private (|Header2|_|) (m : Match) : option =
40 | genHeaderMatcher 2 Header.Header2 m
41 |
42 | let private (|Header3|_|) (m : Match) : option =
43 | genHeaderMatcher 3 Header.Header3 m
44 |
45 | let private (|Header4|_|) (m : Match) : option =
46 | genHeaderMatcher 4 Header.Header4 m
47 |
48 | let private (|Header5|_|) (m : Match) : option =
49 | genHeaderMatcher 5 Header.Header5 m
50 |
51 | let private (|Header6|_|) (m : Match) : option =
52 | genHeaderMatcher 6 Header.Header6 m
53 |
54 | let private generateHeaderPattern (rank : int)=
55 | // $"""(?(]*>(?((?!<\/h%i{rank}>).)*)]*href="(?[^"]*)"((?!<\/h%i{rank}>).)*<\/h%i{rank}>))"""
56 | $"""(?(]*>(?((?!<\/h%i{rank}>).)*)]*href="?(?[^" >]*)"?((?!<\/h%i{rank}>).)*<\/h%i{rank}>))"""
57 |
58 |
59 | // Note: TableOfContent parser only extract information from h2 elements
60 | let parse (pageContent : string) =
61 | let pattern =
62 | [
63 | generateHeaderPattern 2
64 | generateHeaderPattern 3
65 | generateHeaderPattern 4
66 | generateHeaderPattern 5
67 | generateHeaderPattern 6
68 | ]
69 | |> String.concat ("|")
70 |
71 | Regex.Matches(pageContent, pattern, RegexOptions.Singleline)
72 | |> Seq.cast
73 | |> Seq.toList
74 | |> List.choose (fun m ->
75 | match m with
76 | | Header2 x -> Some x
77 | | Header3 x -> Some x
78 | | Header4 x -> Some x
79 | | Header5 x -> Some x
80 | | Header6 x -> Some x
81 | | _ -> None // Doesn't happen but F# force us to handle it
82 | )
83 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "WARNING01": "*********************************************************************************",
12 | "WARNING02": "The C# extension was unable to automatically decode projects in the current",
13 | "WARNING03": "workspace to create a runnable launch.json file. A template launch.json file has",
14 | "WARNING04": "been created as a placeholder.",
15 | "WARNING05": "",
16 | "WARNING06": "If OmniSharp is currently unable to load your project, you can attempt to resolve",
17 | "WARNING07": "this by restoring any missing project dependencies (example: run 'dotnet restore')",
18 | "WARNING08": "and by fixing any reported errors from building the projects in your workspace.",
19 | "WARNING09": "If this allows OmniSharp to now load your project then --",
20 | "WARNING10": " * Delete this file",
21 | "WARNING11": " * Open the Visual Studio Code command palette (View->Command Palette)",
22 | "WARNING12": " * run the command: '.NET: Generate Assets for Build and Debug'.",
23 | "WARNING13": "",
24 | "WARNING14": "If your project requires a more complex launch configuration, you may wish to delete",
25 | "WARNING15": "this configuration and pick a different template using the 'Add Configuration...'",
26 | "WARNING16": "button at the bottom of this file.",
27 | "WARNING17": "*********************************************************************************",
28 | "preLaunchTask": "build",
29 | "program": "${workspaceFolder}/src/Nacara.ApiGen/Source/bin/Debug/net5.0/Nacara.ApiGen.dll",
30 | "args": [
31 | "--project",
32 | "Nacara.Core",
33 | "-lib",
34 | "${workspaceFolder}/src/Nacara.Core/bin/Debug/netstandard2.0/publish/",
35 | "--output",
36 | "${workspaceFolder}/docs/",
37 | "--base-url",
38 | "/Nacara/"
39 | ],
40 | "cwd": "${workspaceFolder}",
41 | "console": "internalConsole",
42 | "stopAtEntry": false
43 | },
44 | {
45 | "name": ".NET Core Attach",
46 | "type": "coreclr",
47 | "request": "attach"
48 | }
49 | ]
50 | }
51 |
--------------------------------------------------------------------------------
/src/Nacara/scripts/live-reload.js:
--------------------------------------------------------------------------------
1 | const refreshCSS = () => {
2 | var sheets = [].slice.call(document.getElementsByTagName("link"));
3 | var head = document.getElementsByTagName("head")[0];
4 | for (var i = 0; i < sheets.length; ++i) {
5 | var elem = sheets[i];
6 | // Remove current sheet
7 | head.removeChild(elem);
8 |
9 | var rel = elem.rel;
10 | // Add a query parameter to the URL to force a refresh (cache buster)
11 | if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
12 | var url = elem.href.replace(/(&|\?)_cacheBuster=\d+/, '');
13 | elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheBuster=' + (new Date().valueOf());
14 | }
15 | // Add the new sheet
16 | head.appendChild(elem);
17 | }
18 | }
19 |
20 | var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
21 | var address = protocol + window.location.host + window.location.pathname;
22 |
23 | const connect = () => {
24 | var socket = new WebSocket(address);
25 | let connected = false;
26 |
27 | socket.onmessage = function (msg) {
28 | var data = JSON.parse(msg.data);
29 |
30 | if (data.type === 'reload') {
31 | // Reload is for all the page
32 | if (data.page == null) {
33 | window.location.reload();
34 | } else {
35 | const targetedPage = data.page;
36 |
37 | const currentPage =
38 | location.pathname.replace("/", "");
39 |
40 | // Reload only if the targeted page is the current page
41 | if (currentPage.startsWith(targetedPage)
42 | // Special case for the root page
43 | || (currentPage === "" && targetedPage === "index")
44 | ) {
45 | window.location.reload();
46 | }
47 | }
48 | }
49 | else if (data.type === 'refreshCSS') {
50 | refreshCSS();
51 | }
52 | };
53 |
54 | socket.onopen = function () {
55 | connected = true;
56 | console.log("Connected to Nacara server...")
57 | console.log("Page will be updated when a file changes")
58 | }
59 |
60 | socket.onclose = function () {
61 | if (connected) {
62 | console.error("Disconnected from Nacara server...")
63 | }
64 | else {
65 | console.error("Could not connect to Nacara server...")
66 | }
67 |
68 | setTimeout(() => {
69 | console.clear();
70 | console.log("Reconnecting to Nacara server...");
71 | connect();
72 | }, 5000);
73 | }
74 | }
75 |
76 | connect();
77 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/Source/Export.fs:
--------------------------------------------------------------------------------
1 | module Layout.Standard.Export
2 |
3 | open Fable.Core.JsInterop
4 | open Nacara.Core.Types
5 | open Node
6 | open Feliz
7 | open Feliz.Bulma
8 |
9 | exportDefault
10 | {
11 | Renderers = [|
12 | {
13 | Name = "standard"
14 | Func = Page.Standard.render
15 | }
16 | {
17 | Name = "navbar-only"
18 | Func =
19 | fun rendererContext pageContext ->
20 | promise {
21 | let! pageContent =
22 | rendererContext.MarkdownToHtml(
23 | pageContext.Content,
24 | pageContext.RelativePath
25 | )
26 |
27 | let content =
28 | Html.div [
29 | prop.dangerouslySetInnerHTML pageContent
30 | ]
31 |
32 | return Page.Minimal.render rendererContext pageContext content
33 | }
34 | }
35 | {
36 | Name = "api"
37 | Func =
38 | fun rendererContext pageContext ->
39 | promise {
40 | let! pageContent =
41 | rendererContext.MarkdownToHtml(
42 | pageContext.Content,
43 | pageContext.RelativePath
44 | )
45 |
46 | let content =
47 | Bulma.container [
48 | Bulma.columns [
49 | Bulma.column [
50 | column.is10Desktop
51 | column.isOffset1Desktop
52 |
53 | prop.children [
54 | Bulma.section [
55 | Bulma.content [
56 | prop.dangerouslySetInnerHTML pageContent
57 | ]
58 | ]
59 | ]
60 | ]
61 | ]
62 | ]
63 |
64 |
65 | return Page.Minimal.render rendererContext pageContext content
66 | }
67 | }
68 | {
69 | Name = "changelog"
70 | Func = Page.Changelog.render
71 | }
72 | |]
73 | Dependencies = [|
74 | {
75 | Source = path.join(Module.__dirname, "./../scripts/menu.js")
76 | Destination = Dependencies.menu
77 | }
78 | |]
79 | }
80 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/components/changelog.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | $changelog-tag-width: 102px;
4 | $changelog-no-category-tag-width: 2rem;
5 |
6 | .changelog {
7 |
8 | // Fix margin of p element inside of list element
9 | // Example of markdown related to that
10 | // - [Fable Gitter](https://gitter.im/fable-compiler/Fable)
11 |
12 | // Fable specific discussion and networking channel <-- This line
13 |
14 | // - [Slack](https://fsharp.org/guides/slack/)
15 | .content {
16 | ol, li {
17 | p:not(:last-child) {
18 | margin-bottom: 0;
19 | }
20 | }
21 | }
22 |
23 | ul.changelog-list {
24 | list-style-type: none;
25 | padding: 0;
26 |
27 | li.changelog-list-item {
28 | padding: .5rem 0;
29 | position: relative;
30 | display: flex;
31 |
32 | &::before {
33 | border-left: 1px solid rgba(122,122,122,.25);
34 | content: ' ';
35 | height: calc(100%);
36 | left: $changelog-tag-width * 0.5;
37 | position: absolute;
38 | top: 0px;
39 | z-index: -1;
40 | }
41 |
42 | &.is-version {
43 | padding-top: 2rem;
44 | padding-bottom: .5rem;
45 | margin: 2rem 0 0 0;
46 |
47 | .tag {
48 | min-width: $changelog-tag-width;
49 | width: unset;
50 | }
51 |
52 | &:not(:first-child) {
53 | border-top: 1px solid rgba(122,122,122,.25);
54 | }
55 |
56 | .release-date {
57 | display: inline-flex;
58 | align-items: center;
59 | margin-left: 1rem;
60 | }
61 |
62 | .anchor {
63 | @include desktop {
64 | padding-top: calc(#{$computed-navbar-height + 2rem});
65 | }
66 |
67 | @include touch {
68 | padding-top: calc(#{$computed-navbar-height + $navbar-height + 2rem});
69 | }
70 | }
71 | }
72 |
73 | .changelog-list-item-category {
74 | margin: 0.5rem 1.25em 0.5rem 0;
75 | width: $changelog-tag-width;
76 | }
77 |
78 | .changelog-list-item-text {
79 | margin-left: calc(#{$changelog-tag-width} + 1rem);
80 | position: relative;
81 |
82 | &::before {
83 | content: '\f054';
84 | font-family: 'Font Awesome 5 Free';
85 | font-weight: 900;
86 | display: block;
87 | position: absolute;
88 | left: -2rem;
89 | }
90 | }
91 |
92 | .changelog-details {
93 | width: 100%;
94 | margin-left: calc(#{$changelog-tag-width} + 1rem);
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/docs/nacara/advanced/custom-layout-fsharp.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: With FSharp
3 | layout: standard
4 | ---
5 |
6 | :::primary{title="Note"}
7 | Creating a layout via F#, is the recommended way when working on complex layout or releasing it as an NPM package.
8 | :::
9 |
10 |
11 |
12 | -
13 |
14 | Create a folder to host your code `Nacara.Layout.Custom`.
15 |
16 | ```
17 | mkdir Nacara.Layout.Custom && cd Nacara.Layout.Custom
18 | ```
19 |
20 |
21 |
22 | -
23 |
24 | Create an FSharp project `Nacara.Layout.Custom.fsproj`
25 |
26 | ```
27 | dotnet new library -lang f#
28 | ```
29 |
30 |
31 |
32 | -
33 |
34 | Add `Nacara.Core` to your project
35 |
36 | ```
37 | dotnet add package Nacara.Core
38 | ```
39 |
40 | By default you have access to `Fable.React`, if you prefer `Feliz` you can add it
41 |
42 | ```
43 | dotnet add package Feliz
44 | ```
45 |
46 |
47 |
48 | -
49 |
50 | Change `Library.fs` content to:
51 |
52 | ```fsharp
53 | module Layout
54 |
55 | open Fable.Core.JsInterop
56 | open Nacara.Core.Types
57 | open Fable.React
58 | open Fable.React.Props
59 |
60 | // Your render function, it is responsible to transform a page into HTML
61 | let render (rendererContext : RendererContext) (pageContext : PageContext) =
62 | promise {
63 | let! pageContent =
64 | rendererContext.MarkdownToHtml(
65 | pageContext.Content,
66 | pageContext.RelativePath
67 | )
68 |
69 | return div [ ]
70 | [
71 | str "Hello, from your custom layout"
72 | br [ ]
73 | div [ DangerouslySetInnerHTML { __html = pageContent} ]
74 | [ ]
75 | ]
76 | }
77 |
78 | // This is how we expose layouts to Nacara
79 | exportDefault
80 | {
81 | Renderers = [|
82 | {
83 | Name = "my-layout"
84 | Func = render
85 | }
86 | |]
87 | Dependencies = [| |]
88 | }
89 | ```
90 |
91 |
92 |
93 | -
94 |
95 | Compile your project to JavaScript using Fable.
96 |
97 | If you don't have Fable installed in your project you need to install it:
98 |
99 | ```
100 | dotnet new tool-manifest
101 | dotnet tool install fable
102 | ```
103 |
104 | Launch the compilation:
105 |
106 | ```sh
107 | dotnet fable --outDir dist --watch
108 | ```
109 |
110 |
111 |
112 | -
113 |
114 | Register your layout in the `nacara.config.json`
115 |
116 | ```js
117 | {
118 | // ...
119 | "layouts": [
120 | "nacara-layout-standard",
121 | "./Nacara.Layout.Custom/dist/Library.js"
122 | ]
123 | }
124 | ```
125 |
126 |
127 |
128 |
129 |
130 | :::info
131 | You can find type documentation in the API section.
132 |
133 | Here are the most important topics when working with custom layouts:
134 |
135 | - [RendererContext](/Nacara/reference/Nacara.Core/nacara-core-types-renderercontext.html) : Context accessible when rendering a page.
136 | - [PageContext](/Nacara/reference/Nacara.Core/nacara-core-types-pagecontext.html) : Represents the context of a page within Nacara.
137 | - [LayoutInfo](/Nacara/reference/Nacara.Core/nacara-core-types-layoutinfo.html) : Exposed contract of a layout.
138 | :::
139 |
--------------------------------------------------------------------------------
/scripts/release-core.js:
--------------------------------------------------------------------------------
1 | import path from "node:path"
2 | import fs from "node:fs"
3 | import chalk from "chalk"
4 | import parseChangelog from "changelog-parser"
5 |
6 | const log = console.log
7 |
8 | /**
9 | * @typedef Options
10 | * @type {object}
11 | * @property {string} baseDirectory - Path to the project directory the project file and the CHANGELOG.md should be at the root of this repository
12 | * @property {string} projectFileName - Name of the project file when we need to check/change the version information
13 | * @property {RegExp} versionRegex - Regex used to detect the current version it should have 3 groups: 1. Text before the version - 2. The version - 3. Rest after the function
14 | * @property {function} publishFn - The function to execute in order to publish the package
15 | */
16 |
17 | /**
18 | *
19 | * @param {Options} options
20 | */
21 | export const release = async ( options ) => {
22 |
23 | // checks if the package.json and CHANGELOG exist
24 | const changelogPath = path.resolve(options.baseDirectory, "CHANGELOG.md")
25 | const packageJsonPath = path.resolve(options.baseDirectory, options.projectFileName)
26 |
27 | if (!fs.existsSync(changelogPath)) {
28 | log(chalk.red(`CHANGELOG.md not found in ${options.baseDirectory}`))
29 | }
30 |
31 | if (!fs.existsSync(packageJsonPath)) {
32 | log(chalk.red(`${options.projectFileName} not found in ${options.baseDirectory}`))
33 | }
34 |
35 | // read files content
36 | const changelogContent = fs.readFileSync(changelogPath).toString().replace("\r\n", "\n")
37 | const packageJsonContent = fs.readFileSync(packageJsonPath).toString()
38 |
39 | const changelog = await parseChangelog({ text: changelogContent })
40 |
41 | let versionInfo = undefined;
42 |
43 | // Find the first version which is not Unreleased
44 | for (const version of changelog.versions) {
45 | if (version.title.toLowerCase() !== "unreleased") {
46 | versionInfo = version;
47 | break;
48 | }
49 | }
50 |
51 | if (versionInfo === undefined) {
52 | log(chalk.red(`No version ready to be released found in the CHANGELOG.md`))
53 | process.exit(1)
54 | }
55 |
56 | const m = options.versionRegex.exec(packageJsonContent)
57 |
58 | if (m === null) {
59 | log(chalk.red(`No version property found in the ${options.projectFileName}`))
60 | process.exit(1)
61 | }
62 |
63 | const lastPublishedVersion = m[2];
64 |
65 | if (versionInfo.version === lastPublishedVersion) {
66 | log(chalk.blue(`Last version has already been published. Skipping...`))
67 | process.exit(0)
68 | }
69 |
70 | log(chalk.blue("New version detected"))
71 |
72 | const newPackageJsonContent = packageJsonContent.replace(options.versionRegex, `$1${versionInfo.version}$3`)
73 |
74 | // Update fsproj file on the disk
75 | fs.writeFileSync(packageJsonPath, newPackageJsonContent)
76 |
77 | try {
78 | await options.publishFn(versionInfo)
79 | } catch (e) {
80 | fs.writeFileSync(packageJsonPath, packageJsonContent)
81 | log(chalk.red("An error occured while publishing the new package"))
82 | log(chalk.blue("The files have been reverted to their original state"))
83 | process.exit(1)
84 | }
85 |
86 | log(chalk.green(`Package published`))
87 | process.exit(0)
88 | }
89 |
--------------------------------------------------------------------------------
/docs/nacara-layout-standard/markdown-features.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown features
3 | layout: standard
4 | ---
5 |
6 | ## Code blocks
7 |
8 | By default, all the code blocks have a "Copy" button associated to them.
9 |
10 | If needed, you can prevent the "Copy" button from being added by adding the attributes `data-disable-copy-button="true"` to any parent containing the code blocks.
11 |
12 | ````html
13 |
14 |
15 |
16 | ```
17 | This code will not have the copy button
18 | ```
19 |
20 | ```
21 | This code will not have the copy button
22 | ```
23 |
24 |
25 |
26 | ```
27 | This code have the copy button
28 | ```
29 | ````
30 |
31 | ## Block containers
32 |
33 | Block container allows you to easily colored message blocks, to emphasize part of your page.
34 |
35 | To create a block container, you need to wrap your text with 3 colons, specify a type.
36 |
37 | ```
38 | :::
39 | Your text goes here
40 | :::
41 | ```
42 |
43 | You can also optionally specify a title
44 |
45 | ```
46 | :::{title="My title"}
47 | Your text goes here
48 | :::
49 | ```
50 |
51 | Available types are:
52 |
53 | - primary
54 | - info
55 | - success
56 | - warning
57 | - danger
58 |
59 |
60 |
61 |
62 | **Code**
63 |
64 |
65 |
66 |
67 | **Preview**
68 |
69 |
70 |
71 |
72 | ```
73 | :::primary{title="Information"}
74 | Your text goes here
75 | :::
76 | ```
77 |
78 |
79 |
80 |
81 | :::primary{title="Information"}
82 | Your text goes here
83 | :::
84 |
85 |
86 |
87 |
88 | ```
89 | :::warning{title="Warning"}
90 | Please read this notice
91 | :::
92 | ```
93 |
94 |
95 |
96 |
97 | :::warning{title="Warning"}
98 | Please read this notice
99 | :::
100 |
101 |
102 |
103 |
104 | ```
105 | :::info
106 | Please read this notice
107 | :::
108 | ```
109 |
110 |
111 |
112 |
113 | :::info
114 | Please read this notice
115 | :::
116 |
117 |
118 |
119 |
120 | ```
121 | :::danger
122 | Something went wrong
123 | :::
124 | ```
125 |
126 |
127 |
128 |
129 | :::danger
130 | Something went wrong
131 | :::
132 |
133 |
134 |
135 |
136 | ```
137 | :::success
138 | Everything looks good
139 | :::
140 | ```
141 |
142 |
143 |
144 |
145 | :::success
146 | Everything looks good
147 | :::
148 |
149 |
150 |
151 |
152 | ## Textual steps
153 |
154 | Textual steps makes it easy to create a step by step guides. It will auto-generate the number of the steps for you.
155 |
156 | To create a textual steps, you need to wrap your text withing a `
160 |
161 | -
162 |
163 | This is step one
164 |
165 |
166 |
167 | -
168 |
169 | This is step two.
170 |
171 | And as you can see **Markdown** syntax can be used inside textual-steps.
172 |
173 | ```js
174 | console.log("Hello")
175 | ```
176 |
177 |
178 |
179 |
180 | ````
181 |
182 | **Preview:**
183 |
184 |
185 |
186 | -
187 |
188 | This is step one
189 |
190 |
191 |
192 | -
193 |
194 | This is step two.
195 |
196 | And as you can see **Markdown** syntax can be used inside textual-steps.
197 |
198 | ```js
199 | console.log("Hello")
200 | ```
201 |
202 |
203 |
204 |
205 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/Generate.fs:
--------------------------------------------------------------------------------
1 | module Nacara.ApiGen.Generate
2 |
3 | open FSharp.Formatting.ApiDocs
4 | open System.IO
5 | open StringBuilder.Extensions
6 | open FSharp.Compiler.Symbols
7 | open System.Text
8 | open System.Text.RegularExpressions
9 | open Render
10 |
11 |
12 | let minimalApiFileContent =
13 | """---
14 | layout: api
15 | ---
16 |
17 | """
18 |
19 | let generateIndexPage
20 | (docsRoot : string)
21 | (generateLink : string -> string)
22 | (namespaces : ApiDocNamespace list)
23 | (destination : string)
24 | : unit =
25 |
26 | let sb = new StringBuilder(minimalApiFileContent)
27 |
28 | renderIndex
29 | sb
30 | generateLink
31 | namespaces
32 |
33 | File.write docsRoot destination sb
34 |
35 |
36 | let generateNamespacePage
37 | (docsRoot : string)
38 | (apiUrl : string)
39 | (generateLink : string -> string)
40 | (ns : ApiDocNamespace) =
41 |
42 | let sb = new StringBuilder(minimalApiFileContent)
43 |
44 | renderNamespace
45 | sb
46 | generateLink
47 | ns
48 |
49 | let destination =
50 | apiUrl + ns.UrlBaseName + ".md"
51 |
52 | File.write docsRoot destination sb
53 |
54 |
55 |
56 | let generateEntityPage
57 | (docsRoot : string)
58 | (apiUrl : string)
59 | (generateLink : string -> string)
60 | (entityInfo : ApiDocEntityInfo) =
61 |
62 | let sb = new StringBuilder(minimalApiFileContent)
63 |
64 | let destination =
65 | apiUrl + entityInfo.Entity.UrlBaseName + ".md"
66 |
67 | sb.WriteLine $"""{entityInfo.Entity.Name}
"""
68 |
69 | sb.NewLine ()
70 |
71 | let nsUrl =
72 | generateLink entityInfo.Namespace.UrlBaseName
73 |
74 | sb.WriteLine ""
75 |
76 | sb.WriteLine "
"
81 |
82 | match entityInfo.ParentModule with
83 | | Some parentModule ->
84 | let url =
85 | generateLink parentModule.UrlBaseName
86 |
87 | sb.WriteLine ""
92 |
93 | | None ->
94 | ()
95 |
96 | sb.WriteLine "
"
97 |
98 | let symbol = entityInfo.Entity.Symbol
99 |
100 | if symbol.IsFSharpRecord then
101 | renderRecordType sb entityInfo
102 |
103 | else if symbol.IsFSharpModule then
104 |
105 | renderDeclaredTypes
106 | sb
107 | generateLink
108 | entityInfo.Entity.NestedEntities
109 |
110 | // A module can contains module declarations
111 | renderDeclaredModules
112 | sb
113 | generateLink
114 | entityInfo.Entity.NestedEntities
115 |
116 | renderValueOrFunction
117 | sb
118 | generateLink
119 | entityInfo.Entity.ValuesAndFuncs
120 |
121 | else if symbol.IsNamespace then
122 |
123 | printfn "TODO: Namespace"
124 |
125 | else if symbol.IsFSharpUnion then
126 | renderUnionType sb entityInfo
127 |
128 | else
129 |
130 | match entityInfo.Entity.Comment.Xml with
131 | | Some _ ->
132 | sb.WriteLine """"""
133 | sb.WriteLine "
Description
"
134 | sb.WriteLine "
"
135 | sb.WriteLine (formatXmlComment entityInfo.Entity.Comment.Xml)
136 | sb.WriteLine "
"
137 | sb.WriteLine "
"
138 |
139 | | None ->
140 | ()
141 |
142 | File.write docsRoot destination sb
143 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/scss/nacara-api.scss:
--------------------------------------------------------------------------------
1 | // API gen specific
2 |
3 | $nacara-api-keyword-color: #a626a4 !default;
4 | $nacara-api-type-color: #c18401 !default;
5 |
6 | .api-code {
7 | font-family: monospace;
8 | margin-bottom: 1rem;
9 | scroll-margin-top: 5.25rem;
10 |
11 | pre {
12 | background-color: transparent;
13 | }
14 |
15 | .line {
16 | white-space: nowrap;
17 | }
18 |
19 | a.record-field-name,
20 | a.union-case-property,
21 | a.property {
22 | // color: #4078f2;
23 | color: darken($primary, 4%);
24 |
25 | &:hover {
26 | text-decoration: underline;
27 | }
28 | }
29 |
30 | span.property {
31 | color: darken($primary, 4%);
32 | }
33 |
34 | @keyframes blink {
35 | 0% {
36 | background-color: lighten($primary, 4%);
37 | color: $white;
38 | }
39 | 100% {
40 | background-color: transparent;
41 | color: $link;
42 | }
43 | }
44 |
45 | .property[id],
46 | a[id] {
47 | &:target {
48 | animation-name: blink;
49 | animation-direction: normal;
50 | animation-duration: 0.75s;
51 | animation-iteration-count: 2;
52 | animation-timing-function: ease;
53 | }
54 |
55 | // On desktop and above we only have the navbar to take into account
56 | @include desktop {
57 | &[id] {
58 | scroll-margin-top: $computed-navbar-height + 1rem;
59 | }
60 | }
61 |
62 | // On touch screen we need to take into account the navbar and the breadcrumb
63 | @include touch {
64 | &[id] {
65 | scroll-margin-top: $computed-navbar-height + $navbar-height + 1rem;
66 | }
67 | }
68 | }
69 |
70 | .keyword {
71 | color: $nacara-api-keyword-color;
72 | }
73 |
74 | .type {
75 | color: $nacara-api-type-color;
76 | }
77 | }
78 |
79 | .docs-summary {
80 | margin-top: 2rem;
81 | margin-bottom: 2rem;
82 | // border-top: 1px solid $grey-dark;
83 | }
84 |
85 | dl.docs-parameters {
86 | margin-left: 1rem;
87 | }
88 |
89 | dl.docs-parameters dt:not(:first-child) {
90 | padding-top: 1rem;
91 | border-top: 1px solid $grey-dark;
92 | }
93 |
94 | /* dl.docs-parameters dt:before {
95 | position: absolute;
96 | margin-left: -0.5rem;
97 | display: inline-block;
98 | content: '-';
99 | } */
100 |
101 | dl.docs-parameters dd {
102 | margin-top: 1em;
103 | margin-bottom: 1em;
104 | }
105 |
106 | .doc-parameter {
107 | margin-bottom: 1rem;
108 | }
109 |
110 | dl.docs-parameters dt code {
111 | color: currentColor;
112 | }
113 |
114 | .docs-summary a.type,
115 | dl.docs-parameters dt a.type {
116 | color: $nacara-api-type-color;
117 | }
118 |
119 | .docs-summary a.type:hover,
120 | .api-code .type:hover,
121 | dl.docs-parameters dt a.type:hover {
122 | text-decoration: underline;
123 | cursor: pointer;
124 | }
125 |
126 | dt.api-code {
127 | .return-type {
128 | a {
129 | color: $nacara-api-type-color;
130 | }
131 | }
132 | }
133 |
134 | .record-field {
135 | .record-field-type {
136 | a {
137 | color: $nacara-api-type-color;
138 |
139 | &:hover {
140 | text-decoration: underline;
141 | }
142 | }
143 | }
144 |
145 | .record-field-name {
146 | // color: $text;
147 |
148 | &:hover {
149 | text-decoration: underline;
150 | }
151 | }
152 | }
153 |
154 | a.union-case-property {
155 | &:hover {
156 | text-decoration: underline;
157 | }
158 | }
159 |
160 | .table.api-code {
161 | td {
162 | padding: 0.25rem;
163 | border: unset;
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/Nacara.ApiGen/Source/Main.fs:
--------------------------------------------------------------------------------
1 | module Nacara.ApiGen.Main
2 |
3 | open FSharp.Formatting.ApiDocs
4 | open System.IO
5 | open Nacara.ApiGen.Generate
6 |
7 | open Argu
8 |
9 | type CliArguments =
10 | | [] Project of string
11 | | [] Lib_Directory of string
12 | | [] Output of string
13 | | Base_Url of string
14 |
15 | interface IArgParserTemplate with
16 |
17 | member this.Usage =
18 | match this with
19 | | Project _ ->
20 | "Your project name"
21 |
22 | | Lib_Directory _ ->
23 | "The source directory where your dll and xml file are located"
24 |
25 | | Output _ ->
26 | "The output directory where the generated files will be written"
27 |
28 | | Base_Url _ ->
29 | "Base URL for your site. This is the path after the domain."
30 |
31 | let parser = ArgumentParser.Create()
32 |
33 |
34 | []
35 | let main argv =
36 | try
37 | let res = parser.ParseCommandLine(inputs = argv, raiseOnUsage = true)
38 |
39 | match res.TryGetResult Project, res.TryGetResult Lib_Directory, res.TryGetResult Output with
40 | | Some project, Some libDir, Some output ->
41 | let cwd = Directory.GetCurrentDirectory()
42 |
43 | let libDir = Path.GetFullPath(libDir, cwd)
44 |
45 | // Compute the output directory path
46 | let output = Path.GetFullPath(output, cwd)
47 |
48 | // Initialize F# Formatting context
49 | let dllFile = Path.Combine(libDir, project + ".dll")
50 | let apiDocInput = ApiDocInput.FromFile(dllFile)
51 |
52 | let baseUrl =
53 | res.TryGetResult Base_Url
54 | |> Option.defaultValue "/"
55 |
56 | let apiDocModel =
57 | ApiDocs.GenerateModel(
58 | [ apiDocInput ],
59 | project,
60 | [],
61 | qualify = true,
62 | libDirs = [
63 | libDir
64 | ],
65 | root = baseUrl
66 | )
67 |
68 | // Load the XML doc file
69 | let xmlFile = Path.Combine(libDir, project + ".xml")
70 |
71 | // Clean the output folder
72 | if Directory.Exists (Path.Combine(output, "reference", project)) then
73 | Directory.Delete(Path.Combine(output, "reference", project), true)
74 |
75 | let apiUrl =
76 | $"reference/{project}/"
77 |
78 | let generateLink (urlBaseName : string) =
79 | $"{baseUrl}reference/{project}/{urlBaseName}.html"
80 |
81 | // Render the API index page
82 | let indexPageDestination =
83 | Path.Combine(output, "reference", project, "index.md")
84 |
85 | generateIndexPage
86 | output
87 | generateLink
88 | apiDocModel.Collection.Namespaces
89 | indexPageDestination
90 |
91 | // Render all the namespaces pages
92 | for ns in apiDocModel.Collection.Namespaces do
93 | generateNamespacePage
94 | output
95 | apiUrl
96 | generateLink
97 | ns
98 |
99 | // Render all the entities pages
100 | for entity in apiDocModel.EntityInfos do
101 | generateEntityPage
102 | output
103 | apiUrl
104 | generateLink
105 | entity
106 |
107 | 0
108 |
109 | // Invalid CLI
110 | | _ ->
111 | parser.PrintUsage() |> printfn "%s"
112 | 1
113 |
114 | with e ->
115 | printfn "%s" e.Message
116 | 0
117 |
--------------------------------------------------------------------------------
/src/Nacara.Core/Bindings/Chokidar.fs:
--------------------------------------------------------------------------------
1 | module Chokidar
2 |
3 | open Fable.Core
4 |
5 | module Events =
6 | let [] Add = "add"
7 | let [] AddDir = "addDir"
8 | let [] Change = "change"
9 | let [] Unlink = "unlink"
10 | let [] UnlinkDir = "unlinkDir"
11 | let [] Ready = "ready"
12 | let [] Raw = "raw"
13 | let [] Error = "error"
14 | let [] All = "all"
15 |
16 | type [] AwaitWriteFinishOptions =
17 | /// Amount of time in milliseconds for a file size to remain constant before emitting its event.
18 | abstract stabilityThreshold: float with get, set
19 | /// File size polling interval.
20 | abstract pollInterval: float with get, set
21 |
22 | type [] IOptions =
23 | /// (regexp or function) files to be ignored. This function or regexp is tested against the whole path, not just filename.
24 | /// If it is a function with two arguments, it gets called twice per path - once with a single argument (the path), second time with two arguments (the path and the fs.Stats object of that path).
25 | abstract ignored: obj with get, set
26 | /// (default: false). Indicates whether the process should continue to run as long as files are being watched.
27 | abstract persistent: bool with get, set
28 | /// (default: false). Indicates whether to watch files that don't have read permissions.
29 | abstract ignorePermissionErrors: bool with get, set
30 | /// (default: false). Indicates whether chokidar should ignore the initial add events or not.
31 | abstract ignoreInitial: bool with get, set
32 | /// (default: 100). Interval of file system polling.
33 | abstract interval: float with get, set
34 | /// (default: 300). Interval of file system polling for binary files (see extensions in src/is-binary).
35 | abstract binaryInterval: float with get, set
36 | /// (default: false on Windows, true on Linux and OS X). Whether to use fs.watchFile (backed by polling), or fs.watch. If polling leads to high CPU utilization, consider setting this to false.
37 | abstract usePolling: bool with get, set
38 | /// (default: true on OS X). Whether to use the fsevents watching interface if available. When set to true explicitly and fsevents is available this supercedes the usePolling setting. When set to false on OS X, usePolling: true becomes the default.
39 | abstract useFsEvents: bool with get, set
40 | /// (default: true). When false, only the symlinks themselves will be watched for changes instead of following the link references and bubbling events through the link's path.
41 | abstract followSymlinks: bool with get, set
42 | /// (default: false). If set to true then the strings passed to .watch() and .add() are treated as literal path names, even if they look like globs.
43 | abstract disableGlobbing: bool with get, set
44 | /// can be set to an object in order to adjust timing params:
45 | abstract awaitWriteFinish: U2 with get, set
46 |
47 | type [] FSWatcher =
48 | abstract add: fileDirOrGlob: string -> unit
49 | abstract add: filesDirsOrGlobs: string array -> unit
50 | abstract unwatch: fileDirOrGlob: string -> unit
51 | abstract unwatch: filesDirsOrGlobs: string array -> unit
52 | /// Listen for an FS event. Available events: add, addDir, change, unlink, unlinkDir, error. Additionally all is available which gets emitted for every non-error event.
53 | abstract on: ``event``: string * clb: (string -> string -> unit) -> unit
54 | abstract on: ``event``: string * clb: (exn -> unit) -> unit
55 | /// Removes all listeners from watched files.
56 | abstract close: unit -> unit
57 | abstract options: IOptions with get, set
58 |
59 | type [] IExports =
60 | /// takes paths to be watched recursively and options
61 | abstract watch: path: string * ?options: IOptions -> FSWatcher
62 | abstract watch: paths: string array * ?options: IOptions -> FSWatcher
63 |
64 | let [] chokidar : IExports = jsNative
65 |
--------------------------------------------------------------------------------
/src/Nacara/Source/FsharpFileParser.fs:
--------------------------------------------------------------------------------
1 | module FsharpFileParser
2 |
3 | open System
4 | open System.Text.RegularExpressions
5 |
6 | let private frontMatterPattern =
7 | """\(\*(\*)+[\r\n]*(?---((?!\*\)).)*---)[\r\n]*(\*)+\)"""
8 |
9 | let rec processFile (result : string list) (lines : string list) =
10 | match lines with
11 | | currentLine :: tail ->
12 | let trimedLine = currentLine.Trim()
13 |
14 | if trimedLine.StartsWith("(*** hide ***)") then
15 | // Skip the lines as long as we don't encounter
16 | // another hide instruction or markdown block comment
17 | let newLines =
18 | tail
19 | |> List.skipWhile (fun line ->
20 | not (line.Trim().StartsWith("(**"))
21 | )
22 |
23 | processFile result newLines
24 |
25 | else if trimedLine.StartsWith("(**") then
26 |
27 | let firstLine = trimedLine.[3..].Trim()
28 |
29 | // Take all lines until we encounter the end of the markdown block comment
30 | let markdownLines =
31 | tail
32 | |> List.takeWhile (fun line ->
33 | not (line.TrimEnd().EndsWith("*)"))
34 | )
35 |
36 | // Remove the line we processed as markdown
37 | // from the lines left to process
38 | let rest =
39 | tail
40 | |> List.skip markdownLines.Length
41 |
42 | let newResult =
43 | result @ firstLine :: markdownLines
44 |
45 | processFile newResult rest
46 |
47 | else
48 |
49 | // Take all the code lines
50 | let codeLines =
51 | tail
52 | |> List.takeWhile (fun line ->
53 | not (line.Trim().StartsWith("(**"))
54 | )
55 |
56 | // Remove the line we processed as code
57 | // from the lines left to process
58 | let rest =
59 | tail
60 | |> List.skip codeLines.Length
61 |
62 | let codeBlockLines =
63 | let codeLines =
64 | codeLines
65 | // Remove the empty lines at the begging of the capture block
66 | // Those empty lines are not meaningful
67 | |> List.skipWhile String.IsNullOrEmpty
68 | // Remove the empty lines at the end of the capture block
69 | // Those empty lines are not meaningful
70 | |> List.rev
71 | |> List.skipWhile String.IsNullOrEmpty
72 | |> List.rev
73 |
74 | // If we don't have any code lines left do nothing
75 | if List.isEmpty codeLines then
76 | []
77 | // Otherwise, create a markdown code block
78 | else
79 | [
80 | "```fs"
81 | yield! codeLines
82 | "```"
83 | ]
84 |
85 | processFile (result @ codeBlockLines) rest
86 |
87 | // All the lines have been processed
88 | | [] ->
89 | result
90 |
91 | let tryParse (fileContent : string) =
92 | let m = Regex.Match(fileContent, frontMatterPattern, RegexOptions.Singleline)
93 |
94 | if m.Success then
95 | // Extract the front matter section
96 | let frontMatter = m.Groups.["front_matter"].Value
97 |
98 | // Remove the front matter section from the file
99 | let fileContent =
100 | fileContent.Substring(m.Index + m.Length)
101 | |> String.splitLines
102 | |> Array.toList
103 | |> processFile []
104 | |> (fun lines ->
105 | String.Join("\n", lines)
106 | )
107 |
108 | frontMatter
109 | + Environment.NewLine
110 | + fileContent
111 | |> Some
112 |
113 | else
114 | None
115 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | log_target_info = (echo "\e[34m================\n $1\n================\e[39m")
2 |
3 | NACARA_LAYOUT_STANDARD_DIR=src/Nacara.Layout.Standard
4 | NACARA_DIR=src/Nacara
5 | NACARA_CORE_DIR=src/Nacara.Core
6 | NACARA_CREATE_DIR=src/Nacara.Create
7 | NACARA_API_GEN_DIR=src/Nacara.ApiGen
8 |
9 | # Base of the Fable commands
10 | NACARA_LAYOUT_STANDARD_FABLE=dotnet fable $(NACARA_LAYOUT_STANDARD_DIR)/Source --outDir $(NACARA_LAYOUT_STANDARD_DIR)/dist
11 | NACARA_FABLE=dotnet fable $(NACARA_DIR)/Source --outDir $(NACARA_DIR)/dist
12 | NODEMON_WATCHER=npx nodemon \
13 | --watch $(NACARA_DIR)/dist \
14 | --watch $(NACARA_LAYOUT_STANDARD_DIR)/dist \
15 | --delay 150ms \
16 | --exec \"nacara watch\"
17 |
18 | setup-dev:
19 | @$(call log_target_info, "Setting up the npm link for local development")
20 | npm install
21 | dotnet tool restore
22 | cd $(NACARA_DIR) && npm install
23 | cd $(NACARA_LAYOUT_STANDARD_DIR) && npm install
24 | cd $(NACARA_DIR) && npm link
25 | cd $(NACARA_LAYOUT_STANDARD_DIR) && npm link
26 | npm link nacara nacara-layout-standard
27 |
28 | unsetup-dev:
29 | @$(call log_target_info, "Unsetting the npm link for local development")
30 | npm unlink nacara nacara-layout-standard
31 | cd $(NACARA_DIR) && npm -g unlink
32 | cd $(NACARA_LAYOUT_STANDARD_DIR) && npm -g unlink
33 |
34 | clean:
35 | @$(call log_target_info, "Cleaning...")
36 | # Clean Nacara.Layout.Standard artifacts
37 | rm -rf $(NACARA_LAYOUT_STANDARD_DIR)/dist
38 | rm -rf $(NACARA_LAYOUT_STANDARD_DIR)/Source/bin
39 | rm -rf $(NACARA_LAYOUT_STANDARD_DIR)/Source/obj
40 | # Clean Nacara artifacts
41 | rm -rf $(NACARA_DIR)/dist
42 | rm -rf $(NACARA_DIR)/Source/bin
43 | rm -rf $(NACARA_DIR)/Source/obj
44 | # Clean Nacara.Core artifacts
45 | rm -rf $(NACARA_CORE_DIR)/dist
46 | rm -rf $(NACARA_CORE_DIR)/Source/bin
47 | rm -rf $(NACARA_CORE_DIR)/Source/obj
48 | # Clean generated documentation
49 | rm -rf docs_deploy
50 |
51 | watch: clean
52 | # Make sure that the dist directories exists
53 | # Otherwise, nodemon cannot listen to them
54 | @$(call log_target_info, "Setup directories for the watcher")
55 | mkdir $(NACARA_LAYOUT_STANDARD_DIR)/dist
56 | mkdir $(NACARA_DIR)/dist
57 | # Start the different watcher
58 | # 1. Nacara
59 | # 2. Nacara.Layout.Standard
60 | # 3. Nodemon to restart nacara on changes
61 | @$(call log_target_info, "Watching...")
62 | npx concurrently -p none \
63 | "$(NODEMON_WATCHER)" \
64 | "$(NACARA_LAYOUT_STANDARD_FABLE) --watch --sourceMaps" \
65 | "$(NACARA_FABLE) --watch --sourceMaps"
66 |
67 | nodemon:
68 | $(NODEMON_WATCHER)
69 |
70 | build: clean
71 | @$(call log_target_info, "Building...")
72 | $(NACARA_FABLE)
73 | $(NACARA_LAYOUT_STANDARD_FABLE)
74 |
75 | generate-docs: build
76 | @$(call log_target_info, "Generating documentation...")
77 | @# Publish Nacare.Core to have all the dll files available in a single folder
78 | dotnet publish $(NACARA_CORE_DIR)
79 | @# Generate the API reference files
80 | cd $(NACARA_API_GEN_DIR)/Source \
81 | && dotnet run -f net6.0 -- \
82 | --project Nacara.Core \
83 | -lib ../../Nacara.Core/bin/Debug/netstandard2.0/publish/ \
84 | --output ../../../docs/ \
85 | --base-url /Nacara/
86 | npx nacara
87 |
88 | test: build
89 | @$(call log_target_info, "Testing...")
90 | cd $(NACARA_API_GEN_DIR)/Tests && dotnet run
91 |
92 |
93 | release: test
94 | @$(call log_target_info, "Releasing...")
95 | @# Remove .fable/.gitignore files otherwise npm doesn't publish that directory
96 | rm -rf $(NACARA_DIR)/dist/.fable/.gitignore
97 | rm -rf $(NACARA_LAYOUT_STANDARD_DIR)/dist/.fable/.gitignore
98 | @# Publish the packages
99 | node ./scripts/release-npm.js $(NACARA_DIR)
100 | node ./scripts/release-nuget.js $(NACARA_CORE_DIR) Nacara.Core.fsproj
101 | node ./scripts/release-npm.js $(NACARA_LAYOUT_STANDARD_DIR)
102 | node ./scripts/release-npm.js $(NACARA_CREATE_DIR)
103 | node ./scripts/release-nuget.js $(NACARA_API_GEN_DIR) Source/Nacara.ApiGen.fsproj
104 |
105 | publish-docs: release generate-docs
106 | @$(call log_target_info, "Publishing...")
107 | npx gh-pages --dist docs_deploy
108 |
--------------------------------------------------------------------------------
/docs/style.scss:
--------------------------------------------------------------------------------
1 | @import "./../node_modules/bulma/sass/utilities/initial-variables";
2 |
3 | // Color palette
4 | // https://lospec.com/palette-list/fluffy8
5 |
6 | /////////////////////////////////
7 | /// Customize Bulma
8 | /////////////////////////////////
9 | $primary: #7679db;
10 | $text: #2b2b2b;
11 | $danger: #c43636;
12 |
13 | @import "./../node_modules/bulma/sass/utilities/derived-variables";
14 |
15 | /////////////////////////////////
16 | /// nacara-layout-standard customizations
17 | /// Do not touch unless you know what you are doing
18 | /////////////////////////////////
19 | $navbar-breakpoint: 0px;
20 | $navbar-padding-vertical: 0.5rem;
21 | $navbar-padding-horizontal: 1rem;
22 | /////////////////////////////////
23 |
24 | // Specific to gatsby-remark-vscode usage
25 | $content-pre-padding: unset;
26 |
27 | /////////////////////////////////
28 | /// Customize Bulma
29 | /////////////////////////////////
30 |
31 | $navbar-item-color: $white;
32 | $navbar-background-color: $primary;
33 | $navbar-item-active-color: $white;
34 | $navbar-item-active-background-color: lighten($primary, 4%);
35 | $navbar-item-hover-color: $white;
36 | $navbar-item-hover-background-color: lighten($primary, 4%);
37 | $navbar-dropdown-item-active-background-color: lighten($primary, 4%);
38 | $navbar-dropdown-item-hover-background-color: lighten($primary, 4%);
39 | $navbar-dropdown-item-hover-color: $white;
40 | $navbar-dropdown-item-active-color: $white;
41 |
42 | $menu-item-active-background-color: $primary;
43 | $menu-item-active-color: $white;
44 | $menu-item-hover-color: $primary;
45 | $menu-item-hover-background-color: transparent;
46 | $menu-label-font-size: $size-6;
47 | $menu-item-radius: $radius-large $radius-large;
48 |
49 | $footer-background-color: $primary;
50 | $footer-color: $white;
51 |
52 | $link: darken($primary, 4%);
53 | $code: $red;
54 |
55 | $body-size: 14px;
56 |
57 | @import "../node_modules/bulma/sass/utilities/_all.sass";
58 | @import "./../node_modules/bulma/bulma.sass";
59 | @import "./../node_modules/nacara-layout-standard/scss/nacara.scss";
60 |
61 | // Begin gatsby-remark-vscode specific
62 | :root {
63 | --grvsc-padding-v: 1.25rem;
64 | }
65 |
66 | // Make the code use the full width for when user use line highlighting
67 | .content {
68 | pre > code {
69 | width: 100%;
70 | }
71 | }
72 | // End gatsby-remark-vscode specific
73 |
74 | // Override bulma
75 | .navbar {
76 | .navbar-dropdown {
77 | @include desktop {
78 | // Force navbar item text color otherwise it is the same as $navbar-item-color
79 | // Which is white in our case...
80 | .navbar-item {
81 | color: $text;
82 | }
83 | }
84 | }
85 |
86 | .navbar-link {
87 | &:not(.is-arrowless)::after {
88 | border-color: $white;
89 | }
90 | }
91 | }
92 |
93 | // Nacara documentation site specific rules
94 |
95 | .content {
96 | .table {
97 | &.is-vcentered {
98 | td,
99 | th {
100 | vertical-align: middle;
101 | }
102 | }
103 |
104 | .label-cell {
105 | white-space: nowrap;
106 | text-align: center !important;
107 | vertical-align: middle !important;
108 | }
109 |
110 | // Force call to take the maximum place
111 | .fullwidth-cell {
112 | width: 100%;
113 | }
114 | }
115 | }
116 |
117 | .index-quick-start {
118 | background-color: $primary;
119 | color: $white;
120 | padding: 3rem;
121 |
122 | @include desktop {
123 | border-radius: $radius-large;
124 | }
125 |
126 | .terminal {
127 | background-color: $black-ter;
128 | overflow-x: auto;
129 |
130 | .line {
131 | white-space: nowrap;
132 |
133 | .path {
134 | color: $primary;
135 | }
136 |
137 | .prompt {
138 | color: $danger;
139 | }
140 |
141 | .command {
142 | color: #f0e68c;
143 | }
144 |
145 | .output {
146 | color: $grey;
147 | }
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/nacara.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "serverPort": 8081,
3 | "siteMetadata": {
4 | "title": "Nacara",
5 | "url": "https://mangelmaxime.github.io",
6 | "baseUrl": "/Nacara/",
7 | "editUrl" : "https://github.com/MangelMaxime/Nacara/edit/master/docs",
8 | "favIcon": "favicon-180x180.png"
9 | },
10 | "navbar": {
11 | "start": [
12 | {
13 | "pinned": true,
14 | "label": "Docs",
15 | "items": [
16 | {
17 | "section": "nacara",
18 | "label": "Nacara",
19 | "description": "Learn more about how Nacara works,and how to setup it in your project",
20 | "url": "/Nacara/nacara/introduction.html"
21 | },
22 | "divider",
23 | {
24 | "section": "nacara-layout-standard",
25 | "label": "Nacara.Layout.Standard",
26 | "description": "Default layout used by Nacara",
27 | "url": "/Nacara/nacara-layout-standard/introduction.html"
28 | },
29 | "divider",
30 | {
31 | "section": "nacara-apigen",
32 | "label": "Nacara.ApiGen",
33 | "description": "API documentation generator for Nacara",
34 | "url": "/Nacara/nacara-apigen/introduction.html"
35 | }
36 | ]
37 | },
38 | {
39 | "section": "reference",
40 | "label": "API",
41 | "url": "/Nacara/reference/Nacara.Core/index.html"
42 | },
43 | {
44 | "label": "Changelogs",
45 | "items": [
46 | {
47 | "section": "changelogs",
48 | "label": "Nacara",
49 | "url": "/Nacara/changelogs/nacara.html"
50 | },
51 | {
52 | "section": "changelogs",
53 | "label": "Nacara.Core",
54 | "url": "/Nacara/changelogs/nacara-core.html"
55 | },
56 | {
57 | "section": "changelogs",
58 | "label": "Nacara.Standard.Layout",
59 | "url": "/Nacara/changelogs/nacara-layout-standard.html"
60 | },
61 | {
62 | "section": "changelogs",
63 | "label": "Nacara.ApiGen",
64 | "url": "/Nacara/changelogs/nacara-apigen.html"
65 | }
66 | ]
67 | },
68 | {
69 | "url": "https://gitter.im/fable-compiler/Fable",
70 | "label": "Support"
71 | },
72 | {
73 | "url": "https://www.patreon.com/MangelMaxime",
74 | "icon": "fab fa-patreon",
75 | "label": "Donate",
76 | "iconColor": "#ff424b"
77 | }
78 | ],
79 | "end": [
80 | {
81 | "url": "https://github.com/MangelMaxime/Nacara",
82 | "icon": "fab fa-github",
83 | "label": "GitHub"
84 | },
85 | {
86 | "url": "https://twitter.com/MangelMaxime",
87 | "icon": "fab fa-twitter",
88 | "label": "Twitter"
89 | }
90 | ]
91 | },
92 | "remarkPlugins": [
93 | {
94 | "resolve": "gatsby-remark-vscode",
95 | "property": "remarkPlugin",
96 | "options": {
97 | "theme": "Atom One Light",
98 | "extensions": [
99 | "vscode-theme-onelight"
100 | ]
101 | }
102 | },
103 | {
104 | "resolve": "remark-github",
105 | "options": {
106 | "repository": "MangelMaxime/Nacara"
107 | }
108 | }
109 | ],
110 | "layouts": [
111 | "nacara-layout-standard"
112 | ]
113 | }
114 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Write.fs:
--------------------------------------------------------------------------------
1 | module Write
2 |
3 | open Nacara.Core.Types
4 | open Fable.Core.JsInterop
5 | open Fable.Core
6 | open Fable.React
7 | open Node
8 |
9 | // While waiting for React 18
10 | // Adding .js to the import fix the ESM import issue
11 | // https://github.com/facebook/react/issues/20235#issuecomment-861836181
12 | []
13 | let ReactDomServer: IReactDomServer = jsNative
14 |
15 | let sassFile (outputStyle : Sass.OutputStyle, destinationFolder : string, sourceFolder : string, relativeFilePath : string) =
16 | promise {
17 |
18 | let source =
19 | relativeFilePath
20 | |> Directory.join sourceFolder
21 |
22 | let sassOption =
23 | jsOptions(fun o ->
24 | o.file <- source
25 | o.outputStyle <- outputStyle
26 | )
27 |
28 | let sassResult = Sass.sass.renderSync sassOption
29 |
30 | let destination =
31 | relativeFilePath
32 | |> Directory.join destinationFolder
33 | |> File.changeExtension "css"
34 |
35 | do! File.write destination (sassResult.css.toString())
36 | return source
37 | }
38 |
39 | let copyFile (destinationFolder : string, sourceFolder : string, relativeFilePath : string) =
40 | promise {
41 | let destination =
42 | relativeFilePath
43 | |> Directory.join destinationFolder
44 |
45 | let source =
46 | relativeFilePath
47 | |> Directory.join sourceFolder
48 |
49 | do! File.copy source destination
50 | return source
51 | }
52 |
53 | let copyFileWithDestination (destinationFolder : string, sourceFileName : string, destinationFileName : string) =
54 | promise {
55 | let destination =
56 | destinationFileName
57 | |> Directory.join destinationFolder
58 |
59 | do! File.copy sourceFileName destination
60 | return (sourceFileName, destination)
61 | }
62 |
63 | []
64 | type ProcessMarkdownArgs =
65 | {
66 | PageContext : PageContext
67 | Layouts : JS.Map
68 | Partials : Partial list
69 | Menus : MenuConfig list
70 | Config : Config
71 | Pages : PageContext list
72 | RemarkPlugins : RemarkPlugin array
73 | RehypePlugins : RehypePlugin array
74 | }
75 |
76 | exception ProcessFileErrorException of pageContext : PageContext * errorMessage : string
77 |
78 | let markdown (args : ProcessMarkdownArgs) =
79 | promise {
80 | let layoutRenderer =
81 | args.Layouts.get(args.PageContext.Layout)
82 |
83 | if isNull (box layoutRenderer) then
84 | return raise (ProcessFileErrorException (args.PageContext, $"Layout renderer '%s{args.PageContext.Layout}' is unknown"))
85 | else
86 | try
87 | let categoryMenu =
88 | args.Menus
89 | |> List.tryFind (fun menuConfig ->
90 | menuConfig.Section = args.PageContext.Section
91 | )
92 | |> Option.map (fun menuConfig -> menuConfig.Items)
93 |
94 | let rendererContext =
95 | {
96 | Config = args.Config
97 | SectionMenu = categoryMenu
98 | Partials = args.Partials |> List.toArray
99 | Menus = args.Menus |> List.toArray
100 | Pages = args.Pages |> List.toArray
101 | // MarkdownToHtml = markdownToHtml args.RemarkPlugins args.RehypePlugins
102 | }
103 |
104 | let! reactContent =
105 | layoutRenderer rendererContext args.PageContext
106 |
107 | let fileContent = ReactDomServer.renderToStaticMarkup reactContent
108 |
109 | let destination =
110 | args.PageContext.RelativePath
111 | |> Directory.join args.Config.DestinationFolder
112 | |> File.changeExtension "html"
113 |
114 | do! File.write destination fileContent
115 |
116 | return args.PageContext
117 | with
118 | | error ->
119 | return raise (ProcessFileErrorException (args.PageContext, error.Message))
120 | }
121 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: navbar-only
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 | Nacara
11 |
12 |
13 | Nacara is a static site generator focused on simplicity
14 |
15 |
16 | Get up and running
17 |
18 |
19 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Get up and running in seconds.
64 |
65 |
66 |
67 |
68 |
69 | ~
70 | $
71 | npm init nacara
72 |
73 |
74 | ~
75 | $
76 | cd my-site
77 |
78 |
79 | ~/my-site
80 | $
81 | npm run watch
82 |
83 |
84 | Browse to http://localhost:8080
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | Nacara is open-source and free.
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/docs/nacara/guides/section-menu.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Section menu
3 | layout: standard
4 | ---
5 |
6 | A menu is specific to a [section](/Nacara/documentation/nacara/guides/create-a-section.html).
7 |
8 | The menu is optional but it helps you:
9 |
10 | * Group your content
11 | * Display a menu when visiting a page on the related section
12 | * Optionally: Provide navigation buttons
13 |
14 | ## Define a menu
15 |
16 | The menu of a section is defined by creating a `menu.json` file at the top level of your section.
17 |
18 | ```
19 | docs
20 | ├── blog
21 | └── documentation
22 | └── menu.json
23 | ```
24 |
25 | Here you have **2** sections but only `documentation` has a menu.
26 |
27 | ## `menu.json`
28 |
29 | The `menu.json` consists in a list of `MenuItem` which can be a `string` or an `object`.
30 |
31 | ```fsharp
32 | type MenuItem =
33 | | Page of MenuItemPage
34 | | List of MenuItemList
35 | | Link of MenuItemLink
36 |
37 | type Menu = MenuItem list
38 | ```
39 |
40 | ## Mastering your menu.json
41 |
42 | Your `menu.json` supports different items:
43 |
44 | - [Page](#Page:-Link-to-a-page): link to a page hosted on your site
45 | - [Link](#Link:-link-to-any-page): link to an arbitrary URL
46 | - [Section](#Section:-organize-your-menu): group several `MenuItem`
47 |
48 | ### Page: Link to a page
49 |
50 | **Page**, allows you to link to a page from your `source` folder.
51 |
52 | You need to provide the relative path from the `source` folder.
53 |
54 | **Example:**
55 |
56 | ```
57 | docs
58 | ├── blog
59 | └── documentation
60 | └── page1.md
61 | └── page2.md
62 | └── menu.json
63 | ```
64 |
65 | File: `docs/documentation/menu.json`
66 |
67 | ```json
68 | [
69 | "documentation/page1",
70 | "documentation/page2",
71 | ]
72 | ```
73 |
74 | The `title` front-matter will be used as the text to display in the menu.
75 |
76 | ### Link: link to any page
77 |
78 | **Link**, allows you to link to any page (internal or external) to your website.
79 |
80 | **Properties**
81 |
82 |
83 |
84 |
85 | | Name |
86 | Required |
87 | Description |
88 |
89 |
90 |
91 |
92 | type |
93 | X |
94 | Always set to link |
95 |
96 |
97 | label |
98 | X |
99 | Text to display in the menu |
100 |
101 |
102 | href |
103 | X |
104 | URL to link to |
105 |
106 |
107 |
108 |
109 | **Example:**
110 |
111 | ```json
112 | [
113 | {
114 | "type": "link",
115 | "label": "Fable",
116 | "href": "http://fable.io"
117 | }
118 | ]
119 | ```
120 |
121 | ### Section: organize your menu
122 |
123 |
124 | **Section**, allows you to organize your menu in different sections.
125 |
126 | **Properties**
127 |
128 |
129 |
130 |
131 | | Name |
132 | Required |
133 | Description |
134 |
135 |
136 |
137 |
138 | type |
139 | X |
140 | Always set to section |
141 |
142 |
143 | label |
144 | X |
145 | Text to display in the menu |
146 |
147 |
148 | items |
149 | X |
150 | List of MenuItems |
151 |
152 |
153 |
154 |
155 | **Example:**
156 |
157 | ```json
158 | [
159 | "documentation/page-1",
160 | {
161 | "type": "section",
162 | "label": "Guides",
163 | "items": [
164 | "documentation/guides/guide-1",
165 | {
166 | "type": "section",
167 | "label": "Advanced",
168 | "items": [
169 | "documentation/guides/advanced/guide-1"
170 | ]
171 | }
172 | ]
173 | }
174 | ]
175 | ```
176 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | packages/
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.pfx
193 | *.publishsettings
194 | node_modules/
195 | orleans.codegen.cs
196 |
197 | # Since there are multiple workflows, uncomment next line to ignore bower_components
198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
199 | #bower_components/
200 |
201 | # RIA/Silverlight projects
202 | Generated_Code/
203 |
204 | # Backup & report files from converting an old project file
205 | # to a newer Visual Studio version. Backup files are not needed,
206 | # because we have git ;-)
207 | _UpgradeReport_Files/
208 | Backup*/
209 | UpgradeLog*.XML
210 | UpgradeLog*.htm
211 |
212 | # SQL Server files
213 | *.mdf
214 | *.ldf
215 |
216 | # Business Intelligence projects
217 | *.rdl.data
218 | *.bim.layout
219 | *.bim_*.settings
220 |
221 | # Microsoft Fakes
222 | FakesAssemblies/
223 |
224 | # GhostDoc plugin setting file
225 | *.GhostDoc.xml
226 |
227 | # Node.js Tools for Visual Studio
228 | .ntvs_analysis.dat
229 |
230 | # Visual Studio 6 build log
231 | *.plg
232 |
233 | # Visual Studio 6 workspace options file
234 | *.opt
235 |
236 | # Visual Studio LightSwitch build output
237 | **/*.HTMLClient/GeneratedArtifacts
238 | **/*.DesktopClient/GeneratedArtifacts
239 | **/*.DesktopClient/ModelManifest.xml
240 | **/*.Server/GeneratedArtifacts
241 | **/*.Server/ModelManifest.xml
242 | _Pvt_Extensions
243 |
244 | # Paket dependency manager
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | TestResults.xml
255 |
256 | dist/
257 |
258 |
259 | docs/build/
260 | docs/scss/extra/
261 | temp/
262 |
263 | \.DS_Store
264 |
265 | demos/Thoth\.Elmish\.Demo/output/
266 |
267 | \.ionide/
268 | fableBuild/
269 | docs_deploy/
270 | .nacara/
271 |
272 | # Ignore generated API file
273 | docs/reference/
274 |
--------------------------------------------------------------------------------
/src/Nacara.Create/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const { Confirm, prompt } = require('enquirer');
4 | const fs = require('fs').promises;
5 | const path = require('path');
6 | const execSync = require('child_process').execSync;
7 | const chalk = require('chalk');
8 | const shell = require('shelljs');
9 |
10 | const resolve = (relativePath) => {
11 | return path.join(__dirname, relativePath);
12 | };
13 |
14 | const nacaraConfig = (options) => {
15 | return `
16 | // For more information about the config, please visit:
17 | // https://mangelmaxime.github.io/Nacara/nacara/configuration.html
18 | export default {
19 | "siteMetadata": {
20 | "url": "https://your-nacara-test-site.com",
21 | "baseUrl": "/",
22 | // Please change this to your repo
23 | "editUrl" : "https://github.com/MangelMaxime/Nacara/edit/master/docs",
24 | "title": "${options.title}"
25 | },
26 | "navbar": {
27 | "start": [
28 | {
29 | "section": "documentation",
30 | "url": "/documentation/introduction.html",
31 | "label": "Documentation"
32 | }
33 | ],
34 | "end": [
35 | // Please change it your repo or delete the item if you don't need it
36 | {
37 | "url": "https://github.com/MangelMaxime/Nacara",
38 | "icon": "fab fa-github",
39 | "label": "Github"
40 | }
41 | ]
42 | },
43 | "remarkPlugins": [
44 | {
45 | "resolve": "gatsby-remark-vscode",
46 | "property": "remarkPlugin",
47 | "options": {
48 | "theme": "Atom One Light",
49 | "extensions": [
50 | "vscode-theme-onelight"
51 | ]
52 | }
53 | }
54 | ],
55 | "layouts": [
56 | "nacara-layout-standard"
57 | ]
58 | }
59 | `.trim();
60 |
61 | }
62 |
63 | const directoryExists = async (directory) => {
64 | try {
65 | await fs.access(directory);
66 | return true;
67 | } catch (err) {
68 | return false;
69 | }
70 | };
71 |
72 | const run = async () => {
73 |
74 | const response = await prompt(
75 | [
76 | {
77 | type: 'input',
78 | name: 'title',
79 | message: 'What would you like to call your site?',
80 | initial: 'My Nacara Site'
81 | },
82 | {
83 | type: 'input',
84 | name: 'destination',
85 | message: 'What would you like to name the folder where your site will be created?',
86 | initial: 'my-site'
87 | }
88 | ]
89 | );
90 |
91 | const destination = (relativePath) =>{
92 | return path.join(response.destination, relativePath)
93 | }
94 |
95 | // If the directory already exist ask confirmation before deleting it
96 | if (await directoryExists(response.destination)) {
97 | const askConfirmation = new Confirm({
98 | message: `The folder '${response.destination}' already exists. Do you want to delete it?`
99 | });
100 |
101 | const userConfirmation = await askConfirmation.run();
102 |
103 | // User gave permission to delete the folder
104 | // Delete it
105 | if (userConfirmation) {
106 | shell.rm('-rf', response.destination);
107 | }
108 | // User asked to not delete the folder
109 | // Stop here
110 | else {
111 | console.log(chalk.red('Aborting, please chose another destination or accept to delete the folder'));
112 | process.exit(1);
113 | }
114 | }
115 |
116 | shell.mkdir(response.destination);
117 |
118 | console.log('Generating nacara.config.json file...');
119 | await fs.writeFile(destination('nacara.config.js'), nacaraConfig(response));
120 |
121 | console.log('Setting up minimal site...');
122 | shell.cp('-R', resolve('./templates/docs'), destination('docs'));
123 |
124 | shell.cp(resolve('./templates/style.scss'), destination('docs/style.scss'));
125 |
126 | shell.cp(resolve('./templates/gitignore'), destination('.gitignore'));
127 | shell.cp(resolve('./templates/package.json'), destination('./package.json'));
128 |
129 | shell.ls([
130 | `${response.destination}/docs/**/*.md`,
131 | `${response.destination}/package.json`
132 | ]).forEach(function (file) {
133 | shell.sed('-i', /{{REPLACE_WITH_SITE_TITLE}}/g, response.title, file);
134 | });
135 |
136 | await fs.copyFile(resolve('./templates/babel.config.json'), destination('./babel.config.json'));
137 |
138 | console.log('Installing NPM dependencies...');
139 |
140 | const npmArgs =
141 | [
142 | "nacara",
143 | "nacara-layout-standard",
144 | "bulma",
145 | "@babel/register",
146 | "@babel/preset-react",
147 | "akamud/vscode-theme-onelight ",
148 | "gatsby-remark-vscode",
149 | // We need to force unified to be of the latest version
150 | // because of gatsby...
151 | // In the future, I will create a standalone version of gatsby-remark-vscode
152 | "unified@latest"
153 | ].join(" ")
154 |
155 | execSync(
156 | `npm install --silent --save-dev ${npmArgs}`,
157 | {
158 | cwd: response.destination,
159 | stdio: 'inherit'
160 | }
161 | );
162 |
163 | console.log(chalk.green('\nYour site is ready!\n'));
164 |
165 | console.log(`Usage instructions:\n`);
166 | console.log(` 1. Move into the generated folder '${response.destination}'`);
167 | console.log(` 2. Run 'npm run watch'`);
168 | console.log(` 3. Browser to http://localhost:8080 to see your website`);
169 | }
170 |
171 | (async () => {
172 | try {
173 | await run();
174 | }
175 | catch (e) {
176 | console.error(chalk.red("An error occurred:"));
177 | console.error(chalk.red(e));
178 | }
179 | })();
180 |
--------------------------------------------------------------------------------
/docs/nacara/directory-structure.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Directory structure
3 | layout: standard
4 | ---
5 |
6 | A basic Nacara project have a structure similar to this:
7 |
8 | ```
9 | ├── docs
10 | │ ├── _partials
11 | │ │ └── [...]
12 | │ ├── documentation
13 | │ │ └── [...]
14 | │ ├── scss
15 | │ │ └── [...]
16 | │ └── style.scss
17 | ├── docs_deploy
18 | │ └── [...]
19 | ├── lightner
20 | │ ├── grammars
21 | │ └── themes
22 | ├── nacara.config.json
23 | ├── package.json
24 | ```
25 |
26 | To understand it better, we present this overview:
27 |
28 |
29 |
30 |
31 | | File / Directory |
32 | Description |
33 |
34 |
35 |
36 |
37 |
38 | .nacara
39 | |
40 |
41 |
42 | Internal folder where Nacara put generated or cached files
43 |
44 |
45 | You should add this folder to your .gitignore
46 |
47 | |
48 |
49 |
50 |
51 | docs
52 | |
53 |
54 | Default folder where you place your website source files like:
55 |
56 | - Static resources
57 | - Markdown files to convert
58 | - Style files
59 |
60 | |
61 |
62 |
63 |
64 | docs/style.scss
65 | or
66 | docs/style.sass
67 | |
68 |
69 |
70 | Main entry file for styling your application, generated file will be docs_deploy/style.css
71 |
72 |
73 | Only one of those files should be used at a time.
74 |
75 | |
76 |
77 |
78 |
79 | docs/scss
80 | or
81 | docs/sass
82 | |
83 |
84 | This is where you place your SASS/SCSS files imported by style.scss.
85 | |
86 |
87 |
88 |
89 | docs/_partials
90 | |
91 |
92 | This is where you place your partials files like footer.jsx
93 | |
94 |
95 |
96 |
97 | docs_deploy
98 | |
99 |
100 | This is where Nacara will put the generated files (by default).
101 | You should add this folder to your .gitignore.
102 | |
103 |
104 |
105 |
106 | lightner/themes
107 | |
108 |
109 |
110 |
111 | Recommended place to place your theme file user by [Code-lightner](https://github.com/MangelMaxime/Code-Lightner) to provides code highlights.
112 |
113 | |
114 |
115 |
116 |
117 | lightner/grammars
118 | |
119 |
120 |
121 |
122 | Recommended place to place your grammars files user by [Code-lightner](https://github.com/MangelMaxime/Code-Lightner) to provides code highlights.
123 |
124 | |
125 |
126 |
127 |
128 | nacara.config.json
129 | |
130 |
131 | Nacara config file.
132 | |
133 |
134 |
135 |
136 | package.json
137 | |
138 |
139 | File used to configure your NPM dependencies like Nacara, Babel, nacara-layout-standard.
140 | |
141 |
142 |
143 |
144 | docs/index.md
145 | or
146 | docs/\*\*/\*.md
147 | |
148 |
149 | Any markdown file encounter are going to be transform by Nacara into HTML using the layout provided via the front-matter property.
150 | |
151 |
152 |
153 |
154 | docs/\*\*/\*.fs
155 | or
156 | docs/\*\*/\*.fsx
157 | |
158 |
159 | F# files are tested to see if there are [literate](/nacara/guides/literate-files.html) files.
160 | If they are, they are transformed like as markdown files.
161 | If not, there are copied as is to the output folder.
162 | |
163 |
164 |
165 |
166 | docs/\*\*/menu.json
167 | |
168 |
169 | Menu configuration for the section it is in.
170 | |
171 |
172 |
173 |
174 | Other files
175 | or
176 | folders
177 | |
178 |
179 | Except for the special cases listed above, all the other file and folder will be copied into the destination folder.
180 | |
181 |
182 |
183 |
184 |
--------------------------------------------------------------------------------
/src/Nacara/Source/Build.fs:
--------------------------------------------------------------------------------
1 | module Build
2 |
3 | open Nacara.Core.Types
4 | open Fable.Core
5 | open Fable.React
6 | open Elmish
7 | open Node
8 |
9 | []
10 | type Msg =
11 | | ProcessNextFile
12 | | ErrorWhileProcessingAFile of exn
13 | | GenerateNoJekyllFile
14 |
15 | []
16 | type Model =
17 | {
18 | GenerationIsOk : bool
19 | Config : Config
20 | Layouts : JS.Map
21 | Pages : PageContext list
22 | Menus : MenuConfig list
23 | Partials : Partial list
24 | ProcessQueue : QueueFile list
25 | }
26 |
27 | []
28 | type InitArgs =
29 | {
30 | Layouts : LayoutInfo array
31 | Config : Config
32 | ProcessQueue : QueueFile list
33 | Pages : PageContext list
34 | Menus : MenuConfig list
35 | Partials : Partial list
36 | }
37 |
38 | let init (args : InitArgs) =
39 | let layoutCache =
40 | let keyValues =
41 | args.Layouts
42 | |> Array.collect (fun info ->
43 | info.Renderers
44 | |> Array.map (fun renderer ->
45 | renderer.Name, renderer.Func
46 | )
47 | )
48 |
49 | JS.Constructors.Map.Create(keyValues)
50 |
51 | {
52 | GenerationIsOk = true
53 | Layouts = layoutCache
54 | Config = args.Config
55 | Pages = args.Pages
56 | Menus = args.Menus
57 | Partials = args.Partials
58 | ProcessQueue = args.ProcessQueue
59 | }
60 | , Cmd.batch [
61 | Cmd.ofMsg ProcessNextFile
62 | Cmd.ofMsg GenerateNoJekyllFile
63 | ]
64 |
65 | let update (msg : Msg) (model : Model) =
66 | match msg with
67 | | GenerateNoJekyllFile ->
68 | let action () =
69 | File.write
70 | (model.Config.DestinationFolder + "/" + ".nojekyll")
71 | ""
72 |
73 | model
74 | , Cmd.OfPromise.attempt action () ErrorWhileProcessingAFile
75 |
76 | | ProcessNextFile ->
77 | match model.ProcessQueue with
78 | | file :: tail ->
79 | let cmd =
80 | match file with
81 | | QueueFile.Markdown pageContext ->
82 | let args =
83 | {
84 | PageContext = pageContext
85 | Layouts = model.Layouts
86 | Partials = model.Partials
87 | Menus = model.Menus
88 | Config = model.Config
89 | Pages = model.Pages
90 | RemarkPlugins = model.Config.RemarkPlugins
91 | RehypePlugins = model.Config.RehypePlugins
92 | } : Write.ProcessMarkdownArgs
93 |
94 | let action =
95 | Write.markdown
96 | >> Promise.map (fun _ ->
97 | Log.log $"Processed: %s{pageContext.RelativePath}"
98 | )
99 | >> Promise.catch (fun error ->
100 | Log.error $"Error while processing markdown file: %s{pageContext.PageId}"
101 | JS.console.error error
102 | raise error
103 | )
104 |
105 | Cmd.OfPromise.either action args (fun _ -> ProcessNextFile) ErrorWhileProcessingAFile
106 |
107 | | QueueFile.Sass filePath ->
108 | let args =
109 | Sass.OutputStyle.Compressed
110 | , model.Config.DestinationFolder
111 | , model.Config.SourceFolder
112 | , filePath
113 |
114 | let action =
115 | Write.sassFile
116 | >> Promise.map (fun _ ->
117 | Log.log $"Processed: %s{filePath}"
118 | )
119 | >> Promise.catch (fun error ->
120 | Log.error $"Error while processing SASS file: %s{filePath}"
121 | JS.console.error error
122 | raise error
123 | )
124 |
125 | Cmd.OfPromise.either action args (fun _ -> ProcessNextFile) ErrorWhileProcessingAFile
126 |
127 | | QueueFile.JavaScript filePath
128 | | QueueFile.Other filePath ->
129 | let args =
130 | model.Config.DestinationFolder
131 | , model.Config.SourceFolder
132 | , filePath
133 |
134 | let action =
135 | Write.copyFile
136 | >> Promise.map (fun _ ->
137 | Log.log $"Copied: %s{filePath}"
138 | )
139 | >> Promise.catch (fun error ->
140 | Log.error $"Error while copying %s{filePath}"
141 | JS.console.log error
142 | )
143 |
144 | Cmd.OfPromise.either action args (fun _ -> ProcessNextFile) ErrorWhileProcessingAFile
145 |
146 | | QueueFile.LayoutDependency layoutDependency ->
147 | let args =
148 | model.Config.DestinationFolder
149 | , layoutDependency.Source
150 | , layoutDependency.Destination
151 |
152 | let action args =
153 | Write.copyFileWithDestination args
154 | |> Promise.catch (fun error ->
155 | Log.error $"Error while copying %s{layoutDependency.Source} to %s{layoutDependency.Destination}"
156 | JS.console.log error
157 | raise error
158 | )
159 |
160 | Cmd.OfPromise.either action args (fun _ -> ProcessNextFile) ErrorWhileProcessingAFile
161 |
162 | { model with
163 | ProcessQueue = tail
164 | }
165 | , cmd
166 |
167 | | [] ->
168 |
169 | // Log result of the build and exist accordingly
170 | // We exit when all the generation has been done like that, if they are several error
171 | // the user see all of them at the same time
172 | if model.GenerationIsOk then
173 | Log.success "Generation succeeded"
174 | ``process``.exit(ExitCode.OK)
175 |
176 | else
177 | Log.error "Generation failed"
178 | ``process``.exit(ExitCode.COMPLETED_WITH_ERROR)
179 |
180 | model
181 | , Cmd.none
182 |
183 | | ErrorWhileProcessingAFile _ ->
184 | { model with
185 | GenerationIsOk = false
186 | }
187 | , Cmd.ofMsg ProcessNextFile
188 |
--------------------------------------------------------------------------------
/src/Nacara.Layout.Standard/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## 1.8.0 - 2022-11-09
10 |
11 | * Update to React 18
12 |
13 | ## 1.7.0 - 2022-03-25
14 |
15 | ### Added
16 |
17 | * Fix #168: If a title is set a changelog entry, then display it at the top of the page
18 |
19 | ## 1.6.0 - 2022-02-10
20 |
21 | ### Fixed
22 |
23 | * Fix #157: Make the menu scrollable if it goes out of screen (by @64J0)
24 |
25 | ## 1.5.0 - 2021-12-07
26 |
27 | ### Fixed
28 |
29 | * Fix #149: Fix anchor visibility and link display inside headers
30 |
31 | ## 1.4.2 - 2021-12-05
32 |
33 | ### Fixed
34 |
35 | * Fix render of "Other" items category for the changelog
36 |
37 | ## 1.4.1 - 2021-12-05
38 |
39 | ### Fixed
40 |
41 | * In the `standard` layout, center the content when on widescreen.
42 |
43 | ## 1.4.0 - 2021-12-02
44 |
45 | ### Added
46 |
47 | * Add a `Improved` category to the changelog
48 |
49 | ### Fixed
50 |
51 | * Fix #144: `toc: false` disabled the whole menu and not just the TOC
52 | * Fix #145: Don't generate an empty `` if the TOC contains no elements
53 |
54 | ### Improved
55 |
56 | * Fix #148: Allow changelog items to be define outside of a category
57 |
58 | This allows support of RELEASE_NOTES files too.
59 |
60 | * Optimize content size based on the display
61 | * Don't generate empty line in the CHANGELOG page
62 | * Improve the margin between the changelog items
63 |
64 | ### Changed
65 |
66 | * Change the visual of the Changelog page
67 |
68 | ## 1.3.0 - 2021-11-30
69 |
70 | ### Added
71 |
72 | * Addd `$nacara-navbar-dropdown-floating-max-width` SCSS variable
73 |
74 | ### Changed
75 |
76 | * Make the floating dropdown able to return the text to new line automatically
77 | * The dropdown description now accept an HTML string
78 |
79 | ### Removed
80 |
81 | * Can't force new line using `\n` in the dropdown description
82 |
83 | ## 1.2.1 - 2021-11-09
84 |
85 | ### Fixed
86 |
87 | * Make the menu-container scrollable on mobile
88 |
89 | ### Added
90 |
91 | * Fix #108: If the dropdown correspond to the active page, we indicate it visually
92 |
93 | ## 1.2.0 - 2021-11-08
94 |
95 | ### Added
96 |
97 | * We can disable the TOC generation from `standard` by setting `toc: false` in the front-matter
98 |
99 | ## 1.1.2 - 2021-11-08
100 |
101 | ### Fixed
102 |
103 | * Fix missing `fable_modules` folder from `dist`
104 |
105 | ## 1.1.1 - 2021-11-07
106 |
107 | ### Fixed
108 |
109 | * Fix the TOC parser to support minified HTML code too
110 |
111 | ## 1.1.0 - 2021-11-04
112 |
113 | ### Added
114 |
115 | * Adapt to support Nacara 1.1.0
116 |
117 | ## 1.0.1 - 2021-10-28
118 |
119 | ### Changed
120 |
121 | * Fix warning: Use of deprecated folder mapping "./" in the "exports"
122 |
123 | ## 1.0.0 - 2021-10-28
124 |
125 | ### Added
126 |
127 | * Release v1.0.0
128 | * Added ApiGen minimal style
129 |
130 | ## 1.0.0-beta-017 - 2021-10-26
131 |
132 | ### Fixed
133 |
134 | * Add missing `has-footer` to the body element when a footer is added via the partials.
135 |
136 | ## 1.0.0-beta-016 - 2021-10-26
137 |
138 | ### Fixed
139 |
140 | * Place correctly the footer even when there is not enought content to fill the whole page
141 |
142 | ### Changed
143 |
144 | * In watch mode, general a local link for the brand item instead of redirecting the live website.
145 |
146 | ## 1.0.0-beta-015 - 2021-10-25
147 |
148 | ### Changed
149 |
150 | * Simplify the `Page.Minimal.render` function because I was often forgetting to add the new arguments when using JavaScript
151 | * Simplify the layout names
152 |
153 | * `nacara-standard` -> `standard`
154 | * `nacara-navbar-only` -> `navbar-only`
155 | * `nacara-changelog` -> `changelog`
156 |
157 | ### Added
158 |
159 | * Add `api` layout
160 |
161 | ## 1.0.0-beta-014 - 2021-09-30
162 |
163 | ### Added
164 |
165 | * Add support for the partial inside the dropdowns
166 | * Add support for `toc` front-matter which allows to customize the Headers interval
167 |
168 | ### Fixed
169 |
170 | * Improve the hover behavior when using a fullwidth dropdown. Before this fix, the user needed to be really quick to access the dropdown content because of the gap between the Navbar Item and the Dropdown body.
171 |
172 | ## 1.0.0-beta-013 - 2021-09-28
173 |
174 | ### Fixed
175 |
176 | * Re-try fix exports so people can import the distributes files for their own custom layouts
177 |
178 | ## 1.0.0-beta-012 - 2021-09-28
179 |
180 | ### Added
181 |
182 | * Improve reloading system to reload only if the change concern the current page.
183 |
184 | This avoid reloading the page before it is ready when regenerating a lot of pages.
185 |
186 | ### Fixed
187 |
188 | * Force table cells to break words on mobile if needed allowing for a better display
189 | * Try fix exports so people can import the distributes files for their own custom layouts
190 |
191 | ## 1.0.0-beta-011 - 2021-09-26
192 |
193 | ### Fixed
194 |
195 | * Add files from `js` folder to the published package
196 |
197 | ## 1.0.0-beta-010 - 2021-09-26
198 |
199 | ### Added
200 |
201 | * PR #87: Add breadcrumb on desktop (by @mabasic)
202 | * PR #87: Add current section name in the navbar on mobile too
203 | * Fix #90: Complete rework of the navbar behavior
204 | - Added support for `Dropdown`
205 | - Better mobile support
206 | * Fix #96: Add partials support
207 | * Fix #77: Add footer support
208 |
209 | ### Changed
210 |
211 | * PR #86: Make the Edit button a bit more discrete (by @mabasic)
212 | * PR #85: Change the Navigation buttons, to be styled as "Fat buttons" now (by @mabasic)
213 | * Fix #91: The edit button is not aligned correctly with the breadcrumb text (by @mabasic)
214 | * Fix #90: Us a different button style differentiate the navbar burger menu from the breadcrumb menu on mobile
215 | * Fix #44: Move the "site metadata info" into a siteMetadata property in `nacara.config.json` (by @mabasic)
216 | * Move the NPM package to be a pure ESM package
217 | * Switch to `remark` and `rehype` for doing the markdown parsing
218 |
219 | ### Fixed
220 |
221 | * Fix #112: If there is no menu or toc, center the page content
222 |
223 | ## 1.0.0-beta-009 - 2021-08-23
224 |
225 | ### Changed
226 |
227 | * Fix typo in `date-disable-copy-button` attributes. It has been renamed to `data-disable-copy-button="true"`
228 |
229 | ## 1.0.0-beta-008 - 2021-08-21
230 |
231 | ### Added
232 |
233 | * Fix #8: Generate a `favIcon` tag if the property is set in `nacara.config.json`
234 |
235 | ### Fixed
236 |
237 | * Fix #62: Improve menu display when using a combination of item/section
238 |
239 | ## 1.0.0-beta-007 - 2021-08-19
240 |
241 | ### Changed
242 |
243 | * Move the fix "margin of p element inside of list element" to the changelog layout only
244 |
245 | ## 1.0.0-beta-006 - 2021-08-18
246 |
247 | ### Changed
248 |
249 | * Fix #53: Include `nacara/scripts/live-reload.js` when using Nacara in watch mode
250 |
251 | ## 1.0.0-beta-005 - 2021-08-05
252 |
253 | ### Fixed
254 |
255 | * PR #56: Add responsive metadata so the website is rendered correctly on mobile
256 |
257 | ## 1.0.0-beta-004 - 2021-07-30
258 |
259 | ### Changed
260 |
261 | * Publish `.fable` folder
262 |
263 | ## 1.0.0-beta-003 - 2021-07-30
264 |
265 | ### Changed
266 |
267 | * Publish `.fable` folder
268 |
269 | ## 1.0.0-beta-002 - 2021-07-30
270 |
271 | ### Changed
272 |
273 | * Publish `.fable` folder
274 |
275 | ## 1.0.0-beta-001 - 2021-07-29
276 |
277 | ### Added
278 |
279 | * Initial release as a standalone package
280 |
--------------------------------------------------------------------------------
/Nacara.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{73B853F7-42BA-45D8-8B5F-32FD8BB1D9DD}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nacara.Core", "src\Nacara.Core\Nacara.Core.fsproj", "{EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nacara", "Nacara", "{F691D4BF-6A44-4F45-A48D-08AD74C35812}"
11 | EndProject
12 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nacara", "src\Nacara\Source\Nacara.fsproj", "{10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}"
13 | EndProject
14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nacara.Layout.Standard", "Nacara.Layout.Standard", "{D9CBA35D-81C4-4ACA-BA26-2EF0E10AFECD}"
15 | EndProject
16 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nacara.Layouts.Standard", "src\Nacara.Layout.Standard\Source\Nacara.Layouts.Standard.fsproj", "{B0BF4209-1F8A-4041-99AA-2FB092E1A596}"
17 | EndProject
18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Nacara.ApiGen", "Nacara.ApiGen", "{A92658AE-2EE4-4B0D-96A5-915A1ACDCB21}"
19 | EndProject
20 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nacara.ApiGen", "src\Nacara.ApiGen\Source\Nacara.ApiGen.fsproj", "{47F5787A-ED21-435A-B01C-48FD6CE092DB}"
21 | EndProject
22 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Nacara.ApiGen.Tests", "src\Nacara.ApiGen\Tests\Nacara.ApiGen.Tests.fsproj", "{7DAA7671-A915-408E-B412-73B3A516E626}"
23 | EndProject
24 | Global
25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
26 | Debug|Any CPU = Debug|Any CPU
27 | Debug|x64 = Debug|x64
28 | Debug|x86 = Debug|x86
29 | Release|Any CPU = Release|Any CPU
30 | Release|x64 = Release|x64
31 | Release|x86 = Release|x86
32 | EndGlobalSection
33 | GlobalSection(SolutionProperties) = preSolution
34 | HideSolutionNode = FALSE
35 | EndGlobalSection
36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
37 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
38 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
39 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|x64.ActiveCfg = Debug|Any CPU
40 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|x64.Build.0 = Debug|Any CPU
41 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|x86.ActiveCfg = Debug|Any CPU
42 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Debug|x86.Build.0 = Debug|Any CPU
43 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
44 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|Any CPU.Build.0 = Release|Any CPU
45 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|x64.ActiveCfg = Release|Any CPU
46 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|x64.Build.0 = Release|Any CPU
47 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|x86.ActiveCfg = Release|Any CPU
48 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB}.Release|x86.Build.0 = Release|Any CPU
49 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
50 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|Any CPU.Build.0 = Debug|Any CPU
51 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|x64.ActiveCfg = Debug|Any CPU
52 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|x64.Build.0 = Debug|Any CPU
53 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|x86.ActiveCfg = Debug|Any CPU
54 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Debug|x86.Build.0 = Debug|Any CPU
55 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|Any CPU.ActiveCfg = Release|Any CPU
56 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|Any CPU.Build.0 = Release|Any CPU
57 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|x64.ActiveCfg = Release|Any CPU
58 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|x64.Build.0 = Release|Any CPU
59 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|x86.ActiveCfg = Release|Any CPU
60 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15}.Release|x86.Build.0 = Release|Any CPU
61 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|Any CPU.Build.0 = Debug|Any CPU
63 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|x64.ActiveCfg = Debug|Any CPU
64 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|x64.Build.0 = Debug|Any CPU
65 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|x86.ActiveCfg = Debug|Any CPU
66 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Debug|x86.Build.0 = Debug|Any CPU
67 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|Any CPU.ActiveCfg = Release|Any CPU
68 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|Any CPU.Build.0 = Release|Any CPU
69 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|x64.ActiveCfg = Release|Any CPU
70 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|x64.Build.0 = Release|Any CPU
71 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|x86.ActiveCfg = Release|Any CPU
72 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596}.Release|x86.Build.0 = Release|Any CPU
73 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
74 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
75 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|x64.ActiveCfg = Debug|Any CPU
76 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|x64.Build.0 = Debug|Any CPU
77 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|x86.ActiveCfg = Debug|Any CPU
78 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Debug|x86.Build.0 = Debug|Any CPU
79 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
80 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|Any CPU.Build.0 = Release|Any CPU
81 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|x64.ActiveCfg = Release|Any CPU
82 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|x64.Build.0 = Release|Any CPU
83 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|x86.ActiveCfg = Release|Any CPU
84 | {47F5787A-ED21-435A-B01C-48FD6CE092DB}.Release|x86.Build.0 = Release|Any CPU
85 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
86 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|Any CPU.Build.0 = Debug|Any CPU
87 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|x64.ActiveCfg = Debug|Any CPU
88 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|x64.Build.0 = Debug|Any CPU
89 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|x86.ActiveCfg = Debug|Any CPU
90 | {7DAA7671-A915-408E-B412-73B3A516E626}.Debug|x86.Build.0 = Debug|Any CPU
91 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|Any CPU.ActiveCfg = Release|Any CPU
92 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|Any CPU.Build.0 = Release|Any CPU
93 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|x64.ActiveCfg = Release|Any CPU
94 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|x64.Build.0 = Release|Any CPU
95 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|x86.ActiveCfg = Release|Any CPU
96 | {7DAA7671-A915-408E-B412-73B3A516E626}.Release|x86.Build.0 = Release|Any CPU
97 | EndGlobalSection
98 | GlobalSection(NestedProjects) = preSolution
99 | {EA2D1C6A-99E8-41F3-9F4A-FB07625468EB} = {73B853F7-42BA-45D8-8B5F-32FD8BB1D9DD}
100 | {F691D4BF-6A44-4F45-A48D-08AD74C35812} = {73B853F7-42BA-45D8-8B5F-32FD8BB1D9DD}
101 | {10DD2ED4-7B72-4AA4-B0C8-9E9CB9DCFF15} = {F691D4BF-6A44-4F45-A48D-08AD74C35812}
102 | {D9CBA35D-81C4-4ACA-BA26-2EF0E10AFECD} = {73B853F7-42BA-45D8-8B5F-32FD8BB1D9DD}
103 | {B0BF4209-1F8A-4041-99AA-2FB092E1A596} = {D9CBA35D-81C4-4ACA-BA26-2EF0E10AFECD}
104 | {A92658AE-2EE4-4B0D-96A5-915A1ACDCB21} = {73B853F7-42BA-45D8-8B5F-32FD8BB1D9DD}
105 | {47F5787A-ED21-435A-B01C-48FD6CE092DB} = {A92658AE-2EE4-4B0D-96A5-915A1ACDCB21}
106 | {7DAA7671-A915-408E-B412-73B3A516E626} = {A92658AE-2EE4-4B0D-96A5-915A1ACDCB21}
107 | EndGlobalSection
108 | EndGlobal
109 |
--------------------------------------------------------------------------------