14 Commits
1.0 ... 1.2

Author SHA1 Message Date
8ca55cb710 Better coding style 2020-05-10 12:37:22 +02:00
aa4600b588 Sitemap query now fully asynchronous
Sitemap generation is now independent from the navbar generation, and
it will be added to the navbar only once the navbar has been created.

Also, better style for code.
2020-05-10 12:00:47 +02:00
2f297b6374 Updated minimum version of the Dart SDK required 2020-05-10 11:59:10 +02:00
9f6a32f5d2 Added build directory to gitignore 2020-05-10 11:58:51 +02:00
34ac1480d3 Upgraded Dart version, better Dockerfile and renamed project
Dart is now upgraded to version 2.8

Any change in dependencies or Dart tooling won’t trigger Docker to
reinstall anything Ruby-related in the image afterward

Name of the Dart package and its homepage were changed, forgot to do
that
2020-05-09 17:45:46 +02:00
fc1556128c Fixes #4
Also put in common some code between TOC and Pages dropdown, made style
more mobile-friendly.
2020-05-09 15:16:42 +02:00
3c942b4b8f Fixes bug introduced in e819866
Table of content was made unavailable regardless of whether one was
provided by the page or not. This commit fixes this issue by making an
unavailable TOC only if one is not provided by the web page, otherwise
the TOC will be available.
2020-05-09 14:45:26 +02:00
90106df0f6 Added debug mode for backend 2020-05-07 15:46:36 +02:00
a82fa74a6b Fixes #2
Horizontal margin for content was reduced in order to better fit mobile
devices
2020-05-07 15:39:06 +02:00
e81986683c Fixes issue #5
The absence of a table of contents made the Dart code crash. This commit
adds a default TOC if non already exists.
2020-05-07 15:36:50 +02:00
cc6519c302 Link best practices, and removed font for faster loading time 2020-05-06 11:48:51 +02:00
705e35e971 Removed some fonts from styling 2020-05-05 22:46:12 +02:00
61fe6f71a4 modified from final to const as it won’t change value 2020-05-05 19:33:57 +02:00
404e79211e Made it easier to change header image 2020-05-05 19:20:28 +02:00
11 changed files with 141 additions and 100 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
/.packages
/pubspec.lock
/.sass-cache/
/build/

View File

@@ -1,7 +1,11 @@
FROM google/dart:2.7
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
@@ -11,10 +15,8 @@ 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/
# ADD . /app/
ADD web /app/
ADD start.sh /app/
CMD ["./start.sh"]

View File

@@ -100,11 +100,39 @@
#+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, dont forget to rebuild your
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
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
And with docker-compose, you would add the following line to your ~owb~
service:
#+BEGIN_SRC yaml
environment:
- RELEASE=debug
#+END_SRC
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?
Lets say you serve your files on org.example.com, add the following lines to

View File

@@ -6,5 +6,7 @@ services:
ports:
- 8010:8080
restart: always
environment:
- RELEASE=debug
volumes:
- ./web:/app/web

View File

@@ -1,11 +1,11 @@
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.7.0 <3.0.0'
dependencies:
html: '^0.14.0+3'

View File

@@ -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

View File

@@ -1,9 +1,9 @@
import './reorganize_html.dart' show reorganizeHtml;
import './theme.dart' show enableThemeChanger, setTheme;
import './parse_sitemap.dart' show getSitemap;
Future<void> main() async {
await setTheme();
await reorganizeHtml().then((_) {
enableThemeChanger();
});
await reorganizeHtml().then((_) => enableThemeChanger());
await getSitemap();
}

View File

@@ -9,9 +9,7 @@ String getPageTitle() {
Element makeIcon(List<String> classes, [String id]) {
final icon = Element.tag('i')..classes.addAll(classes);
if (id != null) {
icon.attributes['id'] = id;
}
icon.attributes['id'] = id ?? '';
return icon;
}
@@ -28,24 +26,12 @@ 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);
..append(pages);
}
Element makeShareLink(Element icon, String url) {
@@ -54,6 +40,7 @@ Element makeShareLink(Element icon, String url) {
..append(Element.a()
..attributes['href'] = url
..attributes['target'] = '_blank'
..attributes['rel'] = 'noreferrer'
..append(icon));
}

View File

@@ -1,15 +1,15 @@
import 'dart:html' show HttpRequest;
import 'dart:html' as html show HttpRequest, Element, querySelector;
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart' show Element;
import 'package:html/dom.dart' as dom show Element;
final excluded_keywords = ['index', 'CONTRIBUTING', 'LICENSE', 'README'];
final excluded_keywords = {'index', 'CONTRIBUTING', 'LICENSE', 'README'};
// Get the sitemap content
Future<String> getSitemap() async {
Future<String> fetchRemoteSitemap() async {
const path = 'sitemap.html';
try {
return await HttpRequest.getString(path);
return await html.HttpRequest.getString(path);
} catch (e) {
print('Couldnt open $path');
}
@@ -17,10 +17,10 @@ Future<String> getSitemap() async {
}
// Parse the list of elements and detect pages from this list
Map<String, String> detectPages(List<Element> sitemap, [String prefix]) {
Map<String, String> detectPages(List<dom.Element> sitemap, [String prefix]) {
final links = <String, String>{};
for (var elem in sitemap) {
for(var kw in excluded_keywords) {
for (var kw in excluded_keywords) {
if (elem.outerHtml.contains(kw)) {
continue;
}
@@ -31,7 +31,9 @@ Map<String, String> detectPages(List<Element> sitemap, [String prefix]) {
final text = elem.firstChild.text;
links[url] = (prefix == null) ? text : '$text ($prefix)';
} else {
final prefix = elem.firstChild.text;
prefix = (prefix == null)
? elem.firstChild.text
: '$prefix / ${elem.firstChild.text}';
final ul = elem.children[0].children;
links.addAll(detectPages(ul, prefix));
}
@@ -42,7 +44,31 @@ Map<String, String> detectPages(List<Element> sitemap, [String prefix]) {
// 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 content = await fetchRemoteSitemap();
final sitemap = parse(content).getElementsByClassName('org-ul')[0].children;
return detectPages(sitemap);
}
Future sleep(Duration time) async {
return Future.delayed(time);
}
Future<html.Element> getSitemap() async {
final sitemap = await parseSitemap();
final pages = <html.Element>[];
sitemap.forEach((url, name) {
final link = html.Element.li()
..classes.add('dropdown-item')
..append(html.Element.a()
..attributes['href'] = url
..innerText = name);
pages.add(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,13 +2,15 @@ import 'dart:html';
import './navbar.dart' show makeNavbar;
const image_header =
'https://phundrak.fra1.cdn.digitaloceanspaces.com/img/mahakala-monochrome.png';
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['src'] = image_header
..attributes['alt'] = 'Logo'
..attributes['heigh'] = '150px'
..attributes['width'] = '150px')
..append(querySelector('h1'));
@@ -28,10 +30,8 @@ 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'];
img.insertAdjacentElement('beforeBegin', link);
@@ -41,30 +41,31 @@ void linkifyImg() {
}
Future<void> reorganizeHtml() async {
final content = querySelector('#content');
// 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();
// 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());

View File

@@ -1,20 +1,3 @@
@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 *****************************************************************/
$switch-small-screen: "only screen and (max-width: 600px)";
@@ -79,7 +62,7 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
transition: background 500ms ease-in-out, color 1s ease-in-out;
pre {
box-shadow: 3px 3px $dark;
box-shadow: 3px 5px $dark;
border-color: $light;
}
@@ -277,7 +260,7 @@ $gradient-accent3-light-right: linear-gradient(to right, $light, $accent3);
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;
@@ -353,7 +336,7 @@ header {
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,7 +348,7 @@ header {
pointer-events: auto;
}
#table-of-contents {
#table-of-contents, #drop-page {
top: $navbar-height / 1.3;
opacity: 1;
z-index: 5;
@@ -381,15 +364,26 @@ header {
transform: translateX(-75%);
}
#drop-page {
#drop-page, #table-of-contents {
flex-direction: column;
transform: translateX(-40%);
li {
padding: 5px;
transform: translateX(-45%);
top: -40px;
height: 0;
min-width: 350px;
overflow-y: auto;
font-size: 0.9em;
}
@media #{$switch-small-screen} {
#drop-page {
transform: translateX(-27.5%);
}
}
#drop-share {
#drop-share, #drop-page {
li {
padding: 10px;
}
@@ -401,7 +395,6 @@ header {
a {
width: 100%;
height: 100%;
size: 0.7rem;
padding-left: 10px;
padding-right: 10px;
font-weight: bold;
@@ -409,17 +402,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 +427,10 @@ header {
margin: 0 auto;
text-align: justify;
@media #{$switch-small-screen} {
padding: 50px 20px;
}
a {
font-style: italic;
}