Compare commits

..

No commits in common. "master" and "1.1" have entirely different histories.
master ... 1.1

14 changed files with 565 additions and 668 deletions

3
.gitignore vendored
View File

@ -4,6 +4,3 @@
/.packages /.packages
/pubspec.lock /pubspec.lock
/.sass-cache/ /.sass-cache/
/build/
*.scssc
/Book.html

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
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"]

View File

@ -46,76 +46,65 @@
that is also simple: it is the only one that provides a ~--watch~ option so 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. it automatically recompiles SCSS code to CSS when the SCSS code is changed.
** How to run this project ** How to run this backend
This backend delivers only two main files: This backend delivers only two main files:
- =/dart/main.dart.js= The main dart file compiled to Javascript (you dont - =/dart/main.dart.js= The main dart file compiled to Javascript (you dont
need to worry about the others), need to worry about the others),
- =/style/style.css= The main style file compiled to CSS. - =/style/style.css= The main style file compiled to CSS.
This is everything you need for beautiful org-generated websites. This is everything you need for beautiful org-generated websites.
While I tried to run this inside a Docker environment serving the Dart and *** Running locally
CSS files on a HTTP port to which I tried to redirect requests, I found the You could install Dart on your machine, as well as the Ruby implementation
easiest way was to actually compile everything into a ~build~ directory and of SASS with its dependencies. Next, you will need to install ~webdev~ and
to simlink its content to the root directory of your org website like so: install the Dart dependencies:
#+BEGIN_SRC sh #+BEGIN_SRC sh
cd /path/to/your/org/website $ pub global activate webdev
ln -s /path/to/your/owb/directory/build/* $ pub get
#+END_SRC #+END_SRC
Here is what I add at the top of my org files in order to get it working: By the way, you have to ensure your Dart caches bins are in your ~$PATH~.
#+BEGIN_SRC org They are generally installed in your ~$HOME/.pub-cache/bin~ directory.
,#+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.
To compile the project, you will need to install Dart on your machine as well Then, you have to run ~start.sh~, and youre good to go! Content will be
as a SCSS compiler. While the latter is up to you, make sur the result CSS delivered on the 8080 port. If you wish to deliver content to another port,
files are in the same directory as their original SCSS counterpart. As for you can edit this file.
the Dart part of the project, you can run the following to create a release
build of the project:
#+BEGIN_SRC sh #+BEGIN_SRC sh
pub global activate webdev ./start.sh
webdev build -r
#+END_SRC #+END_SRC
If ~pub~ is not found as an executable, make sure ~$HOME/.pub-cache/bin~ is *** Docker
in your ~$PATH~. Same goes for ~webdev~ even after you install it. 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
** Running in development mode Sass. In order to run OWB, you can first build the Docker image:
# 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, dont 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 #+BEGIN_SRC sh
RELEASE=debug ./start.sh docker build . --tag owb:1.0
#+END_SRC #+END_SRC
Running it with Docker, you would use the following command: And then you can run it:
#+BEGIN_SRC sh #+BEGIN_SRC sh
docker run \ docker run \
-p 8080:8080 \ -p 8080:8080 \
-v ./web:/app/web \ -v ./web:/app/web \
-e RELEASE=debug \
--restart always \ --restart always \
--detach \ --detach \
--name owb \ --name owb \
owb:1.0 owb:1.0
#+END_SRC #+END_SRC
And with docker-compose, you would add the following line to your ~owb~ *** Docker-compose
service: This repository also provides a ~docker-compose.yml~ file for easier Docker
#+BEGIN_SRC yaml usage with ~docker-compose~. If you wish to run your backend in
environment: release-mode, simply run the following:
- RELEASE=debug #+BEGIN_SRC sh
docker-compose up --detach
#+END_SRC #+END_SRC
Any other value to this environment variable will make your backend run in ** Running in development mode
release mode (actually, it will only make ~webdev~ run in release 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, dont forget to rebuild your
image.
** How can I use this in my org files? ** How can I use this in my org files?
Lets say you serve your files on org.example.com, add the following lines to Lets say you serve your files on org.example.com, add the following lines to

10
docker-compose.yml Normal file
View File

@ -0,0 +1,10 @@
version: '3'
services:
owb:
build: .
ports:
- 8010:8080
restart: always
volumes:
- ./web:/app/web

View File

@ -1,15 +1,16 @@
name: orgwebsitebackend name: languephundrakcom
description: A bare-bone server for org-generated websites. description: A bare-bone server for my linguistics website.
version: 1.0.0 version: 1.0.0
homepage: https://labs.phundrak.com/phundrak/org-website-backend homepage: https://langue.phundrak.com
author: Lucien Cartier-Tilet <lucien@phundrak.com> author: Lucien Cartier-Tilet <lucien@phundrak.com>
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.5.0 <3.0.0'
dependencies: dependencies:
html: '^0.14.0+3'
dev_dependencies: dev_dependencies:
build_runner: ">=1.10.1 <2.0.0" build_runner: ^1.8.0
build_web_compilers: ">=2.9.0 <3.0.0" build_web_compilers: ^2.9.0
pedantic: ">=1.9.0 <2.0.0" pedantic: ^1.9.0

View File

@ -1,8 +1,3 @@
#!/bin/bash #!/bin/bash
sass --watch web/style/:web/style -tcompressed & 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

View File

@ -1,4 +1,3 @@
import './parse_sitemap.dart' show createSitemap;
import './reorganize_html.dart' show reorganizeHtml; import './reorganize_html.dart' show reorganizeHtml;
import './theme.dart' show enableThemeChanger, setTheme; import './theme.dart' show enableThemeChanger, setTheme;
@ -6,6 +5,5 @@ Future<void> main() async {
await setTheme(); await setTheme();
await reorganizeHtml().then((_) { await reorganizeHtml().then((_) {
enableThemeChanger(); enableThemeChanger();
createSitemap();
}); });
} }

View File

@ -1,43 +1,118 @@
import 'dart:html' show querySelector, Element, window; import 'dart:html';
import 'dart:svg' show SvgElement;
// You will see here and there some 'tabindex' attributes added to various HTML import './parse_sitemap.dart' show parseSitemap;
// elements, and Im sure you will ask "Why? They dont 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.
// Icons from https://materialdesignicons.com/ // Returns the title of the current webpage
final icons = { String getPageTitle() {
'home': SvgElement.svg( return querySelector('title').text;
'<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>'),
};
// Get the current pages title Element makeIcon(List<String> classes, [String id]) {
String getPageTitle() => querySelector('title')!.text!; 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'
..attributes['rel'] = 'noreferrer'
..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')));
}
// Create the home button
Future<Element> makeHome() async { Future<Element> makeHome() async {
return Element.li() return Element.li()
..classes.add('nav-item') ..classes.add('nav-item')
@ -45,113 +120,12 @@ Future<Element> makeHome() async {
'afterBegin', 'afterBegin',
Element.a() Element.a()
..attributes['href'] = '/' ..attributes['href'] = '/'
..append(makeIcon(icons['home']!))); ..append(makeIcon(['fas', 'fa-home'])));
} }
// Create a clickable icon // Add a navbar atop of the HTML body, containing two buttons:
// `t_elem` must be an SVG declared in the above `icons` variable. // - One back to home
Element makeIcon(SvgElement t_elem) { // - A dropdown to each page detected in the sitemap
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 { Future<Element> makeNavbar() async {
final navbar_content = Element.ul()..classes.add('navbar-nav'); final navbar_content = Element.ul()..classes.add('navbar-nav');
final home = await makeHome(); final home = await makeHome();

View File

@ -1,10 +1,13 @@
import 'dart:html' show HttpRequest, Element, querySelector; import 'dart:html' show HttpRequest;
final excluded_keywords = {'index', 'CONTRIBUTING', 'LICENSE', 'README'}; import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' show Element;
final excluded_keywords = ['index', 'CONTRIBUTING', 'LICENSE', 'README'];
// Get the sitemap content // Get the sitemap content
Future<String> fetchRemoteSitemap() async { Future<String> getSitemap() async {
const path = '/sitemap.html'; const path = 'sitemap.html';
try { try {
return await HttpRequest.getString(path); return await HttpRequest.getString(path);
} catch (e) { } catch (e) {
@ -14,50 +17,32 @@ Future<String> fetchRemoteSitemap() async {
} }
// Parse the list of elements and detect pages from this list // Parse the list of elements and detect pages from this list
Map<String, String> detectPages(List<Element> t_sitemap, Map<String, String> detectPages(List<Element> sitemap, [String prefix]) {
[String? t_prefix, Map<String, String>? t_links]) { final links = <String, String>{};
t_links ??= <String, String>{}; for (var elem in sitemap) {
for(var kw in excluded_keywords) {
// parse each element in sitemap if (elem.outerHtml.contains(kw)) {
for (var elem in t_sitemap) {
// if its 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; continue;
} }
t_links!['/$url'] = ((t_prefix == null) ? text : '$text ($t_prefix)')!; }
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)';
} else { } else {
final prefix = (t_prefix == null) final prefix = elem.firstChild.text;
? elem.firstChild!.text!.replaceAll('\n', '') final ul = elem.children[0].children;
: '$t_prefix / ${elem.firstChild!.text}'.replaceAll('\n', ''); links.addAll(detectPages(ul, prefix));
t_links = detectPages(elem.children[0].children, prefix, t_links);
} }
} }
return t_links!; return links;
} }
// This function returns a Map which contains all links to languages detected // This function returns a Map which contains all links to languages detected
// from the sitemap. // from the sitemap.
Future<Map<String, String>> parseSitemap() async { Future<Map<String, String>> parseSitemap() async {
final content = await fetchRemoteSitemap(); final content = await getSitemap();
final sitemap = Element.ul()..innerHtml = content; final sitemap = parse(content).getElementsByClassName('org-ul')[0].children;
final sitemapNodes = sitemap.querySelector('ul')!.children; return detectPages(sitemap);
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);
});
} }

View File

@ -1,19 +1,9 @@
import 'dart:html' import 'dart:html';
show DivElement, Element, querySelector, querySelectorAll, window;
import './navbar.dart' show makeNavbar; import './navbar.dart' show makeNavbar;
const image_header = '/img/icon.webp'; const image_header =
'https://phundrak.fra1.cdn.digitaloceanspaces.com/img/mahakala-monochrome.png';
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 { Future<Element> makeHeader() async {
var header = Element.tag('header'); var header = Element.tag('header');
@ -21,10 +11,10 @@ Future<Element> makeHeader() async {
..append(Element.img() ..append(Element.img()
..attributes['src'] = image_header ..attributes['src'] = image_header
..attributes['alt'] = 'Logo' ..attributes['alt'] = 'Logo'
..attributes['height'] = '150px' ..attributes['heigh'] = '150px'
..attributes['width'] = '150px') ..attributes['width'] = '150px')
..append(querySelector('h1')!); ..append(querySelector('h1'));
final subt = header.querySelector('.subtitle'); var subt = querySelector('.subtitle');
if (subt != null) { if (subt != null) {
header.append(subt); header.append(subt);
} }
@ -40,10 +30,12 @@ Future<void> wrapTables() async {
} }
// All images that are not nested inside a link will be linkified to themselves. // All images that are not nested inside a link will be linkified to themselves.
Future<void> linkifyImg() async { void linkifyImg() {
querySelectorAll('img').forEach((img) { querySelectorAll('img').forEach((img) {
if (img.parent!.tagName == 'P') { print(img.attributes['src']);
final link = Element.a()..attributes['href'] = img.attributes['src']!; print(img.parent.tagName);
if (img.parent.tagName == 'P') {
final link = Element.a()..attributes['href'] = img.attributes['src'];
img.insertAdjacentElement('beforeBegin', link); img.insertAdjacentElement('beforeBegin', link);
link.append(img); link.append(img);
} }
@ -51,41 +43,30 @@ Future<void> linkifyImg() async {
} }
Future<void> reorganizeHtml() async { Future<void> reorganizeHtml() async {
final ls = window.localStorage; final content = querySelector('#content');
final lastUpdate = '20210330';
if (ls['last-update'] != lastUpdate) {
ls['last-update'] = lastUpdate;
window.location.reload();
}
// Make navbar // Make navbar
final navbar = await makeNavbar(); await makeNavbar().then((navbar) {
querySelector('body').insertAdjacentElement('afterBegin', navbar);
});
// Make header // Make header
final header = await makeHeader(); await makeHeader().then((header) {
content.insertAdjacentElement('beforeBegin', header);
// Add decorative divs to source block wrappers final subtitle = querySelector('.subtitle');
await makeDecorativeButtonsOrgSrc(); if (subtitle != null) {
header.append(subtitle);
}
});
// wrap tables in container for better SCSS display // wrap tables in container for better SCSS display
await wrapTables(); await wrapTables();
// Make images not linking somewhere link to themselves linkifyImg();
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 // Add correct class to TOC
final toc = (querySelector('#table-of-contents') ?? querySelector('#toc-drop')
(Element.div() .append(querySelector('#table-of-contents')..classes.add('dropdown'));
..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 // Remove all <br> tags from HTML
querySelectorAll('br').forEach((br) => br.remove()); querySelectorAll('br').forEach((br) => br.remove());

View File

@ -1,20 +1,53 @@
import 'dart:html' show window, querySelector; 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')
};
final localStorage = window.localStorage; final localStorage = window.localStorage;
Future<void> setTheme([String? theme]) async { var currentTheme = themes[localStorage['theme']];
theme ??= localStorage['theme'] ??
(window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light');
localStorage['theme'] = theme;
querySelector('body')!
..classes.clear()
..classes.add(theme);
}
void enableThemeChanger() { void enableThemeChanger() {
final themes = <String>['light', 'dark', 'black']; final darkBtn = querySelector('#darkBtn');
themes.forEach((theme) => final lightBtn = querySelector('#lightBtn');
querySelector('#${theme}Btn')!.onClick.listen((_) => setTheme(theme))); 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();
} }

View File

@ -1,2 +0,0 @@
@import "themes.scss";
@import "style.scss";

View File

@ -1,4 +1,4 @@
// Variables ////////////////////////////////////////////////////////////////// /* Variables *****************************************************************/
$switch-small-screen: "only screen and (max-width: 600px)"; $switch-small-screen: "only screen and (max-width: 600px)";
$switch-smaller-screen: "only screen and (max-width: 400px)"; $switch-smaller-screen: "only screen and (max-width: 400px)";
@ -6,16 +6,257 @@ $switch-smaller-screen: "only screen and (max-width: 400px)";
$navbar-height: 70px; $navbar-height: 70px;
$postamble-height: 55px; $postamble-height: 55px;
// Style ////////////////////////////////////////////////////////////////////// // 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 5px $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 *********************************************************************/
* { * {
outline: none; outline: none;
} }
html {
scroll-behavior: smooth;
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -88,13 +329,14 @@ header {
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
min-height: 3rem; min-height: 3rem;
margin-top: 1rem;
padding: 0.5rem; padding: 0.5rem;
top: 0; top: 0;
box-shadow: rgba(2, 8, 20, 0.1) 0px 0.175em 0.5em; box-shadow: rgba(2, 8, 20, 0.1) 0px 0.175em 0.5em;
transform: translateX(-40%); transform: translateX(-40%);
transition: opacity 500ms ease-in-out, top 500ms ease-in-out, height 500ms ease-in-out; transition: opacity 500ms ease-in-out, top 500ms ease-in-out;
} }
.has-dropdown { .has-dropdown {
@ -106,11 +348,11 @@ header {
pointer-events: auto; pointer-events: auto;
} }
#table-of-contents, #drop-page { #table-of-contents {
top: $navbar-height; top: $navbar-height / 1.3;
opacity: 1; opacity: 1;
z-index: 5; z-index: 5;
height: 80vh; height: 500%;
pointer-events: auto; pointer-events: auto;
} }
} }
@ -122,33 +364,11 @@ header {
transform: translateX(-75%); transform: translateX(-75%);
} }
.themeBtn { #drop-page {
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; flex-direction: column;
transform: translateX(-40%);
transform: translateX(-45%); li {
top: -40px; padding: 5px;
height: 0;
min-width: 300px;
overflow-y: auto;
font-size: 0.9em;
}
@media #{$switch-small-screen} {
#drop-page {
transform: translateX(-27.5%);
} }
} }
@ -164,6 +384,7 @@ header {
a { a {
width: 100%; width: 100%;
height: 100%; height: 100%;
size: 0.7rem;
padding-left: 10px; padding-left: 10px;
padding-right: 10px; padding-right: 10px;
font-weight: bold; font-weight: bold;
@ -171,9 +392,17 @@ header {
} }
#table-of-contents { #table-of-contents {
flex-direction: column;
padding: 20px; padding: 20px;
float: right; float: right;
overflow-y: auto;
height: 0%;
width: 75%; 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; transition: height 500ms ease-in-out, opacity 500ms ease-in-out, top 500ms ease-in-out;
li { li {
@ -196,10 +425,6 @@ header {
margin: 0 auto; margin: 0 auto;
text-align: justify; text-align: justify;
@media #{$switch-small-screen} {
padding: 50px 20px;
}
a { a {
font-style: italic; font-style: italic;
} }
@ -239,10 +464,7 @@ header {
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: serif; text-align: center;
font-style: italic;
margin-left: 2em;
margin-right: 60px;
} }
h2 { h2 {
@ -283,7 +505,7 @@ h5 {
.tooltiptext { .tooltiptext {
visibility: hidden; visibility: hidden;
margin-left: -20px; margin-left: -60px; /* Half the width */
bottom: 100%; bottom: 100%;
left: 10%; left: 10%;
padding: 5px 10px; padding: 5px 10px;
@ -293,6 +515,7 @@ h5 {
position: absolute; position: absolute;
z-index: 5; z-index: 5;
&::after { &::after {
content: " "; content: " ";
position: absolute; position: absolute;
@ -332,22 +555,9 @@ th, td {
padding: 10px; padding: 10px;
} }
img, svg { img {
max-height: 600px; max-height: 600px;
max-width: 100%; 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 { .figure {
@ -358,72 +568,8 @@ ul {
padding-inline-start: 20px; padding-inline-start: 20px;
} }
.org-src-container, pre.src {
pre { background: $dark;
border-radius: .5em; color: $light;
}
.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; 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;
}

View File

@ -1,230 +0,0 @@
// 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;
}