Compare commits
52 Commits
|
@ -4,3 +4,6 @@
|
|||
/.packages
|
||||
/pubspec.lock
|
||||
/.sass-cache/
|
||||
/build/
|
||||
*.scssc
|
||||
/Book.html
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -1,20 +0,0 @@
|
|||
FROM google/dart:2.7
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Get Dart dependencies
|
||||
RUN mkdir -p /pub-cache
|
||||
ENV PUB_CACHE=/pub-cache
|
||||
ENV PATH="${PATH}:/pub-cache/bin"
|
||||
RUN pub global activate webdev
|
||||
ADD pubspec.* /app/
|
||||
RUN pub get
|
||||
RUN pub get --offline
|
||||
|
||||
# Get Ruby Sass
|
||||
RUN apt update && apt install ruby-sass ruby-dev build-essential -y
|
||||
RUN gem install sass-listen
|
||||
|
||||
ADD . /app/
|
||||
|
||||
CMD ["./start.sh"]
|
85
README.org
85
README.org
|
@ -46,65 +46,76 @@
|
|||
that is also simple: it is the only one that provides a ~--watch~ option so
|
||||
it automatically recompiles SCSS code to CSS when the SCSS code is changed.
|
||||
|
||||
** How to run this backend
|
||||
** How to run this project
|
||||
This backend delivers only two main files:
|
||||
- =/dart/main.dart.js= The main dart file compiled to Javascript (you don’t
|
||||
need to worry about the others),
|
||||
- =/style/style.css= The main style file compiled to CSS.
|
||||
This is everything you need for beautiful org-generated websites.
|
||||
|
||||
*** Running locally
|
||||
You could install Dart on your machine, as well as the Ruby implementation
|
||||
of SASS with its dependencies. Next, you will need to install ~webdev~ and
|
||||
install the Dart dependencies:
|
||||
#+BEGIN_SRC sh
|
||||
$ pub global activate webdev
|
||||
$ pub get
|
||||
#+END_SRC
|
||||
While I tried to run this inside a Docker environment serving the Dart and
|
||||
CSS files on a HTTP port to which I tried to redirect requests, I found the
|
||||
easiest way was to actually compile everything into a ~build~ directory and
|
||||
to simlink its content to the root directory of your org website like so:
|
||||
#+BEGIN_SRC sh
|
||||
cd /path/to/your/org/website
|
||||
ln -s /path/to/your/owb/directory/build/*
|
||||
#+END_SRC
|
||||
|
||||
By the way, you have to ensure your Dart cache’s bins are in your ~$PATH~.
|
||||
They are generally installed in your ~$HOME/.pub-cache/bin~ directory.
|
||||
Here is what I add at the top of my org files in order to get it working:
|
||||
#+BEGIN_SRC org
|
||||
,#+HTML_HEAD_EXTRA: <link rel="stylesheet" href="/style/style.css"/>
|
||||
,#+HTML_HEAD_EXTRA: <script defer src="/dart/main.dart.js"></script>
|
||||
#+END_SRC
|
||||
In this order, it will make your website display stuff a bit faster.
|
||||
|
||||
Then, you have to run ~start.sh~, and you’re good to go! Content will be
|
||||
delivered on the 8080 port. If you wish to deliver content to another port,
|
||||
you can edit this file.
|
||||
#+BEGIN_SRC sh
|
||||
./start.sh
|
||||
#+END_SRC
|
||||
To compile the project, you will need to install Dart on your machine as well
|
||||
as a SCSS compiler. While the latter is up to you, make sur the result CSS
|
||||
files are in the same directory as their original SCSS counterpart. As for
|
||||
the Dart part of the project, you can run the following to create a release
|
||||
build of the project:
|
||||
#+BEGIN_SRC sh
|
||||
pub global activate webdev
|
||||
webdev build -r
|
||||
#+END_SRC
|
||||
|
||||
*** Docker
|
||||
A Dockerfile is also provided so you can run this server inside a Docker
|
||||
container, and thus you can avoid the hassle of installing Dart and Ruby
|
||||
Sass. In order to run OWB, you can first build the Docker image:
|
||||
#+BEGIN_SRC sh
|
||||
docker build . --tag owb:1.0
|
||||
#+END_SRC
|
||||
If ~pub~ is not found as an executable, make sure ~$HOME/.pub-cache/bin~ is
|
||||
in your ~$PATH~. Same goes for ~webdev~ even after you install it.
|
||||
|
||||
And then you can run it:
|
||||
** Running in development mode
|
||||
# To run this backend in development mode, you will have to remove the
|
||||
# ~--release~ option from the ~webdev~ command in the ~start.sh~ file. This
|
||||
# will allow webdev to compile Dart files faster, but at the price of slower
|
||||
# compiled Javascript files. If you use Docker, don’t forget to rebuild your
|
||||
# image.
|
||||
To run this backend in development mode, you can add to your environment the
|
||||
variable ~RELEASE~ with the value ~debug~. Running the backend locally, you
|
||||
would start it like so:
|
||||
#+BEGIN_SRC sh
|
||||
RELEASE=debug ./start.sh
|
||||
#+END_SRC
|
||||
|
||||
Running it with Docker, you would use the following command:
|
||||
#+BEGIN_SRC sh
|
||||
docker run \
|
||||
-p 8080:8080 \
|
||||
-v ./web:/app/web \
|
||||
-e RELEASE=debug \
|
||||
--restart always \
|
||||
--detach \
|
||||
--name owb \
|
||||
owb:1.0
|
||||
#+END_SRC
|
||||
|
||||
*** Docker-compose
|
||||
This repository also provides a ~docker-compose.yml~ file for easier Docker
|
||||
usage with ~docker-compose~. If you wish to run your backend in
|
||||
release-mode, simply run the following:
|
||||
#+BEGIN_SRC sh
|
||||
docker-compose up --detach
|
||||
And with docker-compose, you would add the following line to your ~owb~
|
||||
service:
|
||||
#+BEGIN_SRC yaml
|
||||
environment:
|
||||
- RELEASE=debug
|
||||
#+END_SRC
|
||||
|
||||
** Running in development mode
|
||||
To run this backend in development mode, you will have to remove the
|
||||
~--release~ option from the ~webdev~ command in the ~start.sh~ file. This
|
||||
will allow webdev to compile Dart files faster, but at the price of slower
|
||||
compiled Javascript files. If you use Docker, don’t forget to rebuild your
|
||||
image.
|
||||
Any other value to this environment variable will make your backend run in
|
||||
release mode (actually, it will only make ~webdev~ run in release mode).
|
||||
|
||||
** How can I use this in my org files?
|
||||
Let’s say you serve your files on org.example.com, add the following lines to
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
owb:
|
||||
build: .
|
||||
ports:
|
||||
- 8010:8080
|
||||
restart: always
|
||||
volumes:
|
||||
- ./web:/app/web
|
15
pubspec.yaml
15
pubspec.yaml
|
@ -1,16 +1,15 @@
|
|||
name: languephundrakcom
|
||||
description: A bare-bone server for my linguistics website.
|
||||
name: orgwebsitebackend
|
||||
description: A bare-bone server for org-generated websites.
|
||||
version: 1.0.0
|
||||
homepage: https://langue.phundrak.com
|
||||
homepage: https://labs.phundrak.com/phundrak/org-website-backend
|
||||
author: Lucien Cartier-Tilet <lucien@phundrak.com>
|
||||
|
||||
environment:
|
||||
sdk: '>=2.5.0 <3.0.0'
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
html: '^0.14.0+3'
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^1.8.0
|
||||
build_web_compilers: ^2.9.0
|
||||
pedantic: ^1.9.0
|
||||
build_runner: ">=1.10.1 <2.0.0"
|
||||
build_web_compilers: ">=2.9.0 <3.0.0"
|
||||
pedantic: ">=1.9.0 <2.0.0"
|
||||
|
|
7
start.sh
7
start.sh
|
@ -1,3 +1,8 @@
|
|||
#!/bin/bash
|
||||
sass --watch web/style/:web/style -tcompressed &
|
||||
webdev serve --release --hostname 0.0.0.0
|
||||
|
||||
[ "$RELEASE" == "debug" ] \
|
||||
&& webdev serve --hostname 0.0.0.0 \
|
||||
|| webdev serve --release --hostname 0.0.0.0
|
||||
|
||||
# webdev serve --release --hostname 0.0.0.0
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import './parse_sitemap.dart' show createSitemap;
|
||||
import './reorganize_html.dart' show reorganizeHtml;
|
||||
import './theme.dart' show enableThemeChanger, setTheme;
|
||||
|
||||
|
@ -5,5 +6,6 @@ Future<void> main() async {
|
|||
await setTheme();
|
||||
await reorganizeHtml().then((_) {
|
||||
enableThemeChanger();
|
||||
createSitemap();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,117 +1,43 @@
|
|||
import 'dart:html';
|
||||
import 'dart:html' show querySelector, Element, window;
|
||||
import 'dart:svg' show SvgElement;
|
||||
|
||||
import './parse_sitemap.dart' show parseSitemap;
|
||||
// You will see here and there some 'tabindex' attributes added to various HTML
|
||||
// elements, and I’m sure you will ask "Why? They don’t serve any purpose, do
|
||||
// they?". Well you are wrong. Webkit has a **terrible** implementation of
|
||||
// `:focus-within`, and the dropdowns will **not** work unless the parent
|
||||
// element, in this case a `<li>` has a `tabindex` attribute set to `0` and its
|
||||
// first child set to `-1`.
|
||||
//
|
||||
// Screw WebKit, and screw Apple for using such a terrible web engine.
|
||||
|
||||
// Returns the title of the current webpage
|
||||
String getPageTitle() {
|
||||
return querySelector('title').text;
|
||||
}
|
||||
// Icons from https://materialdesignicons.com/
|
||||
final icons = {
|
||||
'home': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" /></svg>'),
|
||||
'pages': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M19,2L14,6.5V17.5L19,13V2M6.5,5C4.55,5 2.45,5.4 1,6.5V21.16C1,21.41 1.25,21.66 1.5,21.66C1.6,21.66 1.65,21.59 1.75,21.59C3.1,20.94 5.05,20.5 6.5,20.5C8.45,20.5 10.55,20.9 12,22C13.35,21.15 15.8,20.5 17.5,20.5C19.15,20.5 20.85,20.81 22.25,21.56C22.35,21.61 22.4,21.59 22.5,21.59C22.75,21.59 23,21.34 23,21.09V6.5C22.4,6.05 21.75,5.75 21,5.5V7.5L21,13V19C19.9,18.65 18.7,18.5 17.5,18.5C15.8,18.5 13.35,19.15 12,20V13L12,8.5V6.5C10.55,5.4 8.45,5 6.5,5V5Z" /></svg>'),
|
||||
'toc': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M3,4H7V8H3V4M9,5V7H21V5H9M3,10H7V14H3V10M9,11V13H21V11H9M3,16H7V20H3V16M9,17V19H21V17H9"/></svg>'),
|
||||
'share': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M18,16.08C17.24,16.08 16.56,16.38 16.04,16.85L8.91,12.7C8.96,12.47 9,12.24 9,12C9,11.76 8.96,11.53 8.91,11.3L15.96,7.19C16.5,7.69 17.21,8 18,8A3,3 0 0,0 21,5A3,3 0 0,0 18,2A3,3 0 0,0 15,5C15,5.24 15.04,5.47 15.09,5.7L8.04,9.81C7.5,9.31 6.79,9 6,9A3,3 0 0,0 3,12A3,3 0 0,0 6,15C6.79,15 7.5,14.69 8.04,14.19L15.16,18.34C15.11,18.55 15.08,18.77 15.08,19C15.08,20.61 16.39,21.91 18,21.91C19.61,21.91 20.92,20.61 20.92,19A2.92,2.92 0 0,0 18,16.08Z" /></svg>'),
|
||||
'twitter': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M22.46,6C21.69,6.35 20.86,6.58 20,6.69C20.88,6.16 21.56,5.32 21.88,4.31C21.05,4.81 20.13,5.16 19.16,5.36C18.37,4.5 17.26,4 16,4C13.65,4 11.73,5.92 11.73,8.29C11.73,8.63 11.77,8.96 11.84,9.27C8.28,9.09 5.11,7.38 3,4.79C2.63,5.42 2.42,6.16 2.42,6.94C2.42,8.43 3.17,9.75 4.33,10.5C3.62,10.5 2.96,10.3 2.38,10C2.38,10 2.38,10 2.38,10.03C2.38,12.11 3.86,13.85 5.82,14.24C5.46,14.34 5.08,14.39 4.69,14.39C4.42,14.39 4.15,14.36 3.89,14.31C4.43,16 6,17.26 7.89,17.29C6.43,18.45 4.58,19.13 2.56,19.13C2.22,19.13 1.88,19.11 1.54,19.07C3.44,20.29 5.7,21 8.12,21C16,21 20.33,14.46 20.33,8.79C20.33,8.6 20.33,8.42 20.32,8.23C21.16,7.63 21.88,6.87 22.46,6Z" /></svg>'),
|
||||
'reddit': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M14.5 15.41C14.58 15.5 14.58 15.69 14.5 15.8C13.77 16.5 12.41 16.56 12 16.56C11.61 16.56 10.25 16.5 9.54 15.8C9.44 15.69 9.44 15.5 9.54 15.41C9.65 15.31 9.82 15.31 9.92 15.41C10.38 15.87 11.33 16 12 16C12.69 16 13.66 15.87 14.1 15.41C14.21 15.31 14.38 15.31 14.5 15.41M10.75 13.04C10.75 12.47 10.28 12 9.71 12C9.14 12 8.67 12.47 8.67 13.04C8.67 13.61 9.14 14.09 9.71 14.08C10.28 14.08 10.75 13.61 10.75 13.04M14.29 12C13.72 12 13.25 12.5 13.25 13.05S13.72 14.09 14.29 14.09C14.86 14.09 15.33 13.61 15.33 13.05C15.33 12.5 14.86 12 14.29 12M22 12C22 17.5 17.5 22 12 22S2 17.5 2 12C2 6.5 6.5 2 12 2S22 6.5 22 12M18.67 12C18.67 11.19 18 10.54 17.22 10.54C16.82 10.54 16.46 10.7 16.2 10.95C15.2 10.23 13.83 9.77 12.3 9.71L12.97 6.58L15.14 7.05C15.16 7.6 15.62 8.04 16.18 8.04C16.75 8.04 17.22 7.57 17.22 7C17.22 6.43 16.75 5.96 16.18 5.96C15.77 5.96 15.41 6.2 15.25 6.55L12.82 6.03C12.75 6 12.68 6.03 12.63 6.07C12.57 6.11 12.54 6.17 12.53 6.24L11.79 9.72C10.24 9.77 8.84 10.23 7.82 10.96C7.56 10.71 7.2 10.56 6.81 10.56C6 10.56 5.35 11.21 5.35 12C5.35 12.61 5.71 13.11 6.21 13.34C6.19 13.5 6.18 13.62 6.18 13.78C6.18 16 8.79 17.85 12 17.85C15.23 17.85 17.85 16.03 17.85 13.78C17.85 13.64 17.84 13.5 17.81 13.34C18.31 13.11 18.67 12.6 18.67 12Z" /></svg>'),
|
||||
'email': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M20,8L12,13L4,8V6L12,11L20,6M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4Z" /></svg>'),
|
||||
'linkedin': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M19 3A2 2 0 0 1 21 5V19A2 2 0 0 1 19 21H5A2 2 0 0 1 3 19V5A2 2 0 0 1 5 3H19M18.5 18.5V13.2A3.26 3.26 0 0 0 15.24 9.94C14.39 9.94 13.4 10.46 12.92 11.24V10.13H10.13V18.5H12.92V13.57C12.92 12.8 13.54 12.17 14.31 12.17A1.4 1.4 0 0 1 15.71 13.57V18.5H18.5M6.88 8.56A1.68 1.68 0 0 0 8.56 6.88C8.56 5.95 7.81 5.19 6.88 5.19A1.69 1.69 0 0 0 5.19 6.88C5.19 7.81 5.95 8.56 6.88 8.56M8.27 18.5V10.13H5.5V18.5H8.27Z" /></svg>'),
|
||||
'facebook': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M12 2.04C6.5 2.04 2 6.53 2 12.06C2 17.06 5.66 21.21 10.44 21.96V14.96H7.9V12.06H10.44V9.85C10.44 7.34 11.93 5.96 14.22 5.96C15.31 5.96 16.45 6.15 16.45 6.15V8.62H15.19C13.95 8.62 13.56 9.39 13.56 10.18V12.06H16.34L15.89 14.96H13.56V21.96A10 10 0 0 0 22 12.06C22 6.53 17.5 2.04 12 2.04Z" /></svg>'),
|
||||
'theme': SvgElement.svg(
|
||||
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M12,18V6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,15.31L23.31,12L20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31Z" /></svg>'),
|
||||
};
|
||||
|
||||
Element makeIcon(List<String> classes, [String id]) {
|
||||
final icon = Element.tag('i')..classes.addAll(classes);
|
||||
if (id != null) {
|
||||
icon.attributes['id'] = id;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
|
||||
Future<Element> makeToc() async {
|
||||
return Element.li()
|
||||
..attributes['id'] = 'toc-drop'
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(makeIcon(['fas', 'fa-list-ol'], 'tocBtn')));
|
||||
}
|
||||
|
||||
Future<Element> makePages() async {
|
||||
var pages = Element.ul()
|
||||
..attributes['id'] = 'drop-page'
|
||||
..classes.add('dropdown');
|
||||
await parseSitemap().then((final sitemap) => {
|
||||
sitemap.forEach((url, name) {
|
||||
final link = Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..insertAdjacentElement(
|
||||
'afterBegin',
|
||||
Element.a()
|
||||
..attributes['href'] = url
|
||||
..innerText = name);
|
||||
pages.insertAdjacentElement('beforeEnd', link);
|
||||
})
|
||||
});
|
||||
return Element.li()
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(makeIcon(['fas', 'fa-flag'])))
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..insertAdjacentElement('beforeEnd', pages);
|
||||
}
|
||||
|
||||
Element makeShareLink(Element icon, String url) {
|
||||
return Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..append(Element.a()
|
||||
..attributes['href'] = url
|
||||
..attributes['target'] = '_blank'
|
||||
..append(icon));
|
||||
}
|
||||
|
||||
Future<Element> makeShare() async {
|
||||
return Element.li()
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(makeIcon(['fas', 'fa-share-alt'])))
|
||||
..append(Element.ul()
|
||||
..classes.add('dropdown')
|
||||
..attributes['id'] = 'drop-share'
|
||||
..append(makeShareLink(
|
||||
makeIcon(['fab', 'fa-twitter-square']),
|
||||
'https://twitter.com/share?text=${getPageTitle()}'
|
||||
'&url=${window.location.href}'))
|
||||
..append(makeShareLink(makeIcon(['fab', 'fa-reddit-square']),
|
||||
'https://www.reddit.com/submit?title=${getPageTitle()}s&url=${window.location.href}'))
|
||||
..append(makeShareLink(makeIcon(['fas', 'fa-envelope-square']),
|
||||
'mailto:?subject=${getPageTitle}&body=${window.location.href}'))
|
||||
..append(makeShareLink(
|
||||
makeIcon(['fab', 'fa-linkedin']),
|
||||
'https://www.linkedin.com/shareArticle?mini=true&url=${window.location.href}'
|
||||
'&title=${getPageTitle()}'))
|
||||
..append(makeShareLink(makeIcon(['fab', 'fa-facebook-square']),
|
||||
'https://www.facebook.com/sharer/sharer.php?u=${window.location.href}')));
|
||||
}
|
||||
|
||||
Future<Element> makeThemeChanger() async {
|
||||
Element makeThemeItem(String t_btnId, Element t_icon, String t_text) {
|
||||
return Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..append(Element.span()
|
||||
..attributes['id'] = t_btnId
|
||||
..append(t_icon)
|
||||
..appendText(' $t_text'));
|
||||
}
|
||||
|
||||
return Element.li()
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(Element.span()
|
||||
..classes.add('fa-stack')
|
||||
..style.verticalAlign = 'top'
|
||||
..append(makeIcon(['fas', 'fa-sun', 'fa-stack-1x'])
|
||||
..style.fontSize = '0.9em')
|
||||
..append(makeIcon(['fas', 'fa-moon', 'fa-stack-1x']))))
|
||||
..append(Element.ul()
|
||||
..classes.add('dropdown')
|
||||
..attributes['id'] = 'theme-dropdown'
|
||||
..append(makeThemeItem('lightBtn', makeIcon(['fas', 'fa-sun']), 'Clair'))
|
||||
..append(
|
||||
makeThemeItem('darkBtn', makeIcon(['fas', 'fa-lightbulb']), 'Sombre'))
|
||||
..append(
|
||||
makeThemeItem('blackBtn', makeIcon(['fas', 'fa-moon']), 'Noir')));
|
||||
}
|
||||
// Get the current page’s title
|
||||
String getPageTitle() => querySelector('title')!.text!;
|
||||
|
||||
// Create the home button
|
||||
Future<Element> makeHome() async {
|
||||
return Element.li()
|
||||
..classes.add('nav-item')
|
||||
|
@ -119,19 +45,120 @@ Future<Element> makeHome() async {
|
|||
'afterBegin',
|
||||
Element.a()
|
||||
..attributes['href'] = '/'
|
||||
..append(makeIcon(['fas', 'fa-home'])));
|
||||
..append(makeIcon(icons['home']!)));
|
||||
}
|
||||
|
||||
// Add a navbar atop of the HTML body, containing two buttons:
|
||||
// - One back to home
|
||||
// - A dropdown to each page detected in the sitemap
|
||||
// Create a clickable icon
|
||||
// `t_elem` must be an SVG declared in the above `icons` variable.
|
||||
Element makeIcon(SvgElement t_elem) {
|
||||
return t_elem..classes.add('nav-icon');
|
||||
}
|
||||
|
||||
// Create the dropdown sitemap
|
||||
Future<Element> makePages() async {
|
||||
return Element.li()
|
||||
..attributes['tabindex'] = '0'
|
||||
..append(Element.a()
|
||||
..attributes['tabindex'] = '-1'
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(makeIcon(icons['pages']!)))
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..append(Element.ul()
|
||||
..attributes['id'] = 'drop-page'
|
||||
..classes.add('dropdown'));
|
||||
}
|
||||
|
||||
// Create the array of share icons
|
||||
Future<Element> makeShare() async {
|
||||
// Create a share button
|
||||
Element makeShareLink(Element t_icon, String t_url) {
|
||||
return Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..append(Element.a()
|
||||
..attributes['href'] = t_url
|
||||
..attributes['target'] = '_blank'
|
||||
..attributes['rel'] = 'noreferrer'
|
||||
..append(t_icon));
|
||||
}
|
||||
|
||||
return Element.li()
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..attributes['tabindex'] = '0'
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..attributes['tabindex'] = '-1'
|
||||
..append(makeIcon(icons['share']!)))
|
||||
..append(Element.ul()
|
||||
..classes.add('dropdown')
|
||||
..attributes['id'] = 'drop-share'
|
||||
..append(makeShareLink(
|
||||
makeIcon(icons['twitter']!),
|
||||
'https://twitter.com/share?text=${getPageTitle()}'
|
||||
'&url=${window.location.href}'))
|
||||
..append(makeShareLink(makeIcon(icons['reddit']!),
|
||||
'https://www.reddit.com/submit?title=${getPageTitle()}s&url=${window.location.href}'))
|
||||
..append(makeShareLink(makeIcon(icons['email']!),
|
||||
'mailto:?subject=$getPageTitle&body=${window.location.href}'))
|
||||
..append(makeShareLink(
|
||||
makeIcon(icons['linkedin']!),
|
||||
'https://www.linkedin.com/shareArticle?mini=true&url=${window.location.href}'
|
||||
'&title=${getPageTitle()}'))
|
||||
..append(makeShareLink(makeIcon(icons['facebook']!),
|
||||
'https://www.facebook.com/sharer/sharer.php?u=${window.location.href}')));
|
||||
}
|
||||
|
||||
// Create the theme changer
|
||||
Future<Element> makeThemeChanger() async {
|
||||
Element makeThemeItem(String t_btnId) {
|
||||
return Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..append(Element.span()
|
||||
..attributes['id'] = t_btnId
|
||||
..classes.add('themeBtn'));
|
||||
}
|
||||
|
||||
return Element.li()
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..attributes['tabindex'] = '0'
|
||||
..append(Element.a()
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..attributes['tabindex'] = '-1'
|
||||
..append(Element.span()
|
||||
..style.verticalAlign = 'top'
|
||||
..append(makeIcon(icons['theme']!))))
|
||||
..append(Element.ul()
|
||||
..classes.add('dropdown')
|
||||
..attributes['id'] = 'theme-dropdown'
|
||||
..append(makeThemeItem('lightBtn'))
|
||||
..append(makeThemeItem('darkBtn'))
|
||||
..append(makeThemeItem('blackBtn')));
|
||||
}
|
||||
|
||||
// Create the dropdown table of contents
|
||||
Future<Element> makeToc() async {
|
||||
return Element.li()
|
||||
..attributes['id'] = 'toc-drop'
|
||||
..attributes['tabindex'] = '0'
|
||||
..classes.addAll(['nav-item', 'has-dropdown'])
|
||||
..append(Element.a()
|
||||
..attributes['tabindex'] = '-1'
|
||||
..attributes['href'] = 'javascript:void(0)'
|
||||
..append(makeIcon(icons['toc']!)));
|
||||
}
|
||||
|
||||
// Add a navbar atop of the HTML body, containing:
|
||||
// - A back to home button
|
||||
// - A dropdown sitemap
|
||||
// - A dropdown table of contents
|
||||
// - A dropdown array of share icons
|
||||
// - A theme changer
|
||||
Future<Element> makeNavbar() async {
|
||||
final navbar_content = Element.ul()..classes.add('navbar-nav');
|
||||
final home = await makeHome();
|
||||
final pages = await makePages();
|
||||
final toc = await makeToc();
|
||||
final share = await makeShare();
|
||||
final theme = await makeThemeChanger();
|
||||
final home = await makeHome();
|
||||
final pages = await makePages();
|
||||
final toc = await makeToc();
|
||||
final share = await makeShare();
|
||||
final theme = await makeThemeChanger();
|
||||
|
||||
navbar_content
|
||||
..append(home)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import 'dart:html' show HttpRequest;
|
||||
import 'dart:html' show HttpRequest, Element, querySelector;
|
||||
|
||||
import 'package:html/parser.dart' show parse;
|
||||
import 'package:html/dom.dart' show Element;
|
||||
|
||||
final excluded_keywords = ['index', 'CONTRIBUTING', 'LICENSE', 'README'];
|
||||
final excluded_keywords = {'index', 'CONTRIBUTING', 'LICENSE', 'README'};
|
||||
|
||||
// Get the sitemap content
|
||||
Future<String> getSitemap() async {
|
||||
const path = 'sitemap.html';
|
||||
Future<String> fetchRemoteSitemap() async {
|
||||
const path = '/sitemap.html';
|
||||
try {
|
||||
return await HttpRequest.getString(path);
|
||||
} catch (e) {
|
||||
|
@ -17,32 +14,50 @@ Future<String> getSitemap() async {
|
|||
}
|
||||
|
||||
// Parse the list of elements and detect pages from this list
|
||||
Map<String, String> detectPages(List<Element> sitemap, [String prefix]) {
|
||||
final links = <String, String>{};
|
||||
for (var elem in sitemap) {
|
||||
for(var kw in excluded_keywords) {
|
||||
if (elem.outerHtml.contains(kw)) {
|
||||
Map<String, String> detectPages(List<Element> t_sitemap,
|
||||
[String? t_prefix, Map<String, String>? t_links]) {
|
||||
t_links ??= <String, String>{};
|
||||
|
||||
// parse each element in sitemap
|
||||
for (var elem in t_sitemap) {
|
||||
// if it’s a link
|
||||
if (elem.innerHtml!.startsWith('<a')) {
|
||||
elem = elem.children[0];
|
||||
final url = elem.attributes['href']!;
|
||||
final text = elem.children[0].text;
|
||||
if (excluded_keywords.contains(text) ||
|
||||
excluded_keywords.contains(url.substring(0, url.length - 5))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (elem.innerHtml.startsWith('<a')) {
|
||||
elem = elem.firstChild;
|
||||
final url = elem.attributes['href'];
|
||||
final text = elem.firstChild.text;
|
||||
links[url] = (prefix == null) ? text : '$text ($prefix)';
|
||||
t_links!['/$url'] = ((t_prefix == null) ? text : '$text ($t_prefix)')!;
|
||||
} else {
|
||||
final prefix = elem.firstChild.text;
|
||||
final ul = elem.children[0].children;
|
||||
links.addAll(detectPages(ul, prefix));
|
||||
final prefix = (t_prefix == null)
|
||||
? elem.firstChild!.text!.replaceAll('\n', '')
|
||||
: '$t_prefix / ${elem.firstChild!.text}'.replaceAll('\n', '');
|
||||
t_links = detectPages(elem.children[0].children, prefix, t_links);
|
||||
}
|
||||
}
|
||||
return links;
|
||||
return t_links!;
|
||||
}
|
||||
|
||||
// This function returns a Map which contains all links to languages detected
|
||||
// from the sitemap.
|
||||
Future<Map<String, String>> parseSitemap() async {
|
||||
final content = await getSitemap();
|
||||
final sitemap = parse(content).getElementsByClassName('org-ul')[0].children;
|
||||
return detectPages(sitemap);
|
||||
final content = await fetchRemoteSitemap();
|
||||
final sitemap = Element.ul()..innerHtml = content;
|
||||
final sitemapNodes = sitemap.querySelector('ul')!.children;
|
||||
return detectPages(sitemapNodes);
|
||||
}
|
||||
|
||||
Future<void> createSitemap() async {
|
||||
final sitemap = await parseSitemap();
|
||||
final pages = querySelector('#drop-page')!;
|
||||
sitemap.forEach((url, name) {
|
||||
final link = Element.li()
|
||||
..classes.add('dropdown-item')
|
||||
..append(Element.a()
|
||||
..attributes['href'] = url
|
||||
..innerText = name);
|
||||
pages.append(link);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,18 +1,30 @@
|
|||
import 'dart:html';
|
||||
import 'dart:html'
|
||||
show DivElement, Element, querySelector, querySelectorAll, window;
|
||||
|
||||
import './navbar.dart' show makeNavbar;
|
||||
|
||||
const image_header = '/img/icon.webp';
|
||||
|
||||
Future<void> makeDecorativeButtonsOrgSrc() async {
|
||||
for (var pre in querySelectorAll('.org-src-container')) {
|
||||
pre
|
||||
..append(Element.div()..attributes['class'] = 'closeButton')
|
||||
..append(Element.div()..attributes['class'] = 'minButton')
|
||||
..append(Element.div()..attributes['class'] = 'maxButton')
|
||||
..append(Element.div()..attributes['class'] = 'floatButton');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Element> makeHeader() async {
|
||||
var header = Element.tag('header');
|
||||
header
|
||||
..append(Element.img()
|
||||
..attributes['src'] =
|
||||
'https://cdn.phundrak.com/img/mahakala-monochrome.png'
|
||||
..attributes['alt'] = 'Logo de Phundrak'
|
||||
..attributes['heigh'] = '150px'
|
||||
..attributes['src'] = image_header
|
||||
..attributes['alt'] = 'Logo'
|
||||
..attributes['height'] = '150px'
|
||||
..attributes['width'] = '150px')
|
||||
..append(querySelector('h1'));
|
||||
var subt = querySelector('.subtitle');
|
||||
..append(querySelector('h1')!);
|
||||
final subt = header.querySelector('.subtitle');
|
||||
if (subt != null) {
|
||||
header.append(subt);
|
||||
}
|
||||
|
@ -28,12 +40,10 @@ Future<void> wrapTables() async {
|
|||
}
|
||||
|
||||
// All images that are not nested inside a link will be linkified to themselves.
|
||||
void linkifyImg() {
|
||||
Future<void> linkifyImg() async {
|
||||
querySelectorAll('img').forEach((img) {
|
||||
print(img.attributes['src']);
|
||||
print(img.parent.tagName);
|
||||
if (img.parent.tagName == 'P') {
|
||||
final link = Element.a()..attributes['href'] = img.attributes['src'];
|
||||
if (img.parent!.tagName == 'P') {
|
||||
final link = Element.a()..attributes['href'] = img.attributes['src']!;
|
||||
img.insertAdjacentElement('beforeBegin', link);
|
||||
link.append(img);
|
||||
}
|
||||
|
@ -41,30 +51,41 @@ void linkifyImg() {
|
|||
}
|
||||
|
||||
Future<void> reorganizeHtml() async {
|
||||
final content = querySelector('#content');
|
||||
final ls = window.localStorage;
|
||||
final lastUpdate = '20210330';
|
||||
if (ls['last-update'] != lastUpdate) {
|
||||
ls['last-update'] = lastUpdate;
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// Make navbar
|
||||
await makeNavbar().then((navbar) {
|
||||
querySelector('body').insertAdjacentElement('afterBegin', navbar);
|
||||
});
|
||||
final navbar = await makeNavbar();
|
||||
|
||||
// Make header
|
||||
await makeHeader().then((header) {
|
||||
content.insertAdjacentElement('beforeBegin', header);
|
||||
final subtitle = querySelector('.subtitle');
|
||||
if (subtitle != null) {
|
||||
header.append(subtitle);
|
||||
}
|
||||
});
|
||||
final header = await makeHeader();
|
||||
|
||||
// Add decorative divs to source block wrappers
|
||||
await makeDecorativeButtonsOrgSrc();
|
||||
|
||||
// wrap tables in container for better SCSS display
|
||||
await wrapTables();
|
||||
|
||||
linkifyImg();
|
||||
// Make images not linking somewhere link to themselves
|
||||
await linkifyImg();
|
||||
|
||||
// Add navbar to page
|
||||
querySelector('body')!.insertAdjacentElement('afterBegin', navbar);
|
||||
|
||||
// Add headet to page
|
||||
querySelector('#content')!.insertAdjacentElement('beforeBegin', header);
|
||||
|
||||
// Add correct class to TOC
|
||||
querySelector('#toc-drop')
|
||||
.append(querySelector('#table-of-contents')..classes.add('dropdown'));
|
||||
final toc = (querySelector('#table-of-contents') ??
|
||||
(Element.div()
|
||||
..attributes['id'] = 'table-of-contents'
|
||||
..innerText = 'Table of Contents Unavailable'))
|
||||
..classes.add('dropdown');
|
||||
navbar.querySelector('#toc-drop')!.append(toc);
|
||||
|
||||
// Remove all <br> tags from HTML
|
||||
querySelectorAll('br').forEach((br) => br.remove());
|
||||
|
|
|
@ -1,53 +1,20 @@
|
|||
import 'dart:html';
|
||||
|
||||
class Theme {
|
||||
String _name;
|
||||
String _icon;
|
||||
|
||||
Theme(String t_name, String t_icon) {
|
||||
_name = t_name;
|
||||
_icon = t_icon;
|
||||
}
|
||||
|
||||
String getIcon() => _icon;
|
||||
String getName() => _name;
|
||||
}
|
||||
|
||||
final themes = {
|
||||
'light': Theme('light', 'fa-sun'),
|
||||
'dark': Theme('dark', 'fa-lightbulb'),
|
||||
'black': Theme('black', 'fa-moon')
|
||||
};
|
||||
import 'dart:html' show window, querySelector;
|
||||
|
||||
final localStorage = window.localStorage;
|
||||
|
||||
var currentTheme = themes[localStorage['theme']];
|
||||
Future<void> setTheme([String? theme]) async {
|
||||
theme ??= localStorage['theme'] ??
|
||||
(window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light');
|
||||
localStorage['theme'] = theme;
|
||||
querySelector('body')!
|
||||
..classes.clear()
|
||||
..classes.add(theme);
|
||||
}
|
||||
|
||||
void enableThemeChanger() {
|
||||
final darkBtn = querySelector('#darkBtn');
|
||||
final lightBtn = querySelector('#lightBtn');
|
||||
final blackBtn = querySelector('#blackBtn');
|
||||
|
||||
lightBtn.onClick.listen((_) => switchTheme(themes['light']));
|
||||
darkBtn.onClick.listen((_) => switchTheme(themes['dark']));
|
||||
blackBtn.onClick.listen((_) => switchTheme(themes['black']));
|
||||
}
|
||||
|
||||
Future<void> setTheme() async {
|
||||
if (currentTheme == null) {
|
||||
currentTheme = themes['light'];
|
||||
localStorage['theme'] = currentTheme.getName();
|
||||
}
|
||||
querySelector('body')
|
||||
..classes.clear()
|
||||
..classes.add(currentTheme.getName());
|
||||
}
|
||||
|
||||
void switchTheme(Theme currentTheme) {
|
||||
// Set HTML theme
|
||||
querySelector('body')
|
||||
..classes.clear()
|
||||
..classes.add(currentTheme.getName());
|
||||
// Set storage theme
|
||||
localStorage['theme'] = currentTheme.getName();
|
||||
final themes = <String>['light', 'dark', 'black'];
|
||||
themes.forEach((theme) =>
|
||||
querySelector('#${theme}Btn')!.onClick.listen((_) => setTheme(theme)));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
@import "themes.scss";
|
||||
@import "style.scss";
|
|
@ -1,21 +1,4 @@
|
|||
@import url(https://fonts.googleapis.com/css?family=Lato:400,700,400italic,700italic|Roboto+Slab:400,700|Inconsolata:400,700);
|
||||
@font-face {
|
||||
font-family: "DoulosSIL";
|
||||
font-display: swap;
|
||||
src: url("/fonts/DoulosSIL-R.woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Noto Sans Runes";
|
||||
font-display: swap;
|
||||
src: url("../fonts/NotoSansRunic-Regular.ttf");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Helvetica Neue";
|
||||
font-display: swap;
|
||||
src: url("../fonts/HelveticaNeue.ttf");
|
||||
}
|
||||
|
||||
/* Variables *****************************************************************/
|
||||
// Variables //////////////////////////////////////////////////////////////////
|
||||
|
||||
$switch-small-screen: "only screen and (max-width: 600px)";
|
||||
$switch-smaller-screen: "only screen and (max-width: 400px)";
|
||||
|
@ -23,261 +6,20 @@ $switch-smaller-screen: "only screen and (max-width: 400px)";
|
|||
$navbar-height: 70px;
|
||||
$postamble-height: 55px;
|
||||
|
||||
// Themes /////////////////////////////////////////////////////////////////////
|
||||
|
||||
$dark: rgba( 52, 73, 94, 1);
|
||||
$black: rgba( 0, 0, 0, 1);
|
||||
$accent1: rgba( 93, 115, 126, 1);
|
||||
$accent2: rgba( 92, 172, 126, 1);
|
||||
$accent3: rgba(197, 193, 155, 1);
|
||||
$light: #eee;
|
||||
$grey1: #f8f8f8;
|
||||
$grey2: #dbe1e8;
|
||||
$grey3: #b2becd;
|
||||
$grey4: #6c7983;
|
||||
$grey5: #454e56;
|
||||
$grey6: #12181b;
|
||||
|
||||
// Accent 1
|
||||
// Black
|
||||
$gradient-accent1-black-left: linear-gradient(to left, $black, $accent1, $accent1);
|
||||
$gradient-accent1-black-right: linear-gradient(to right, $black, $accent1, $accent1);
|
||||
// Dark
|
||||
$gradient-accent1-dark-left: linear-gradient(to left, $dark, $accent1);
|
||||
$gradient-accent1-dark-right: linear-gradient(to right, $dark, $accent1);
|
||||
// Light
|
||||
$gradient-accent1-light-left: linear-gradient(to left, $light, $accent1);
|
||||
$gradient-accent1-light-right: linear-gradient(to right, $light, $accent1);
|
||||
// Accent 2
|
||||
// Black
|
||||
$gradient-accent2-black-left: linear-gradient(to left, $black, $accent2, $accent2);
|
||||
$gradient-accent2-black-right: linear-gradient(to right, $black, $accent2, $accent2);
|
||||
// Dark
|
||||
$gradient-accent2-dark-left: linear-gradient(to left, $dark, $accent2);
|
||||
$gradient-accent2-dark-right: linear-gradient(to right, $dark, $accent2);
|
||||
// Light
|
||||
$gradient-accent2-light-left: linear-gradient(to left, $light, $accent2);
|
||||
$gradient-accent2-light-right: linear-gradient(to right, $light, $accent2);
|
||||
// Accent 3
|
||||
// Black
|
||||
$gradient-accent3-black-left: linear-gradient(to left, $black, $accent3, $accent3);
|
||||
$gradient-accent3-black-right: linear-gradient(to right, $black, $accent3, $accent3);
|
||||
// Dark
|
||||
$gradient-accent3-dark-left: linear-gradient(to left, $dark, $accent3);
|
||||
$gradient-accent3-dark-right: linear-gradient(to right, $dark, $accent3);
|
||||
// Light
|
||||
$gradient-accent3-light-left: linear-gradient(to left, $light, $accent3);
|
||||
$gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
|
||||
|
||||
.light {
|
||||
$bg-nav: $gradient-accent3-light-right;
|
||||
$border-color: $accent1;
|
||||
|
||||
color: $dark;
|
||||
background: $light;
|
||||
|
||||
transition: background 500ms ease-in-out, color 1s ease-in-out;
|
||||
|
||||
pre {
|
||||
box-shadow: 3px 3px $dark;
|
||||
border-color: $light;
|
||||
}
|
||||
|
||||
pre.src {
|
||||
&::before {
|
||||
background-color: $light;
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar, header {
|
||||
background: $bg-nav;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: $gradient-accent3-light-left;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border-bottom: 1px dotted $accent3;
|
||||
|
||||
.tooltiptext {
|
||||
background-color: $accent3;
|
||||
color: $dark;
|
||||
|
||||
&::after {
|
||||
border-color: $accent3 transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
background: $accent3;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
#content {
|
||||
a {
|
||||
box-shadow: inset 0 -3px 0 $accent3;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -23px 0 $accent3;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid $dark;
|
||||
}
|
||||
|
||||
th {
|
||||
background: darken($light, 5%);
|
||||
}
|
||||
|
||||
.gentree {
|
||||
filter: invert(0%);
|
||||
transition: filter 1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.dark, .black {
|
||||
$bg-nav: $gradient-accent2-dark-right;
|
||||
$border-color: $dark;
|
||||
|
||||
color: $light;
|
||||
background: $dark;
|
||||
|
||||
transition: background 500ms ease-in-out, color 1s ease-in-out;
|
||||
|
||||
pre {
|
||||
box-shadow: 3px 3px $dark;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre.src {
|
||||
&::before {
|
||||
background-color: $dark;
|
||||
color: $light;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar, header {
|
||||
background: $bg-nav;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: $gradient-accent2-dark-left;
|
||||
color: $light;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border-bottom: 1px dotted $accent1;
|
||||
|
||||
.tooltiptext {
|
||||
background-color: $accent1;
|
||||
color: $light;
|
||||
|
||||
&::after {
|
||||
border-color: $accent1 transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
background: $accent3;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
#content {
|
||||
a {
|
||||
box-shadow: inset 0 -3px 0 $accent2;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -23px 0 $accent2;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid $accent1;
|
||||
}
|
||||
|
||||
th {
|
||||
background: darken($dark, 2.5%);
|
||||
}
|
||||
|
||||
.gentree {
|
||||
filter: invert(100%);
|
||||
transition: filter 1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.black {
|
||||
$bg-nav: $gradient-accent1-black-right;
|
||||
|
||||
background: $black;
|
||||
|
||||
pre {
|
||||
box-shadow: 3px 3px $light;
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre.src {
|
||||
&::before {
|
||||
background-color: $black;
|
||||
color: $light;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar, header {
|
||||
background: $bg-nav;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: $gradient-accent1-black-left;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
background: $dark;
|
||||
color: $light;
|
||||
}
|
||||
|
||||
#content {
|
||||
a {
|
||||
box-shadow: inset 0 -3px 0 $accent1;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -23px 0 $accent1;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid $light;
|
||||
}
|
||||
|
||||
th {
|
||||
background: lighten($black, 15%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Style *********************************************************************/
|
||||
// Style //////////////////////////////////////////////////////////////////////
|
||||
|
||||
* {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Noto Sans Runes", "DoulosSIL", "Lato", "proxima-nova", "Helvetica Neue", Arial, sans-serif;
|
||||
font-family: "Lato", "proxima-nova", Arial, sans-serif;
|
||||
font-size: 1.2em;
|
||||
|
||||
transition: background 500ms ease-in-out, color 1s ease-in-out;
|
||||
|
@ -346,14 +88,13 @@ header {
|
|||
align-items: center;
|
||||
justify-content: space-around;
|
||||
min-height: 3rem;
|
||||
margin-top: 1rem;
|
||||
padding: 0.5rem;
|
||||
top: 0;
|
||||
|
||||
box-shadow: rgba(2, 8, 20, 0.1) 0px 0.175em 0.5em;
|
||||
transform: translateX(-40%);
|
||||
|
||||
transition: opacity 500ms ease-in-out, top 500ms ease-in-out;
|
||||
transition: opacity 500ms ease-in-out, top 500ms ease-in-out, height 500ms ease-in-out;
|
||||
}
|
||||
|
||||
.has-dropdown {
|
||||
|
@ -365,11 +106,11 @@ header {
|
|||
pointer-events: auto;
|
||||
}
|
||||
|
||||
#table-of-contents {
|
||||
top: $navbar-height / 1.3;
|
||||
#table-of-contents, #drop-page {
|
||||
top: $navbar-height;
|
||||
opacity: 1;
|
||||
z-index: 5;
|
||||
height: 500%;
|
||||
height: 80vh;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
@ -381,11 +122,33 @@ header {
|
|||
transform: translateX(-75%);
|
||||
}
|
||||
|
||||
#drop-page {
|
||||
.themeBtn {
|
||||
content: ' ';
|
||||
border: 2px solid white;
|
||||
border-radius: 50%;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#drop-page, #table-of-contents {
|
||||
flex-direction: column;
|
||||
transform: translateX(-40%);
|
||||
li {
|
||||
padding: 5px;
|
||||
|
||||
transform: translateX(-45%);
|
||||
top: -40px;
|
||||
|
||||
height: 0;
|
||||
min-width: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@media #{$switch-small-screen} {
|
||||
#drop-page {
|
||||
transform: translateX(-27.5%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,7 +164,6 @@ header {
|
|||
a {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
size: 0.7rem;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
font-weight: bold;
|
||||
|
@ -409,17 +171,9 @@ header {
|
|||
}
|
||||
|
||||
#table-of-contents {
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
float: right;
|
||||
overflow-y: auto;
|
||||
height: 0%;
|
||||
width: 75%;
|
||||
min-width: 350px;
|
||||
transform: translateX(-45%);
|
||||
font-size: 0.9em;
|
||||
top: -40px;
|
||||
|
||||
transition: height 500ms ease-in-out, opacity 500ms ease-in-out, top 500ms ease-in-out;
|
||||
|
||||
li {
|
||||
|
@ -442,6 +196,10 @@ header {
|
|||
margin: 0 auto;
|
||||
text-align: justify;
|
||||
|
||||
@media #{$switch-small-screen} {
|
||||
padding: 50px 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -481,7 +239,10 @@ header {
|
|||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
text-align: center;
|
||||
font-family: serif;
|
||||
font-style: italic;
|
||||
margin-left: 2em;
|
||||
margin-right: 60px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
|
@ -522,7 +283,7 @@ h5 {
|
|||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
|
||||
margin-left: -60px; /* Half the width */
|
||||
margin-left: -20px;
|
||||
bottom: 100%;
|
||||
left: 10%;
|
||||
padding: 5px 10px;
|
||||
|
@ -532,7 +293,6 @@ h5 {
|
|||
position: absolute;
|
||||
z-index: 5;
|
||||
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
|
@ -572,9 +332,22 @@ th, td {
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
img, svg {
|
||||
max-height: 600px;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header > img {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
border-radius: 75px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
max-width: 20px;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.figure {
|
||||
|
@ -585,8 +358,72 @@ ul {
|
|||
padding-inline-start: 20px;
|
||||
}
|
||||
|
||||
pre.src {
|
||||
background: $dark;
|
||||
color: $light;
|
||||
.org-src-container,
|
||||
pre {
|
||||
border-radius: .5em;
|
||||
}
|
||||
|
||||
.org-src-container {
|
||||
position: relative;
|
||||
padding-left: 1em;
|
||||
box-shadow: 3px 3px 10px rgba(0,0,0,.3);
|
||||
min-height: 5.5em;
|
||||
margin: 0 auto;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
border: none;
|
||||
box-shadow: none !important;
|
||||
margin: .5em;
|
||||
padding: 2.75em 1.5em !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
pre.src::before {
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
pre.src-fish::before {
|
||||
content: 'fish';
|
||||
}
|
||||
|
||||
pre.src-rust::before {
|
||||
content: 'Rust';
|
||||
}
|
||||
|
||||
.closeButton,
|
||||
.minButton,
|
||||
.maxButton,
|
||||
.floatButton {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
width: .8em;
|
||||
height: .8em;
|
||||
border-radius: .4em;
|
||||
z-index: 2;
|
||||
left: .3em;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
top: .3em;
|
||||
}
|
||||
|
||||
.minButton {
|
||||
top: 1.4em;
|
||||
}
|
||||
|
||||
.maxButton {
|
||||
top: 2.5em;
|
||||
}
|
||||
|
||||
.floatButton {
|
||||
bottom: .3em;
|
||||
}
|
||||
|
||||
.twitter-tweet {
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
// Variables //////////////////////////////////////////////////////////////////
|
||||
|
||||
$tooltip-underline-size: 3px;
|
||||
|
||||
// Themes /////////////////////////////////////////////////////////////////////
|
||||
|
||||
$nord0: #2e3440;
|
||||
$nord1: #3b4252;
|
||||
$nord2: #434c5e;
|
||||
$nord3: #4c566a;
|
||||
$nord4: #d8dee9;
|
||||
$nord5: #e5e9f0;
|
||||
$nord6: #eceff4;
|
||||
$nord7: #8fbcbb;
|
||||
$nord8: #88c0d0;
|
||||
$nord9: #81a1c1;
|
||||
$nord10: #5e81ac;
|
||||
$nord11: #bf616a;
|
||||
$nord12: #d08770;
|
||||
$nord13: #ebcb8b;
|
||||
$nord14: #a3be8c;
|
||||
$nord15: #b48ead;
|
||||
$dark: $nord1;
|
||||
$light: $nord5;
|
||||
$accent1: $nord7;
|
||||
$accent2: $nord9;
|
||||
$accent3: $nord8;
|
||||
|
||||
.light {
|
||||
$border-color: $accent1;
|
||||
|
||||
color: $dark;
|
||||
background: $light;
|
||||
|
||||
$bg-nav: linear-gradient(to right, $nord6, $nord4);
|
||||
.navbar, header, .dropdown {
|
||||
background: $bg-nav;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
.navbar svg {
|
||||
fill: $dark;
|
||||
}
|
||||
|
||||
transition: background 500ms ease-in-out, color 1s ease-in-out;
|
||||
|
||||
pre {
|
||||
border-color: $light;
|
||||
}
|
||||
|
||||
pre.src, pre.example {
|
||||
&::before {
|
||||
background-color: $nord6;
|
||||
color: $dark;
|
||||
}
|
||||
}
|
||||
|
||||
pre.src, pre.example, .org-src-container {
|
||||
background: $light;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: $bg-nav;
|
||||
color: $dark;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border-bottom: $tooltip-underline-size dotted $accent3;
|
||||
|
||||
.tooltiptext {
|
||||
background-color: $accent3;
|
||||
color: $dark;
|
||||
|
||||
&::after {
|
||||
border-color: $accent3 transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
a {
|
||||
box-shadow: inset 0 -3px 0 $accent3;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -23px 0 $accent3;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid $dark;
|
||||
}
|
||||
|
||||
th {
|
||||
background: darken($light, 5%);
|
||||
}
|
||||
|
||||
.gentree {
|
||||
filter: invert(0%);
|
||||
transition: filter 1s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
.black, .dark {
|
||||
$border-color: $nord1;
|
||||
|
||||
color: $nord6;
|
||||
background: $nord0;
|
||||
|
||||
$bg-nav: linear-gradient(to right, $nord3, $nord1);
|
||||
.navbar, header, .dropdown {
|
||||
background: $bg-nav;
|
||||
color: $light;
|
||||
}
|
||||
|
||||
.navbar svg {
|
||||
fill: $light;
|
||||
}
|
||||
|
||||
transition: background 500ms ease-in-out, color 1s ease-in-out;
|
||||
|
||||
pre {
|
||||
border: none;
|
||||
}
|
||||
|
||||
pre.src, pre.example {
|
||||
&::before {
|
||||
background-color: $nord0;
|
||||
color: $nord6;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
background: $bg-nav;
|
||||
color: $nord6;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border-bottom: $tooltip-underline-size dotted $nord7;
|
||||
|
||||
.tooltiptext {
|
||||
background-color: $nord7;
|
||||
color: $nord6;
|
||||
|
||||
&::after {
|
||||
border-color: $nord7 transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#content {
|
||||
a {
|
||||
box-shadow: inset 0 -3px 0 $nord7;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 -23px 0 $nord7;
|
||||
transition: box-shadow 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid $nord7;
|
||||
}
|
||||
|
||||
th {
|
||||
background: darken($nord0, 2.5%);
|
||||
}
|
||||
|
||||
.gentree {
|
||||
filter: invert(100%);
|
||||
transition: filter 1s ease-in-out;
|
||||
}
|
||||
|
||||
pre.src, pre.example, .org-src-container {
|
||||
background: $nord1;
|
||||
color: $nord5;
|
||||
}
|
||||
}
|
||||
|
||||
.black {
|
||||
background: black;
|
||||
color: $nord4;
|
||||
|
||||
$bg-nav: linear-gradient(to right, $nord2, $nord0);
|
||||
.navbar, header, .dropdown {
|
||||
background: $bg-nav;
|
||||
}
|
||||
|
||||
pre.src, pre.example, .org-src-container {
|
||||
background: $nord0;
|
||||
color: $nord4;
|
||||
}
|
||||
}
|
||||
|
||||
#lightBtn {
|
||||
background: $light;
|
||||
}
|
||||
|
||||
#darkBtn {
|
||||
background: $nord0;
|
||||
}
|
||||
|
||||
#blackBtn {
|
||||
background: black;
|
||||
}
|
||||
|
||||
pre {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
background-color: $nord11;
|
||||
}
|
||||
|
||||
.minButton {
|
||||
background-color: $nord12;
|
||||
}
|
||||
|
||||
.maxButton {
|
||||
background-color: $nord14;
|
||||
}
|
||||
|
||||
.floatButton {
|
||||
background-color: $nord15;
|
||||
}
|
Reference in New Issue