21 Commits
1.2 ... 1.2.1

Author SHA1 Message Date
8b984e301f Fix #1
Also make website more mobile-friendly for smaller devices. And ignore
temporary scssc files from cache SCSS compiling.
2020-07-27 00:18:42 +02:00
272bd93ed8 Finally fix issue #9 2020-07-26 21:22:06 +02:00
167cad1ce3 Fix some errors with webpages nested in directories 2020-07-26 17:36:45 +02:00
71f38b13e9 Added correct path in README 2020-06-23 11:16:54 +02:00
1331fdaf79 Removed Docker files 2020-06-23 11:15:11 +02:00
bffbd0c873 Updated README 2020-06-23 11:14:53 +02:00
659e406c5e Removed some leftover from previous version 2020-06-23 11:14:36 +02:00
fe05e145ab Underline of tooltiped text is now more visible 2020-05-26 20:54:37 +02:00
94ab71402d Fixed position issue with tooltips 2020-05-26 20:42:18 +02:00
b804764e24 Fixed subtitle selection 2020-05-26 02:14:13 +02:00
4ed2d6bdc4 Fixed broken sitemap parsing 2020-05-26 02:13:48 +02:00
2334518ecc Removed unused code 2020-05-26 02:13:28 +02:00
26b198d485 Code style 2020-05-26 00:52:05 +02:00
fcfd8330ca Smoother scroll behavior 2020-05-26 00:36:15 +02:00
82d3e3d018 Simpler code, removed a loop, code style 2020-05-26 00:35:55 +02:00
06994305ac Code style, updated code for better icon handling 2020-05-26 00:35:34 +02:00
99c2127cc8 Fixes #7
Fontawesome was removed from the source code and is a dependency no
longer needed by websites running with OWB
2020-05-22 01:51:55 +02:00
08b8116f6f Header image now easier to change, no need to modify source code 2020-05-21 08:29:59 +02:00
9725e24b38 Fixes #8
The default theme is now chosen based on the user’s preference stored in
their device. If the device uses a dark theme, the website’s dark theme
will be used by default. Otherwise, the light theme will be used by
default.
2020-05-21 07:34:10 +02:00
d69682ec06 Fixed issue where sitemap wouldn’t load 2020-05-21 07:33:22 +02:00
c9d263b12c Simplified file, removed unused import 2020-05-21 07:32:47 +02:00
10 changed files with 260 additions and 232 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@
/pubspec.lock /pubspec.lock
/.sass-cache/ /.sass-cache/
/build/ /build/
*.scssc

View File

@@ -1,22 +0,0 @@
FROM google/dart:2.8
WORKDIR /app
# Get Ruby Sass
RUN apt update && apt install ruby-sass ruby-dev build-essential -y
RUN gem install sass-listen
# 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
# ADD . /app/
ADD web /app/
ADD start.sh /app/
CMD ["./start.sh"]

View File

@@ -46,58 +46,41 @@
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 backend ** How to run this project
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.
*** Running locally While I tried to run this inside a Docker environment serving the Dart and
You could install Dart on your machine, as well as the Ruby implementation CSS files on a HTTP port to which I tried to redirect requests, I found the
of SASS with its dependencies. Next, you will need to install ~webdev~ and easiest way was to actually compile everything into a ~build~ directory and
install the Dart dependencies: to simlink its content to the root directory of your org website like so:
#+BEGIN_SRC sh #+BEGIN_SRC sh
$ pub global activate webdev cd /path/to/your/org/website
$ pub get ln -s /path/to/your/owb/directory/build/*
#+END_SRC #+END_SRC
By the way, you have to ensure your Dart caches bins are in your ~$PATH~. Here is what I add at the top of my org files in order to get it working:
They are generally installed in your ~$HOME/.pub-cache/bin~ directory. #+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 youre good to go! Content will be To compile the project, you will need to install Dart on your machine as well
delivered on the 8080 port. If you wish to deliver content to another port, as a SCSS compiler. While the latter is up to you, make sur the result CSS
you can edit this file. files are in the same directory as their original SCSS counterpart. As for
#+BEGIN_SRC sh the Dart part of the project, you can run the following to create a release
./start.sh build of the project:
#+END_SRC #+BEGIN_SRC sh
pub global activate webdev
webdev build -r
#+END_SRC
*** Docker If ~pub~ is not found as an executable, make sure ~$HOME/.pub-cache/bin~ is
A Dockerfile is also provided so you can run this server inside a Docker in your ~$PATH~. Same goes for ~webdev~ even after you install it.
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
And then you can run it:
#+BEGIN_SRC sh
docker run \
-p 8080:8080 \
-v ./web:/app/web \
--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
#+END_SRC
** Running in development mode ** Running in development mode
# To run this backend in development mode, you will have to remove the # To run this backend in development mode, you will have to remove the

View File

@@ -1,12 +0,0 @@
version: '3'
services:
owb:
build: .
ports:
- 8010:8080
restart: always
environment:
- RELEASE=debug
volumes:
- ./web:/app/web

View File

@@ -1,9 +1,10 @@
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;
import './parse_sitemap.dart' show getSitemap;
Future<void> main() async { Future<void> main() async {
await setTheme(); await setTheme();
await reorganizeHtml().then((_) => enableThemeChanger()); await reorganizeHtml()
await getSitemap(); .then((_) => enableThemeChanger())
.then((_) => createSitemap());
} }

View File

@@ -1,104 +1,49 @@
import 'dart:html'; import 'dart:html';
import 'dart:svg';
import './parse_sitemap.dart' show parseSitemap; // You will see here and there some 'tabindex' attributes added to various HTML
// 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.
// Returns the title of the current webpage // Icons from https://materialdesignicons.com/
String getPageTitle() { final icons = {
return querySelector('title').text; '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>'),
'sun': SvgElement.svg(
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M3.55,18.54L4.96,19.95L6.76,18.16L5.34,16.74M11,22.45C11.32,22.45 13,22.45 13,22.45V19.5H11M12,5.5A6,6 0 0,0 6,11.5A6,6 0 0,0 12,17.5A6,6 0 0,0 18,11.5C18,8.18 15.31,5.5 12,5.5M20,12.5H23V10.5H20M17.24,18.16L19.04,19.95L20.45,18.54L18.66,16.74M20.45,4.46L19.04,3.05L17.24,4.84L18.66,6.26M13,0.55H11V3.5H13M4,10.5H1V12.5H4M6.76,4.84L4.96,3.05L3.55,4.46L5.34,6.26L6.76,4.84Z" /></svg>'),
'lightbulb': SvgElement.svg(
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M12,6A6,6 0 0,1 18,12C18,14.22 16.79,16.16 15,17.2V19A1,1 0 0,1 14,20H10A1,1 0 0,1 9,19V17.2C7.21,16.16 6,14.22 6,12A6,6 0 0,1 12,6M14,21V22A1,1 0 0,1 13,23H11A1,1 0 0,1 10,22V21H14M20,11H23V13H20V11M1,11H4V13H1V11M13,1V4H11V1H13M4.92,3.5L7.05,5.64L5.63,7.05L3.5,4.93L4.92,3.5M16.95,5.63L19.07,3.5L20.5,4.93L18.37,7.05L16.95,5.63Z" /></svg>'),
'moon': SvgElement.svg(
'<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="currentColor" d="M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z" /></svg>'),
};
Element makeIcon(List<String> classes, [String id]) { // Get the current pages title
final icon = Element.tag('i')..classes.addAll(classes); String getPageTitle() => querySelector('title').text;
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');
return Element.li()
..append(Element.a()
..attributes['href'] = 'javascript:void(0)'
..append(makeIcon(['fas', 'fa-flag'])))
..classes.addAll(['nav-item', 'has-dropdown'])
..append(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')
@@ -106,19 +51,120 @@ Future<Element> makeHome() async {
'afterBegin', 'afterBegin',
Element.a() Element.a()
..attributes['href'] = '/' ..attributes['href'] = '/'
..append(makeIcon(['fas', 'fa-home']))); ..append(makeIcon(icons['home'])));
} }
// Add a navbar atop of the HTML body, containing two buttons: // Create a clickable icon
// - One back to home // `t_elem` must be an SVG declared in the above `icons` variable.
// - A dropdown to each page detected in the sitemap Element makeIcon(SvgElement t_elem) {
final icon = t_elem..classes.add('nav-icon');
return icon;
}
// Create the dropdown sitemap
Future<Element> makePages() async {
var pages = Element.ul()
..attributes['id'] = 'drop-page'
..classes.add('dropdown');
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(pages);
}
// 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, [Element t_icon]) {
return Element.li()
..classes.add('dropdown-item')
..append(Element.span()..attributes['id'] = t_btnId);
}
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', makeIcon(icons['sun'])))
..append(makeThemeItem('darkBtn', makeIcon(icons['lightbulb'])))
..append(makeThemeItem('blackBtn', makeIcon(icons['moon']))));
}
// 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();
final pages = await makePages(); final pages = await makePages();
final toc = await makeToc(); final toc = await makeToc();
final share = await makeShare(); final share = await makeShare();
final theme = await makeThemeChanger(); final theme = await makeThemeChanger();
navbar_content navbar_content
..append(home) ..append(home)

View File

@@ -7,7 +7,7 @@ final excluded_keywords = {'index', 'CONTRIBUTING', 'LICENSE', 'README'};
// Get the sitemap content // Get the sitemap content
Future<String> fetchRemoteSitemap() async { Future<String> fetchRemoteSitemap() async {
const path = 'sitemap.html'; const path = '/sitemap.html';
try { try {
return await html.HttpRequest.getString(path); return await html.HttpRequest.getString(path);
} catch (e) { } catch (e) {
@@ -17,28 +17,30 @@ 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<dom.Element> sitemap, [String prefix]) { Map<String, String> detectPages(List<dom.Element> t_sitemap,
final links = <String, String>{}; [String t_prefix, Map<String, String> t_links]) {
for (var elem in sitemap) { t_links ??= <String, String>{};
for (var kw in excluded_keywords) {
if (elem.outerHtml.contains(kw)) { // parse each element in sitemap
continue; for (var elem in t_sitemap) {
} // if its a link
}
if (elem.innerHtml.startsWith('<a')) { if (elem.innerHtml.startsWith('<a')) {
elem = elem.firstChild; elem = elem.firstChild;
final url = elem.attributes['href']; final url = elem.attributes['href'];
final text = elem.firstChild.text; final text = elem.firstChild.text;
links[url] = (prefix == null) ? text : '$text ($prefix)'; if (excluded_keywords.contains(text) ||
excluded_keywords.contains(url.substring(0, url.length - 5))) {
continue;
}
t_links['/$url'] = (t_prefix == null) ? text : '$text ($t_prefix)';
} else { } else {
prefix = (prefix == null) final prefix = (t_prefix == null)
? elem.firstChild.text ? elem.firstChild.text.replaceAll('\n', '')
: '$prefix / ${elem.firstChild.text}'; : '$t_prefix / ${elem.firstChild.text}'.replaceAll('\n', '');
final ul = elem.children[0].children; t_links = detectPages(elem.children[0].children, prefix, t_links);
links.addAll(detectPages(ul, prefix));
} }
} }
return links; return t_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
@@ -49,26 +51,15 @@ Future<Map<String, String>> parseSitemap() async {
return detectPages(sitemap); return detectPages(sitemap);
} }
Future sleep(Duration time) async { Future<void> createSitemap() async {
return Future.delayed(time);
}
Future<html.Element> getSitemap() async {
final sitemap = await parseSitemap(); final sitemap = await parseSitemap();
final pages = <html.Element>[]; final pages = html.querySelector('#drop-page');
sitemap.forEach((url, name) { sitemap.forEach((url, name) {
final link = html.Element.li() final link = html.Element.li()
..classes.add('dropdown-item') ..classes.add('dropdown-item')
..append(html.Element.a() ..append(html.Element.a()
..attributes['href'] = url ..attributes['href'] = url
..innerText = name); ..innerText = name);
pages.add(link); pages.append(link);
}); });
var drop_container;
do {
await sleep(Duration(seconds: 1));
drop_container = html.querySelector('#drop-page');
} while (drop_container != null);
pages.forEach((link) => drop_container.append(link));
return drop_container;
} }

View File

@@ -2,8 +2,7 @@ import 'dart:html';
import './navbar.dart' show makeNavbar; import './navbar.dart' show makeNavbar;
const image_header = const image_header = '/img/icon.png';
'https://phundrak.fra1.cdn.digitaloceanspaces.com/img/mahakala-monochrome.png';
Future<Element> makeHeader() async { Future<Element> makeHeader() async {
var header = Element.tag('header'); var header = Element.tag('header');
@@ -14,7 +13,7 @@ Future<Element> makeHeader() async {
..attributes['heigh'] = '150px' ..attributes['heigh'] = '150px'
..attributes['width'] = '150px') ..attributes['width'] = '150px')
..append(querySelector('h1')); ..append(querySelector('h1'));
var subt = querySelector('.subtitle'); final subt = header.querySelector('.subtitle');
if (subt != null) { if (subt != null) {
header.append(subt); header.append(subt);
} }

View File

@@ -21,8 +21,6 @@ final themes = {
final localStorage = window.localStorage; final localStorage = window.localStorage;
var currentTheme = themes[localStorage['theme']];
void enableThemeChanger() { void enableThemeChanger() {
final darkBtn = querySelector('#darkBtn'); final darkBtn = querySelector('#darkBtn');
final lightBtn = querySelector('#lightBtn'); final lightBtn = querySelector('#lightBtn');
@@ -34,10 +32,12 @@ void enableThemeChanger() {
} }
Future<void> setTheme() async { Future<void> setTheme() async {
if (currentTheme == null) { final currentTheme = themes[localStorage['theme']] ??
currentTheme = themes['light']; () {
localStorage['theme'] = currentTheme.getName(); final devicePrefersDark =
} window.matchMedia('(prefers-color-scheme: dark)').matches;
return themes[devicePrefersDark ? 'dark' : 'light'];
}();
querySelector('body') querySelector('body')
..classes.clear() ..classes.clear()
..classes.add(currentTheme.getName()); ..classes.add(currentTheme.getName());

View File

@@ -6,6 +6,8 @@ $switch-smaller-screen: "only screen and (max-width: 400px)";
$navbar-height: 70px; $navbar-height: 70px;
$postamble-height: 55px; $postamble-height: 55px;
$tooltip-underline-size: 3px;
// Themes ///////////////////////////////////////////////////////////////////// // Themes /////////////////////////////////////////////////////////////////////
$dark: rgba( 52, 73, 94, 1); $dark: rgba( 52, 73, 94, 1);
@@ -77,13 +79,17 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
background: $bg-nav; background: $bg-nav;
} }
.navbar svg {
fill: $dark;
}
.status { .status {
background: $gradient-accent3-light-left; background: $gradient-accent3-light-left;
color: $dark; color: $dark;
} }
.tooltip { .tooltip {
border-bottom: 1px dotted $accent3; border-bottom: $tooltip-underline-size dotted $accent3;
.tooltiptext { .tooltiptext {
background-color: $accent3; background-color: $accent3;
@@ -151,13 +157,17 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
background: $bg-nav; background: $bg-nav;
} }
.navbar svg {
fill: $light;
}
.status { .status {
background: $gradient-accent2-dark-left; background: $gradient-accent2-dark-left;
color: $light; color: $light;
} }
.tooltip { .tooltip {
border-bottom: 1px dotted $accent1; border-bottom: $tooltip-underline-size dotted $accent1;
.tooltiptext { .tooltiptext {
background-color: $accent1; background-color: $accent1;
@@ -170,8 +180,8 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
} }
.dropdown { .dropdown {
background: $accent3; background: $dark;
color: $dark; color: $light;
} }
#content { #content {
@@ -257,6 +267,10 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
outline: none; outline: none;
} }
html {
scroll-behavior: smooth;
}
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -359,11 +373,34 @@ header {
} }
#theme-dropdown { #theme-dropdown {
width: 250px; width: 150px;
flex-direction: row; flex-direction: row;
transform: translateX(-75%); transform: translateX(-75%);
} }
#lightBtn, #darkBtn, #blackBtn {
content: ' ';
border: 2px solid white;
border-radius: 50%;
width: 1rem;
height: 1rem;
display: inline-block;
vertical-align: middle;
margin-right: 10px;
}
#lightBtn {
background: $light;
}
#darkBtn {
background: $dark;
}
#blackBtn {
background: $black;
}
#drop-page, #table-of-contents { #drop-page, #table-of-contents {
flex-direction: column; flex-direction: column;
@@ -371,7 +408,7 @@ header {
top: -40px; top: -40px;
height: 0; height: 0;
min-width: 350px; min-width: 300px;
overflow-y: auto; overflow-y: auto;
font-size: 0.9em; font-size: 0.9em;
@@ -511,7 +548,7 @@ h5 {
.tooltiptext { .tooltiptext {
visibility: hidden; visibility: hidden;
margin-left: -60px; /* Half the width */ margin-left: -20px;
bottom: 100%; bottom: 100%;
left: 10%; left: 10%;
padding: 5px 10px; padding: 5px 10px;
@@ -521,7 +558,6 @@ h5 {
position: absolute; position: absolute;
z-index: 5; z-index: 5;
&::after { &::after {
content: " "; content: " ";
position: absolute; position: absolute;
@@ -566,6 +602,11 @@ img {
max-width: 100%; max-width: 100%;
} }
.nav-icon {
max-width: 20px;
max-height: 20px;
}
.figure { .figure {
padding: 0; padding: 0;
} }