├── description
├── doc
├── images
│ └── hastyscribe.png
├── HastyScribe_UserGuide.md
├── -getting-started.md
├── -credits.md
├── -api.md
├── -syntax-block-classes.md
├── -syntax-block-lists.md
├── -usage.md
├── -syntax-block.md
├── -overview.md
├── -syntax.md
└── -syntax-inline.md
├── .gitignore
├── src
├── hastyscribepkg
│ ├── vendor
│ │ └── markdown
│ │ │ ├── linux
│ │ │ └── libmarkdown.a
│ │ │ ├── macosx
│ │ │ └── libmarkdown.a
│ │ │ └── windows
│ │ │ └── libmarkdown.a
│ ├── config.nim
│ ├── data
│ │ ├── hastystyles.notes.css
│ │ ├── hastyscribe.svg
│ │ ├── hastyscribe-original.svg
│ │ ├── hastystyles.css
│ │ └── hastystyles.links.css
│ ├── niftylogger.nim
│ ├── utils.nim
│ ├── consts.nim
│ └── markdown.nim
├── hastyscribe.nim.cfg
└── hastyscribe.nim
├── LICENSE
├── .github
└── workflows
│ ├── ci.yml
│ └── add-artifacts-to-current-release.yml
├── hastyscribe.nimble
└── README.md
/description:
--------------------------------------------------------------------------------
1 | A professional markdown compiler.
2 |
--------------------------------------------------------------------------------
/doc/images/hastyscribe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h3rald/hastyscribe/HEAD/doc/images/hastyscribe.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | nimcache/
2 | build/
3 | README.htm
4 | hastyscribe.exe
5 | hastyscribe
6 | nakefile
7 | packages/
8 | *.zip
9 | test.md
10 | test.html?
11 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/vendor/markdown/linux/libmarkdown.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h3rald/hastyscribe/HEAD/src/hastyscribepkg/vendor/markdown/linux/libmarkdown.a
--------------------------------------------------------------------------------
/src/hastyscribepkg/vendor/markdown/macosx/libmarkdown.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h3rald/hastyscribe/HEAD/src/hastyscribepkg/vendor/markdown/macosx/libmarkdown.a
--------------------------------------------------------------------------------
/src/hastyscribepkg/vendor/markdown/windows/libmarkdown.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/h3rald/hastyscribe/HEAD/src/hastyscribepkg/vendor/markdown/windows/libmarkdown.a
--------------------------------------------------------------------------------
/src/hastyscribepkg/config.nim:
--------------------------------------------------------------------------------
1 | const
2 | pkgVersion* = "2.1.0"
3 | pkgAuthor* = "Fabio Cevasco"
4 | pkgDescription* = "Self-contained markdown compiler generating self-contained HTML documents"
5 |
--------------------------------------------------------------------------------
/doc/HastyScribe_UserGuide.md:
--------------------------------------------------------------------------------
1 | % HastyScribe User Guide
2 | % Fabio Cevasco
3 | % -
4 |
5 | {@ -overview.md || 1 @}
6 | {@ -getting-started.md || 1 @}
7 | {@ -usage.md || 1 @}
8 | {@ -syntax.md || 1 @}
9 | {@ -api.md || 1 @}
10 | {@ -credits.md || 1 @}
11 |
--------------------------------------------------------------------------------
/src/hastyscribe.nim.cfg:
--------------------------------------------------------------------------------
1 | # https://blog.filippo.io/easy-windows-and-linux-cross-compilers-for-macos/
2 |
3 | amd64.windows.gcc.path = "/usr/local/bin"
4 | amd64.windows.gcc.exe = "x86_64-w64-mingw32-gcc"
5 | amd64.windows.gcc.linkerexe = "x86_64-w64-mingw32-gcc"
6 |
7 | amd64.linux.gcc.path = "/usr/local/bin"
8 | amd64.linux.gcc.exe = "x86_64-linux-musl-gcc"
9 | amd64.linux.gcc.linkerexe = "x86_64-linux-musl-gcc"
10 |
11 | --gc = "refc"
12 | --opt = "size"
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2013-2023 Fabio Cevasco
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/doc/-getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Downloading Pre-built Binaries
4 |
5 | {# release -> [HastyScribe for $1]({{release}}{{$version}}/hastyscribe_v{{$version}}_$2.zip)#}
6 |
7 | The easiest way to get {{hs}} is by downloading one of the prebuilt binaries from the [Github Release Page][release]:
8 |
9 | * {#release||Mac OS X (x64)||macosx_x64#}
10 | * {#release||Windows (x64)||windows_x64#}
11 | * {#release||Linux (x64)||linux_x64#}
12 |
13 | ## Installing using Nimble
14 |
15 | If you already have [Nim][nim] installed on your computer, you can simply run
16 |
17 | [nimble install hastyscribe](class:cmd)
18 |
19 | ## Building from Source
20 |
21 | To build on a different operating system and architecture from the ones for which a pre-built binary is provided, you also need to get or build the `markdown` static library (see [Orc/discount](https://github.com/Orc/discount) for more information and sources).
22 |
23 | Then:
24 |
25 | 1. Download and install [Nim][nim].
26 | 3. Clone the HastyScribe [repository]({{repo -> https://github.com/h3rald/hastyscribe}}).
27 | 4. Run the following command:
28 |
29 | `nimble build -d:release --passL:"-static -L
-lmarkdown"`
30 |
31 | Where `` is a directory containing the `libmarkdown.a` static library.
32 |
--------------------------------------------------------------------------------
/doc/-credits.md:
--------------------------------------------------------------------------------
1 | # Credits
2 |
3 | HastyScribe is powered by the following open source software (see [LICENSE.md]({{repo}}/blob/master/LICENSE.md) for licensing details):
4 |
5 | * The wonderful [Discount][discount] C library, used to parse markdown code.
6 | * The ...awesome [FontAwesome][fa] font, used for all the icons.
7 | * The beautiful [Mr Bedfort][sudtipos] font, used as the base for the {{hs}} logo.
8 |
9 | Special thanks to:
10 |
11 | * Philip Wernersbach, Joshua Ellis, and [ZoomRmc](https://github.com/ZoomRmc) for contributing to {{hs}}.
12 | * Andreas Rumpf, creator of the amazing [Nim][nim] programming language, used to implement {{hs}}.
13 |
14 | -> [](class:fa-creative-commons) [](class:fa-creative-commons-by) [](class:fa-creative-commons-nc) [](class:fa-creative-commons-nd) <-
15 |
16 | [nim]: http://nim-lang.org/
17 | [df]: https://daringfireball.net/projects/markdown/
18 | [discount]: http://www.pell.portland.or.us/~orc/Code/discount/
19 | [pandoc]: http://johnmacfarlane.net/pandoc/
20 | [md-syntax]: https://daringfireball.net/projects/markdown/syntax
21 | [fa]:https://fontawesome.com
22 | [fa-icons]:https://fontawesome.com/icons
23 | [pme]:http://michelf.com/projects/php-markdown/extra/
24 | [sudtipos]:http://www.sudtipos.com/
25 | [release]:{{release -> https://github.com/h3rald/hastyscribe/releases/download/v}}
26 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | # Controls when the action will run.
4 | on:
5 | # Triggers the workflow on push or pull request events but only for the master branch
6 | push:
7 | branches: [master]
8 | pull_request:
9 | branches: [master]
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
15 | jobs:
16 | # This workflow contains a single job called "ci"
17 | ci:
18 | # The type of runner that the job will run on
19 | runs-on: ubuntu-latest
20 | env:
21 | CHOOSENIM_CHOOSE_VERSION: stable
22 | CHOOSENIM_NO_ANALYTICS: 1
23 |
24 | # Steps represent a sequence of tasks that will be executed as part of the job
25 | steps:
26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
27 | - uses: actions/checkout@v2
28 |
29 | - name: install musl-gcc
30 | run: sudo apt-get install -y musl-tools
31 |
32 | - name: Update $PATH
33 | run: echo "$HOME/.nimble/bin" >> $GITHUB_PATH
34 |
35 | - name: Install Nim
36 | run: |
37 | curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
38 | sh init.sh -y
39 |
40 | - name: Build
41 | run: |
42 | nimble build -y -d:release --passL:-static --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc
43 | ./hastyscribe doc/HastyScribe_UserGuide.md
44 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/data/hastystyles.notes.css:
--------------------------------------------------------------------------------
1 | .tip > p:first-child:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23009926' d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z' /%3E %3C/svg%3E") }
2 | .sidebar > p:first-child:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23555555' d='M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z' /%3E %3C/svg%3E") }
3 | .note > p:first-child:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 384 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23264c72' d='M32 32C32 14.3 46.3 0 64 0H320c17.7 0 32 14.3 32 32s-14.3 32-32 32H290.5l11.4 148.2c36.7 19.9 65.7 53.2 79.5 94.7l1 3c3.3 9.8 1.6 20.5-4.4 28.8s-15.7 13.3-26 13.3H32c-10.3 0-19.9-4.9-26-13.3s-7.7-19.1-4.4-28.8l1-3c13.8-41.5 42.8-74.8 79.5-94.7L93.5 64H64C46.3 64 32 49.7 32 32zM160 384h64v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V384z' /%3E %3C/svg%3E") }
4 | .warning > p:first-child:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23705400' d='M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z' /%3E %3C/svg%3E") }
--------------------------------------------------------------------------------
/src/hastyscribepkg/niftylogger.nim:
--------------------------------------------------------------------------------
1 | import std/[
2 | logging,
3 | strutils,
4 | terminal,
5 | exitprocs,
6 | ]
7 |
8 | if isatty(stdin):
9 | addExitProc(resetAttributes)
10 |
11 | type
12 | NiftyLogger* = ref object of Logger
13 |
14 | proc logPrefix*(level: Level): tuple[msg: string, color: ForegroundColor] =
15 | case level:
16 | of lvlDebug:
17 | return ("---", fgMagenta)
18 | of lvlInfo:
19 | return ("(i)", fgCyan)
20 | of lvlNotice:
21 | return (" ", fgBlue)
22 | of lvlWarn:
23 | return ("(!)", fgYellow)
24 | of lvlError:
25 | return ("(!)", fgRed)
26 | of lvlFatal:
27 | return ("(x)", fgRed)
28 | else:
29 | return (" ", fgDefault)
30 |
31 | method log*(logger: NiftyLogger; level: Level; args: varargs[string, `$`]) =
32 | var f = stdout
33 | if level >= getLogFilter() and level >= logger.levelThreshold:
34 | if level >= lvlWarn:
35 | f = stderr
36 | let ln = substituteLog(logger.fmtStr, level, args)
37 | let prefix = level.logPrefix()
38 | f.setForegroundColor(prefix.color)
39 | f.write(prefix.msg)
40 | f.write(ln)
41 | resetAttributes()
42 | f.write("\n")
43 | if level in {lvlError, lvlFatal}: flushFile(f)
44 |
45 | proc newNiftyLogger*(levelThreshold = lvlAll; fmtStr = " "): NiftyLogger =
46 | new result
47 | result.fmtStr = fmtStr
48 | result.levelThreshold = levelThreshold
49 |
50 | proc getLogLevel*(): string =
51 | return LevelNames[getLogFilter()].toLowerAscii
52 |
53 | proc setLogLevel*(val: var string): string {.discardable.} =
54 | var lvl: Level
55 | case val:
56 | of "debug":
57 | lvl = lvlDebug
58 | of "info":
59 | lvl = lvlInfo
60 | of "notice":
61 | lvl = lvlNotice
62 | of "warn":
63 | lvl = lvlWarn
64 | of "error":
65 | lvl = lvlError
66 | of "fatal":
67 | lvl = lvlFatal
68 | of "none":
69 | lvl = lvlNone
70 | else:
71 | val = "warn"
72 | lvl = lvlWarn
73 | setLogFilter(lvl)
74 | return val
75 |
--------------------------------------------------------------------------------
/hastyscribe.nimble:
--------------------------------------------------------------------------------
1 | import
2 | ospaths
3 |
4 | template thisModuleFile: string = instantiationInfo(fullPaths = true).filename
5 |
6 | when fileExists(thisModuleFile.parentDir / "src/hastyscribepkg/config.nim"):
7 | # In the git repository the Nimble sources are in a ``src`` directory.
8 | import src/hastyscribepkg/config
9 | else:
10 | # When the package is installed, the ``src`` directory disappears.
11 | import hastyscribepkg/config
12 |
13 | # Package
14 |
15 | version = pkgVersion
16 | author = pkgAuthor
17 | description = pkgDescription
18 | license = "MIT"
19 | bin = @["hastyscribe"]
20 | srcDir = "src"
21 | installExt = @["nim", "json", "a", "css", "png", "svg", "c", "h", "in"]
22 |
23 | requires "nim >= 2.0.0", "nimquery >= 2.0.1"
24 |
25 | # Tasks
26 |
27 | const
28 | compile = "nim c -d:release"
29 | linux_x64 = "--cpu:amd64 --os:linux --passL:-static"
30 | windows_x64 = "--cpu:amd64 --os:windows"
31 | macosx_x64 = ""
32 | hs = "src/hastyscribe"
33 | hs_file = "src/hastyscribe.nim"
34 | zip = "zip -X"
35 |
36 | proc shell(command, args: string, dest = "") =
37 | exec command & " " & args & " " & dest
38 |
39 | proc filename_for(os: string, arch: string): string =
40 | return "hastyscribe" & "_v" & version & "_" & os & "_" & arch & ".zip"
41 |
42 | task windows_x64_build, "Build HastyScribe for Windows (x64)":
43 | shell compile, windows_x64, hs_file
44 |
45 | task linux_x64_build, "Build HastyScribe for Linux (x64)":
46 | shell compile, linux_x64, hs_file
47 |
48 | task macosx_x64_build, "Build HastyScribe for Mac OS X (x64)":
49 | shell compile, macosx_x64, hs_file
50 |
51 | task release, "Release HastyScribe":
52 | echo "\n\n\n WINDOWS - x64:\n\n"
53 | windows_x64_buildTask()
54 | shell zip, filename_for("windows", "x64"), hs & ".exe"
55 | shell "rm", hs & ".exe"
56 | echo "\n\n\n LINUX - x64:\n\n"
57 | linux_x64_buildTask()
58 | shell zip, filename_for("linux", "x64"), hs
59 | shell "rm", hs
60 | echo "\n\n\n MAC OS X - x64:\n\n"
61 | macosx_x64_buildTask()
62 | shell zip, filename_for("macosx", "x64"), hs
63 | shell "rm", hs
64 | echo "\n\n\n ALL DONE!"
65 |
--------------------------------------------------------------------------------
/doc/-api.md:
--------------------------------------------------------------------------------
1 | # Nim API
2 |
3 | Besides its command libe, you can also import {{hs}} as a library within your [Nim](https://nim-lang.org) program.
4 |
5 | ## Types
6 |
7 | {{hs}} exposes the following Nim types:
8 |
9 | ```
10 | HastyOptions* = object
11 | toc*: bool
12 | input*: string
13 | output*: string
14 | css*: string
15 | js*: string
16 | watermark*: string
17 | fragment*: bool
18 | embed*: bool
19 |
20 | HastyFields* = Table[string, string]
21 | HastySnippets* = Table[string, string]
22 | HastyMacros* = Table[string, string]
23 | HastyLinkStyles* = Table[string, string]
24 | HastyIconStyles* = Table[string, string]
25 | HastyNoteStyles* = Table[string, string]
26 | HastyBadgeStyles* = Table[string, string]
27 |
28 | HastyScribe* = object
29 | options: HastyOptions
30 | fields: HastyFields
31 | snippets: HastySnippets
32 | macros: HastyMacros
33 | document: string
34 | linkStyles: HastyLinkStyles
35 | iconStyles: HastyIconStyles
36 | noteStyles: HastyNoteStyles
37 | badgeStyles: HastyBadgeStyles
38 | ```
39 |
40 | ## Procs
41 |
42 | {{hs}} exposes the following [proc](class:kwd)s.
43 |
44 | ### newHastyScribe
45 |
46 | proc newHastyScribe*(options: HastyOptions, fields: HastyFields): HastyScribe
47 |
48 | Instantiates a new {{hs}} object.
49 |
50 | ### compileFragment
51 |
52 | proc compileFragment*(hs: var HastyScribe, input, dir: string, toc = false): string {.discardable.}
53 |
54 | Compiles the [input](class:kwd) markdown text into an HTML fragment, without embedding stylesheets or fonts. [dir](class:kwd) identifies the directory containing the input text (it is only used to resolve transclusions).
55 |
56 | ### compileDocument
57 |
58 | proc compileDocument*(hs: var HastyScribe, input, dir: string): string {.discardable.}
59 |
60 | Compiles the [input](class:kwd) markdown text into a self-contained HTML document, embedding stylesheets and fonts. [dir](class:kwd) identifies the directory containing the input text (it is only used to resolve transclusions).
61 |
62 | ### compile
63 |
64 | proc compile*(hs: var HastyScribe, input_file: string)
65 |
66 | Compiles the markdown file [input\_file](class:kwd) into a self-contained HTML document.
67 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/utils.nim:
--------------------------------------------------------------------------------
1 | import std/[
2 | base64,
3 | os,
4 | strutils,
5 | pegs,
6 | hashes,
7 | ]
8 |
9 | import
10 | consts
11 |
12 | template style_tag*(css: string): string =
13 | ""
14 |
15 | template style_link_tag*(css: string): string =
16 | ""
17 |
18 | proc encode_image*(contents, format: string): string =
19 | if format == "svg":
20 | let encoded_svg = contents.multireplace([
21 | ("\"", "'"),
22 | ("%", "%25"),
23 | ("#", "%23"),
24 | ("{", "%7B"),
25 | ("}", "%7D"),
26 | ("<", "%3C"),
27 | (">", "%3E"),
28 | (" ", "%20"),
29 | ])
30 | "data:image/svg+xml,$#" % [encoded_svg]
31 | else:
32 | "data:image/$format;base64,$enc_contents" % ["format", format, "enc_contents", contents.encode]
33 |
34 | proc encode_image_file*(file, format: string): string =
35 | if (file.fileExists):
36 | let contents = file.readFile
37 | return encode_image(contents, format)
38 | else:
39 | stderr.writeLine("Warning: image '" & file & "' not found.")
40 | return file
41 |
42 | proc image_format*(imgfile: string): string =
43 | let peg_imgformat = peg"i'.png' / i'.jpg' / i'.jpeg' / i'.gif' / i'.svg' / i'.bmp' / i'.webp' @$"
44 | return imgfile.substr(imgfile.find(peg_imgformat)+1, imgfile.len-1)
45 |
46 | proc watermark_css*(imgfile: string): string =
47 | if imgfile == "":
48 | result = ""
49 | else:
50 | let img = imgfile.encode_image_file(imgfile.image_format)
51 | result = (watermark_style % [img]).style_tag
52 |
53 | proc add_jump_to_top_links*(document: string): string =
54 | document.replacef(peg"''", "")
55 |
56 | proc makeFNameUnique*(baseName, dir: string): string =
57 | ## Uses file placement (`dir`) as a unique name identifier
58 | ## Files in relative root (`dir` is empty) are returned unchanged.
59 | if dir notin ["", ".", "./"]:
60 | let
61 | dir = when dosLikeFileSystem: dir.replace('\\', '/') else: dir
62 | hashBytes = cast[array[sizeof(Hash), byte]](hash(dir))
63 | uniquePrefix = encode(hashBytes, safe=true)
64 | baseName & '_' & uniquePrefix
65 | else: baseName
66 |
67 | proc minifyCss*(css: string): string =
68 | css.parallelReplace([
69 | (peg" '/*' @ '*/'", ""),
70 | (peg" {\w} \s* {[ \>\< ]} \s* {\w / '*'} ", "$1$2$3" ),
71 | (peg" \s* { [ \,\{\}\[\]\:\; ] } \s* ", "$1"),
72 | (peg" \s+ ", " "),
73 | (peg""" ')' \s* \" """, ")\"" ),
74 | ])
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://nimble.directory/pkg/hastyscribe)
2 |
3 | 
4 | 
5 |
6 | # HastyScribe
7 |
8 | _HastyScribe_ is a simple command-line program able to convert [markdown](http://daringfireball.net/projects/markdown) files into HTML files.
9 |
10 | ## Usage
11 |
12 | **hastyscribe** **[** options **]** _filename-or-glob-expression_ ...
13 |
14 | Where:
15 |
16 | - _filename-or-glob-expression_ is a valid markdown file or [glob]() expression that will be compiled into HTML. Multiple files and/or globs are supported.
17 | - The following options are supported:
18 | - **--output-file=** causes HastyScribe to write output to a local file (Use `--output-file=-` to output to standard output).
19 | - **--output-dir=** or **-d=** allow you to specify an output directory for the generated HTML files. When used, it will override the `--output-file` option. Please note that this option does not preserve the input directory structure (that, for example, can be observed while traversing glob patterns); all output files will be placed directly in the specified directory.
20 | - **--field/=** causes HastyScribe to define custom field and set it to a specific value.
21 | - **--user-css=** causes HastyScribe to insert the contents of the specified local file as a CSS stylesheet.
22 | - **--user-js=** causes HastyScribe to insert the contents of the specified local file as a Javascript script.
23 | - **--watermark=** causes HastyScribe to embed and display an image as a watermark throughout the document.
24 | - **--notoc** causes HastyScribe to output HTML documents _without_ automatically generating a Table of Contents at the start.
25 | - **--noembed** causes styles and images not to be embedded.
26 | - **--fragment** causes HastyScribe to output just an HTML fragment instead of a full document, without embedding any image, font or stylesheet.
27 | - **--iso** enables HastyScribe to use the ISO 8601 date format (e.g., 2000-12-31) in the footer of the generated HTML documents.
28 | - **--minify-css** uses an unsophisticated minifier on the built-in stylesheet before embedding it into HTML. Ignored when combined with `--noembed`.
29 | - **--no-clobber** or **-n** prevents HastyScribe from overwriting existing files. If a file with the same name already exists, HastyScribe will issue a warning and will not overwrite it.
30 | - **--help** causes HastyScribe to display the usage information and quit.
31 |
32 | → For more information, see the [HastyScribe User Guide](https://h3rald.com/hastyscribe/HastyScribe_UserGuide.htm)
33 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/consts.nim:
--------------------------------------------------------------------------------
1 | import std/[pegs]
2 | from std/strutils import strip
3 |
4 | type
5 | CssSelPriority* = enum csspIgnore, csspLowProto, csspDom, csspExt, csspProto
6 | Rule = tuple[selValue: string, definition: string] ## CSS selector rule
7 | Rules = seq[Rule]
8 |
9 | const CssSelLowPriorityProtos = ["http", "https"]
10 |
11 | proc parseLinkRules(css: string): array[CssSelPriority, Rules] =
12 | ## Parses a CSS in format adgering to `hastystyles.links.css`:
13 | ## Each line is a link styling with a single selector of
14 | ## `^=` / `*=` / `$=` and a `:before`
15 | # TODO: - Support multiple selectors for a styling:
16 | # either in form of "a[href$='.zip']:before, a[href$='.gz']:before"
17 | # or "a:is([href$='.zip'], [href$='.gz']):before"
18 | # - Support `:after`
19 | let peg_linkstyle_def = peg"""
20 | styles <- definition*
21 | definition <- 'a[href' op '\'' val '\']:before' \s* @ (\n / $)
22 | op <- ['^*$'] '='
23 | val <- [a-z0-9-.#]+
24 | """
25 | var attr: tuple[kind: CssSelPriority; selValue: string]
26 | var linkRules: array[CssSelPriority, Rules]
27 | let parse = peg_linkstyle_def.eventParser:
28 | pkNonTerminal:
29 | leave:
30 | #debugEcho "leaving ", p.nt.name, " len=", length
31 | if length > 0:
32 | case p.nt.name
33 | of "op":
34 | # debugEcho " op:", s.substr(start, start+1)
35 | attr.kind = case s[start]:
36 | of '$': csspExt # endsWiths
37 | of '*': csspDom # contains
38 | of '^': csspProto # startsWith
39 | else: csspIgnore
40 | of "val":
41 | attr.selValue = s.substr(start, start+length-1)
42 | if attr.kind == csspProto and attr.selValue in CssSelLowPriorityProtos:
43 | attr.kind = csspLowProto
44 | of "definition":
45 | let definition = s.substr(start, start+length-1).strip()
46 | if attr.kind == csspIgnore or attr.selValue == "" or definition == "":
47 | echo "Error parsing `stylesheet_links`!"; quit(1)
48 | linkRules[attr.kind].add((attr.selValue, definition))
49 | attr = (csspLowProto, "")
50 | else: discard # parsed the file
51 | discard parse(css)
52 | linkRules
53 |
54 |
55 | const
56 | stylesheet* = "./data/hastystyles.css".slurp
57 | stylesheet_badges* = "./data/hastystyles.badges.css".slurp
58 | stylesheet_icons* = "./data/hastystyles.icons.css".slurp
59 | #stylesheet_links* = "./data/hastystyles.links.css".slurp
60 | css_rules_links* = parseLinkRules("./data/hastystyles.links.css".slurp)
61 | stylesheet_notes* = "./data/hastystyles.notes.css".slurp
62 | hastyscribe_logo* = "./data/hastyscribe.svg".slurp
63 | watermark_style* = """
64 | #container {
65 | position: relative;
66 | z-index: 0;
67 | }
68 | #container:after {
69 | content: "";
70 | opacity: 0.1;
71 | z-index: -1;
72 | position: absolute;
73 | top: 0;
74 | left: 0;
75 | bottom: 0;
76 | right: 0;
77 | background-image: url($1);
78 | background-repeat: no-repeat;
79 | background-position: center 70px;
80 | background-attachment: fixed;
81 | }
82 | """
83 | eof_separator* = ""
84 |
--------------------------------------------------------------------------------
/doc/-syntax-block-classes.md:
--------------------------------------------------------------------------------
1 | # Class Blocks
2 |
3 | ## Notes
4 |
5 | [Discount][discount] supports the definition of _class blocks_: [div](class:kwd)s with a class attribute. The syntax is very similar to the one used for [block quotes](#Block-Quotes), with the addition of the class name wrapped in [%](class:kwd)s on the first line.
6 |
7 | In {{hs}}, this syntax is used to produce notes, [tips](#Tips), [warmings](#Warnings), [sidebars](#Sidebars) and [terminal sessions](#Terminal-Sessions).
8 |
9 | {{input-text}}
10 |
11 | ~~~
12 | > %note%
13 | > Note
14 | >
15 | > This is a note.
16 | ~~~
17 |
18 | {{output-text}}
19 |
20 | > %note%
21 | > Note
22 | >
23 | > This is a note.
24 |
25 | ## Tips
26 |
27 | Tips are used for optional information that can help the user in some way.
28 |
29 | {{input-text}}
30 |
31 | ~~~
32 | > %tip%
33 | > Tip
34 | >
35 | > This is a tip.
36 | ~~~
37 |
38 | {{output-text}}
39 |
40 | > %tip%
41 | > Tip
42 | >
43 | > This is a tip.
44 |
45 | ## Warnings
46 |
47 | Warnings are used for important information the user should not overlook.
48 |
49 | {{input-text}}
50 |
51 | ~~~
52 | > %warning%
53 | > Warning
54 | >
55 | > This is a warning or an important note.
56 | ~~~
57 |
58 | {{output-text}}
59 |
60 | > %warning%
61 | > Warning
62 | >
63 | > This is a warning or an important note.
64 |
65 | ## Sidebars
66 |
67 | Sidebars are used for digressions and asides.
68 |
69 | {{input-text}}
70 |
71 | ~~~
72 | > %sidebar%
73 | > This is a _sidebar_
74 | >
75 | > Although not always placed on the side of the page, _sidebars_ contain
76 | > additional content and asides.
77 | ~~~
78 |
79 | {{output-text}}
80 |
81 | > %sidebar%
82 | > This is a _sidebar_
83 | >
84 | > Although not always placed on the side of the page, _sidebars_ contain additional content and asides.
85 |
86 | ## Blank Sidebars
87 |
88 | Blank sidebars can be customized to include custom icons. They are suitable for conditional (operating system or browser specific) instructions.
89 |
90 | {{input-text}}
91 |
92 | ~~~
93 | > %blank-sidebar%
94 | > [](class:chrome)[](class:firefox)[](class:edge) Browser Support
95 | >
96 | > This web app supports only modern browsers, i.e. the latest versions of Google Chrome, Mozilla Firefox and Microsoft Edge, but _not_ Microsoft Internet Explorer.
97 | ~~~
98 |
99 | {{output-text}}
100 |
101 | > %blank-sidebar%
102 | > [](class:chrome)[](class:firefox)[](class:edge) Browser Support
103 | >
104 | > This web app supports only modern browsers, i.e. the latest versions of Google Chrome, Mozilla Firefox and Microsoft Edge, but _not_ Microsoft Internet Explorer.
105 |
106 | ## Terminal Sessions
107 |
108 | Terminal sessions are used to display commands entered in a terminal, in sequence, without displaying their output.
109 |
110 | {{input-text}}
111 |
112 | ~~~
113 | > %terminal%
114 | >
115 | > cd src
116 | >
117 | > ./configure
118 | >
119 | > make && sudo make install
120 | ~~~
121 |
122 | {{output-text}}
123 |
124 | > %terminal%
125 | > cd src
126 | >
127 | > ./configure
128 | >
129 | > make && sudo make install
130 |
131 | If commands must be executed as a super-user, use the [terminal-su](class:kwd) class instead:
132 |
133 | {{input-text}}
134 |
135 | ~~~
136 | > %terminal-su%
137 | >
138 | > shutdown -h now
139 | ~~~
140 |
141 | {{output-text}}
142 |
143 | > %terminal-su%
144 | >
145 | > shutdown -h now
146 |
147 |
--------------------------------------------------------------------------------
/doc/-syntax-block-lists.md:
--------------------------------------------------------------------------------
1 | # Lists
2 |
3 | ## Unordered Lists
4 |
5 | {{input-text}}
6 |
7 | ~~~
8 | * An item
9 | * Another item
10 | * And another...
11 | ~~~
12 |
13 | {{output-text}}
14 |
15 | * An item
16 | * Another item
17 | * And another...
18 |
19 | ## Ordered Lists
20 |
21 | {{input-text}}
22 |
23 | ~~~
24 | 1. First item
25 | 2. Second item
26 | 3. Third item
27 | ~~~
28 |
29 | {{output-text}}
30 |
31 | 1. First item
32 | 2. Second item
33 | 3. Third item
34 |
35 | > %tip%
36 | > Tip
37 | >
38 | > You don't have to write numbers in order -- any number followed by a dot will do.
39 |
40 | ## Alphabetical Lists
41 |
42 | {{input-text}}
43 |
44 | ~~~
45 | a. First item
46 | b. Second item
47 | c. Third item
48 | ~~~
49 |
50 | {{output-text}}
51 |
52 | a. First item
53 | d. Second item
54 | c. Third item
55 |
56 | > %tip%
57 | > Tip
58 | >
59 | > You don't have to write letters in order -- any letter followed by a dot will do.
60 |
61 | ## Checklists
62 | {{input-text}}
63 |
64 | ~~~
65 | - [ ] Do something
66 | - [ ] Do something else
67 | - [x] Done!
68 | ~~~
69 |
70 | {{output-text}}
71 |
72 | - [ ] Do something
73 | - [ ] Do something else
74 | - [x] Done!
75 |
76 | ## Unstyled Lists
77 |
78 | {{input-text}}
79 |
80 | ~~~
81 | > %unstyled%
82 | > * An item
83 | > * Another item
84 | > * And another...
85 | ~~~
86 |
87 | {{output-text}}
88 |
89 | > %unstyled%
90 | > * An item
91 | > * Another item
92 | > * And another...
93 |
94 |
95 | ## Nested Lists
96 |
97 | To create a list within a list, simply indent the whole nested list with four space.
98 |
99 |
100 | {{input-text}}
101 |
102 | ~~~
103 | * This is a normal list
104 | * Another item
105 | * A nested unordered list
106 | * Another item
107 | * Back in the main list
108 | a. A nested alphabetical list
109 | b. Another item
110 | ~~~
111 |
112 | {{output-text}}
113 |
114 | * This is a normal list
115 | * Another item
116 | * A nested unordered list
117 | * Another item
118 | * Back in the main list
119 | a. A nested alphabetical list
120 | b. Another item
121 |
122 | ## Definition Lists
123 |
124 | In some cases you may want to write a list of terms and their corresponding definitions. You could use an ordinary unordered list, but semantically speaking the _proper_ type of list to use in this case is a definition list.
125 |
126 | {{input-text}}
127 |
128 | ~~~
129 | unordered list
130 | : A list for unordered items. Also called _bulleted list_.
131 | ordered list
132 | : A list for ordered items. Also called _numbered list_.
133 | alphabetical list
134 | : Technically speaking just an ordered list, but formatted with letters instead
135 | of numbers
136 | definition list
137 | : A list of terms and definitions.
138 | ~~~
139 |
140 | {{output-text}}
141 |
142 | unordered list
143 | : A list for unordered items. Also called _bulleted list_.
144 | ordered list
145 | : A list for ordered items. Also called _numbered list_.
146 | alphabetical list
147 | : Technically speaking just an ordered list, but formatted with letters instead
148 | of numbers
149 | definition list
150 | : A list of terms and definitions.
151 |
152 | Alternatively, you can write the above definition list as follows:
153 |
154 | ~~~
155 | =unordered list=
156 | A list for unordered items. Also called _bulleted list_.
157 | =ordered list=
158 | A list for ordered items. Also called _numbered list_.
159 | =alphabetical list=
160 | Technically speaking just an ordered list, but formatted with letters instead
161 | of numbers
162 | =definition list=
163 | A list of terms and definitions.
164 | ~~~
165 |
166 |
167 |
--------------------------------------------------------------------------------
/doc/-usage.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | {{hs}} is a command-line application that can compile one or more Markdown files into one or more HTML files with the same name(s).
4 |
5 | ## Command Line Syntax
6 |
7 | [hastyscribe](class:cmd) **[** [_<options>_](class:opt) **]** _filename-or-glob-expression_ ...
8 |
9 | Where:
10 |
11 | * _filename-or-glob-expression_ is a valid markdown file or [glob](http://en.wikipedia.org/wiki/Glob_(programming\)) expression that will be compiled into HTML. Multiple files and/or globs are supported.
12 | * The following options are supported:
13 | * [\-\-output-file=<file>](class:opt) causes {{hs}} to write output to a local file (Use [\-\-output-file=-](class:opt) to output to standard output).
14 | * [\-\-output-dir=<dir>](class:opt) or [\-d=<dir>](class:opt) allow you to specify an output directory for the generated HTML files. When used, it will override the [\-\-output-file](class:opt) option. Please note that this option does not preserve the input directory structure (that, for example, can be observed while traversing glob patterns); all output files will be placed directly in the specified directory.
15 | * [\-\-field/<field>=<value>](class:opt) causes {{hs}} to set a custom field to a specific value.
16 | * [\-\-user-css=<file>](class:opt) causes {{hs}} to insert the contents of the specified local file as a CSS stylesheet.
17 | * [\-\-user-js=<file>](class:opt) causes {{hs}} to insert the contents of the specified local file as a Javascript script.
18 | * [\-\-watermark=<file>](class:opt) causes {{hs}} to embed and display an image as a watermark throughout the document.
19 | * [\-\-notoc](class:opt) causes {{hs}} to output HTML documents _without_ automatically generating a Table of Contents at the start.
20 | * [\-\-noembed](class:opt) causes styles and images not to be embedded.
21 | * [\-\-fragment](class:opt) causes {{hs}} to output just an HTML fragment instead of a full document, without embedding any image, font or stylesheet.
22 | * [\-\-minify-css](class:opt) uses an unsophisticated minifier on the built-in stylesheet before embedding it into HTML. Ignored when combined with [\-\-noembed](class:opt).
23 | * [\-\-iso](class:opt) enables {{hs}} to use the ISO 8601 date format (e.g., 2000-12-31) in the footer of the generated HTML documents.
24 | * [\-\-no-clobber](class:opt) or [\-n](class:opt) prevents {{hs}} from overwriting existing files. If a file with the same name already exists, {{hs}} will issue a warning and will not overwrite it.
25 | * [\-\-help](class:opt) causes {{hs}} to display the usage information and quit.
26 |
27 | ## Linux and macOS Examples
28 |
29 | Executing {{hs}} to compile [my_markdown_file.md](class:file) within the current directory:
30 |
31 | > %terminal%
32 | > ./hastyscribe my\_markdown\_file.md
33 |
34 | Executing {{hs}} to compile all [.md](class:ext) files within the current directory:
35 |
36 | > %terminal%
37 | > ./hastyscribe \*.md
38 |
39 | Executing {{hs}} to compile all [.md](class:ext) files within the current and [in](clas:file) directories and save all the files to directory [out](class:file), preventing any overwrites:
40 |
41 | > %terminal%
42 | > ./hastyscribe --no-clobber -d=out \*.md in/\*.md
43 |
44 | ## Windows Examples
45 |
46 | Executing {{hs}} to compile [my_markdown_file.md](class:file) within the current directory:
47 |
48 | > %terminal%
49 | > hastyscribe.exe my\_markdown\_file.md
50 |
51 | Executing {{hs}} to compile all [.md](class:ext) files within the current directory:
52 |
53 | > %terminal%
54 | > hastyscribe.exe \*.md
55 |
56 | Executing {{hs}} to compile all [.md](class:ext) files within the current and [in](clas:file) directories and save all the files to directory [out](class:file), preventing any overwrites:
57 |
58 | > %terminal%
59 | > hastyscribe.exe --no-clobber -d=out \*.md in\\\*.md
60 |
61 | > %tip%
62 | > Tip
63 | >
64 | > You can also drag a Markdown file directly on [hastyscribe.exe](class:kwd) to compile it to HTML.
65 |
--------------------------------------------------------------------------------
/doc/-syntax-block.md:
--------------------------------------------------------------------------------
1 | # Block-level Formatting
2 |
3 | ## Headings
4 |
5 | Headings can be specified simply by prepending [#](class:kwd)s to text, as follows:
6 |
7 | # Heading 1
8 | ## Heading 2
9 | ### Heading 3
10 | #### Heading 4
11 | ##### Heading 5
12 | ###### Heading 6
13 |
14 | > %note%
15 | > Note
16 | >
17 | > If you use [Document Headers](#Document-Headers), A [H1](class:kwd) is used for the title of the {{hs}} document. Within the document, start using headings from [H2](class:kwd).
18 |
19 | ## Tables
20 |
21 | {{hs}} supports [PHP Markdown Extra][pme] table syntax using pipes and dashes.
22 |
23 | {{input-text}}
24 |
25 | ~~~
26 | Column Header 1 | Column Header 2 | Column Header 3
27 | ----------------|-----------------|----------------
28 | Cell 1,1 | Cell 1,2 | Cell 1, 3
29 | Cell 2,1 | Cell 2,2 | Cell 2, 3
30 | Cell 3,1 | Cell 3,2 | Cell 3, 3
31 | ~~~
32 |
33 | {{output-text}}
34 |
35 | Column Header 1 | Column Header 2 | Column Header 3
36 | ----------------|-----------------|----------------
37 | Cell 1,1 | Cell 1,2 | Cell 1, 3
38 | Cell 2,1 | Cell 2,2 | Cell 2, 3
39 | Cell 3,1 | Cell 3,2 | Cell 3, 3
40 |
41 | > %note%
42 | > Note
43 | >
44 | > Multi-row cells are not supported. If you need more complex tables, use HTML code instead.
45 |
46 |
47 | > %sidebar%
48 | > Responsive Tables
49 | >
50 | > To make tables responsive, put them in a _responsive_ block, like in the previous example. The [responsive](class:kwd) class causes a table not to shrink and makes it scrollable horizontally on small devices.
51 |
52 | ## Block Quotes
53 |
54 | Block quotes can be created simply by prepending a [>](class:kwd) to a line, and they can be nested by prepending additional [>](class:kwd)s.
55 |
56 | {{input-text}}
57 |
58 | ~~~
59 | > This is a block quote.
60 | > > This is a nested quote.
61 | ~~~
62 |
63 | {{output-text}}
64 |
65 | > This is a block quote.
66 | > > This is a nested quote.
67 |
68 | ## Code Blocks
69 |
70 | To format a block of source code, indent it by at least four spaces. Here's the result:
71 |
72 | proc encode_image_file*(file, format): string =
73 | if (file.existsFile):
74 | let contents = file.readFile
75 | return encode_image(contents, format)
76 | else:
77 | echo("Warning: image '"& file &"' not found.")
78 | return file
79 |
80 | Alternatively, you can also use Github-style fenced blocks, by adding three tildes (~~~) or backticks (```) before and after the source code.
81 |
82 | > %warning%
83 | > Warning
84 | >
85 | > {{hs}} does not support syntax highlighting for code blocks. To do so, it would require Javascript and {{hs}} is currently kept purposedly "Javascript-free".
86 |
87 |
88 | ## Images
89 |
90 | {{input-text -> The following HastyScribe Markdown code:}}
91 |
92 | ~~~
93 | 
94 | ~~~
95 |
96 | {{output-text -> Produces the following output:}}
97 |
98 | 
99 |
100 | > %tip%
101 | > Tip
102 | >
103 | > You can use URL placeholders for images as well, exactly like for links.
104 |
105 | > %warning%
106 | > Limitations on automatic image download
107 | >
108 | > {{hs}} will attempt to download all HTTP image links. Note that:
109 | >
110 | > * If no response is received within 5 seconds, the download will be aborted.
111 | > * Connecting through a proxy is currently not supported.
112 | > * To download an image via HTTPS, you must explicitly recompile {{hs}} with [-d:ssl](class:kwd) and OpenSSL must be installed on your system.
113 | >
114 | > If {{hs}} is unable to download an image, it will leave it linked.
115 |
116 | ## Details
117 |
118 | {{input-text}}
119 |
120 | ~~~
121 |
122 | Details
123 | The `details` element can be used to create a disclosure element whose contents are only visible when the element is toggled open.
124 |
125 | ~~~
126 |
127 | {{output-text}}
128 |
129 |
130 | Details
131 | The `details` element can be used to create a disclosure element whose contents are only visible when the element is toggled open.
132 |
133 |
134 | ## Footnotes
135 |
136 | {{input-text}}
137 |
138 | ~~~
139 | This is some text[^1]
140 |
141 | [^1]: This is a footnote!
142 | ~~~
143 |
144 | {{output-text}}
145 |
146 | This is some text[^1]
147 |
148 | [^1]: This is a footnote!
149 |
150 |
151 | {@ -syntax-block-lists.md || 1 @}
152 |
153 | {@ -syntax-block-classes.md || 1 @}
154 |
--------------------------------------------------------------------------------
/doc/-overview.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | {{hs -> HastyScribe}} is a self-contained {{mdlink -> [Markdown][df]}} compiler that can create single-file HTML documents. All documents created by {{hs}} use well-formed HTML and embed all stylesheets, fonts, and images that are necessary to display them in any (modern) browser (don't even try to display them in IE8 or lower).
4 |
5 | In other words, all documents created by HastyScribe are constituted by only one [.HTML](class:ext) file, for easy distribution.
6 |
7 | ## Rationale
8 |
9 | There are plenty of programs and services that can convert {{mdlink}} into HTML but they are typically either too simple --they convert {{md -> markdown}} code into an HTML fragment-- or too complex --they produce a well-formed document, but they require too much configuration, or the installation of additional software dependencies.
10 |
11 | Sometimes you just want to write your document in markdown, and get a full HTML file out, ready to be distributed, ideally with no dependencies (external stylesheets or images) --that's where {{hs}} comes in.
12 |
13 | {{hs}}:
14 |
15 | * lets you focus on content and keeps things simple, while giving you all the power of {{disclink -> [Discount][discount]}}-enriched {{md}} (plus some more goodies).
16 | * takes care of styling your documents properly, making sure they look good on your desktop and even on small screens, ready to be distributed.
17 | * is a single, small executable file, with no dependencies. To be fair, it's about 1MB in size when compiled for OSX -- but that's only because the {{hs}} executable embeds all the fonts and stylesheets it needs to produce documents.
18 |
19 | ## Key Features
20 |
21 | ### Standard Markdown
22 |
23 | {{hs}} supports standard {{md}} for formatting text. Markdown is a lightweight markup language created by John Gruber, and used on many web sites and programs to enable users to write HTML code _without actually writing HTML tags_.
24 |
25 | > %tip%
26 | > Tip
27 | >
28 | > You can learn about Markdown syntax in the [Syntax Reference](#Syntax-Reference) section of this document. Alternatively, you can also read the original [Markdown syntax page][md-syntax] on John Gruber's blog, Daring Fireball.
29 |
30 | ### Discount Extensions
31 |
32 | Standard markdown is great, but sometimes you wish it had a few more features, like tables or fenced code blocks perhaps. The good news is that under the hood {{hs}} uses {{disclink}}, a markdown compiler library written in C that extends markdown with a few useful extensions, which allow you to, for example:
33 |
34 | * format blocks of texts to create [notes](#Notes) and [sidebars](#Sidebars)
35 | * style text using CSS classes
36 | * create definition lists and alphabetical lists
37 |
38 | ### Text Snippets
39 |
40 | Although not part of neither {{md}} nor Discount, {{hs}} allows you to create text [snippets](#Snippets) to reuse content. Useful when you have to use a sentence or a formatted block of text over and over in a document, or shorten long words (like the word _{{hs}}_ in this document [](class:fa-face-smile)).
41 |
42 | ### Custom Fields
43 |
44 | {{hs}} also supports [fields](#Fields) to easily include things like the current date or time, but also custom values specified as command-line parameters.
45 |
46 | ### Content Transclusion
47 |
48 | When managing long documents, you can take advantage of {{hs}}'s [transclusion](#Transclusion) support to split your content into several files, and transclude them as you see fit.
49 |
50 | ### Substitution Macros
51 |
52 | If you find yourself writing chunks of text that follows the same format except for some content, you can define simple text substitution [macros](#Macros) for even higher content reuse.
53 |
54 | ### Image (and font) Embedding
55 |
56 | {{hs}} only produces single HTML files. With _no dependencies_:
57 |
58 | * By default, the HastyScribe, FontAwesome, Source Sans Pro, and Source Code Pro fonts are automatically embedded.
59 | * All referenced images (both local and remote) are automatically embedded using the {{datauri -> [data URI scheme](http://en.wikipedia.org/wiki/Data_URI_scheme)}}.
60 |
61 | ### FontAwesome Icons
62 |
63 | [FontAwesome][fa] icons can be used in [badges](#Badges) or simply to customize text. [](class:fa-thumbs-up)
64 |
65 | ### Notes, tips, warnings, sidebars and badges
66 |
67 | > %sidebar%
68 | > About notes etc.
69 | >
70 | > HastyScribe has built-in [tips](#Tips), [notes](#Notes), [warnings](#Warnings), [sidebars](#Sidebars), like this one.
71 |
72 | [...and this is a comment badge.](class:draftcomment)
73 |
74 | ### Responsive Design
75 |
76 | All HTML documents created by {{hs}} are responsive and can be viewed perfectly on small devices.
77 |
78 | ### Printed Media Support
79 |
80 | {{hs}}'s stylesheet contains styles that are specific for printed media. This means that if you try to print an HTML file generated by {{hs}} it will paginate properly and it will display headers and footers (with page numbers).
81 |
82 |
--------------------------------------------------------------------------------
/.github/workflows/add-artifacts-to-current-release.yml:
--------------------------------------------------------------------------------
1 | name: Add Artifacts to Current Release
2 |
3 | # Controls when the action will run.
4 | on:
5 | # Allows you to run this workflow manually from the Actions tab
6 | workflow_dispatch:
7 |
8 | jobs:
9 | release:
10 | name: 'Build and upload artifacts'
11 | runs-on: ${{ matrix.os }}
12 | strategy:
13 | matrix:
14 | os:
15 | - ubuntu-latest
16 | - macos-latest
17 | - windows-latest
18 |
19 | env:
20 | CHOOSENIM_CHOOSE_VERSION: stable
21 | CHOOSENIM_NO_ANALYTICS: 1
22 |
23 | steps:
24 | # Cancel other actions of the same type that might be already running
25 | - name: 'Cancel similar actions in progress'
26 | uses: styfle/cancel-workflow-action@0.6.0
27 | with:
28 | access_token: ${{ github.token }}
29 |
30 | # Detects OS and provide Nim-friendly OS identifiers
31 | - name: Detect current OS
32 | id: os
33 | run: echo "os=${{matrix.os == 'ubuntu-latest' && 'linux' || matrix.os == 'macos-latest' && 'macosx' || matrix.os == 'windows-latest' && 'windows'}}" >> $GITHUB_OUTPUT
34 |
35 | # Checks out the repository
36 | - uses: actions/checkout@v2
37 |
38 | # Installs libraries
39 | - name: install musl-gcc
40 | run: sudo apt-get install -y musl-tools
41 | if: matrix.os == 'ubuntu-latest'
42 |
43 | # Sets path (Linux, macOS)
44 | - name: Update $PATH
45 | shell: bash
46 | run: |
47 | echo "$HOME/.nimble/bin" >> $GITHUB_PATH
48 | echo $GITHUB_WORKSPACE >> $GITHUB_PATH
49 |
50 | # Sets path (Windows)
51 | - name: Update %PATH%
52 | run: |
53 | echo "${HOME}/.nimble/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
54 | echo "${GITHUB_WORKSPACE}" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
55 | echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
56 | if: matrix.os == 'windows-latest'
57 |
58 | # Install the Nim compiler
59 | - name: Install Nim
60 | run: |
61 | curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
62 | sh init.sh -y
63 |
64 | # Temporary Windows-specific fix (missing certificates for nimble)
65 | - name: Install cert (temporary fix, windows only)
66 | run: |
67 | curl https://curl.se/ca/cacert.pem -L -o cacert.pem
68 | if: matrix.os == 'windows-latest'
69 |
70 | # Build for Linux
71 | - name: Build (Linux)
72 | run: |
73 | nimble build -v -y --passL:-static -d:release --gcc.exe:musl-gcc --gcc.linkerexe:musl-gcc --mm:refc --opt:size
74 | if: matrix.os == 'ubuntu-latest'
75 |
76 | # Build for macOS/Windows
77 | - name: Build (macOS, Windows)
78 | shell: bash
79 | run: |
80 | nimble build -v -y -d:release --mm:refc --opt:size
81 | if: matrix.os == 'macos-latest' || matrix.os == 'windows-latest'
82 |
83 |
84 | # UPX compress (*nix)
85 | - name: UPX
86 | uses: svenstaro/upx-action@v2
87 | with:
88 | files: |
89 | hastyscribe
90 | args: --best --force
91 | if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest'
92 |
93 | # UPX compress (Windows)
94 | - name: UPX
95 | uses: svenstaro/upx-action@v2
96 | with:
97 | files: |
98 | hastyscribe.exe
99 | args: --best --force
100 | if: matrix.os == 'windows-latest'
101 |
102 | # Retrieve ID and Name of the current (draft) release
103 | - name: "Get current release"
104 | id: current-release
105 | uses: InsonusK/get-latest-release@v1.0.1
106 | with:
107 | myToken: ${{ github.token }}
108 | exclude_types: "release"
109 | view_top: 1
110 |
111 | # Package the resulting Linux/macOS binary
112 | - name: Create artifact (Linux, macOS)
113 | run: zip hastyscribe_${{steps.current-release.outputs.tag_name}}_${{steps.os.outputs.os}}_x64.zip hastyscribe
114 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest'
115 |
116 | # Package the resulting Windows binary
117 | - name: Create artifact (Windows)
118 | run: Compress-Archive -Path hastyscribe.exe -DestinationPath hastyscribe_${{steps.current-release.outputs.tag_name}}_windows_x64.zip
119 | if: matrix.os == 'windows-latest'
120 |
121 | # Upload artifacts to current draft release
122 | - name: "Upload to current release"
123 | uses: xresloader/upload-to-github-release@v1
124 | env:
125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
126 | with:
127 | file: "hastyscribe_v*.zip"
128 | overwrite: true
129 | tag_name: ${{steps.current-release.outputs.tag_name}}
130 | release_id: ${{steps.current-release.outputs.id }}
131 | verbose: true
132 |
--------------------------------------------------------------------------------
/doc/-syntax.md:
--------------------------------------------------------------------------------
1 | # Syntax Reference
2 |
3 | ## Document Headers
4 |
5 | {{hs}} supports [Pandoc][pandoc]-style Document Headers, as implemented by the [Discount][discount] library. Basically, you can specify the title of the document, author and date as the first three lines of the document, prepending each with a [%](class:kwd), like this
6 |
7 | ~~~
8 | % HastyScribe User Guide
9 | % Fabio Cevasco
10 | % -
11 | ~~~
12 |
13 | > %warning%
14 | > Important
15 | >
16 | > * The order of the document headers is significant.
17 | > * If you want to use the current date, enter [% -](class:kwd) in the third line.
18 |
19 | ## Transclusion
20 |
21 | When writing a long document, it is often useful to split it into many different files, to manage its contents better. {{hs}} provides basic content transclusion support through the following syntax:
22 |
23 | \{@ my-file.md || 1 @\}
24 |
25 | When a file is processed, the line above will cause the contents of file [my-file.md](class:file) to be included in the current file, as if they were part of it. Additionally, when using content transclusion syntax, it is mandatory to specify a number between 0 and 5 to indicate the _offset_ of the headings present in the transcluded file. In this example, the heading numbers of all headings present in [my-file.md](class:file) will be increased by 1, so any [h2](class:kwd) will become [h3](class:kwd), any [h3](class:kwd) will become [h4](class:kwd), and so on.
26 |
27 | > %warning%
28 | > Limitations
29 | >
30 | > * It is recommended to place all transcluded files in the same folder as the transcluding file. If a transcluded file includes any image, its relative path will be interpreted as if it was relative to the transcluding file.
31 | > * Heading offset will only work if headings are created using [#](class:kwd)s. Underline syntax for [h1](class:kwd) and [h2](class:kwd) is not supported.
32 |
33 | ## Snippets
34 |
35 | If you want to reuse a few words or even entire blocks of texts, you can use {{hs}}'s snippets.
36 |
37 | A snippet definition is constituted by an identifier, followed by an arrow ([->](class:kwd)), followed by some text -- all wrapped in double curly brackets.
38 |
39 | The following definition creates a snippet called [test](class:kwd) which is transformed into the text "This is a test snippet.".
40 |
41 | \{\{test -> This is a test snippet.\}\}
42 |
43 | Once a snippet is defined _anywhere_ in the document, you can use its identifier wrapped in double curly brackets (\{\{test}\}\} in the previous example) anywhere in the document to reuse the specified text.
44 |
45 | > %sidebar%
46 | > Alternative Snippet Definition Syntax
47 | >
48 | > When a document is compiled, both snippets _and snippet defininotions_ are evaluated to their body text. To avoid snippet definitions being evaluated, you can use a double arrow ([=>](class:kwd)) in the definition:
49 | >
50 | > \{\{test => This snippet definition will not be evaluated to its body text.\}\}
51 |
52 | ## Fields
53 |
54 | Besides user-defined snippets, {{hs}} also support fields, which can be used to insert current time and date information in a variety of formats:
55 |
56 | > %responsive%
57 | > Source | Output
58 | > --------------------------------------------|----------------------
59 | > \{\{$timestamp\}\} | {{$timestamp}}
60 | > \{\{$date\}\} | {{$date}}
61 | > \{\{$full-date\}\} | {{$full-date}}
62 | > \{\{$long-date\}\} | {{$long-date}}
63 | > \{\{$medium-date\}\} | {{$medium-date}}
64 | > \{\{$short-date\}\} | {{$short-date}}
65 | > \{\{$short-time\}\} | {{$short-time}}
66 | > \{\{$short-time-24\}\} | {{$short-time-24}}
67 | > \{\{$time\}\} | {{$time}}
68 | > \{\{$time-24\}\} | {{$time-24}}
69 | > \{\{$day\}\} | {{$day}}
70 | > \{\{$short-day\}\} | {{$short-day}}
71 | > \{\{$month\}\} | {{$month}}
72 | > \{\{$short-month\}\} | {{$short-month}}
73 | > \{\{$year\}\} | {{$year}}
74 | > \{\{$short-year\}\} | {{$short-year}}
75 | > \{\{$weekday\}\} | {{$weekday}}
76 | > \{\{$weekday-abbr\}\} | {{$weekday-abbr}}
77 | > \{\{$month-name\}\} | {{$month-name}}
78 | > \{\{$month-name-abbr\}\} | {{$month-name-abbr}}
79 | > \{\{$timezone-offset\}\} | {{$timezone-offset}}
80 |
81 | Additionally, you can define your own custom fields via command-line parameters, using the [\-\-field/](class:arg) dynamic parameter, like this:
82 |
83 | > %terminal%
84 | > hastyscribe my-document.md \-\-field/product:HastyScribe \-\-field/version:1.2.0
85 |
86 | In this case it will be possible to access the [product](class:kwd) and [product](class:kwd) fields within [my-document.md](class:file) using \{\{$product\}\} and \{\{$version\}\}.
87 |
88 | ## Macros
89 |
90 | If snippets are not enough, and you want to reuse chunks of _similar_ content, you can define substitution macros using the following syntax:
91 |
92 | \{#greet => Hello, $1! Are you $2?#\}
93 |
94 | This defines a macro called [greet](class:kwd) that takes two parameters which will be substituted instead of [$1](class:kwd) and [$2](class:kwd). To use the macro, use the following syntax:
95 |
96 | \{#greet||Fabio||ready#\}
97 |
98 | > %note%
99 | > Note
100 | >
101 | > * Like snippets, macros can be multiline.
102 | > * Spaces and newline character are preseved ad the start and end of parameters.
103 | > * You can use snippets and fields within macros (but you cannot nest macros inside other macros).
104 | > * You can define macros using either [->](class:kwd) or [=>](class:kwd), although [=>](class:kwd) is preferred.
105 |
106 | {@ -syntax-inline.md || 1 @}
107 |
108 | {@ -syntax-block.md || 1 @}
109 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/data/hastyscribe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/markdown.nim:
--------------------------------------------------------------------------------
1 | const
2 | MKDIO_D* = true
3 | type
4 | MMIOT* = int
5 | mkd_flag_t* = cuint
6 |
7 | {.push importc, cdecl.}
8 | # line builder for markdown()
9 | #
10 | proc mkd_in*(a2: ptr FILE; a3: mkd_flag_t): ptr MMIOT
11 | # assemble input from a file
12 | proc mkd_string*(a2: cstring; a3: cint; a4: mkd_flag_t): ptr MMIOT
13 | # assemble input from a buffer
14 | # line builder for github flavoured markdown
15 | #
16 | proc gfm_in*(a2: ptr FILE; a3: mkd_flag_t): ptr MMIOT
17 | # assemble input from a file
18 | proc gfm_string*(a2: cstring; a3: cint; a4: mkd_flag_t): ptr MMIOT
19 | # assemble input from a buffer
20 | proc mkd_basename*(a2: ptr MMIOT; a3: cstring)
21 | proc mkd_initialize*()
22 | proc mkd_with_html5_tags*()
23 | proc mkd_shlib_destructor*()
24 | # compilation, debugging, cleanup
25 | #
26 | proc mkd_compile*(a2: ptr MMIOT; a3: mkd_flag_t): cint
27 | proc mkd_cleanup*(a2: ptr MMIOT)
28 | # markup functions
29 | #
30 | proc mkd_dump*(a2: ptr MMIOT; a3: ptr FILE; a4: cint; a5: cstring): cint
31 | proc markdown*(a2: ptr MMIOT; a3: ptr FILE; a4: mkd_flag_t): cint
32 | proc mkd_line*(a2: cstring; a3: cint; a4: cstringArray; a5: mkd_flag_t): cint
33 | type
34 | mkd_sta_function_t* = proc (a2: cint; a3: pointer): cint
35 | proc mkd_string_to_anchor*(a2: cstring; a3: cint; a4: mkd_sta_function_t;
36 | a5: pointer; a6: cint)
37 | proc mkd_xhtmlpage*(a2: ptr MMIOT; a3: cint; a4: ptr FILE): cint
38 | # header block access
39 | #
40 | proc mkd_doc_title*(a2: ptr MMIOT): cstring
41 | proc mkd_doc_author*(a2: ptr MMIOT): cstring
42 | proc mkd_doc_date*(a2: ptr MMIOT): cstring
43 | # compiled data access
44 | #
45 | proc mkd_document*(a2: ptr MMIOT; a3: cstringArray): cint
46 | proc mkd_toc*(a2: ptr MMIOT; a3: cstringArray): cint
47 | proc mkd_css*(a2: ptr MMIOT; a3: cstringArray): cint
48 | proc mkd_xml*(a2: cstring; a3: cint; a4: cstringArray): cint
49 | # write-to-file functions
50 | #
51 | proc mkd_generatehtml*(a2: ptr MMIOT; a3: ptr FILE): cint
52 | proc mkd_generatetoc*(a2: ptr MMIOT; a3: ptr FILE): cint
53 | proc mkd_generatexml*(a2: cstring; a3: cint; a4: ptr FILE): cint
54 | proc mkd_generatecss*(a2: ptr MMIOT; a3: ptr FILE): cint
55 | const
56 | mkd_style* = mkd_generatecss
57 | proc mkd_generateline*(a2: cstring; a3: cint; a4: ptr FILE; a5: mkd_flag_t): cint
58 | const
59 | mkd_text* = mkd_generateline
60 | # url generator callbacks
61 | #
62 | type
63 | mkd_callback_t* = proc (a2: cstring; a3: cint; a4: pointer): cstring
64 | mkd_free_t* = proc (a2: cstring; a3: pointer)
65 | proc mkd_e_url*(a2: pointer; a3: mkd_callback_t)
66 | proc mkd_e_flags*(a2: pointer; a3: mkd_callback_t)
67 | proc mkd_e_free*(a2: pointer; a3: mkd_free_t)
68 | proc mkd_e_data*(a2: pointer; a3: pointer)
69 | # version#.
70 | #
71 | var markdown_version*: ptr char
72 | proc mkd_mmiot_flags*(a2: ptr FILE; a3: ptr MMIOT; a4: cint)
73 | proc mkd_flags_are*(a2: ptr FILE; a3: mkd_flag_t; a4: cint)
74 | proc mkd_ref_prefix*(a2: ptr MMIOT; a3: cstring)
75 | {.pop.}
76 |
77 | # special flags for markdown() and mkd_text()
78 | #
79 | const
80 | MKD_NOLINKS* = 0x00000001
81 | MKD_NOIMAGE* = 0x00000002
82 | MKD_NOPANTS* = 0x00000004
83 | MKD_NOHTML* = 0x00000008
84 | MKD_STRICT* = 0x00000010
85 | MKD_TAGTEXT* = 0x00000020
86 | MKD_NO_EXT* = 0x00000040
87 | MKD_CDATA* = 0x00000080
88 | MKD_NOSUPERSCRIPT* = 0x00000100
89 | MKD_NORELAXED* = 0x00000200
90 | MKD_NOTABLES* = 0x00000400
91 | MKD_NOSTRIKETHROUGH* = 0x00000800
92 | MKD_TOC* = 0x00001000
93 | MKD_1_COMPAT* = 0x00002000
94 | MKD_AUTOLINK* = 0x00004000
95 | MKD_SAFELINK* = 0x00008000
96 | MKD_NOHEADER* = 0x00010000
97 | MKD_TABSTOP* = 0x00020000
98 | MKD_NODIVQUOTE* = 0x00040000
99 | MKD_NOALPHALIST* = 0x00080000
100 | MKD_NODLIST* = 0x00100000
101 | MKD_EXTRA_FOOTNOTE* = 0x00200000
102 | MKD_NOSTYLE* = 0x00400000
103 | MKD_NODLDISCOUNT* = 0x00800000
104 | MKD_DLEXTRA* = 0x01000000
105 | MKD_FENCEDCODE* = 0x02000000
106 | MKD_IDANCHOR* = 0x04000000
107 | MKD_GITHUBTAGS* = 0x08000000
108 | MKD_URLENCODEDANCHOR* = 0x10000000
109 | MKD_LATEX* = 0x40000000
110 | MKD_EMBED* = MKD_NOLINKS or MKD_NOIMAGE or MKD_TAGTEXT
111 |
112 | ## High Level API
113 |
114 | import
115 | std/pegs
116 |
117 | const
118 | DefaultFlags = MKD_TOC or MKD_1_COMPAT or MKD_EXTRA_FOOTNOTE or MKD_DLEXTRA or MKD_FENCEDCODE or MKD_GITHUBTAGS or MKD_URLENCODEDANCHOR or MKD_LATEX
119 |
120 | type TMDMetaData* = object
121 | title*: string = ""
122 | author*: string = ""
123 | date*: string = ""
124 | toc*: string = ""
125 | css*: string = ""
126 |
127 | proc md*(s: string, f = 0): string =
128 | var flags: uint32
129 | if (f == 0):
130 | flags = DefaultFlags
131 | else:
132 | flags = uint32(f)
133 | var str = cstring(s&" ")
134 | var mmiot = mkd_string(str, cint(str.len-1), flags)
135 | discard mkd_compile(mmiot, flags)
136 | var res = allocCStringArray([""])
137 | discard mkd_document(mmiot, res)
138 | result = cstringArrayToSeq(res)[0]
139 | mkd_cleanup(mmiot)
140 | return
141 |
142 | proc md*(s: string, f = 0; data: out TMDMetaData): string =
143 | data = default(TMDMetaData)
144 | var flags: uint32
145 | if (f == 0):
146 | flags = DefaultFlags
147 | else:
148 | flags = uint32(f)
149 | # Check if Pandoc style metadata is present
150 | var valid_metadata = false
151 | var contents = s
152 | let peg_pandoc = peg"""
153 | definition <- ^{line} {line}? {line}?
154 | line <- '\%' @ \n
155 | """
156 | var matches: array[0..2, string]
157 | let (s, e) = contents.findBounds(peg_pandoc, matches)
158 | # the pattern must start at the beginning of the file
159 | if s == 0:
160 | if matches[0] != "" and matches[1] != "" and matches[2] != "":
161 | valid_metadata = true
162 | else:
163 | # incomplete metadata, remove the whole pandoc section to not confuse discount
164 | contents = contents[e-1 .. ^1]
165 | var str = cstring(contents)
166 | var mmiot = mkd_string(str, cint(str.len), flags)
167 | if valid_metadata:
168 | data.title = $mkd_doc_title(mmiot)
169 | data.author = $mkd_doc_author(mmiot)
170 | data.date = $mkd_doc_date(mmiot)
171 | discard mkd_compile(mmiot, flags)
172 | # Process TOC
173 | if (int(flags) and MKD_TOC) == MKD_TOC:
174 | var toc = allocCStringArray(@[""])
175 | if mkd_toc(mmiot, toc) > 0:
176 | data.toc = cstringArrayToSeq(toc)[0]
177 | # Process CSS
178 | data.css = block:
179 | var css = allocCStringArray(newSeq[string](10))
180 | if mkd_css(mmiot, css) > 0: cstringArrayToSeq(css)[0]
181 | else: ""
182 | # Generate HTML
183 | let html = block:
184 | var res = allocCStringArray([""])
185 | if mkd_document(mmiot, res) > 0: cstringArrayToSeq(res)[0]
186 | else: ""
187 | mkd_cleanup(mmiot)
188 | html
189 |
--------------------------------------------------------------------------------
/doc/-syntax-inline.md:
--------------------------------------------------------------------------------
1 | # Inline Formatting
2 |
3 | The following table lists all the most common ways to format inline text:
4 |
5 | > %responsive%
6 | > Source | Output
7 | > ----------------------------------------------------|--------------------
8 | > `**strong emphasis**` or `__strong emphasis__` | __strong emphasis__
9 | > `*emphasis*` or `_emphasis_` | *emphasis*
10 | > `~~deleted text~~` | ~~deleted text~~
11 | > `inserted text` | inserted text
12 | > ```code` `` | `code`
13 | > `[HTML](abbr:Hypertext Markup Language)` | [HTML](abbr:Hypertext Markup Language)
14 | > `CTRL+C` | CTRL+C
15 | > `marked` | marked.
16 | > `Sample output: This is a test.` | Sample output: This is a test.
17 | > `Set the variable test to 1.` | Set the variable test to 1.
18 | > `This is a short quotation
` | This is a short quotation
19 | > `Hamlet, by William Shakespeare.` | Hamlet, by William Shakespeare.
20 | > `A [.md](class:ext) file` | A [.md](class:ext) file
21 | > `[my_markdown_file.md](class:file) file` | [my_markdown_file.md](class:file) file
22 |
23 | > %tip%
24 | > Tip
25 | >
26 | > The [kwd](class:kwd), [opt](class:kwd), [file](class:kwd), [dir](class:kwd), [arg](class:kwd), [tt](class:kwd) and [cmd](class:kwd) classes are all rendered as inline monospace text. [kwd](class:kwd) and [ext](class:ext) are also rendered in bold.
27 |
28 |
29 | ## SmartyPants Substitutions
30 |
31 | Special characters can be easily entered using some special character sequences.
32 |
33 | {{hs}} supports all the sequences supported by [Discount][discount]:
34 |
35 | * `` text‘’ → “text”.
36 | * `"double-quoted text"` → “double-quoted text”
37 | * `'single-quoted text'` → ‘single-quoted text’
38 | * `don't` → don’t. as well as anything-else’t. (But foo'tbar is just foo'tbar.)
39 | * `it's` → it’s, as well as anything-else’s (except not foo'sbar and the like.)
40 | * `(tm)` → ™
41 | * `(r)` → ®
42 | * `(c)` → ©
43 | * `1/4th` → 1/4th. Same goes for 1/2 and 3/4.
44 | * `...` or `. . .` → …
45 | * `---` → —
46 | * `--` → –
47 | * `A^B` becomes A^B. Complex superscripts can be enclosed in brackets, so `A^(B+2)` → A^(B+2).
48 |
49 |
50 | ## Icons
51 |
52 | {{hs}} bundles the [FontAwesome][fa] icon font. To prepend an icon to text you can use Discount's _class:_ pseudo-protocol, and specify a valid [fa-*](class:kwd) (non-alias) class.
53 |
54 | Examples:
55 |
56 | > %responsive%
57 | > Source | Output
58 | > -----------------------------------------|------------
59 | > `[a paper plane](class:fa-paper-plane)` | [ a paper plane](class:fa-paper-plane)
60 | > `[Galactic Empire](class:fa-empire)` | [ Galactic Empire](class:fa-empire)
61 | > `[Rebel Alliance](class:fa-rebel)` | [ Rebel Alliance](class:fa-rebel)
62 |
63 | > %tip%
64 | > Tip
65 | >
66 | > See the [FontAwesome Icon Reference][fa-icons] for a complete list of all CSS classes to use for icons (aliases are not supported).
67 |
68 | ## Badges
69 |
70 | Badges are shorthands for [Icons](#Icons) formatted with different colors. To add a _badge_ to some inline text, use the corresponding class among those listed in the following table. For example, the following code:
71 |
72 | [Genoa, Italy](class:badge-geo)
73 |
74 | produces the following result:
75 |
76 | [Genoa, Italy](class:badge-geo)
77 |
78 | {{hs}} currently supports the following badges:
79 |
80 | > %responsive%
81 | > Class | Badge | Class | Badge
82 | > ---------------------------|------------------------------------|---------------------------|-----------------------------
83 | > `badge-todo` | [](class:badge-todo) |`badge-user` | [](class:badge-user)
84 | > `badge-fixme` | [](class:badge-fixme) |`badge-tag` | [](class:badge-tag)
85 | > `badge-deadline` | [](class:badge-deadline) |`badge-tags` | [](class:badge-tags)
86 | > `badge-comment` | [](class:badge-comment) |`badge-attachment` | [](class:badge-attachment)
87 | > `badge-urgent` | [](class:badge-urgent) |`badge-bug` | [](class:badge-bug)
88 | > `badge-verify` | [](class:badge-verify) |`badge-geo` | [](class:badge-geo)
89 | > `badge-project` | [](class:badge-project) |`badge-square` | [](class:badge-square)
90 | > `badge-star` | [](class:badge-star) |`badge-check` | [](class:badge-check)
91 | > `badge-heart` | [](class:badge-heart) |`badge-rss` | [](class:badge-rss)
92 | > `badge-lock` | [](class:badge-lock) |`badge-danger` | [](class:badge-danger)
93 | > `badge-unlock` | [](class:badge-unlock) |`badge-question` | [](class:badge-question)
94 | > `badge-folder` | [](class:badge-folder) |`badge-flag` | [](class:badge-flag)
95 | > `badge-story` | [](class:badge-story) |`badge-feature` | [](class:badge-feature)
96 | > `badge-add` | [](class:badge-add) |`badge-remove` | [](class:badge-remove)
97 | > `badge-time` | [](class:badge-time) |`badge-date` | [](class:badge-date)
98 | > `badge-html5` | [](class:badge-html5) |`badge-css3` | [](class:badge-css3)
99 | > `badge-apple` | [](class:badge-apple) |`badge-windows` | [](class:badge-windows)
100 | > `badge-linux` | [](class:badge-linux) |`badge-android` | [](class:badge-android)
101 | > `badge-freebsd` | [](class:badge-freebsd) |`badge-aws` | [](class:badge-aws)
102 | > `badge-idea` | [](class:badge-idea) |`badge-link` | [](class:badge-link)
103 | > `badge-chrome` | [](class:badge-chrome) |`badge-firefox` | [](class:badge-firefox)
104 | > `badge-ie` | [](class:badge-ie) |`badge-edge` | [](class:badge-edge)
105 | > `badge-safari` | [](class:badge-safari) |`badge-opera` | [](class:badge-opera)
106 | > `badge-php` | [](class:badge-php) |`badge-erlang` | [](class:badge-erlang)
107 | > `badge-python` | [](class:badge-python) |`badge-java` | [](class:badge-java)
108 | > `badge-nodejs` | [](class:badge-nodejs) |`badge-js` | [](class:badge-js)
109 | > `badge-toggle-on` | [](class:badge-toggle-on) |`badge-toggle-off` | [](class:badge-toggle-off)
110 | > `badge-debian` | [](class:badge-debian) |`badge-fedora` | [](class:badge-fedora)
111 | > `badge-centos` | [](class:badge-centos) |`badge-suse` | [](class:badge-suse)
112 | > `badge-redhat` | [](class:badge-redhat) |`badge-ubuntu` | [](class:badge-ubuntu)
113 | > `badge-rust` | [](class:badge-rust) |`badge-go` | [](class:badge-go)
114 | > `badge-rpi` | [](class:badge-rpi) |`badge-markdown` | [](class:badge-markdown)
115 | > `badge-react` | [](class:badge-react) |`badge-angular` | [](class:badge-angular)
116 | > `badge-vue` | [](class:badge-vue) |`badge-code` | [](class:badge-code)
117 | > `badge-address` | [](class:badge-address) |`badge-org` | [](class:badge-org)
118 | > `badge-toxic` | [](class:badge-toxic) |`badge-network` | [](class:badge-network)
119 | > `badge-upload` | [](class:badge-upload) |`badge-download` | [](class:badge-download)
120 |
121 |
122 | ## Anchors
123 |
124 | You can define HTML anchors inline by wrapping their ID in hashes. For example, the following code:
125 |
126 | Some text goes here. \#some_text\#
127 |
128 | Is converted to:
129 |
130 | Some text goes here.
131 |
132 | > %note%
133 | > Note
134 | >
135 | > * Anchor markup must be preceded by at least one space.
136 | > * IDs must start with a letter, and can contain letters, numbers, and any of the following characters: `_` `-` `.` `:`
137 |
138 | ## Links
139 |
140 | > %responsive%
141 | > Source | Output
142 | > ----------------------------------------|------------
143 | > `[H3RALD](https://h3rald.com/)` | [H3RALD](https://h3rald.com/)
144 | > `[H3RALD](https://h3rald.com/ "H3RALD")`| [H3RALD](https://h3rald.com/ "H3RALD")
145 | > `` |
146 |
147 | Additionally, you can define placeholders for URLs and link titles, like this:
148 |
149 | `[h3rald]: https://h3rald.com/ "Fabio Cevasco's Web Site"`
150 |
151 | And use them in hyperlinks (note the usage of square brackets instead of round brackets):
152 |
153 | `[H3RALD][h3rald]`
154 |
155 | > %sidebar%
156 | > Link Icons
157 | >
158 | > {{hs}} automatically adds an envelope icon to email links, an arrow icon to links to external web sites, and logo icons to links to well-known web sites:
159 | >
160 | > * [h3rald@h3rad.com](mailto:h3rald@h3rald.com)
161 | > * [@h3rald](https://twitter.com/h3rald)
162 | > * [fabiocevasco](https://it.linkedin.com/in/fabiocevasco)
163 |
164 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/data/hastyscribe-original.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/hastyscribe.nim:
--------------------------------------------------------------------------------
1 | import std/[
2 | macros,
3 | os,
4 | parseopt,
5 | strutils,
6 | times,
7 | pegs,
8 | xmltree,
9 | tables,
10 | httpclient,
11 | logging,
12 | critbits,
13 | sets,
14 | ]
15 |
16 | from nimquery import querySelectorAll
17 | from std/htmlparser import parseHtml
18 |
19 | import
20 | hastyscribepkg/niftylogger,
21 | hastyscribepkg/markdown,
22 | hastyscribepkg/config,
23 | hastyscribepkg/consts,
24 | hastyscribepkg/utils
25 |
26 | export
27 | consts
28 |
29 | when defined(windows) and defined(amd64):
30 | {.passL: "-static -L"&getProjectPath()&"/hastyscribepkg/vendor/markdown/windows -lmarkdown".}
31 | elif defined(linux) and defined(amd64):
32 | {.passL: "-static -L"&getProjectPath()&"/hastyscribepkg/vendor/markdown/linux -lmarkdown".}
33 | elif defined(macosx) and defined(amd64):
34 | {.passL: "-Bstatic -L"&getProjectPath()&"/hastyscribepkg/vendor/markdown/macosx -lmarkdown -Bdynamic".}
35 |
36 |
37 | type
38 | HastyOptions* = object
39 | toc*: bool = true
40 | input*: string = ""
41 | output*: string = ""
42 | css*: string = ""
43 | js*: string = ""
44 | watermark*: string
45 | fragment*: bool = false
46 | embed*: bool = true
47 | iso*: bool = false
48 | minifycss*: bool = false
49 | noclobber*: bool = false
50 | outputToDir*: bool = false
51 | processingMultiple: bool = false
52 | HastyFields* = Table[string, string]
53 | HastySnippets* = Table[string, string]
54 | HastyMacros* = Table[string, string]
55 | HastyIconStyles* = Table[string, string]
56 | HastyNoteStyles* = Table[string, string]
57 | HastyBadgeStyles* = Table[string, string]
58 | HastyScribe* = object
59 | options: HastyOptions
60 | fields: HastyFields
61 | snippets: HastySnippets
62 | macros: HastyMacros
63 | document: string
64 | hastyStylesheet: string
65 | iconStyles: HastyIconStyles
66 | noteStyles: HastyNoteStyles
67 | badgeStyles: HastyBadgeStyles
68 |
69 | if logging.getHandlers().len == 0:
70 | newNiftyLogger().addHandler()
71 |
72 | proc initFields(fields: HastyFields): HastyFields {.gcsafe.} =
73 | result = initTable[string, string]()
74 | for key, value in fields.pairs:
75 | result[key] = value
76 | var now = getTime().local()
77 | result["timestamp"] = $now.toTime.toUnix().int
78 | result["date"] = now.format("yyyy-MM-dd")
79 | result["full-date"] = now.format("dddd, MMMM d, yyyy")
80 | result["long-date"] = now.format("MMMM d, yyyy")
81 | result["medium-date"] = now.format("MMM d, yyyy")
82 | result["short-date"] = now.format("M/d/yy")
83 | result["short-time-24"] = now.format("HH:mm")
84 | result["short-time"] = now.format("HH:mm tt")
85 | result["time-24"] = now.format("HH:mm:ss")
86 | result["time"] = now.format("HH:mm:ss tt")
87 | result["day"] = now.format("dd")
88 | result["month"] = now.format("MM")
89 | result["year"] = now.format("yyyy")
90 | result["short-day"] = now.format("d")
91 | result["short-month"] = now.format("M")
92 | result["short-year"] = now.format("yy")
93 | result["weekday"] = now.format("dddd")
94 | result["weekday-abbr"] = now.format("dd")
95 | result["month-name"] = now.format("MMMM")
96 | result["month-name-abbr"] = now.format("MMM")
97 | result["timezone-offset"] = now.format("zzz")
98 |
99 | proc newHastyScribe*(options: HastyOptions, fields: HastyFields): HastyScribe =
100 | HastyScribe(
101 | options: options,
102 | fields: initFields(fields),
103 | snippets: initTable[string, string](),
104 | macros: initTable[string, string](),
105 | document: "",
106 | hastyStylesheet: (
107 | if not options.embed: ""
108 | elif options.minifycss: stylesheet.minifyCss()
109 | else: stylesheet
110 | ),
111 | )
112 |
113 | # Utility Procedures
114 |
115 | proc embed_images(hs: var HastyScribe, dir: string) =
116 | let peg_img = peg"""
117 | image <- '
Image will be linked instead"
145 | continue
146 | else:
147 | imgcontent = encode_image_file(current_dir & imgfile, imgformat)
148 | let imgrep = img.replace("\"" & img_file & "\"", "\"" & imgcontent & "\"")
149 | doc = doc.replace(img, imgrep)
150 | hs.document = doc
151 |
152 | proc preprocess*(hs: var HastyScribe, document, dir: string, offset = 0): string
153 |
154 | proc applyHeadingOffset(contents: string, offset: int): string =
155 | if offset == 0:
156 | return contents
157 | let peg_heading = peg"""heading <- (^ / \n){'#'+}"""
158 | var handleHeading = proc (index: int, count: int, matches: openArray[string]): string =
159 | let heading = matches[0]
160 | result = "\n" & "#".repeat(heading.len + offset)
161 | return contents.replace(peg_heading, handleHeading)
162 |
163 | # Transclusion with heading offset:
164 | # {@ some/file.md || 1 @}
165 | proc parse_transclusions(hs: var HastyScribe, document: string, dir = "", offset = 0): string =
166 | result = document.applyHeadingOffset(offset)
167 | let peg_transclusion = peg"""
168 | transclusion <- '{\@' \s* {path} \s* '||' \s* {offset} \s* '\@}'
169 | path <- [^|]+
170 | offset <- [0-5]
171 | """
172 | var cwd = dir
173 | if cwd != "":
174 | cwd = cwd & "/"
175 | for transclusion in document.findAll(peg_transclusion):
176 | var matches: array[0..1, string]
177 | discard transclusion.match(peg_transclusion, matches)
178 | let path = cwd & matches[0].strip
179 | let value = matches[1].strip
180 | let offset = value.split("||")[0].parseInt() + offset
181 | if path.fileExists():
182 | let fileInfo = path.splitFile()
183 | var contents, s = ""
184 | var delimiter = 0
185 | var f:File
186 | discard f.open(path)
187 | # Ignore headers
188 | try:
189 | discard f.readLine(s)
190 | if not s.startsWith("----"):
191 | delimiter = 2
192 | contents &= s&"\n"
193 | else:
194 | delimiter = 1
195 | while f.readLine(s):
196 | if delimiter >= 2:
197 | contents &= s&"\n"
198 | else:
199 | if s.startsWith("----"):
200 | delimiter.inc
201 | except CatchableError:
202 | discard
203 | f.close()
204 | result = result.replace(transclusion, hs.parse_transclusions(contents, fileInfo.dir, offset))
205 | else:
206 | warn "File '$1' not found" % [path]
207 | result = result.replace(transclusion, "")
208 |
209 | # Macro Definition:
210 | # {#test -> This is a $1}
211 | #
212 | # Macro Usage:
213 | # {#test||simple test}
214 | proc parse_macros(hs: var HastyScribe, document: string): string =
215 | let peg_macro_def = peg"""
216 | definition <- '{#' \s* {id} \s* deftype {@} '#}'
217 | deftype <- '->' / '=>'
218 | id <- [a-zA-Z0-9_-]+
219 | """
220 | let peg_macro_instance = peg"""
221 | instance <- "{#" \s* {id} \s* "||" \s* {@} "#}"
222 | id <- [a-zA-Z0-9_-]+
223 | """
224 | result = document
225 | for def in document.findAll(peg_macro_def):
226 | var matches: array[0..1, string]
227 | discard def.match(peg_macro_def, matches)
228 | let id = matches[0].strip
229 | let value = matches[1].strip
230 | hs.macros[id] = value
231 | result = result.replace(def, "")
232 | for instance in findAll(result, peg_macro_instance):
233 | var matches: array[0..1, string]
234 | discard instance.match(peg_macro_instance, matches)
235 | let id = matches[0].strip
236 | let value = matches[1].strip
237 | let params = value.split("||")
238 | if hs.macros.hasKey(id):
239 | try:
240 | result = result.replace(instance, hs.macros[id] % params)
241 | except CatchableError:
242 | warn "Incorrect number of parameters specified for macro '$1'\n -> Instance: $2" % [id, instance]
243 | else:
244 | warn "Macro '" & id & "' not defined."
245 | result = result.replace(instance, "")
246 |
247 | # Field Usage:
248 | # {{$timestamp}}
249 | proc parse_fields(hs: var HastyScribe, document: string): string {.gcsafe.} =
250 | let peg_field = peg"""
251 | field <- '{{' \s* '$' {id} \s* '}}'
252 | id <- [a-zA-Z0-9_-]+
253 | """
254 | result = document
255 | for field in document.findAll(peg_field):
256 | var matches:array[0..0, string]
257 | discard field.match(peg_field, matches)
258 | var id = matches[0].strip
259 | if hs.fields.hasKey(id):
260 | result = result.replace(field, hs.fields[id])
261 | else:
262 | warn "Field '" & id & "' not defined."
263 | result = result.replace(field, "")
264 |
265 |
266 | proc load_styles(hs: var HastyScribe) =
267 | type
268 | StyleRuleMatches = array[0..1, string]
269 | # Icons
270 | let peg_iconstyle_def = peg"""
271 | definition <- { '.' {icon} ':before' \s* '{' @ (\n / $) }
272 | icon <- 'fa-' [a-z0-9-]+
273 | """
274 | for def in stylesheet_icons.findAll(peg_iconstyle_def):
275 | var matches: StyleRuleMatches
276 | discard def.match(peg_iconstyle_def, matches)
277 | hs.iconStyles[matches[1].strip] = matches[0].strip
278 | # Badges
279 | let peg_badgestyle_def = peg"""
280 | definition <- { '.' {badge} ':before' \s* '{' @ (\n / $) }
281 | badge <- 'badge-' [a-z0-9-]+
282 | """
283 | for def in stylesheet_badges.findAll(peg_badgestyle_def):
284 | var matches: StyleRuleMatches
285 | discard def.match(peg_badgestyle_def, matches)
286 | hs.badgeStyles[matches[1].strip] = matches[0].strip
287 | # Notes
288 | let peg_notestyle_def = peg"""
289 | definition <- { '.' {note} \s* '> p:first-child:before {' \s* @ (\n / $) }
290 | note <- [a-z]+
291 | """
292 | for def in stylesheet_notes.findAll(peg_notestyle_def):
293 | var matches: StyleRuleMatches
294 | discard def.match(peg_notestyle_def, matches)
295 | hs.noteStyles[matches[1].strip] = matches[0].strip
296 | # Links -> already in `consts.css_rules_links`
297 |
298 | # Snippet Definition:
299 | # {{test -> My test snippet}}
300 | #
301 | # Snippet Usage:
302 | # {{test}}
303 | proc parse_snippets(hs: var HastyScribe, document: string): string =
304 | let peg_snippet_def = peg"""
305 | definition <- '{{' \s* {id} \s* {deftype} {@} '}}'
306 | deftype <- '->' / '=>'
307 | id <- [a-zA-Z0-9_-]+
308 | """
309 | let peg_snippet = peg"""
310 | snippet <- '{{' \s* {id} \s* '}}'
311 | id <- [a-zA-Z0-9_-]+
312 | """
313 | type
314 | TSnippetDef = array[0..2, string]
315 | TSnippet = array[0..0, string]
316 | result = document
317 | for def in document.findAll(peg_snippet_def):
318 | var matches:TSnippetDef
319 | discard def.match(peg_snippet_def, matches)
320 | var id = matches[0].strip
321 | var value = matches[2].strip(true, false)
322 | hs.snippets[id] = value
323 | if matches[1] == "=>":
324 | value = ""
325 | result = result.replace(def, value)
326 | for snippet in document.findAll(peg_snippet):
327 | var matches:TSnippet
328 | discard snippet.match(peg_snippet, matches)
329 | var id = matches[0].strip
330 | if hs.snippets.hasKey(id):
331 | result = result.replace(snippet, hs.snippets[id])
332 | else:
333 | warn "Snippet '" & id & "' not defined."
334 | result = result.replace(snippet, "")
335 |
336 | proc remove_escapes(hs: var HastyScribe, document: string): string =
337 | ## Substitute escaped brackets or hashes *after* preprocessing
338 | document.replacef(peg"'\\' {'{' / '}' / '#'}", "$1")
339 |
340 | proc parse_anchors(hs: var HastyScribe, document: string): string =
341 | let peg_anchor = peg"""
342 | anchor <- \s '#' {id} '#'
343 | id <- [a-zA-Z][a-zA-Z0-9:._-]+
344 | """
345 | document.replacef(peg_anchor, """ """)
346 |
347 | proc preprocess*(hs: var HastyScribe, document, dir: string, offset = 0): string =
348 | result = hs.parse_transclusions(document, dir, offset)
349 | result = hs.parse_fields(result)
350 | result = hs.parse_snippets(result)
351 | result = hs.parse_macros(result)
352 | result = hs.parse_anchors(result)
353 | result = hs.remove_escapes(result)
354 |
355 | proc getTableValue(table: Table[string, string], key: string, obj: string): string =
356 | try:
357 | return table[key]
358 | except CatchableError:
359 | warn obj & " not found: " & key
360 |
361 | proc create_optional_css*(hs: HastyScribe, document: string): string =
362 | ## Analyzes the provided HTML document for using elements matching
363 | ## the set of "hastystyles" CSS rules and prepares a custom CSS with the
364 | ## used resources.
365 | let html = document.parseHtml()
366 | var rules: seq[string]
367 |
368 | block icons_badges_notes:
369 | var selSet: HashSet[string]
370 | template fillFrom(rules: var seq[string]; t: Table[string, string]; selector, obj: string) =
371 | for el in html.querySelectorAll(selector): selSet.incl(el.attr("class"))
372 | for selV in selSet.items: rules.add(getTableValue(t, selV, obj))
373 | selSet.init()
374 | rules.fillFrom(hs.iconStyles, "span[class^=fa-]", "Icon") # Check icons
375 | rules.fillFrom(hs.badgeStyles, "span[class^=badge-]", "Badge") # Check badges
376 | rules.fillFrom(hs.noteStyles, "div.tip, div.warning, div.note, div.sidebar", "Note") # Check notes
377 |
378 | block linkStyles: # Check links
379 | # Init with `document-top`: it's added to the document later with `utils.add_jump_to_top_links`
380 | var linkHrefs: CritBitTree[void] = ["#document-top"].toCritBitTree()
381 | for link in html.querySelectorAll("a[href]"): linkHrefs.incl(link.attr("href"))
382 | var linkRulesSets: array[CssSelPriority, CritBitTree[void]]
383 | for href in linkHrefs.keys:
384 | block search:
385 | for prio in countDown(csspProto, csspLowProto): # Traverse rules in order of decreasing priority
386 | for (val, rule) in css_rules_links[prio]:
387 | let match =
388 | case prio:
389 | of csspLowProto, csspProto: href.startsWith(val)
390 | of csspDom: href.contains(val)
391 | of csspExt: href.endsWith(val)
392 | else: false
393 | if match: linkRulesSets[prio].incl(rule); break search
394 | # Adding rules in order of increasing priority
395 | for prio in CssSelPriority:
396 | for rule in linkRulesSets[prio]: rules.add(rule)
397 |
398 | rules.join("\n").style_tag()
399 |
400 |
401 | # Public API
402 |
403 | proc compileFragment*(hs: var HastyScribe, input, dir: string, toc = false): string {.discardable.} =
404 | hs.document = input
405 | # Parse transclusions, fields, snippets, and macros
406 | hs.document = hs.preprocess(hs.document, dir)
407 | # Process markdown
408 | var flags = MKD_EXTRA_FOOTNOTE or MKD_NOHEADER or MKD_DLEXTRA or MKD_FENCEDCODE or MKD_GITHUBTAGS or MKD_URLENCODEDANCHOR
409 | if toc:
410 | flags = flags or MKD_TOC
411 | hs.document = hs.document.md(flags)
412 | return hs.document
413 |
414 | proc compileDocument*(hs: var HastyScribe, input, dir: string): string {.discardable.} =
415 | hs.document = input
416 | # Load style rules to be included on-demand
417 | hs.load_styles()
418 | # Parse transclusions, fields, snippets, and macros
419 | hs.document = hs.preprocess(hs.document, dir)
420 | # Process markdown
421 | var metadata: TMDMetaData
422 | hs.document = hs.document.md(0, metadata)
423 |
424 | # Document Variables
425 | const hastyscribe_img = """
426 |
427 | """ % encode_image(hastyscribe_logo, "svg")
428 | let
429 | (headings, toc) = if hs.options.toc and metadata.toc != "":
430 | (" class=\"headings\"", "" & metadata.toc & "
")
431 | else: ("", "")
432 | user_css_tag = if hs.options.css == "": "" else:
433 | hs.options.css.readFile.style_tag
434 | user_js_tag = if hs.options.js == "": "" else:
435 | ""
436 | watermark_css_tag = if hs.options.watermark == "": "" else:
437 | watermark_css(hs.options.watermark)
438 |
439 | # Manage metadata
440 | author_footer = if metadata.author == "": "" else:
441 | " " & metadata.author & " –"
442 | title_tag = if metadata.title == "": "" else:
443 | "" & metadata.title & ""
444 | header_tag = if metadata.title == "": "" else:
445 | ""
446 |
447 | (main_css_tag, optional_css_tag) = if hs.options.embed:
448 | (hs.hastyStylesheet.style_tag, hs.create_optional_css(hs.document))
449 | else:
450 | ("", "")
451 |
452 | # Date parsing and validation
453 | let date: string = block:
454 | const IsoDate = initTimeFormat("yyyy-MM-dd")
455 | const DefaultDate = initTimeFormat("MMMM d, yyyy")
456 | let timeinfo: DateTime = try:
457 | parse(metadata.date, IsoDate)
458 | except CatchableError:
459 | local(getTime())
460 | timeinfo.format(if hs.options.iso: IsoDate else: DefaultDate)
461 |
462 | hs.document = """
463 |
464 |
465 | $title_tag
466 |
467 |
468 |
469 |
470 | $main_css_tag
471 | $optional_css_tag
472 | $user_css_tag
473 | $internal_css_tag
474 | $watermark_css_tag
475 |
476 |
477 |
478 |
479 | $header_tag
480 | $toc
481 |
482 | $body
483 |
484 |
488 |
489 | $js
490 | """ % [
491 | "title_tag", title_tag,
492 | "header_tag", header_tag,
493 | "author", metadata.author,
494 | "author_footer", author_footer,
495 | "date", date,
496 | "toc", toc,
497 | "main_css_tag", main_css_tag,
498 | "hastyscribe_img", hastyscribe_img,
499 | "optional_css_tag", optional_css_tag,
500 | "user_css_tag", user_css_tag,
501 | "headings", headings,
502 | "body", hs.document,
503 | "internal_css_tag", metadata.css,
504 | "watermark_css_tag", watermark_css_tag,
505 | "js", user_js_tag]
506 | if hs.options.embed:
507 | hs.embed_images(dir)
508 | hs.document = add_jump_to_top_links(hs.document)
509 | # Use IDs instead of names for anchors
510 | hs.document = hs.document.replace(" ...
579 |
580 | Arguments:
581 | markdown_file_or_glob The markdown (or glob expression) file to compile into HTML.
582 | Options:
583 | --output-file= Write output to .
584 | (Use "--output-file=-" to output to stdout)
585 | --output-dir=, -d= Write output files to . Overrides "output-file".
586 | Input directory structure is not preserved.
587 | --field/= Define a new field called with value .
588 | --user-css= Insert contents of as a CSS stylesheet.
589 | --user-js= Insert contents of as a Javascript script.
590 | --watermark= Use the image in as a watermark.
591 | --notoc Do not generate a Table of Contents.
592 | --noembed If specified, styles and images will not be embedded.
593 | --fragment If specified, an HTML fragment will be generated, without
594 | embedding images or stylesheets.
595 | --iso Use ISO 8601 date format (e.g., 2000-12-31) in the footer.
596 | --minify-css, Minify the built-in stylesheet before embedding.
597 | --no-clobber, -n Do not overwrite existing files.
598 | --help, -h Display the usage information.
599 | --version, -v Print version and exit."""
600 |
601 | type ErrorKinds = enum errENOENT = 2, errEIO = 5
602 |
603 | var
604 | inputs: seq[string]
605 | options = default(HastyOptions)
606 | fields = initTable[string, string]()
607 |
608 | # Parse Parameters
609 | template noVal() =
610 | if val != "": fatal "Option '" & key & "' takes no value"; quit(1)
611 | for kind, key, val in getopt():
612 | case kind
613 | of cmdArgument:
614 | inputs.add(key)
615 | of cmdShortOption, cmdLongOption:
616 | case key
617 | of "notoc":
618 | noVal()
619 | options.toc = false
620 | of "noembed":
621 | noVal()
622 | options.embed = false
623 | of "user-css":
624 | options.css = val
625 | of "user-js":
626 | options.js = val
627 | of "watermark":
628 | options.watermark = val
629 | of "output-file":
630 | if not options.outputToDir:
631 | if val == "": fatal "Output file path can't be empty"; quit(1)
632 | options.output = val
633 | of "d", "output-dir":
634 | options.outputToDir = true
635 | if dirExists(val): options.output = val.normalizedPath()
636 | else:
637 | fatal "Directory '" & val & "' does not exist";
638 | quit(errENOENT.ord)
639 | of "fragment":
640 | noVal()
641 | options.fragment = true
642 | of "iso":
643 | noVal()
644 | options.iso = true
645 | of "minify-css":
646 | noVal()
647 | options.minifycss = true
648 | of "n", "no-clobber", "noclobber":
649 | noVal()
650 | options.noclobber = true
651 | of "v", "version":
652 | echo pkgVersion
653 | quit(0)
654 | of "h", "help":
655 | echo usage
656 | quit(0)
657 | else:
658 | if key.startsWith("field/"):
659 | let val = val
660 | fields[key.replace("field/", "")] = val
661 | else:
662 | warn """Unknown option "$#", ignoring""" % key
663 | of cmdEnd: assert(false)
664 | if inputs.len == 0:
665 | echo usage
666 | quit(0)
667 | else:
668 | var errorsOccurred: set[ErrorKinds] = {}
669 | var paths: CritBitTree[void] # Deduplicates different globs expanding to same files
670 | for glob in inputs:
671 | var globMatchCount = 0
672 | for file in walkFiles(glob):
673 | # TODO: files can still contain relative and absolute paths pointing to the same file
674 | let path = file.normalizedPath()
675 | if paths.containsOrIncl(path):
676 | notice "Input file \"$1\" provided multiple times" % path
677 | globMatchCount.inc()
678 | if globMatchCount == 0:
679 | errorsOccurred.incl errENOENT
680 | fatal "\"$1\" does not match any file" % glob
681 | if paths.len == 0:
682 | errorsOccurred.incl errENOENT
683 | else:
684 | var fileMappings: seq[tuple[path, name: string]]
685 | if paths.len > 1:
686 | options.processingMultiple = true
687 | if not options.outputToDir:
688 | case options.output:
689 | of "": discard
690 | of "-":
691 | notice "Multiple files will be printed to stdout using the\n" &
692 | " \"" & eof_Separator & "\" separator."
693 | else:
694 | warn "Option `output-file` is set but multiple input files given, ignoring"
695 | options.output = ""
696 | fileMappings = fileNameMappings(paths)
697 | else:
698 | for p in paths.keys:
699 | fileMappings.add (path: p, name: "")
700 |
701 | var hs = newHastyScribe(options, fields)
702 | for (path, outName) in fileMappings:
703 | try:
704 | hs.compile(path, outName)
705 | except IOError as e:
706 | errorsOccurred.incl errEIO
707 | fatal e.msg
708 | continue
709 | except ClobberError as e:
710 | warn "File '" & e.msg & "' exists, not overwriting"
711 | continue
712 | info "\"$1\" converted successfully" % path
713 | if errENOENT in errorsOccurred: quit(errENOENT.ord)
714 | elif errEIO in errorsOccurred: quit(errEIO.ord)
715 | else: discard # ok
716 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/data/hastystyles.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
2 | /* ==========================================================================
3 | HTML5 display definitions
4 | ========================================================================== */
5 | /**
6 | * Correct `block` display not defined in IE 8/9.
7 | */
8 | article,
9 | aside,
10 | figcaption,
11 | figure,
12 | footer,
13 | header,
14 | hgroup,
15 | main,
16 | nav,
17 | section,
18 | summary {
19 | display: block;
20 | }
21 | /**
22 | * Correct `inline-block` display not defined in IE 8/9.
23 | */
24 | audio,
25 | canvas,
26 | video {
27 | display: inline-block;
28 | }
29 | /**
30 | * Prevent modern browsers from displaying `audio` without controls.
31 | * Remove excess height in iOS 5 devices.
32 | */
33 | audio:not([controls]) {
34 | display: none;
35 | height: 0;
36 | }
37 | /**
38 | * Address `[hidden]` styling not present in IE 8/9.
39 | * Hide the `template` element in IE, Safari, and Firefox < 22.
40 | */
41 | [hidden],
42 | template {
43 | display: none;
44 | }
45 | /* ==========================================================================
46 | Base
47 | ========================================================================== */
48 | /**
49 | * 1. Set default font family to sans-serif.
50 | * 2. Prevent iOS text size adjust after orientation change, without disabling
51 | * user zoom.
52 | */
53 | html {
54 | font-family: sans-serif;
55 | /* 1 */
56 | text-size-adjust: 100%;
57 | /* 2 */
58 | }
59 | /**
60 | * Remove default margin.
61 | */
62 | body {
63 | margin: 0;
64 | }
65 | /* ==========================================================================
66 | Links
67 | ========================================================================== */
68 | /**
69 | * Remove the gray background color from active links in IE 10.
70 | */
71 | a {
72 | background: transparent;
73 | }
74 | /**
75 | * Address `outline` inconsistency between Chrome and other browsers.
76 | */
77 | a:focus {
78 | outline: thin dotted;
79 | }
80 | /**
81 | * Improve readability when focused and also mouse hovered in all browsers.
82 | */
83 | a:active,
84 | a:hover {
85 | outline: 0;
86 | }
87 | /* ==========================================================================
88 | Typography
89 | ========================================================================== */
90 | /**
91 | * Address variable `h1` font-size and margin within `section` and `article`
92 | * contexts in Firefox 4+, Safari 5, and Chrome.
93 | */
94 | h1 {
95 | font-size: 2em;
96 | margin: 0.67em 0;
97 | }
98 | /**
99 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
100 | */
101 | abbr[title] {
102 | border-bottom: 1px dotted;
103 | }
104 | /**
105 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
106 | */
107 | b,
108 | strong {
109 | font-weight: bold;
110 | }
111 | /**
112 | * Address styling not present in Safari 5 and Chrome.
113 | */
114 | dfn {
115 | font-style: italic;
116 | }
117 | /**
118 | * Address differences between Firefox and other browsers.
119 | */
120 | hr {
121 | -moz-box-sizing: content-box;
122 | box-sizing: content-box;
123 | height: 0;
124 | }
125 | /**
126 | * Address styling not present in IE 8/9.
127 | */
128 | mark {
129 | background: #ff0;
130 | color: #000;
131 | }
132 | /**
133 | * Correct font family set oddly in Safari 5 and Chrome.
134 | */
135 | code,
136 | kbd,
137 | pre,
138 | samp {
139 | font-family: monospace, serif;
140 | font-size: 1em;
141 | }
142 | /**
143 | * Improve readability of pre-formatted text in all browsers.
144 | */
145 | pre {
146 | white-space: pre-wrap;
147 | }
148 | /**
149 | * Set consistent quote types.
150 | */
151 | q {
152 | quotes: "\201C" "\201D" "\2018" "\2019";
153 | }
154 | /**
155 | * Address inconsistent and variable font size in all browsers.
156 | */
157 | small {
158 | font-size: 80%;
159 | }
160 | /**
161 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
162 | */
163 | sub,
164 | sup {
165 | font-size: 75%;
166 | line-height: 0;
167 | position: relative;
168 | vertical-align: baseline;
169 | }
170 | sup {
171 | top: -0.5em;
172 | }
173 | sub {
174 | bottom: -0.25em;
175 | }
176 | /* ==========================================================================
177 | Embedded content
178 | ========================================================================== */
179 | /**
180 | * Remove border when inside `a` element in IE 8/9.
181 | */
182 | img {
183 | border: 0;
184 | }
185 | /**
186 | * Correct overflow displayed oddly in IE 9.
187 | */
188 | svg:not(:root) {
189 | overflow: hidden;
190 | }
191 | /* ==========================================================================
192 | Figures
193 | ========================================================================== */
194 | /**
195 | * Address margin not present in IE 8/9 and Safari 5.
196 | */
197 | figure {
198 | margin: 0;
199 | }
200 | /* ==========================================================================
201 | Forms
202 | ========================================================================== */
203 | /**
204 | * Define consistent border, margin, and padding.
205 | */
206 | fieldset {
207 | border: 1px solid #c0c0c0;
208 | margin: 0 2px;
209 | padding: 0.35em 0.625em 0.75em;
210 | }
211 | /**
212 | * 1. Correct `color` not being inherited in IE 8/9.
213 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
214 | */
215 | legend {
216 | border: 0;
217 | /* 1 */
218 | padding: 0;
219 | /* 2 */
220 | }
221 | /**
222 | * 1. Correct font family not being inherited in all browsers.
223 | * 2. Correct font size not being inherited in all browsers.
224 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
225 | */
226 | button,
227 | input,
228 | select,
229 | textarea {
230 | font-family: inherit;
231 | /* 1 */
232 | font-size: 100%;
233 | /* 2 */
234 | margin: 0;
235 | /* 3 */
236 | }
237 | /**
238 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
239 | * the UA stylesheet.
240 | */
241 | button,
242 | input {
243 | line-height: normal;
244 | }
245 | /**
246 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
247 | * All other form control elements do not inherit `text-transform` values.
248 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
249 | * Correct `select` style inheritance in Firefox 4+ and Opera.
250 | */
251 | button,
252 | select {
253 | text-transform: none;
254 | }
255 | /**
256 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
257 | * and `video` controls.
258 | * 3. Improve usability and consistency of cursor style between image-type
259 | * `input` and others.
260 | */
261 | button,
262 | html input[type="button"],
263 | input[type="reset"],
264 | input[type="submit"] {
265 | cursor: pointer;
266 | /* 3 */
267 | }
268 | /**
269 | * Re-set default cursor for disabled elements.
270 | */
271 | button[disabled],
272 | html input[disabled] {
273 | cursor: default;
274 | }
275 | /**
276 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
277 | * 2. Remove excess padding in IE 8/9/10.
278 | */
279 | input[type="checkbox"],
280 | input[type="radio"] {
281 | box-sizing: border-box;
282 | /* 1 */
283 | padding: 0;
284 | /* 2 */
285 | }
286 | /**
287 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
288 | * (include `-moz` to future-proof).
289 | */
290 | input[type="search"] {
291 | box-sizing: content-box;
292 | }
293 | /**
294 | * Remove inner padding and border in Firefox 4+.
295 | */
296 | button::-moz-focus-inner,
297 | input::-moz-focus-inner {
298 | border: 0;
299 | padding: 0;
300 | }
301 | /**
302 | * 1. Remove default vertical scrollbar in IE 8/9.
303 | * 2. Improve readability and alignment in all browsers.
304 | */
305 | textarea {
306 | overflow: auto;
307 | /* 1 */
308 | vertical-align: top;
309 | /* 2 */
310 | }
311 | /* ==========================================================================
312 | Tables
313 | ========================================================================== */
314 | /**
315 | * Remove most spacing between table cells.
316 | */
317 | table {
318 | border-collapse: collapse;
319 | border-spacing: 0;
320 | }
321 | /* Fonts */
322 | /* Colors */
323 | .mix-monospace {
324 | font-family: monospace;
325 | font-weight: 400;
326 | }
327 | .mix-no-border-radius {
328 | border-radius: 0px;
329 | }
330 | @media screen and (max-width: 639px) {
331 | .mix-responsive {
332 | overflow-y: hidden;
333 | overflow-x: auto;
334 | }
335 | .mix-responsive td {
336 | white-space: nowrap;
337 | }
338 | }
339 | body {
340 | background-color: #fff;
341 | margin: 0 auto;
342 | height: 100%;
343 | zoom: 1;
344 | font-weight: 400;
345 | letter-spacing: normal;
346 | word-spacing: normal;
347 | color: #333;
348 | font: 15px sans-serif;
349 | line-height: 1.4;
350 | -webkit-font-smoothing: antialiased;
351 | width: 960px;
352 | hyphens: auto;
353 | text-align: justify;
354 | }
355 | @media screen and (max-width: 659px) {
356 | body {
357 | width: 100%;
358 | }
359 | }
360 | @media screen and (min-width: 660px) {
361 | body {
362 | width: 660px;
363 | }
364 | }
365 | @media screen and (min-width: 880px) {
366 | body {
367 | width: 880px;
368 | }
369 | }
370 | #main,
371 | #footer,
372 | #header {
373 | width: 94%;
374 | margin: auto;
375 | }
376 | #footer {
377 | border-top: 1px solid #ddd;
378 | text-align: center;
379 | font-size: 75%;
380 | }
381 | #footer p {
382 | line-height: 0.6em;
383 | }
384 | #toc {
385 | margin-top: -10px;
386 | }
387 | #toc ul {
388 | list-style-type: none;
389 | padding-left: 20px;
390 | }
391 | #toc > ul {
392 | padding-left: 0;
393 | }
394 | /* Lists */
395 | ul,
396 | ol {
397 | padding-left: 30px;
398 | }
399 | li p {
400 | margin: 0 auto;
401 | }
402 | .unstyled li {
403 | list-style-type: none;
404 | }
405 | dl dt {
406 | font-weight: 700;
407 | }
408 | dl dd {
409 | padding: 0 0 0.2em 0;
410 | }
411 | /* Headings */
412 | h1,
413 | h2,
414 | h3,
415 | h4,
416 | h5,
417 | h6 {
418 | font-weight: 700;
419 | color: #111;
420 | border-bottom: 1px solid #ddd;
421 | }
422 | h1 {
423 | text-align: center;
424 | }
425 | /* Blocks */
426 | blockquote {
427 | border-left: 3px solid #dedede;
428 | padding: 0px 10px;
429 | margin: 10px 0;
430 | font-style: italic;
431 | }
432 | pre {
433 | font-family: monospace;
434 | font-weight: 400;
435 | color: #222;
436 | white-space: pre-wrap;
437 | margin: auto;
438 | padding: 0;
439 | }
440 | @media screen and (max-width: 639px) {
441 | pre {
442 | overflow-y: hidden;
443 | overflow-x: auto;
444 | }
445 | pre td {
446 | white-space: nowrap;
447 | }
448 | }
449 | pre code {
450 | box-shadow: none;
451 | border: none;
452 | line-height: 1.2em;
453 | }
454 | .responsive {
455 | width: 100%;
456 | }
457 | @media screen and (max-width: 639px) {
458 | .responsive {
459 | overflow-y: hidden;
460 | overflow-x: auto;
461 | }
462 | .responsive td {
463 | white-space: nowrap;
464 | }
465 | }
466 | table {
467 | border-collapse: collapse;
468 | margin: 15px auto;
469 | border-spacing: 0;
470 | empty-cells: show;
471 | min-width: 100%;
472 | }
473 | table thead {
474 | background: #f8f8f8;
475 | color: #222;
476 | text-align: left;
477 | vertical-align: bottom;
478 | }
479 | table td,
480 | table th {
481 | background-color: transparent;
482 | border: 1px solid #999;
483 | font-size: inherit;
484 | margin: 0;
485 | overflow: visible;
486 | padding: 6px 12px;
487 | }
488 | address {
489 | font-style: italic;
490 | color: #999;
491 | }
492 | hr {
493 | border: 0;
494 | height: 1px;
495 | background: #333;
496 | background-image: linear-gradient(to right, #ccc, #333, #ccc);
497 | }
498 | .center {
499 | margin: auto;
500 | text-align: center;
501 | }
502 | /* Inline */
503 | [class^="fa-"]:before,
504 | [href^=http]:before,
505 | [href^=mailto]:before,
506 | [href^=git]:before,
507 | [href^=tel]:before,
508 | [href^=magnet]:before,
509 | [href='#document-top']:before,
510 | [class^="badge-"]:before {
511 | width: 15px;
512 | height: 15px;
513 | background-repeat: no-repeat;
514 | box-sizing: border-box;
515 | content: ' ';
516 | display: inline-block;
517 | vertical-align: text-top;
518 | }
519 | .tip > p:first-child:before,
520 | .warning > p:first-child:before,
521 | .sidebar > p:first-child:before,
522 | .note > p:first-child:before {
523 | width: 15px;
524 | height: 15px;
525 | background-repeat: no-repeat;
526 | box-sizing: border-box;
527 | content: ' ';
528 | display: inline-block;
529 | vertical-align: text-top;
530 | margin-right: 2px;
531 | }
532 | [href^=http]:before,
533 | [href^=mailto]:before,
534 | [href^=git]:before,
535 | [href^=tel]:before,
536 | [href^=magnet]:before {
537 | margin-right: 2px;
538 | }
539 | [href='#document-top'] {
540 | float: right;
541 | }
542 | [href='#document-top']:before {
543 | width: 12px;
544 | height: 12px;
545 | }
546 | #footer a:before {
547 | background: none;
548 | }
549 | #footer a:hover {
550 | text-decoration: none;
551 | }
552 | #footer a:after {
553 | content: none;
554 | }
555 | code,
556 | span.code {
557 | font-family: monospace;
558 | font-weight: 400;
559 | hyphens: none;
560 | color: #B2361E;
561 | font-size: 85%;
562 | }
563 | strong code,
564 | b code {
565 | font-weight: 700;
566 | }
567 | kbd,
568 | span.kbd {
569 | color: #222;
570 | background-color: #f8f8f8;
571 | padding: 0 3px 0;
572 | display: inline-block;
573 | width: auto;
574 | margin: 1px;
575 | border-radius: 2px;
576 | background-clip: padding-box;
577 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
578 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
579 | font-family: monospace;
580 | font-weight: 700;
581 | border-right: 1px solid #bbb;
582 | border-bottom: 1px solid #bbb;
583 | line-height: 1em;
584 | font-size: 80%;
585 | hyphens: none;
586 | }
587 | mark,
588 | span.mark {
589 | padding: 0 1px;
590 | color: #222;
591 | }
592 | abbr,
593 | span.abbr {
594 | font-variant: small-caps;
595 | font-weight: 700;
596 | hyphens: none;
597 | }
598 | abbr:hover,
599 | span.abbr:hover {
600 | cursor: help;
601 | }
602 | samp,
603 | span.samp {
604 | font-size: 90%;
605 | color: #222;
606 | background-color: transparent;
607 | padding: 0 3px 0;
608 | display: inline-block;
609 | width: auto;
610 | margin: 1px;
611 | border-radius: 2px;
612 | background-clip: padding-box;
613 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
614 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
615 | hyphens: none;
616 | }
617 | var,
618 | span.var {
619 | font-size: 90%;
620 | font-family: monospace;
621 | font-weight: 700;
622 | font-style: normal;
623 | hyphens: none;
624 | }
625 | cite,
626 | span.cite {
627 | font-weight: 700;
628 | font-style: italic;
629 | hyphens: none;
630 | }
631 | q,
632 | span.q {
633 | font-style: italic;
634 | color: #666;
635 | hyphens: none;
636 | }
637 | span.tt,
638 | span.cmd,
639 | span.opt,
640 | span.arg,
641 | span.kwd,
642 | span.ext,
643 | span.file,
644 | span.dir {
645 | font-size: 90%;
646 | font-family: monospace;
647 | font-weight: 400;
648 | hyphens: none;
649 | }
650 | span.ext,
651 | span.kwd {
652 | font-weight: 700;
653 | }
654 | img {
655 | max-width: 100%;
656 | }
657 | /* Links */
658 | a,
659 | a:visited {
660 | color: #4183c4;
661 | text-decoration: none;
662 | }
663 | a:hover,
664 | a:visited:hover {
665 | text-decoration: underline;
666 | }
667 | a.hastyscribe-logo:before {
668 | display: inline;
669 | }
670 | a.hastyscribe-logo img {
671 | vertical-align: middle;
672 | }
673 | /* Checkboxes */
674 | li.github_checkbox {
675 | list-style-type: none;
676 | }
677 | .note {
678 | background-clip: padding-box;
679 | border-radius: 3px;
680 | margin: 10px auto;
681 | padding: 2px 4px 0 4px;
682 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
683 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
684 | color: #264c72;
685 | background-color: #d8ebf8;
686 | border: 1px solid #A4D1EF;
687 | font-size: 95%;
688 | }
689 | .note code,
690 | .note samp,
691 | .note pre {
692 | color: #264c72;
693 | }
694 | .note a {
695 | color: #264c72;
696 | }
697 | .note p {
698 | margin: 0 auto;
699 | }
700 | @media screen and (min-width: 880px) {
701 | .note {
702 | margin-left: 80px;
703 | margin-right: 80px;
704 | }
705 | }
706 | .note > p {
707 | margin: 0 auto;
708 | }
709 | .note > p:first-child {
710 | font-weight: 700;
711 | color: #264c72;
712 | }
713 | .note pre,
714 | .note code {
715 | color: #19324c;
716 | }
717 | .note a {
718 | color: #132639;
719 | }
720 | .tip {
721 | background-clip: padding-box;
722 | border-radius: 3px;
723 | margin: 10px auto;
724 | padding: 2px 4px 0 4px;
725 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
726 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
727 | color: #009926;
728 | background-color: #d8f5cd;
729 | border: 1px solid #B0EB99;
730 | font-size: 95%;
731 | }
732 | .tip code,
733 | .tip samp,
734 | .tip pre {
735 | color: #009926;
736 | }
737 | .tip a {
738 | color: #264c72;
739 | }
740 | .tip p {
741 | margin: 0 auto;
742 | }
743 | @media screen and (min-width: 880px) {
744 | .tip {
745 | margin-left: 80px;
746 | margin-right: 80px;
747 | }
748 | }
749 | .tip > p {
750 | margin: 0 auto;
751 | }
752 | .tip > p:first-child {
753 | font-weight: 700;
754 | color: #009926;
755 | }
756 | .tip pre,
757 | .tip code {
758 | color: #006619;
759 | }
760 | .tip a {
761 | color: #004d13;
762 | }
763 | .warning {
764 | background-clip: padding-box;
765 | border-radius: 3px;
766 | margin: 10px auto;
767 | padding: 2px 4px 0 4px;
768 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
769 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
770 | color: #705400;
771 | background-color: #FFEBAD;
772 | border: 1px solid #FFDB70;
773 | font-size: 95%;
774 | }
775 | .warning code,
776 | .warning samp,
777 | .warning pre {
778 | color: #705400;
779 | }
780 | .warning a {
781 | color: #264c72;
782 | }
783 | .warning p {
784 | margin: 0 auto;
785 | }
786 | @media screen and (min-width: 880px) {
787 | .warning {
788 | margin-left: 80px;
789 | margin-right: 80px;
790 | }
791 | }
792 | .warning > p {
793 | margin: 0 auto;
794 | }
795 | .warning > p:first-child {
796 | font-weight: 700;
797 | color: #705400;
798 | }
799 | .warning pre,
800 | .warning code {
801 | color: #3d2e00;
802 | }
803 | .warning a {
804 | color: #241b00;
805 | }
806 | .blank-sidebar {
807 | background-clip: padding-box;
808 | border-radius: 3px;
809 | margin: 10px auto;
810 | padding: 2px 4px 0 4px;
811 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
812 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
813 | color: #555;
814 | background-color: #fefefe;
815 | border: 1px solid #fefefe;
816 | font-size: 95%;
817 | }
818 | .blank-sidebar code,
819 | .blank-sidebar samp,
820 | .blank-sidebar pre {
821 | color: #555;
822 | }
823 | .blank-sidebar a {
824 | color: #264c72;
825 | }
826 | .blank-sidebar p {
827 | margin: 0 auto;
828 | }
829 | @media screen and (min-width: 880px) {
830 | .blank-sidebar {
831 | margin-left: 80px;
832 | margin-right: 80px;
833 | }
834 | }
835 | .blank-sidebar > p {
836 | margin: 0 auto;
837 | }
838 | .blank-sidebar > p:first-child {
839 | font-weight: 700;
840 | color: #555;
841 | }
842 | .blank-sidebar pre,
843 | .blank-sidebar code {
844 | color: #3b3b3b;
845 | }
846 | .blank-sidebar a {
847 | color: #2f2f2f;
848 | }
849 | .sidebar {
850 | background-clip: padding-box;
851 | border-radius: 3px;
852 | margin: 10px auto;
853 | padding: 2px 4px 0 4px;
854 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
855 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
856 | color: #555;
857 | background-color: #f8f8f8;
858 | border: 1px solid #f8f8f8;
859 | font-size: 95%;
860 | }
861 | .sidebar code,
862 | .sidebar samp,
863 | .sidebar pre {
864 | color: #555;
865 | }
866 | .sidebar a {
867 | color: #264c72;
868 | }
869 | .sidebar p {
870 | margin: 0 auto;
871 | }
872 | @media screen and (min-width: 880px) {
873 | .sidebar {
874 | margin-left: 80px;
875 | margin-right: 80px;
876 | }
877 | }
878 | .sidebar > p {
879 | margin: 0 auto;
880 | }
881 | .sidebar > p:first-child {
882 | font-weight: 700;
883 | color: #555;
884 | }
885 | .sidebar pre,
886 | .sidebar code {
887 | color: #3b3b3b;
888 | }
889 | .sidebar a {
890 | color: #2f2f2f;
891 | }
892 | .output {
893 | background-clip: padding-box;
894 | border-radius: 3px;
895 | margin: 10px auto;
896 | padding: 2px 4px 0 4px;
897 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
898 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
899 | color: #555;
900 | background-color: #f8f8f8;
901 | border: 1px solid #f8f8f8;
902 | }
903 | .output code,
904 | .output samp,
905 | .output pre {
906 | color: #555;
907 | }
908 | .output a {
909 | color: #264c72;
910 | }
911 | .output p {
912 | margin: 0 auto;
913 | }
914 | @media screen and (min-width: 880px) {
915 | .output {
916 | margin-left: 80px;
917 | margin-right: 80px;
918 | }
919 | }
920 | .terminal {
921 | background-clip: padding-box;
922 | border-radius: 3px;
923 | margin: 10px auto;
924 | padding: 2px 4px 0 4px;
925 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
926 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
927 | color: #eee;
928 | background-color: #222;
929 | border: 1px solid #ccc;
930 | hyphens: none;
931 | padding: 0 3px;
932 | border: 2px solid #999;
933 | white-space: pre-wrap;
934 | border-top: 10px solid #999;
935 | }
936 | .terminal code,
937 | .terminal samp,
938 | .terminal pre {
939 | color: #eee;
940 | }
941 | .terminal a {
942 | color: #264c72;
943 | }
944 | .terminal p {
945 | margin: 0 auto;
946 | }
947 | @media screen and (min-width: 880px) {
948 | .terminal {
949 | margin-left: 80px;
950 | margin-right: 80px;
951 | }
952 | }
953 | @media screen and (max-width: 639px) {
954 | .terminal {
955 | overflow-y: hidden;
956 | overflow-x: auto;
957 | }
958 | .terminal td {
959 | white-space: nowrap;
960 | }
961 | }
962 | .terminal > p,
963 | .terminal > p:first-child {
964 | margin-top: -40px;
965 | margin-bottom: 0;
966 | text-shadow: none;
967 | font-weight: 400;
968 | font-family: monospace;
969 | font-size: 85%;
970 | color: #eee;
971 | }
972 | .terminal > p code,
973 | .terminal > p:first-child code,
974 | .terminal > p pre,
975 | .terminal > p:first-child pre,
976 | .terminal > p samp,
977 | .terminal > p:first-child samp {
978 | font-size: 100%;
979 | }
980 | .terminal > p:first-child,
981 | .terminal > p:first-child:first-child {
982 | margin-top: 0px;
983 | }
984 | .terminal > p:before,
985 | .terminal > p:first-child:before {
986 | font-family: monospace;
987 | font-style: normal;
988 | font-weight: 700;
989 | color: #009926;
990 | content: "$ ";
991 | }
992 | .terminal-su {
993 | background-clip: padding-box;
994 | border-radius: 3px;
995 | margin: 10px auto;
996 | padding: 2px 4px 0 4px;
997 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
998 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
999 | color: #eee;
1000 | background-color: #222;
1001 | border: 1px solid #ccc;
1002 | hyphens: none;
1003 | padding: 0 3px;
1004 | border: 2px solid #999;
1005 | white-space: pre-wrap;
1006 | border-top: 10px solid #999;
1007 | }
1008 | .terminal-su code,
1009 | .terminal-su samp,
1010 | .terminal-su pre {
1011 | color: #eee;
1012 | }
1013 | .terminal-su a {
1014 | color: #264c72;
1015 | }
1016 | .terminal-su p {
1017 | margin: 0 auto;
1018 | }
1019 | @media screen and (min-width: 880px) {
1020 | .terminal-su {
1021 | margin-left: 80px;
1022 | margin-right: 80px;
1023 | }
1024 | }
1025 | @media screen and (max-width: 639px) {
1026 | .terminal-su {
1027 | overflow-y: hidden;
1028 | overflow-x: auto;
1029 | }
1030 | .terminal-su td {
1031 | white-space: nowrap;
1032 | }
1033 | }
1034 | .terminal-su > p,
1035 | .terminal-su > p:first-child {
1036 | margin-top: -40px;
1037 | margin-bottom: 0;
1038 | text-shadow: none;
1039 | font-weight: 400;
1040 | font-family: monospace;
1041 | font-size: 85%;
1042 | color: #eee;
1043 | }
1044 | .terminal-su > p code,
1045 | .terminal-su > p:first-child code,
1046 | .terminal-su > p pre,
1047 | .terminal-su > p:first-child pre,
1048 | .terminal-su > p samp,
1049 | .terminal-su > p:first-child samp {
1050 | font-size: 100%;
1051 | }
1052 | .terminal-su > p:first-child,
1053 | .terminal-su > p:first-child:first-child {
1054 | margin-top: 0px;
1055 | }
1056 | .terminal-su > p:before,
1057 | .terminal-su > p:first-child:before {
1058 | font-family: monospace;
1059 | font-style: normal;
1060 | font-weight: 700;
1061 | color: #009926;
1062 | content: "$ ";
1063 | }
1064 | .terminal-su > p:before,
1065 | .terminal-su > p:first-child:before {
1066 | color: #CC3300;
1067 | content: "# ";
1068 | }
1069 | div .terminal {
1070 | margin: 2px auto;
1071 | }
1072 | @media screen and (min-width: 880px) {
1073 | div .terminal {
1074 | margin-left: 80px;
1075 | margin-right: 80px;
1076 | }
1077 | }
1078 | details {
1079 | font-size: 95%;
1080 | background-clip: padding-box;
1081 | border-radius: 3px;
1082 | margin: 10px auto;
1083 | padding: 2px 4px 0 4px;
1084 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
1085 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
1086 | color: #555;
1087 | background-color: #f8f8f8;
1088 | border: 1px solid #fefefe;
1089 | display: block;
1090 | }
1091 | details code,
1092 | details samp,
1093 | details pre {
1094 | color: #555;
1095 | }
1096 | details a {
1097 | color: #264c72;
1098 | }
1099 | details p {
1100 | margin: 0 auto;
1101 | }
1102 | @media screen and (min-width: 880px) {
1103 | details {
1104 | margin-left: 80px;
1105 | margin-right: 80px;
1106 | }
1107 | }
1108 | details > summary::before {
1109 | content: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23555555' d='M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z' /%3E %3C/svg%3E");
1110 | display: inline-block;
1111 | margin-right: 2px;
1112 | vertical-align: text-top;
1113 | height: 11px;
1114 | width: 11px;
1115 | }
1116 | details[open] > summary::before {
1117 | content: url("data:image/svg+xml,%3Csvg viewBox='0 0 384 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%23555555' d='M169.4 470.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 370.8 224 64c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 306.7L54.6 265.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z' /%3E %3C/svg%3E");
1118 | }
1119 | summary {
1120 | cursor: pointer;
1121 | font-weight: bold;
1122 | }
1123 | summary > * {
1124 | display: inline;
1125 | }
1126 | .headings h1 {
1127 | font-size: 2em;
1128 | }
1129 | .headings h2 {
1130 | font-size: 1.5em;
1131 | counter-reset: h3;
1132 | counter-increment: h2;
1133 | }
1134 | .headings h2::before {
1135 | content: counter(h2) "\00A0\00A0";
1136 | }
1137 | .headings h3 {
1138 | font-size: 1.2em;
1139 | counter-reset: h4;
1140 | counter-increment: h3;
1141 | }
1142 | .headings h3::before {
1143 | content: counter(h2) "." counter(h3) "\00A0\00A0";
1144 | }
1145 | .headings h4 {
1146 | font-size: 1.1em;
1147 | counter-reset: h5;
1148 | counter-increment: h4;
1149 | }
1150 | .headings h4::before {
1151 | content: counter(h2) "." counter(h3) "." counter(h4) "\00A0\00A0";
1152 | }
1153 | .headings h5 {
1154 | font-size: 1em;
1155 | counter-reset: h6;
1156 | counter-increment: h5;
1157 | }
1158 | .headings h5::before {
1159 | content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "\00A0\00A0";
1160 | }
1161 | .headings h6 {
1162 | font-size: 1em;
1163 | counter-increment: h6;
1164 | }
1165 | .headings h6::before {
1166 | content: counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) "\00A0\00A0";
1167 | }
1168 | /***************/
1169 | .headings #toc {
1170 | counter-reset: toc2;
1171 | }
1172 | #toc > ul > li > ul > li > a {
1173 | font-weight: 700;
1174 | }
1175 | .headings #toc li li a {
1176 | counter-reset: toc3;
1177 | counter-increment: toc2;
1178 | }
1179 | .headings #toc li li a::before {
1180 | content: counter(toc2) "\00A0\00A0";
1181 | }
1182 | .headings #toc li li li a {
1183 | counter-reset: toc4;
1184 | counter-increment: toc3;
1185 | }
1186 | .headings #toc li li li a::before {
1187 | content: counter(toc2) "." counter(toc3) "\00A0\00A0";
1188 | }
1189 | .headings #toc li li li li a {
1190 | counter-reset: toc5;
1191 | counter-increment: toc4;
1192 | }
1193 | .headings #toc li li li li a::before {
1194 | content: counter(toc2) "." counter(toc3) "." counter(toc4) "\00A0\00A0";
1195 | }
1196 | .headings #toc li li li li li a {
1197 | counter-reset: toc6;
1198 | counter-increment: toc5;
1199 | }
1200 | .headings #toc li li li li li a::before {
1201 | content: counter(toc2) "." counter(toc3) "." counter(toc4) "." counter(toc5) "\00A0\00A0";
1202 | }
1203 | .headings #toc li li li li li li a {
1204 | counter-increment: toc6;
1205 | }
1206 | .headings #toc li li li li li li a::before {
1207 | content: counter(toc2) "." counter(toc3) "." counter(toc4) "." counter(toc5) "." counter(toc6) "\00A0\00A0";
1208 | }
1209 | @media print {
1210 | body {
1211 | width: 700px;
1212 | }
1213 | /* Table of Contents */
1214 | #toc {
1215 | width: 650px;
1216 | list-style-type: none;
1217 | }
1218 | #toc ul {
1219 | list-style-type: none;
1220 | padding-left: 20px;
1221 | margin-left: 0;
1222 | }
1223 | #toc li {
1224 | padding-left: 0;
1225 | margin-left: 0;
1226 | padding-bottom: 0.2em;
1227 | }
1228 | #toc a {
1229 | text-decoration: none;
1230 | }
1231 | #toc a::after {
1232 | content: leader(".") target-counter(attr(href), page);
1233 | }
1234 | #toc a[href="#preface"]::after {
1235 | content: leader(".") target-counter(attr(href), page, lower-roman);
1236 | }
1237 | #toc a {
1238 | color: #000;
1239 | }
1240 | #toc > li {
1241 | margin: 0.2em 0;
1242 | line-height: 1.2em;
1243 | font-weight: bold;
1244 | padding-bottom: 0.4em;
1245 | font-size: 110%;
1246 | }
1247 | #toc > li > ul > li {
1248 | font-weight: normal;
1249 | font-size: 100%;
1250 | }
1251 | #toc li li li li li li {
1252 | display: none;
1253 | /* None sane of mind would EVER want more than five TOC levels. */
1254 | }
1255 | #toc > li {
1256 | counter-increment: toc1;
1257 | counter-reset: toc2;
1258 | }
1259 | #toc > li > a::before {
1260 | content: counter(toc1) " ";
1261 | }
1262 | #toc > li > a[href="#preface"]::before {
1263 | counter-reset: toc1;
1264 | content: "";
1265 | }
1266 | #toc > li > ul > li {
1267 | counter-increment: toc2;
1268 | counter-reset: toc3;
1269 | }
1270 | #toc > li > ul > li > a::before {
1271 | content: counter(toc1) "." counter(toc2) " ";
1272 | }
1273 | #toc > li > ul > li > ul > li {
1274 | counter-increment: toc3;
1275 | counter-reset: toc4;
1276 | }
1277 | #toc > li > ul > li > ul > li > a::before {
1278 | content: counter(toc1) "." counter(toc2) "." counter(toc3) " ";
1279 | }
1280 | #toc > li > ul > li > ul > li > ul > li {
1281 | counter-increment: toc4;
1282 | counter-reset: toc5;
1283 | }
1284 | #toc > li > ul > li > ul > li > ul > li > a::before {
1285 | content: counter(toc1) "." counter(toc2) "." counter(toc3) "." counter(toc4) " ";
1286 | }
1287 | #toc > li > ul > li > ul > li > ul > li > ul > li {
1288 | counter-increment: toc5;
1289 | counter-reset: toc6;
1290 | }
1291 | #toc > li > ul > li > ul > li > ul > li > ul > li > a::before {
1292 | content: counter(toc1) "." counter(toc2) "." counter(toc3) "." counter(toc4) "." counter(toc5) " ";
1293 | }
1294 | a[href="#document-top"] {
1295 | display: none;
1296 | }
1297 | h1 {
1298 | margin-bottom: 50px;
1299 | border-bottom: 0;
1300 | }
1301 | h2 {
1302 | margin-bottom: 30px;
1303 | page-break-before: always;
1304 | border-bottom: 0;
1305 | }
1306 | h2,
1307 | h3,
1308 | h4,
1309 | h5,
1310 | h6 {
1311 | page-break-after: avoid;
1312 | }
1313 | @page {
1314 | size: A4;
1315 | margin: 50pt 30pt 50pt 30pt;
1316 | padding-top: 20pt;
1317 | @bottom-right {
1318 | content: counter(page);
1319 | font-family: 'sans-serif';
1320 | font-size: 0.8em;
1321 | }
1322 | }
1323 | }
1324 |
--------------------------------------------------------------------------------
/src/hastyscribepkg/data/hastystyles.links.css:
--------------------------------------------------------------------------------
1 | a[href^='#document-top']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M246.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L224 109.3 361.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160zm160 352l-160-160c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L224 301.3 361.4 438.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3z' /%3E %3C/svg%3E") }
2 | a[href^='mailto']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z' /%3E %3C/svg%3E") }
3 | a[href^='git']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M216.29 158.39H137C97 147.9 6.51 150.63 6.51 233.18c0 30.09 15 51.23 35 61-25.1 23-37 33.85-37 49.21 0 11 4.47 21.14 17.89 26.81C8.13 383.61 0 393.35 0 411.65c0 32.11 28.05 50.82 101.63 50.82 70.75 0 111.79-26.42 111.79-73.18 0-58.66-45.16-56.5-151.63-63l13.43-21.55c27.27 7.58 118.7 10 118.7-67.89 0-18.7-7.73-31.71-15-41.07l37.41-2.84zm-63.42 241.9c0 32.06-104.89 32.1-104.89 2.43 0-8.14 5.27-15 10.57-21.54 77.71 5.3 94.32 3.37 94.32 19.11zm-50.81-134.58c-52.8 0-50.46-71.16 1.2-71.16 49.54 0 50.82 71.16-1.2 71.16zm133.3 100.51v-32.1c26.75-3.66 27.24-2 27.24-11V203.61c0-8.5-2.05-7.38-27.24-16.26l4.47-32.92H324v168.71c0 6.51.4 7.32 6.51 8.14l20.73 2.84v32.1zm52.45-244.31c-23.17 0-36.59-13.43-36.59-36.61s13.42-35.77 36.59-35.77c23.58 0 37 12.62 37 35.77s-13.42 36.61-37 36.61zM512 350.46c-17.49 8.53-43.1 16.26-66.28 16.26-48.38 0-66.67-19.5-66.67-65.46V194.75c0-5.42 1.05-4.06-31.71-4.06V154.5c35.78-4.07 50-22 54.47-66.27h38.63c0 65.83-1.34 61.81 3.26 61.81H501v40.65h-60.56v97.15c0 6.92-4.92 51.41 60.57 26.84z' /%3E %3C/svg%3E") }
4 | a[href^='http']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z' /%3E %3C/svg%3E") }
5 | a[href^='magnet']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M0 160v96C0 379.7 100.3 480 224 480s224-100.3 224-224V160H320v96c0 53-43 96-96 96s-96-43-96-96V160H0zm0-32H128V64c0-17.7-14.3-32-32-32H32C14.3 32 0 46.3 0 64v64zm320 0H448V64c0-17.7-14.3-32-32-32H352c-17.7 0-32 14.3-32 32v64z' /%3E %3C/svg%3E") }
6 | a[href^='tel']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C12.1 30.2 0 46 0 64C0 311.4 200.6 512 448 512c18 0 33.8-12.1 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z' /%3E %3C/svg%3E") }
7 | a[href$='.pdf']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M0 64C0 28.7 28.7 0 64 0H224V128c0 17.7 14.3 32 32 32H384V304H176c-35.3 0-64 28.7-64 64V512H64c-35.3 0-64-28.7-64-64V64zm384 64H256V0L384 128zM176 352h32c30.9 0 56 25.1 56 56s-25.1 56-56 56H192v32c0 8.8-7.2 16-16 16s-16-7.2-16-16V448 368c0-8.8 7.2-16 16-16zm32 80c13.3 0 24-10.7 24-24s-10.7-24-24-24H192v48h16zm96-80h32c26.5 0 48 21.5 48 48v64c0 26.5-21.5 48-48 48H304c-8.8 0-16-7.2-16-16V368c0-8.8 7.2-16 16-16zm32 128c8.8 0 16-7.2 16-16V400c0-8.8-7.2-16-16-16H320v96h16zm80-112c0-8.8 7.2-16 16-16h48c8.8 0 16 7.2 16 16s-7.2 16-16 16H448v32h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H448v48c0 8.8-7.2 16-16 16s-16-7.2-16-16V432 368z' /%3E %3C/svg%3E") }
8 | a[href$='.zip']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 384 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM96 48c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm0 64c0-8.8 7.2-16 16-16h32c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16zm-6.3 71.8c3.7-14 16.4-23.8 30.9-23.8h14.8c14.5 0 27.2 9.7 30.9 23.8l23.5 88.2c1.4 5.4 2.1 10.9 2.1 16.4c0 35.2-28.8 63.7-64 63.7s-64-28.5-64-63.7c0-5.5 .7-11.1 2.1-16.4l23.5-88.2zM112 336c-8.8 0-16 7.2-16 16s7.2 16 16 16h32c8.8 0 16-7.2 16-16s-7.2-16-16-16H112z' /%3E %3C/svg%3E") }
9 | a[href*='amazon.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M257.2 162.7c-48.7 1.8-169.5 15.5-169.5 117.5 0 109.5 138.3 114 183.5 43.2 6.5 10.2 35.4 37.5 45.3 46.8l56.8-56S341 288.9 341 261.4V114.3C341 89 316.5 32 228.7 32 140.7 32 94 87 94 136.3l73.5 6.8c16.3-49.5 54.2-49.5 54.2-49.5 40.7-.1 35.5 29.8 35.5 69.1zm0 86.8c0 80-84.2 68-84.2 17.2 0-47.2 50.5-56.7 84.2-57.8v40.6zm136 163.5c-7.7 10-70 67-174.5 67S34.2 408.5 9.7 379c-6.8-7.7 1-11.3 5.5-8.3C88.5 415.2 203 488.5 387.7 401c7.5-3.7 13.3 2 5.5 12zm39.8 2.2c-6.5 15.8-16 26.8-21.2 31-5.5 4.5-9.5 2.7-6.5-3.8s19.3-46.5 12.7-55c-6.5-8.3-37-4.3-48-3.2-10.8 1-13 2-14-.3-2.3-5.7 21.7-15.5 37.5-17.5 15.7-1.8 41-.8 46 5.7 3.7 5.1 0 27.1-6.5 43.1z' /%3E %3C/svg%3E") }
10 | a[href*='bitbucket.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M22.2 32A16 16 0 0 0 6 47.8a26.35 26.35 0 0 0 .2 2.8l67.9 412.1a21.77 21.77 0 0 0 21.3 18.2h325.7a16 16 0 0 0 16-13.4L505 50.7a16 16 0 0 0-13.2-18.3 24.58 24.58 0 0 0-2.8-.2L22.2 32zm285.9 297.8h-104l-28.1-147h157.3l-25.2 147z' /%3E %3C/svg%3E") }
11 | a[href*='blogger.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M162.4 196c4.8-4.9 6.2-5.1 36.4-5.1 27.2 0 28.1.1 32.1 2.1 5.8 2.9 8.3 7 8.3 13.6 0 5.9-2.4 10-7.6 13.4-2.8 1.8-4.5 1.9-31.1 2.1-16.4.1-29.5-.2-31.5-.8-10.3-2.9-14.1-17.7-6.6-25.3zm61.4 94.5c-53.9 0-55.8.2-60.2 4.1-3.5 3.1-5.7 9.4-5.1 13.9.7 4.7 4.8 10.1 9.2 12 2.2 1 14.1 1.7 56.3 1.2l47.9-.6 9.2-1.5c9-5.1 10.5-17.4 3.1-24.4-5.3-4.7-5-4.7-60.4-4.7zm223.4 130.1c-3.5 28.4-23 50.4-51.1 57.5-7.2 1.8-9.7 1.9-172.9 1.8-157.8 0-165.9-.1-172-1.8-8.4-2.2-15.6-5.5-22.3-10-5.6-3.8-13.9-11.8-17-16.4-3.8-5.6-8.2-15.3-10-22C.1 423 0 420.3 0 256.3 0 93.2 0 89.7 1.8 82.6 8.1 57.9 27.7 39 53 33.4c7.3-1.6 332.1-1.9 340-.3 21.2 4.3 37.9 17.1 47.6 36.4 7.7 15.3 7-1.5 7.3 180.6.2 115.8 0 164.5-.7 170.5zm-85.4-185.2c-1.1-5-4.2-9.6-7.7-11.5-1.1-.6-8-1.3-15.5-1.7-12.4-.6-13.8-.8-17.8-3.1-6.2-3.6-7.9-7.6-8-18.3 0-20.4-8.5-39.4-25.3-56.5-12-12.2-25.3-20.5-40.6-25.1-3.6-1.1-11.8-1.5-39.2-1.8-42.9-.5-52.5.4-67.1 6.2-27 10.7-46.3 33.4-53.4 62.4-1.3 5.4-1.6 14.2-1.9 64.3-.4 62.8 0 72.1 4 84.5 9.7 30.7 37.1 53.4 64.6 58.4 9.2 1.7 122.2 2.1 133.7.5 20.1-2.7 35.9-10.8 50.7-25.9 10.7-10.9 17.4-22.8 21.8-38.5 3.2-10.9 2.9-88.4 1.7-93.9z' /%3E %3C/svg%3E") }
12 | a[href*='codepen.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M502.285 159.704l-234-156c-7.987-4.915-16.511-4.96-24.571 0l-234 156C3.714 163.703 0 170.847 0 177.989v155.999c0 7.143 3.714 14.286 9.715 18.286l234 156.022c7.987 4.915 16.511 4.96 24.571 0l234-156.022c6-3.999 9.715-11.143 9.715-18.286V177.989c-.001-7.142-3.715-14.286-9.716-18.285zM278 63.131l172.286 114.858-76.857 51.429L278 165.703V63.131zm-44 0v102.572l-95.429 63.715-76.857-51.429L234 63.131zM44 219.132l55.143 36.857L44 292.846v-73.714zm190 229.715L61.714 333.989l76.857-51.429L234 346.275v102.572zm22-140.858l-77.715-52 77.715-52 77.715 52-77.715 52zm22 140.858V346.275l95.429-63.715 76.857 51.429L278 448.847zm190-156.001l-55.143-36.857L468 219.132v73.714z' /%3E %3C/svg%3E") }
13 | a[href*='deviantart.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M320 93.2l-98.2 179.1 7.4 9.5H320v127.7H159.1l-13.5 9.2-43.7 84c-.3 0-8.6 8.6-9.2 9.2H0v-93.2l93.2-179.4-7.4-9.2H0V102.5h156l13.5-9.2 43.7-84c.3 0 8.6-8.6 9.2-9.2H320v93.1z' /%3E %3C/svg%3E") }
14 | a[href*='digg.chrome']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M81.7 172.3H0v174.4h132.7V96h-51v76.3zm0 133.4H50.9v-92.3h30.8v92.3zm297.2-133.4v174.4h81.8v28.5h-81.8V416H512V172.3H378.9zm81.8 133.4h-30.8v-92.3h30.8v92.3zm-235.6 41h82.1v28.5h-82.1V416h133.3V172.3H225.1v174.4zm51.2-133.3h30.8v92.3h-30.8v-92.3zM153.3 96h51.3v51h-51.3V96zm0 76.3h51.3v174.4h-51.3V172.3z' /%3E %3C/svg%3E") }
15 | a[href*='discordapp.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 640 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z' /%3E %3C/svg%3E") }
16 | a[href*='dropbox.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 528 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M264.4 116.3l-132 84.3 132 84.3-132 84.3L0 284.1l132.3-84.3L0 116.3 132.3 32l132.1 84.3zM131.6 395.7l132-84.3 132 84.3-132 84.3-132-84.3zm132.8-111.6l132-84.3-132-83.6L395.7 32 528 116.3l-132.3 84.3L528 284.8l-132.3 84.3-131.3-85z' /%3E %3C/svg%3E") }
17 | a[href*='etsy.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 384 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M384 348c-1.75 10.75-13.75 110-15.5 132-117.879-4.299-219.895-4.743-368.5 0v-25.5c45.457-8.948 60.627-8.019 61-35.25 1.793-72.322 3.524-244.143 0-322-1.029-28.46-12.13-26.765-61-36v-25.5c73.886 2.358 255.933 8.551 362.999-3.75-3.5 38.25-7.75 126.5-7.75 126.5H332C320.947 115.665 313.241 68 277.25 68h-137c-10.25 0-10.75 3.5-10.75 9.75V241.5c58 .5 88.5-2.5 88.5-2.5 29.77-.951 27.56-8.502 40.75-65.251h25.75c-4.407 101.351-3.91 61.829-1.75 160.25H257c-9.155-40.086-9.065-61.045-39.501-61.5 0 0-21.5-2-88-2v139c0 26 14.25 38.25 44.25 38.25H263c63.636 0 66.564-24.996 98.751-99.75H384z' /%3E %3C/svg%3E") }
18 | a[href*='facebook.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M504 256C504 119 393 8 256 8S8 119 8 256c0 123.78 90.69 226.38 209.25 245V327.69h-63V256h63v-54.64c0-62.15 37-96.48 93.67-96.48 27.14 0 55.52 4.84 55.52 4.84v61h-31.28c-30.8 0-40.41 19.12-40.41 38.73V256h68.78l-11 71.69h-57.78V501C413.31 482.38 504 379.78 504 256z' /%3E %3C/svg%3E") }
19 | a[href*='flickr.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM144.5 319c-35.1 0-63.5-28.4-63.5-63.5s28.4-63.5 63.5-63.5 63.5 28.4 63.5 63.5-28.4 63.5-63.5 63.5zm159 0c-35.1 0-63.5-28.4-63.5-63.5s28.4-63.5 63.5-63.5 63.5 28.4 63.5 63.5-28.4 63.5-63.5 63.5z' /%3E %3C/svg%3E") }
20 | a[href*='foursquare.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 368 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M323.1 3H49.9C12.4 3 0 31.3 0 49.1v433.8c0 20.3 12.1 27.7 18.2 30.1 6.2 2.5 22.8 4.6 32.9-7.1C180 356.5 182.2 354 182.2 354c3.1-3.4 3.4-3.1 6.8-3.1h83.4c35.1 0 40.6-25.2 44.3-39.7l48.6-243C373.8 25.8 363.1 3 323.1 3zm-16.3 73.8l-11.4 59.7c-1.2 6.5-9.5 13.2-16.9 13.2H172.1c-12 0-20.6 8.3-20.6 20.3v13c0 12 8.6 20.6 20.6 20.6h90.4c8.3 0 16.6 9.2 14.8 18.2-1.8 8.9-10.5 53.8-11.4 58.8-.9 4.9-6.8 13.5-16.9 13.5h-73.5c-13.5 0-17.2 1.8-26.5 12.6 0 0-8.9 11.4-89.5 108.3-.9.9-1.8.6-1.8-.3V75.9c0-7.7 6.8-16.6 16.6-16.6h219c8.2 0 15.6 7.7 13.5 17.5z' /%3E %3C/svg%3E") }
21 | a[href*='github.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 496 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z' /%3E %3C/svg%3E") }
22 | a[href*='gitlab.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M503.5 204.6L502.8 202.8L433.1 21.02C431.7 17.45 429.2 14.43 425.9 12.38C423.5 10.83 420.8 9.865 417.9 9.57C415 9.275 412.2 9.653 409.5 10.68C406.8 11.7 404.4 13.34 402.4 15.46C400.5 17.58 399.1 20.13 398.3 22.9L351.3 166.9H160.8L113.7 22.9C112.9 20.13 111.5 17.59 109.6 15.47C107.6 13.35 105.2 11.72 102.5 10.7C99.86 9.675 96.98 9.295 94.12 9.587C91.26 9.878 88.51 10.83 86.08 12.38C82.84 14.43 80.33 17.45 78.92 21.02L9.267 202.8L8.543 204.6C-1.484 230.8-2.72 259.6 5.023 286.6C12.77 313.5 29.07 337.3 51.47 354.2L51.74 354.4L52.33 354.8L158.3 434.3L210.9 474L242.9 498.2C246.6 500.1 251.2 502.5 255.9 502.5C260.6 502.5 265.2 500.1 268.9 498.2L300.9 474L353.5 434.3L460.2 354.4L460.5 354.1C482.9 337.2 499.2 313.5 506.1 286.6C514.7 259.6 513.5 230.8 503.5 204.6z' /%3E %3C/svg%3E") }
23 | a[href*='google.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 488 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z' /%3E %3C/svg%3E") }
24 | a[href*='plus.google.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M256,8C119.1,8,8,119.1,8,256S119.1,504,256,504,504,392.9,504,256,392.9,8,256,8ZM185.3,380a124,124,0,0,1,0-248c31.3,0,60.1,11,83,32.3l-33.6,32.6c-13.2-12.9-31.3-19.1-49.4-19.1-42.9,0-77.2,35.5-77.2,78.1S142.3,334,185.3,334c32.6,0,64.9-19.1,70.1-53.3H185.3V238.1H302.2a109.2,109.2,0,0,1,1.9,20.7c0,70.8-47.5,121.2-118.8,121.2ZM415.5,273.8v35.5H380V273.8H344.5V238.3H380V202.8h35.5v35.5h35.2v35.5Z' /%3E %3C/svg%3E") }
25 | a[href*='news.ycombinator.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M0 32v448h448V32H0zm21.2 197.2H21c.1-.1.2-.3.3-.4 0 .1 0 .3-.1.4zm218 53.9V384h-31.4V281.3L128 128h37.3c52.5 98.3 49.2 101.2 59.3 125.6 12.3-27 5.8-24.4 60.6-125.6H320l-80.8 155.1z' /%3E %3C/svg%3E") }
26 | a[href*='imdb.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M89.5 323.6H53.93V186.2H89.5V323.6zM156.1 250.5L165.2 186.2H211.5V323.6H180.5V230.9L167.1 323.6H145.8L132.8 232.9L132.7 323.6H101.5V186.2H147.6C148.1 194.5 150.4 204.3 151.9 215.6L156.1 250.5zM223.7 323.6V186.2H250.3C267.3 186.2 277.3 187.1 283.3 188.6C289.4 190.3 294 192.8 297.2 196.5C300.3 199.8 302.3 203.1 303 208.5C303.9 212.9 304.4 221.6 304.4 234.7V282.9C304.4 295.2 303.7 303.4 302.5 307.6C301.4 311.7 299.4 315 296.5 317.3C293.7 319.7 290.1 321.4 285.8 322.3C281.6 323.1 275.2 323.6 266.7 323.6H223.7zM259.2 209.7V299.1C264.3 299.1 267.5 298.1 268.6 296.8C269.7 294.8 270.4 289.2 270.4 280.1V226.8C270.4 220.6 270.3 216.6 269.7 214.8C269.4 213 268.5 211.8 267.1 210.1C265.7 210.1 263 209.7 259.2 209.7V209.7zM316.5 323.6V186.2H350.6V230.1C353.5 227.7 356.7 225.2 360.1 223.5C363.7 222 368.9 221.1 372.9 221.1C377.7 221.1 381.8 221.9 385.2 223.3C388.6 224.8 391.2 226.8 393.2 229.5C394.9 232.1 395.9 234.8 396.3 237.3C396.7 239.9 396.1 245.3 396.1 253.5V292.1C396.1 300.3 396.3 306.4 395.3 310.5C394.2 314.5 391.5 318.1 387.5 320.1C383.4 324 378.6 325.4 372.9 325.4C368.9 325.4 363.7 324.5 360.2 322.9C356.7 321.1 353.5 318.4 350.6 314.9L348.5 323.6L316.5 323.6zM361.6 302.9C362.3 301.1 362.6 296.9 362.6 290.4V255C362.6 249.4 362.3 245.5 361.5 243.8C360.8 241.9 357.8 241.1 355.7 241.1C353.7 241.1 352.3 241.9 351.6 243.4C351 244.9 350.6 248.8 350.6 255V291.4C350.6 297.5 351 301.4 351.8 303C352.4 304.7 353.9 305.5 355.9 305.5C358.1 305.5 360.1 304.7 361.6 302.9L361.6 302.9zM418.4 32.04C434.1 33.27 447.1 47.28 447.1 63.92V448.1C447.1 464.5 435.2 478.5 418.9 479.1C418.6 479.1 418.4 480 418.1 480H29.88C29.6 480 29.32 479.1 29.04 479.9C13.31 478.5 1.093 466.1 0 449.7L.0186 61.78C1.081 45.88 13.82 33.09 30.26 31.1H417.7C417.9 31.1 418.2 32.01 418.4 32.04L418.4 32.04zM30.27 41.26C19 42.01 10.02 51.01 9.257 62.4V449.7C9.63 455.1 11.91 460.2 15.7 464C19.48 467.9 24.51 470.3 29.89 470.7H418.1C429.6 469.7 438.7 459.1 438.7 448.1V63.91C438.7 58.17 436.6 52.65 432.7 48.45C428.8 44.24 423.4 41.67 417.7 41.26L30.27 41.26z' /%3E %3C/svg%3E") }
27 | a[href*='instagram.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z' /%3E %3C/svg%3E") }
28 | a[href*='jsfiddle.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 576 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M510.634 237.462c-4.727-2.621-5.664-5.748-6.381-10.776-2.352-16.488-3.539-33.619-9.097-49.095-35.895-99.957-153.99-143.386-246.849-91.646-27.37 15.25-48.971 36.369-65.493 63.903-3.184-1.508-5.458-2.71-7.824-3.686-30.102-12.421-59.049-10.121-85.331 9.167-25.531 18.737-36.422 44.548-32.676 76.408.355 3.025-1.967 7.621-4.514 9.545-39.712 29.992-56.031 78.065-41.902 124.615 13.831 45.569 57.514 79.796 105.608 81.433 30.291 1.031 60.637.546 90.959.539 84.041-.021 168.09.531 252.12-.48 52.664-.634 96.108-36.873 108.212-87.293 11.54-48.074-11.144-97.3-56.832-122.634zm21.107 156.88c-18.23 22.432-42.343 35.253-71.28 35.65-56.874.781-113.767.23-170.652.23 0 .7-163.028.159-163.728.154-43.861-.332-76.739-19.766-95.175-59.995-18.902-41.245-4.004-90.848 34.186-116.106 9.182-6.073 12.505-11.566 10.096-23.136-5.49-26.361 4.453-47.956 26.42-62.981 22.987-15.723 47.422-16.146 72.034-3.083 10.269 5.45 14.607 11.564 22.198-2.527 14.222-26.399 34.557-46.727 60.671-61.294 97.46-54.366 228.37 7.568 230.24 132.697.122 8.15 2.412 12.428 9.848 15.894 57.56 26.829 74.456 96.122 35.142 144.497zm-87.789-80.499c-5.848 31.157-34.622 55.096-66.666 55.095-16.953-.001-32.058-6.545-44.079-17.705-27.697-25.713-71.141-74.98-95.937-93.387-20.056-14.888-41.99-12.333-60.272 3.782-49.996 44.071 15.859 121.775 67.063 77.188 4.548-3.96 7.84-9.543 12.744-12.844 8.184-5.509 20.766-.884 13.168 10.622-17.358 26.284-49.33 38.197-78.863 29.301-28.897-8.704-48.84-35.968-48.626-70.179 1.225-22.485 12.364-43.06 35.414-55.965 22.575-12.638 46.369-13.146 66.991 2.474C295.68 280.7 320.467 323.97 352.185 343.47c24.558 15.099 54.254 7.363 68.823-17.506 28.83-49.209-34.592-105.016-78.868-63.46-3.989 3.744-6.917 8.932-11.41 11.72-10.975 6.811-17.333-4.113-12.809-10.353 20.703-28.554 50.464-40.44 83.271-28.214 31.429 11.714 49.108 44.366 42.76 78.186z' /%3E %3C/svg%3E") }
29 | a[href*='kickstarter.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M400 480H48c-26.4 0-48-21.6-48-48V80c0-26.4 21.6-48 48-48h352c26.4 0 48 21.6 48 48v352c0 26.4-21.6 48-48 48zM199.6 178.5c0-30.7-17.6-45.1-39.7-45.1-25.8 0-40 19.8-40 44.5v154.8c0 25.8 13.7 45.6 40.5 45.6 21.5 0 39.2-14 39.2-45.6v-41.8l60.6 75.7c12.3 14.9 39 16.8 55.8 0 14.6-15.1 14.8-36.8 4-50.4l-49.1-62.8 40.5-58.7c9.4-13.5 9.5-34.5-5.6-49.1-16.4-15.9-44.6-17.3-61.4 7l-44.8 64.7v-38.8z' /%3E %3C/svg%3E") }
30 | a[href*='linkedin.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z' /%3E %3C/svg%3E") }
31 | a[href*='mastodon.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z' /%3E %3C/svg%3E") }
32 | a[href*='medium.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 640 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M180.5,74.262C80.813,74.262,0,155.633,0,256S80.819,437.738,180.5,437.738,361,356.373,361,256,280.191,74.262,180.5,74.262Zm288.25,10.646c-49.845,0-90.245,76.619-90.245,171.095s40.406,171.1,90.251,171.1,90.251-76.619,90.251-171.1H559C559,161.5,518.6,84.908,468.752,84.908Zm139.506,17.821c-17.526,0-31.735,68.628-31.735,153.274s14.2,153.274,31.735,153.274S640,340.631,640,256C640,171.351,625.785,102.729,608.258,102.729Z' /%3E %3C/svg%3E") }
33 | a[href*='npmjs.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 576 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M288 288h-32v-64h32v64zm288-128v192H288v32H160v-32H0V160h576zm-416 32H32v128h64v-96h32v96h32V192zm160 0H192v160h64v-32h64V192zm224 0H352v128h64v-96h32v96h32v-96h32v96h32V192z' /%3E %3C/svg%3E") }
34 | a[href*='pinterest.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 496 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M496 256c0 137-111 248-248 248-25.6 0-50.2-3.9-73.4-11.1 10.1-16.5 25.2-43.5 30.8-65 3-11.6 15.4-59 15.4-59 8.1 15.4 31.7 28.5 56.8 28.5 74.8 0 128.7-68.8 128.7-154.3 0-81.9-66.9-143.2-152.9-143.2-107 0-163.9 71.8-163.9 150.1 0 36.4 19.4 81.7 50.3 96.1 4.7 2.2 7.2 1.2 8.3-3.3.8-3.4 5-20.3 6.9-28.1.6-2.5.3-4.7-1.7-7.1-10.1-12.5-18.3-35.3-18.3-56.6 0-54.7 41.4-107.6 112-107.6 60.9 0 103.6 41.5 103.6 100.9 0 67.1-33.9 113.6-78 113.6-24.3 0-42.6-20.1-36.7-44.8 7-29.5 20.5-61.3 20.5-82.6 0-19-10.2-34.9-31.4-34.9-24.9 0-44.9 25.7-44.9 60.2 0 22 7.4 36.8 7.4 36.8s-24.5 103.8-29 123.2c-5 21.4-3 51.6-.9 71.2C65.4 450.9 0 361.1 0 256 0 119 111 8 248 8s248 111 248 248z' /%3E %3C/svg%3E") }
35 | a[href*='quora.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M440.5 386.7h-29.3c-1.5 13.5-10.5 30.8-33 30.8-20.5 0-35.3-14.2-49.5-35.8 44.2-34.2 74.7-87.5 74.7-153C403.5 111.2 306.8 32 205 32 105.3 32 7.3 111.7 7.3 228.7c0 134.1 131.3 221.6 249 189C276 451.3 302 480 351.5 480c81.8 0 90.8-75.3 89-93.3zM297 329.2C277.5 300 253.3 277 205.5 277c-30.5 0-54.3 10-69 22.8l12.2 24.3c6.2-3 13-4 19.8-4 35.5 0 53.7 30.8 69.2 61.3-10 3-20.7 4.2-32.7 4.2-75 0-107.5-53-107.5-156.7C97.5 124.5 130 71 205 71c76.2 0 108.7 53.5 108.7 157.7.1 41.8-5.4 75.6-16.7 100.5z' /%3E %3C/svg%3E") }
36 | a[href*='reddit.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M201.5 305.5c-13.8 0-24.9-11.1-24.9-24.6 0-13.8 11.1-24.9 24.9-24.9 13.6 0 24.6 11.1 24.6 24.9 0 13.6-11.1 24.6-24.6 24.6zM504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zm-132.3-41.2c-9.4 0-17.7 3.9-23.8 10-22.4-15.5-52.6-25.5-86.1-26.6l17.4-78.3 55.4 12.5c0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.3 24.9-24.9s-11.1-24.9-24.9-24.9c-9.7 0-18 5.8-22.1 13.8l-61.2-13.6c-3-.8-6.1 1.4-6.9 4.4l-19.1 86.4c-33.2 1.4-63.1 11.3-85.5 26.8-6.1-6.4-14.7-10.2-24.1-10.2-34.9 0-46.3 46.9-14.4 62.8-1.1 5-1.7 10.2-1.7 15.5 0 52.6 59.2 95.2 132 95.2 73.1 0 132.3-42.6 132.3-95.2 0-5.3-.6-10.8-1.9-15.8 31.3-16 19.8-62.5-14.9-62.5zM302.8 331c-18.2 18.2-76.1 17.9-93.6 0-2.2-2.2-6.1-2.2-8.3 0-2.5 2.5-2.5 6.4 0 8.6 22.8 22.8 87.3 22.8 110.2 0 2.5-2.2 2.5-6.1 0-8.6-2.2-2.2-6.1-2.2-8.3 0zm7.7-75c-13.6 0-24.6 11.1-24.6 24.9 0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.1 24.9-24.6 0-13.8-11-24.9-24.9-24.9z' /%3E %3C/svg%3E") }
37 | a[href*='slack.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M94.12 315.1c0 25.9-21.16 47.06-47.06 47.06S0 341 0 315.1c0-25.9 21.16-47.06 47.06-47.06h47.06v47.06zm23.72 0c0-25.9 21.16-47.06 47.06-47.06s47.06 21.16 47.06 47.06v117.84c0 25.9-21.16 47.06-47.06 47.06s-47.06-21.16-47.06-47.06V315.1zm47.06-188.98c-25.9 0-47.06-21.16-47.06-47.06S139 32 164.9 32s47.06 21.16 47.06 47.06v47.06H164.9zm0 23.72c25.9 0 47.06 21.16 47.06 47.06s-21.16 47.06-47.06 47.06H47.06C21.16 243.96 0 222.8 0 196.9s21.16-47.06 47.06-47.06H164.9zm188.98 47.06c0-25.9 21.16-47.06 47.06-47.06 25.9 0 47.06 21.16 47.06 47.06s-21.16 47.06-47.06 47.06h-47.06V196.9zm-23.72 0c0 25.9-21.16 47.06-47.06 47.06-25.9 0-47.06-21.16-47.06-47.06V79.06c0-25.9 21.16-47.06 47.06-47.06 25.9 0 47.06 21.16 47.06 47.06V196.9zM283.1 385.88c25.9 0 47.06 21.16 47.06 47.06 0 25.9-21.16 47.06-47.06 47.06-25.9 0-47.06-21.16-47.06-47.06v-47.06h47.06zm0-23.72c-25.9 0-47.06-21.16-47.06-47.06 0-25.9 21.16-47.06 47.06-47.06h117.84c25.9 0 47.06 21.16 47.06 47.06 0 25.9-21.16 47.06-47.06 47.06H283.1z' /%3E %3C/svg%3E") }
38 | a[href*='stack-exchange.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M17.7 332.3h412.7v22c0 37.7-29.3 68-65.3 68h-19L259.3 512v-89.7H83c-36 0-65.3-30.3-65.3-68v-22zm0-23.6h412.7v-85H17.7v85zm0-109.4h412.7v-85H17.7v85zM365 0H83C47 0 17.7 30.3 17.7 67.7V90h412.7V67.7C430.3 30.3 401 0 365 0z' /%3E %3C/svg%3E") }
39 | a[href*='stackoverflow.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 384 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M290.7 311L95 269.7 86.8 309l195.7 41zm51-87L188.2 95.7l-25.5 30.8 153.5 128.3zm-31.2 39.7L129.2 179l-16.7 36.5L293.7 300zM262 32l-32 24 119.3 160.3 32-24zm20.5 328h-200v39.7h200zm39.7 80H42.7V320h-40v160h359.5V320h-40z' /%3E %3C/svg%3E") }
40 | a[href*='trello.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M392.3 32H56.1C25.1 32 0 57.1 0 88c-.1 0 0-4 0 336 0 30.9 25.1 56 56 56h336.2c30.8-.2 55.7-25.2 55.7-56V88c.1-30.8-24.8-55.8-55.6-56zM197 371.3c-.2 14.7-12.1 26.6-26.9 26.6H87.4c-14.8.1-26.9-11.8-27-26.6V117.1c0-14.8 12-26.9 26.9-26.9h82.9c14.8 0 26.9 12 26.9 26.9v254.2zm193.1-112c0 14.8-12 26.9-26.9 26.9h-81c-14.8 0-26.9-12-26.9-26.9V117.2c0-14.8 12-26.9 26.8-26.9h81.1c14.8 0 26.9 12 26.9 26.9v142.1z' /%3E %3C/svg%3E") }
41 | a[href*='tumblr.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 320 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M309.8 480.3c-13.6 14.5-50 31.7-97.4 31.7-120.8 0-147-88.8-147-140.6v-144H17.9c-5.5 0-10-4.5-10-10v-68c0-7.2 4.5-13.6 11.3-16 62-21.8 81.5-76 84.3-117.1.8-11 6.5-16.3 16.1-16.3h70.9c5.5 0 10 4.5 10 10v115.2h83c5.5 0 10 4.4 10 9.9v81.7c0 5.5-4.5 10-10 10h-83.4V360c0 34.2 23.7 53.6 68 35.8 4.8-1.9 9-3.2 12.7-2.2 3.5.9 5.8 3.4 7.4 7.9l22 64.3c1.8 5 3.3 10.6-.4 14.5z' /%3E %3C/svg%3E") }
42 | a[href*='twitter.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z' /%3E %3C/svg%3E") }
43 | a[href*='vimeo.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 448 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M403.2 32H44.8C20.1 32 0 52.1 0 76.8v358.4C0 459.9 20.1 480 44.8 480h358.4c24.7 0 44.8-20.1 44.8-44.8V76.8c0-24.7-20.1-44.8-44.8-44.8zM377 180.8c-1.4 31.5-23.4 74.7-66 129.4-44 57.2-81.3 85.8-111.7 85.8-18.9 0-34.8-17.4-47.9-52.3-25.5-93.3-36.4-148-57.4-148-2.4 0-10.9 5.1-25.4 15.2l-15.2-19.6c37.3-32.8 72.9-69.2 95.2-71.2 25.2-2.4 40.7 14.8 46.5 51.7 20.7 131.2 29.9 151 67.6 91.6 13.5-21.4 20.8-37.7 21.8-48.9 3.5-33.2-25.9-30.9-45.8-22.4 15.9-52.1 46.3-77.4 91.2-76 33.3.9 49 22.5 47.1 64.7z' /%3E %3C/svg%3E") }
44 | a[href*='wikipedia.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 640 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M640 51.2l-.3 12.2c-28.1.8-45 15.8-55.8 40.3-25 57.8-103.3 240-155.3 358.6H415l-81.9-193.1c-32.5 63.6-68.3 130-99.2 193.1-.3.3-15 0-15-.3C172 352.3 122.8 243.4 75.8 133.4 64.4 106.7 26.4 63.4.2 63.7c0-3.1-.3-10-.3-14.2h161.9v13.9c-19.2 1.1-52.8 13.3-43.3 34.2 21.9 49.7 103.6 240.3 125.6 288.6 15-29.7 57.8-109.2 75.3-142.8-13.9-28.3-58.6-133.9-72.8-160-9.7-17.8-36.1-19.4-55.8-19.7V49.8l142.5.3v13.1c-19.4.6-38.1 7.8-29.4 26.1 18.9 40 30.6 68.1 48.1 104.7 5.6-10.8 34.7-69.4 48.1-100.8 8.9-20.6-3.9-28.6-38.6-29.4.3-3.6 0-10.3.3-13.6 44.4-.3 111.1-.3 123.1-.6v13.6c-22.5.8-45.8 12.8-58.1 31.7l-59.2 122.8c6.4 16.1 63.3 142.8 69.2 156.7L559.2 91.8c-8.6-23.1-36.4-28.1-47.2-28.3V49.6l127.8 1.1.2.5z' /%3E %3C/svg%3E") }
45 | a[href*='wordpress.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M61.7 169.4l101.5 278C92.2 413 43.3 340.2 43.3 256c0-30.9 6.6-60.1 18.4-86.6zm337.9 75.9c0-26.3-9.4-44.5-17.5-58.7-10.8-17.5-20.9-32.4-20.9-49.9 0-19.6 14.8-37.8 35.7-37.8.9 0 1.8.1 2.8.2-37.9-34.7-88.3-55.9-143.7-55.9-74.3 0-139.7 38.1-177.8 95.9 5 .2 9.7.3 13.7.3 22.2 0 56.7-2.7 56.7-2.7 11.5-.7 12.8 16.2 1.4 17.5 0 0-11.5 1.3-24.3 2l77.5 230.4L249.8 247l-33.1-90.8c-11.5-.7-22.3-2-22.3-2-11.5-.7-10.1-18.2 1.3-17.5 0 0 35.1 2.7 56 2.7 22.2 0 56.7-2.7 56.7-2.7 11.5-.7 12.8 16.2 1.4 17.5 0 0-11.5 1.3-24.3 2l76.9 228.7 21.2-70.9c9-29.4 16-50.5 16-68.7zm-139.9 29.3l-63.8 185.5c19.1 5.6 39.2 8.7 60.1 8.7 24.8 0 48.5-4.3 70.6-12.1-.6-.9-1.1-1.9-1.5-2.9l-65.4-179.2zm183-120.7c.9 6.8 1.4 14 1.4 21.9 0 21.6-4 45.8-16.2 76.2l-65 187.9C426.2 403 468.7 334.5 468.7 256c0-37-9.4-71.8-26-102.1zM504 256c0 136.8-111.3 248-248 248C119.2 504 8 392.7 8 256 8 119.2 119.2 8 256 8c136.7 0 248 111.2 248 248zm-11.4 0c0-130.5-106.2-236.6-236.6-236.6C125.5 19.4 19.4 125.5 19.4 256S125.6 492.6 256 492.6c130.5 0 236.6-106.1 236.6-236.6z' /%3E %3C/svg%3E") }
46 | a[href*='yahoo.']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M223.69,141.06,167,284.23,111,141.06H14.93L120.76,390.19,82.19,480h94.17L317.27,141.06Zm105.4,135.79a58.22,58.22,0,1,0,58.22,58.22A58.22,58.22,0,0,0,329.09,276.85ZM394.65,32l-93,223.47H406.44L499.07,32Z' /%3E %3C/svg%3E") }
47 | a[href*='youtube.com']:before { background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 576 512' xmlns='http://www.w3.org/2000/svg'%3E %3Cpath fill='%234183c4' d='M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z' /%3E %3C/svg%3E") }
48 |
--------------------------------------------------------------------------------