Compare commits

..

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

24 changed files with 5101 additions and 10126 deletions

202
README.md
View File

@ -5,26 +5,23 @@ deployment
### Install the dependencies ### Install the dependencies
1. curl 1. flex (for hmmlib)
2. curl (for cinera)
### Download, and copy the parser into place ### Download, and prepare the parser
1. `git clone https://git.handmade.network/Annotation-Pushers/Annotation-System.git` 1. `git clone git@gitssh.handmade.network:Annotation-Pushers/Annotation-System.git`
2. `cd Annotation-System/cinera` 2. `cd Annotation-System/hmmlib`
3. `cp ../hmmlib2/hmmlib.h .` 3. `make`
4. `cp hmml.a hmmlib.h ../cinera/`
5. `cd ../cinera/`
Note: For each parser update, remember to copy it into place. Note: For each parser update, remember to make and copy it into place.
### Build ### Build
1. `$SHELL cinera.c` 1. `$SHELL cinera.c`
### Configure
cinera -h
This documents the configuration file format and default settings.
### Configure the server ### Configure the server
If you enforce a strict Content Security Policy and X-Frame-Options in your If you enforce a strict Content Security Policy and X-Frame-Options in your
@ -41,24 +38,56 @@ and [Hardening your HTTP response headers](https://scotthelme.co.uk/hardening-yo
### Run ### Run
cinera -c /path/to/config.conf #### Single Edition operation
cinera test.hmml
This simply generates an HTML file (and updates `cinera_topics.css` if needed)
from `test.hmml` and outputs to `out.html` (configurable with -o).
#### Project Edition operation
cinera -p ProjectID
Setting the ProjectID with the `-p` flag triggers Project Edition. In this
edition _Cinera_ monitors the Project Input Directory for new, edited and
deleted .hmml files, and generates one table of contents / search page and a
player page each for valid sets of annotations (or removes them, if needed).
By default all directories - input and output - are set to the current working
directory. Typical operation will involve setting these flags:
-d Project Input Directory, the directory where the .hmml files reside
-r Asset Root Directory, path shallower than or equal to the CSS, Images and
JS directories
-R Asset Root URL, corresponding to the Root Directory
-c CSS Directory, relative to Root
-i Images Directory, relative to Root
-j JS Directory, relative to Root
-b Output Base Directory, location of the table of contents / search page
-B Output Base URL, corresponding to the Output Base Directory
-t Template Directory
-x Search Template Location, relative to Template Directory
-y Player Template Location, relative to Template Directory
#### Templates #### Templates
*(Global) Search Template* *Search Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_ - `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_
- `<!-- __CINERA_SEARCH__ -->` _the table of contents and search functionality_ - `<!-- __CINERA_SEARCH__ -->` _the table of contents and search functionality_
*Player Template* *Player Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_ - `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_
- `<!-- __CINERA_MENUS__ -->`
- `<!-- __CINERA_PLAYER__ -->` - `<!-- __CINERA_PLAYER__ -->`
- `<!-- __CINERA_SCRIPT__ -->` _must come after `<!-- __CINERA_MENUS__ -->` and `<!-- __CINERA_PLAYER__ -->`_
*Optional tags available for use in your Player Template* *Optional tags available for use in your Player Template*
- `<!-- __CINERA_TITLE__ -->` - `<!-- __CINERA_TITLE__ -->`
- `<!-- __CINERA_VIDEO_ID__ -->` - `<!-- __CINERA_VIDEO_ID__ -->`
- `<!-- __CINERA_VOD_PLATFORM__ -->` - `<!-- __CINERA_VOD_PLATFORM__ -->`
*Other tags available for use in any template* *Other tags available for use in either template*
- Asset tags: - Asset tags:
- `<!-- __CINERA_ASSET__ path.ext -->` - `<!-- __CINERA_ASSET__ path.ext -->`
General purpose tag that outputs the URL of the specified asset General purpose tag that outputs the URL of the specified asset
@ -67,50 +96,28 @@ and [Hardening your HTTP response headers](https://scotthelme.co.uk/hardening-yo
General purpose tag that outputs the URL of the specified asset General purpose tag that outputs the URL of the specified asset
relative to the Images Directory (-i) relative to the Images Directory (-i)
- `<!-- __CINERA_CSS__ path.ext -->` - `<!-- __CINERA_CSS__ path.ext -->`
Convenience tag that outputs a `<link rel="stylesheet"...>` node Convenience tag that outputs a <link rel="stylesheet"...> node
for the specified asset relative to the CSS Directory (-c), for for the specified asset relative to the CSS Directory (-c), for
use inside your `<head>` block use inside your <head> block
- `<!-- __CINERA_JS__ path.ext -->` - `<!-- __CINERA_JS__ path.ext -->`
Convenience tag that outputs a `<script type="text/javascript"...>` Convenience tag that outputs a <script type="text/javascript"...>
node for the specified asset relative to the JS Directory (-j), node for the specified asset relative to the JS Directory (-j),
for use wherever a `<script>` node is valid for use wherever a <script> node is valid
The path.ext in these tags supports parent directories to locate the The path.ext in these tags supports parent directories to locate the
asset file relative to its specified type directory (generic, CSS, image asset file relative to its specified type directory (generic, CSS, image
or JS), including the "../" directory, and paths containing spaces must or JS), including the "../" directory, and paths containing spaces must
be surrounded with double-quotes (\-escapable if the quoted path itself be surrounded with double-quotes (\-escapable if the quoted path itself
contains double-quotes). contains double-quotes).
All these asset tags additionally perform versioning, appending a query string All these asset tags additionally perform revving, appending a query
(-Q) and the file's checksum to the URL. Changes to a file trigger a rehash and string (-Q) and the file's checksum to the URL. Changes to a file
edit of all HTML pages citing this asset. trigger a rehash and edit of all HTML pages citing this asset.
- Navigation / Menu tags:
- `<!-- __CINERA_NAV__ -->`
This menu will contain only the current project, its siblings and
subprojects of both the aforementioned
- `<!-- __CINERA_GLOBAL_NAV__ -->`
This menu will contain every project in the configuration
These navigation tags additionally take one parameter, e.g.
`<!-- __CINERA_NAV__ horizontal -->`:
- `dropdown`
A dropdown menu, that opens on hover
- `horizontal`
More-or-less the same as `dropdown`, but always open
- `plain`
A straightforward `<ul>` for you to style how you like
- `<!-- __CINERA_PROJECT__ -->` - `<!-- __CINERA_PROJECT__ -->`
The project's `html_title` if configured, otherwise its `title`
- `<!-- __CINERA_PROJECT_PLAIN__ -->`
The project's `title`
- `<!-- __CINERA_PROJECT_ID__ -->` - `<!-- __CINERA_PROJECT_ID__ -->`
- `<!-- __CINERA_PROJECT_LINEAGE__ -->`
The IDs of all projects from the root to the current project, separated
by a spaced-slash
- `<!-- __CINERA_SEARCH_URL__ -->` - `<!-- __CINERA_SEARCH_URL__ -->`
- `<!-- __CINERA_THEME__ -->` - `<!-- __CINERA_THEME__ -->`
- `<!-- __CINERA_URL__ -->` - `<!-- __CINERA_URL__ -->` _Only really usable if BaseURL is set (-B)_
- `<!-- __CINERA_CUSTOM0__ -->` - `<!-- __CINERA_CUSTOM0__ -->`
- `<!-- __CINERA_CUSTOM1__ -->` - `<!-- __CINERA_CUSTOM1__ -->`
- `<!-- __CINERA_CUSTOM2__ -->` - `<!-- __CINERA_CUSTOM2__ -->`
@ -128,15 +135,106 @@ invalid, _Cinera_ will tell you what's wrong.
#### Arguments #### Arguments
Usage: ./cinera [option(s)] Usage: ./cinera [option(s)] filename(s)
Options: Options:
-c <config file path> Paths: (advisedly universal, but may be set per-(sub)project as required)
Set the main config file path -r <assets root directory>
Defaults to: $XDG_CONFIG_HOME/cinera/cinera.conf Override default assets root directory (".")
-0 -R <assets root URL>
Dry-run mode. Parse and print the config, but do not modify the Override default assets root URL ("")
filesystem IMPORTANT: -r and -R must correspond to the same location
UNSUPPORTED: If you move files from RootDir, the RootURL should
correspond to the resulting location
-c <CSS directory path>
Override default CSS directory (""), relative to root
-i <images directory path>
Override default images directory (""), relative to root
-j <JS directory path>
Override default JS directory (""), relative to root
-Q <revved resources query string>
Override default query string ("r")
To disable revved resources, set an empty string with -Q ""
Project Settings:
-p <project ID>
Set the project ID, triggering PROJECT EDITION
-m <default medium>
Override default default medium ("programming")
Known project defaults:
bitwise: programming
book: research
coad: research
reader: research
riscy: programming
risc: speech
chat: speech
code: programming
intro-to-c: programming
misc: admin
ray: programming
hmdshow: speech
lecture: speech
stream: programming
special: programming
obbg: programming
sysadmin: admin
-s <style>
Set the style / theme, corresponding to a cinera__*.css file
This is equal to the "project" field in the HMML files by default
Project Input Paths
-d <annotations directory>
Override default annotations directory (".")
-t <templates directory>
Override default templates directory (".")
-x <search template location>
Set search template file path, either absolute or relative to
template directory, and enable integration
-y <player template location>
Set player template file path, either absolute or relative
to template directory, and enable integration
Project Output Paths
-b <base output directory>
Override project's default base output directory (".")
-B <base URL>
Override default base URL ("")
NOTE: This must be set, if -n or -a are to be used
-n <search location>
Override default search location (""), relative to base
-a <player location>
Override default player location (""), relative to base
NOTE: The PlayerURLPrefix is currently hardcoded in cinera.c but
will be configurable in the full configuration system
Single Edition Output Path
-o <output location>
Override default output player location ("out.html")
-1
Open search result links in the same browser tab
NOTE: Ideal for a guide embedded in an iframe
-f
Force integration with an incomplete template
-g
Ignore video privacy status
NOTE: For use with projects whose videos are known to all be public,
to save us having to check their privacy status
-q
Quit after syncing with annotation files in project input directory
UNSUPPORTED: This is likely to be removed in the future
-w
Force quote cache rebuild (memory aid: "wget")
-l <n>
Override default log level (0), where n is from 0 (terse) to 7 (verbose)
-u <seconds>
Override default update interval (4)
-e -e
Display (examine) database and exit Display (examine) database and exit
-v -v

File diff suppressed because it is too large Load Diff

View File

@ -172,8 +172,8 @@ ul.cineraNavPlain li.current > a {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 50%;
padding: 2px; padding: 2px;
margin: 2px;
height: 42px; height: 42px;
width: 42px; width: 42px;
@ -235,8 +235,6 @@ ul.cineraNavPlain li.current > a {
.cineraMenuContainer.visible .cineraMenuContainer.visible
{ {
display: block; display: block;
max-height: 88vh;
overflow-y: auto;
} }
.cineraFilterProject, .cineraFilterProject,
@ -252,7 +250,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenuItem .cineraMenuItem
{ {
font-weight: bold; font-weight: bold;
font-size: 1rem; font-size: 12px;
} }
.cineraQueryContainer { .cineraQueryContainer {
@ -284,7 +282,7 @@ ul.cineraNavPlain li.current > a {
position: absolute; position: absolute;
top: 2px; top: 2px;
right: 5px; right: 5px;
font-size: .5rem; font-size: small;
color: #000000; color: #000000;
height: 100%; height: 100%;
display: none; display: none;
@ -334,7 +332,7 @@ ul.cineraNavPlain li.current > a {
border: 1px solid; border: 1px solid;
text-align: center; text-align: center;
font-size: 1rem; font-size: 16px;
font-weight: bold; font-weight: bold;
flex-direction: column; flex-direction: column;
@ -413,7 +411,7 @@ ul.cineraNavPlain li.current > a {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
flex-shrink: 0; flex-shrink: 0;
font-size: .75rem; font-size: 12px;
line-height: 16px; line-height: 16px;
padding: 5px; padding: 5px;
vertical-align: top; vertical-align: top;
@ -506,16 +504,15 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .cineraHelp, .cineraMenus > .cineraHelp,
.cineraHelp { .cineraHelp {
cursor: pointer; cursor: pointer;
box-sizing: content-box;
border: 1px solid; border: 1px solid;
border-radius: 4px; border-radius: 4px;
height: .5rem; height: 6px;
width: .5rem;
padding: 4px; padding: 4px;
width: 6px;
margin: 2px; margin: 2px;
font-size: .75rem; font-size: 12px;
font-weight: bold; font-weight: bold;
line-height: .5rem; line-height: 6px;
text-align: center; text-align: center;
z-index: 64; z-index: 64;
} }
@ -540,20 +537,10 @@ ul.cineraNavPlain li.current > a {
display: block; display: block;
} }
.cineraHelp .help_container .help_custom_index {
background-color: #159 !important;
}
.cineraHelp .help_container h2 .help_title_key {
padding: .2em;
border: 1px solid;
border-radius: 4px;
}
.cineraHelp .help_container .help_key { .cineraHelp .help_container .help_key {
box-sizing: content-box; box-sizing: content-box;
font-family: Inconsolata; font-family: Inconsolata;
font-size: 1rem; font-size: 16px;
border: 1px solid; border: 1px solid;
display: inline-block; display: inline-block;
background-color: #111111; /* Per project */ background-color: #111111; /* Per project */
@ -602,7 +589,7 @@ ul.cineraNavPlain li.current > a {
.cineraHelp .help_container h2 { .cineraHelp .help_container h2 {
font-size: 1rem; font-size: 16px;
margin-bottom: 8px; margin-bottom: 8px;
} }
@ -624,11 +611,12 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu .credits_container { .cineraMenus > .menu .credits_container {
border: 1px solid; border: 1px solid;
border-top: none; border-top: none;
z-index: -1; /* NOTE(matt): Using "display: none" to hide them proved problematic for scrolling non-visible menus */ display: none;
overflow-y: auto; overflow-y: auto;
position: absolute; position: absolute;
right: 0; right: 0;
top: 100%; top: 100%;
z-index: 8;
} }
.cineraMenus > .menu .refs, .cineraMenus > .menu .refs,
@ -648,7 +636,7 @@ ul.cineraNavPlain li.current > a {
} }
.cineraMenus > .menu .visible { .cineraMenus > .menu .visible {
z-index: 8; display: block;
} }
.cineraMenus > .menu > .refs .ref { .cineraMenus > .menu > .refs .ref {
@ -681,7 +669,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .refs .ref .timecode, .cineraMenus > .menu > .refs .ref .timecode,
.cineraMenus > .menu > .filter_container .filter_mode, .cineraMenus > .menu > .filter_container .filter_mode,
.cineraMenus > .menu > .link_container #cineraLinkMode { .cineraMenus > .menu > .link_container #cineraLinkMode {
font-size: .75rem; font-size: 12px;
} }
.cineraMenus > .menu > .filter_container .filter_mode, .cineraMenus > .menu > .filter_container .filter_mode,
@ -711,7 +699,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .refs .ref .quote_byline, .cineraMenus > .menu > .refs .ref .quote_byline,
.cineraMenus > .menu > .filter_container .filter_title, .cineraMenus > .menu > .filter_container .filter_title,
.cineraMenus > .menu > .credits_container .credit .role { .cineraMenus > .menu > .credits_container .credit .role {
font-size: .64rem; font-size: 10px;
} }
.cineraMenus > .menu > .refs .ref .source, .cineraMenus > .menu > .refs .ref .source,
@ -746,11 +734,6 @@ ul.cineraNavPlain li.current > a {
margin-right: 4px; margin-right: 4px;
} }
.cineraMenus > .menu > .filter_container .filter_header {
position: sticky;
top: 0;
}
.cineraMenus > .menu > .filter_container .filter_mode, .cineraMenus > .menu > .filter_container .filter_mode,
.cineraMenus > .menu > .link_container #cineraLinkMode { .cineraMenus > .menu > .link_container #cineraLinkMode {
cursor: pointer; cursor: pointer;
@ -770,13 +753,11 @@ ul.cineraNavPlain li.current > a {
text-align: center; text-align: center;
} }
.cineraMenus > .menu > .filter_container .filter_header .filter_titles,
.cineraMenus > .menu > .filter_container .filters { .cineraMenus > .menu > .filter_container .filters {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
} }
.cineraMenus > .menu > .filter_container .filter_header .filter_titles > *,
.cineraMenus > .menu > .filter_container .filters > * { .cineraMenus > .menu > .filter_container .filters > * {
width: 50%; width: 50%;
flex-grow: 1; flex-grow: 1;
@ -842,14 +823,6 @@ ul.cineraNavPlain li.current > a {
overflow: hidden; overflow: hidden;
} }
.cineraPlayerContainer .video_container .direct_video {
display: flex;
}
.cineraPlayerContainer .video_container .direct_video video {
outline: none;
}
.cineraPlayerContainer .markers_container { .cineraPlayerContainer .markers_container {
flex-shrink: 1; flex-shrink: 1;
overflow-y: scroll; overflow-y: scroll;
@ -859,7 +832,7 @@ ul.cineraNavPlain li.current > a {
.cineraPlayerContainer .markers_container > .episodeMarker { .cineraPlayerContainer .markers_container > .episodeMarker {
text-decoration: none; text-decoration: none;
display: flex; display: flex;
font-size: .75rem; font-size: 11px;
font-weight: bold; font-weight: bold;
} }
@ -887,7 +860,7 @@ ul.cineraNavPlain li.current > a {
} }
.cineraPlayerContainer .markers_container > .episodeMarker div:not(:nth-of-type(2)) { .cineraPlayerContainer .markers_container > .episodeMarker div:not(:nth-of-type(2)) {
font-size: .9rem; font-size: 15px;
font-weight: normal; font-weight: normal;
} }
@ -928,7 +901,7 @@ ul.cineraNavPlain li.current > a {
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent { .cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
display: inline-block; display: inline-block;
font-size: .8rem; font-size: 14px;
} }
.cinera:not(.mobile) .cineraPlayerContainer .markers_container > .markers .marker.skip { .cinera:not(.mobile) .cineraPlayerContainer .markers_container > .markers .marker.skip {
@ -967,7 +940,7 @@ ul.cineraNavPlain li.current > a {
} }
.cineraPlayerContainer .markers_container > .markers .marker .timecode { .cineraPlayerContainer .markers_container > .markers .marker .timecode {
font-size: .6rem; font-size: 9px;
font-style: normal; font-style: normal;
padding-right: 8px; padding-right: 8px;
position: relative; position: relative;
@ -985,7 +958,6 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .filter_container .filter_content .category, .cineraMenus > .menu > .filter_container .filter_content .category,
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category { .cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category {
box-sizing: content-box;
border-radius: 50%; border-radius: 50%;
height: 5px; height: 5px;
width: 5px; width: 5px;
@ -1006,21 +978,6 @@ ul.cineraNavPlain li.current > a {
/* NOTE(matt): Mobile Style */ /* NOTE(matt): Mobile Style */
#cineraIndex.mobile .cineraMenu.ViewSettings .cineraMenuItem,
#cineraIndex.mobile .cineraFilterProject .cineraText,
#cineraIndex.mobile .cineraIndexEntries div a {
display: flex;
box-sizing: border-box;
align-items: center;
min-height: 10mm; /* NOTE(matt): From https://web.dev/accessible-tap-targets/ */
}
#cineraIndex.mobile .cineraMenu.ViewSettings .cineraMenuItem,
#cineraIndex.mobile .cineraFilterProject .cineraText,
#cineraIndex.mobile .cineraIndexEntries div {
margin: 8px; /* NOTE(matt): From https://web.dev/accessible-tap-targets/ */
}
#cineraIndex.mobile #cineraResults .dayContainer { #cineraIndex.mobile #cineraResults .dayContainer {
flex-direction: column; flex-direction: column;
} }

View File

@ -1,23 +0,0 @@
// NOTE(matt): To use, source this file inside the element you wish to be obscured while processing occurs.
// After processing is complete, call FlipClear()
var ThisScriptElement = document.currentScript;
var NodeToHide = ThisScriptElement.parentNode;
var CineraClearElement = document.createElement("DIV");
CineraClearElement.style.position = "fixed";
CineraClearElement.style.height = "100%";
CineraClearElement.style.width = "100%";
CineraClearElement.style.zIndex = 64;
var CineraPlacedClear = NodeToHide.appendChild(CineraClearElement);
var Colour = getBackgroundColourRGB(CineraPlacedClear);
CineraPlacedClear.style.background = "rgb(" + Colour.R + ", " + Colour.G + ", " + Colour.B + ")"
document.body.style.overflowY = "hidden";
function
FlipClear(BodyOverflowY)
{
document.body.style.overflowY = BodyOverflowY ? BodyOverflowY : null;
CineraPlacedClear.parentNode.removeChild(CineraPlacedClear);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,40 @@
var cinera = document.querySelector(".cinera");
var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location; var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location;
var originalTextContent = {
TitleQuotes: null,
TitleReferences: null,
TitleCredits: null,
EpisodePrev: null,
EpisodeNext: null,
};
var menuState = [];
var titleBar = cinera.querySelector(".cineraMenus");
var quotesMenu = null;
var referencesMenu = null;
var filterMenu = null;
var viewsMenu = null;
var linkMenu = null;
var creditsMenu = null;
var sourceMenus = null;
var helpButton = null;
var helpDocumentation = null;
// NOTE(matt): One set of markers per page. There is code to support multiple, which we may want to extend everywhere
var MarkersContainer = cinera.querySelector(".markers_container");
var views = {
REGULAR: 0,
THEATRE: 1,
SUPERTHEATRE: 2,
};
var devices = {
DESKTOP: 0,
MOBILE: 1,
};
var CineraProps = { var CineraProps = {
C: null, C: null,
V: views.REGULAR, V: views.REGULAR,
@ -15,26 +50,266 @@ var CineraProps = {
FlexDirection: null, FlexDirection: null,
JustifyContent: null, JustifyContent: null,
O: null, O: null,
IsMobile: IsMobile(), D: IsMobile() ? devices.MOBILE : devices.DESKTOP,
ScrollX: null, ScrollX: null,
ScrollY: null, ScrollY: null,
VODPlatform: null,
}; };
CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile); CineraProps.O = GetRealOrientation(CineraProps.IsMobile);
if(titleBar)
{
quotesMenu = titleBar.querySelector(".quotes_container");
if(quotesMenu)
{
originalTextContent.TitleQuotes = quotesMenu.previousElementSibling.textContent;
menuState.push(quotesMenu);
var quoteItems = quotesMenu.querySelectorAll(".ref");
if(quoteItems)
{
for(var i = 0; i < quoteItems.length; ++i)
{
quoteItems[i].addEventListener("mouseenter", function(ev) {
mouseOverQuotes(this);
})
};
}
var quoteTimecodes = quotesMenu.querySelectorAll(".refs .ref .ref_indices .timecode");
for (var i = 0; i < quoteTimecodes.length; ++i) {
quoteTimecodes[i].addEventListener("click", function(ev) {
if (player) {
var time = ev.currentTarget.getAttribute("data-timestamp");
mouseSkipToTimecode(player, time, ev);
}
});
}
var lastFocusedQuote = null;
}
referencesMenu = titleBar.querySelector(".references_container");
if(referencesMenu)
{
originalTextContent.TitleReferences = referencesMenu.previousElementSibling.textContent;
menuState.push(referencesMenu);
var referenceItems = referencesMenu.querySelectorAll(".ref");
if(referenceItems)
{
for(var i = 0; i < referenceItems.length; ++i)
{
referenceItems[i].addEventListener("mouseenter", function(ev) {
mouseOverReferences(this);
})
};
var lastFocusedReference = null;
var lastFocusedIdentifier = null;
}
var refTimecodes = referencesMenu.querySelectorAll(".refs .ref .ref_indices .timecode");
for (var i = 0; i < refTimecodes.length; ++i) {
refTimecodes[i].addEventListener("click", function(ev) {
if (player) {
var time = ev.currentTarget.getAttribute("data-timestamp");
mouseSkipToTimecode(player, time, ev);
}
});
}
}
if(referencesMenu || quotesMenu)
{
var refSources = titleBar.querySelectorAll(".refs .ref"); // This is for both quotes and refs
for (var i = 0; i < refSources.length; ++i) {
refSources[i].addEventListener("click", function(ev) {
if (player) {
player.pause();
}
});
}
}
filterMenu = titleBar.querySelector(".filter_container");
if(filterMenu)
{
menuState.push(filterMenu);
var lastFocusedCategory = null;
var lastFocusedTopic = null;
var lastFocusedMedium = null;
var filter = filterMenu.parentNode;
var filterModeElement = filter.querySelector(".filter_mode");
filterModeElement.addEventListener("click", function(ev) {
ev.stopPropagation();
toggleFilterMode();
});
var filterMode = filterModeElement.classList[1];
var filterItems = filter.querySelectorAll(".filter_content");
var filterInitState = new Object();
var filterState = new Object();
for(var i = 0; i < filterItems.length; ++i)
{
filterItems[i].addEventListener("mouseenter", function(ev) {
navigateFilter(this);
})
filterItems[i].addEventListener("click", function(ev) {
ev.stopPropagation();
filterItemToggle(this);
});
var filterItemName = filterItems[i].classList.item(1);
if(filterItems[i].parentNode.classList.contains("filter_topics"))
{
filterInitState[filterItemName] = { "type" : "topic", "off": (filterItems[i].classList.item(2) == "off") };
filterState[filterItemName] = { "type" : "topic", "off": (filterItems[i].classList.item(2) == "off") };
}
else
{
filterInitState[filterItemName] = { "type" : "medium", "off": (filterItems[i].classList.item(2) == "off") };
filterState[filterItemName] = { "type" : "medium", "off": (filterItems[i].classList.item(2) == "off") };
}
}
}
viewsMenu = titleBar.querySelector(".views");
if(viewsMenu && CineraProps.D !== devices.MOBILE)
{
menuState.push(viewsMenu);
var viewsContainer = viewsMenu.querySelector(".views_container");
viewsMenu.addEventListener("mouseenter", function(ev) {
handleMouseOverViewsMenu();
});
viewsMenu.addEventListener("mouseleave", function(ev) {
viewsContainer.style.display = "none";
});
var viewItems = viewsMenu.querySelectorAll(".view");
for(var i = 0; i < viewItems.length; ++i)
{
viewItems[i].addEventListener("click", function(ev) {
switch(this.getAttribute("data-id"))
{
case "regular":
case "theatre":
{
toggleTheatreMode();
} break;
case "super":
{
toggleSuperTheatreMode();
} break;
}
});
}
}
linkMenu = titleBar.querySelector(".link_container");
linkAnnotation = true;
if(linkMenu)
{
menuState.push(linkMenu);
var linkMode = linkMenu.querySelector("#cineraLinkMode");
var link = linkMenu.querySelector("#cineraLink");
linkMode.addEventListener("click", function(ev) {
ev.stopPropagation();
toggleLinkMode(linkMode, link);
});
link.addEventListener("click", function(ev) {
CopyToClipboard(link);
toggleMenuVisibility(linkMenu);
});
}
creditsMenu = titleBar.querySelector(".credits_container");
if(creditsMenu)
{
originalTextContent.TitleCredits = creditsMenu.previousElementSibling.textContent;
menuState.push(creditsMenu);
var lastFocusedCreditItem = null;
var creditItems = creditsMenu.querySelectorAll(".person, .support");
for(var i = 0; i < creditItems.length; ++i)
{
creditItems[i].addEventListener("mouseenter", function(ev) {
if(this != lastFocusedCreditItem)
{
lastFocusedCreditItem.classList.remove("focused");
unfocusSprite(lastFocusedCreditItem);
if(lastFocusedCreditItem.classList.contains("support"))
{
setSpriteLightness(lastFocusedCreditItem.firstChild);
}
lastFocusedCreditItem = this;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
if(focusedElement.classList.contains("support"))
{
setSpriteLightness(focusedElement.firstChild);
}
}
})
}
}
sourceMenus = titleBar.querySelectorAll(".menu");
helpButton = titleBar.querySelector(".cineraHelp");
helpDocumentation = helpButton.querySelector(".help_container");
BindHelp(helpButton, helpDocumentation);
}
var focusedElement = null;
var focusedIdentifier = null;
var playerContainer = cinera.querySelector(".cineraPlayerContainer")
var prevEpisode = playerContainer.querySelector(".episodeMarker.prev");
if(prevEpisode) { originalTextContent.EpisodePrev = prevEpisode.firstChild.textContent; }
var nextEpisode = playerContainer.querySelector(".episodeMarker.next");
if(nextEpisode) { originalTextContent.EpisodeNext = nextEpisode.firstChild.textContent; }
var testMarkers = playerContainer.querySelectorAll(".marker");
// NOTE(matt): All the originalTextContent values must be set by this point, because the player's construction may need them
var MobileCineraContentRuleSelector = ".cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker .cineraContent"; var MobileCineraContentRuleSelector = ".cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker .cineraContent";
var MobileCineraContentRule = GetOrSetRule(MobileCineraContentRuleSelector); var MobileCineraContentRule = GetOrSetRule(MobileCineraContentRuleSelector);
var MenuContainerRuleSelector = ".cineraMenus > .menu .quotes_container, .cineraMenus > .menu .references_container, .cineraMenus > .menu .filter_container, .cineraMenus > .menu .views_container, .cineraMenus > .menu .link_container, .cineraMenus > .menu .credits_container"; var MenuContainerRuleSelector = ".cineraMenus > .menu .quotes_container, .cineraMenus > .menu .references_container, .cineraMenus > .menu .filter_container, .cineraMenus > .menu .views_container, .cineraMenus > .menu .link_container, .cineraMenus > .menu .credits_container";
var MenuContainerRule = GetOrSetRule(MenuContainerRuleSelector); var MenuContainerRule = GetOrSetRule(MenuContainerRuleSelector);
var cinera = document.querySelector(".cinera"); if(CineraProps.D == devices.MOBILE)
var player = new Player(cinera, onRefChanged); {
InitMobileStyle();
}
else
{
var MenuMaxHeight = cinera.offsetHeight - titleBar.offsetHeight - 4;
MenuContainerRule.style.maxHeight = MenuMaxHeight + "px";
}
var player = new Player(playerContainer, onRefChanged);
if(CineraProps.D == devices.MOBILE)
{
ConnectMobileControls(player);
}
var cineraViewStorageItem = "cineraView";
if(viewsMenu && localStorage.getItem(cineraViewStorageItem))
{
toggleTheatreMode();
}
InitScrollEventListener(cinera);
window.addEventListener("resize", function() { window.addEventListener("resize", function() {
if(CineraProps.IsMobile) if(CineraProps.IsMobile)
{ {
setTimeout(DelayedUpdateSize, 512, player); setTimeout(player.updateSize, 512);
} }
else else
{ {
@ -42,10 +317,10 @@ window.addEventListener("resize", function() {
} }
}); });
screen.orientation.onchange = function() { window.onorientationchange = function() {
if(CineraProps.IsMobile) if(CineraProps.IsMobile)
{ {
setTimeout(DelayedUpdateSize, 512, player); setTimeout(player.updateSize, 512);
} }
else else
{ {
@ -60,17 +335,43 @@ document.addEventListener("keydown", function(ev) {
key = "capitalSpace"; key = "capitalSpace";
} }
if(!ev.getModifierState("Control") && player.handleKey(key) == true && player.MenusFocused.Item) if(!ev.getModifierState("Control") && handleKey(key) == true && focusedElement)
{ {
ev.preventDefault(); ev.preventDefault();
} }
}); });
document.addEventListener("fullscreenchange", function() { for(var i = 0; i < sourceMenus.length; ++i)
if(!document.fullscreenElement && CineraProps.V == views.SUPERTHEATRE) {
{ sourceMenus[i].addEventListener("mouseenter", function(ev) {
CineraProps.V = views.THEATRE; handleMenuTogglerInteraction(this, ev.type);
localStorage.setItem(player.cineraViewStorageItem, views.THEATRE); })
player.updateSize(); sourceMenus[i].addEventListener("mouseleave", function(ev) {
} handleMenuTogglerInteraction(this, ev.type);
}); })
sourceMenus[i].addEventListener("click", function(ev) {
handleMenuTogglerInteraction(this, ev.type);
})
};
var colouredItems = playerContainer.querySelectorAll(".author, .member, .project");
for(i = 0; i < colouredItems.length; ++i)
{
setTextLightness(colouredItems[i]);
}
var topicDots = cinera.querySelectorAll(".category");
for(var i = 0; i < topicDots.length; ++i)
{
setDotLightness(topicDots[i]);
}
var lastAnnotationStorageItem = "cineraTimecode_" + window.location.pathname;
var lastAnnotation;
if(location.hash) {
player.setTime(location.hash.startsWith('#') ? location.hash.substr(1) : location.hash);
}
else if(lastAnnotation = localStorage.getItem(lastAnnotationStorageItem))
{
player.setTime(lastAnnotation);
}

File diff suppressed because it is too large Load Diff

View File

@ -13,9 +13,6 @@ DeriveReliableWindowDimensions()
Y: null, Y: null,
}; };
var ScrollPosX = window.scrollX;
var ScrollPosY = window.scrollY;
var DisplaySettings = []; var DisplaySettings = [];
for(var i = 0; i < document.body.children.length; ++i) for(var i = 0; i < document.body.children.length; ++i)
{ {
@ -43,47 +40,26 @@ DeriveReliableWindowDimensions()
Child.style.display = DisplaySettings.shift(); Child.style.display = DisplaySettings.shift();
} }
ScrollTriggeredInternally = true;
window.scroll(ScrollPosX, ScrollPosY);
return Result; return Result;
} }
function
GetWindowDim(IsMobile)
{
var Result = {
X: null,
Y: null,
};
if(IsMobile)
{
Result = DeriveReliableWindowDimensions();
}
else
{
Result.X = document.documentElement.clientWidth;
Result.Y = document.documentElement.clientHeight;
}
return Result;
}
function IsVisible(Element, WindowDim) {
var BoundingRect = Element.getBoundingClientRect();
return ((BoundingRect.top >= 0 && BoundingRect.top <= WindowDim.Y) ||
(BoundingRect.bottom >= 0 && BoundingRect.bottom <= WindowDim.Y) ||
(BoundingRect.top < 0 && BoundingRect.bottom > WindowDim.Y))
&& ((BoundingRect.left >= 0 && BoundingRect.left <= WindowDim.X) ||
(BoundingRect.right >= 0 && BoundingRect.right <= WindowDim.X) ||
(BoundingRect.left < 0 && BoundingRect.right > WindowDim.X));
}
function function
GetRealOrientation(PreferredLandscape, IsMobile) GetRealOrientation(PreferredLandscape, IsMobile)
{ {
var Result = screen.orientation.angle; var Result = window.orientation;
var WindowDim = GetWindowDim(IsMobile); var WindowDim = {
X: null,
Y: null,
};
if(IsMobile)
{
WindowDim = DeriveReliableWindowDimensions();
}
else
{
WindowDim.X = document.body.clientWidth;
WindowDim.Y = window.innerHeight;
}
if(WindowDim.Y > WindowDim.X) if(WindowDim.Y > WindowDim.X)
{ {
Result = orientations.PORTRAIT; Result = orientations.PORTRAIT;
@ -192,12 +168,6 @@ function enableSprite(Element)
function disableSprite(Element) function disableSprite(Element)
{ {
if(Element.classList.contains("focused"))
{
focusSprite(Element);
}
else
{
if(Element.classList.contains("cineraSprite")) if(Element.classList.contains("cineraSprite"))
{ {
setSpriteLightness(Element); setSpriteLightness(Element);
@ -207,7 +177,6 @@ function disableSprite(Element)
{ {
disableSprite(Element.children[i]); disableSprite(Element.children[i]);
} }
}
} }
function unfocusSprite(Element) function unfocusSprite(Element)
@ -240,7 +209,7 @@ function IsMobile() {
function GetRule(SelectorText) function GetRule(SelectorText)
{ {
// NOTE(matt): Modifying CSS style // NOTE(matt): Modifying CSS style
// From https://stackoverflow.com/a/566445 // from https://stackoverflow.com/a/566445
// https://usefulangle.com/post/39/adding-css-to-stylesheet-with-javascript // https://usefulangle.com/post/39/adding-css-to-stylesheet-with-javascript
var Result = undefined; var Result = undefined;
@ -249,8 +218,6 @@ function GetRule(SelectorText)
for(var StyleSheetIndex = StyleSheets.length - 1; StyleSheetIndex >= 0; --StyleSheetIndex) for(var StyleSheetIndex = StyleSheets.length - 1; StyleSheetIndex >= 0; --StyleSheetIndex)
{ {
var ThisSheet = StyleSheets[StyleSheetIndex]; var ThisSheet = StyleSheets[StyleSheetIndex];
if(ThisSheet.href !== null && ThisSheet.href.includes(location.hostname))
{
var Rules = ThisSheet[cssRuleCode]; var Rules = ThisSheet[cssRuleCode];
for(var RuleIndex = Rules.length - 1; RuleIndex >= 0; --RuleIndex) for(var RuleIndex = Rules.length - 1; RuleIndex >= 0; --RuleIndex)
{ {
@ -263,7 +230,6 @@ function GetRule(SelectorText)
} }
if(Result !== undefined) { break; } if(Result !== undefined) { break; }
} }
}
return Result; return Result;
} }
@ -282,126 +248,73 @@ GetRulesOfStyleSheetIndex(Index)
return Result; return Result;
} }
function
GetLocalStyleSheet()
{
var Result;
var StyleSheets = document.styleSheets;
for(var StyleSheetIndex = StyleSheets.length - 1; StyleSheetIndex >= 0; --StyleSheetIndex)
{
var This = StyleSheets[StyleSheetIndex];
if(This.href !== null && This.href.includes(location.hostname) && !This.disabled)
{
Result = This;
break;
}
}
return Result;
}
function function
GetOrSetRule(SelectorText) GetOrSetRule(SelectorText)
{ {
var Result = GetRule(SelectorText); var Result = GetRule(SelectorText);
if(Result === undefined) if(Result === undefined)
{ {
var StyleSheet = GetLocalStyleSheet(); var StyleSheet = document.styleSheets[0];
if(StyleSheet) var RuleIndex = StyleSheet.insertRule(SelectorText + "{}", StyleSheet.length - 1);
{ var Rules = GetRulesOfStyleSheetIndex(0);
var cssRuleCode = document.all ? 'rules' : 'cssRules'; // account for IE and FF
var Rules = StyleSheet[cssRuleCode];
var RuleIndex = StyleSheet.insertRule(SelectorText + "{}", Rules.length - 1);
Result = Rules[RuleIndex]; Result = Rules[RuleIndex];
} }
}
return Result; return Result;
} }
function IsInRangeEx(Min, N, Max)
{
return N > Min && N < Max;
}
/* Auto-scrolling */ /* Auto-scrolling */
var ScrollTriggeredInternally = false;
var LastScrollYPos = 0; var LastScrollYPos = 0;
var ScrollTicking = false; var ScrollTicking = false;
var ScrollerFunction; var ScrollerFunction;
var ScrollCondition; var ScrollCondition;
function ScrollTo(Element, ScrollPos, IsMobile, StickyObscuringElement) { function ScrollTo(Element, ScrollPos) {
if(Element.offsetWidth && Element.offsetHeight)
{
var ScrolledToTop = ScrollPos == 0;
var ScrolledToBottom = (window.innerHeight + Math.ceil(window.pageYOffset)) >= document.body.scrollHeight;
if(!ScrolledToTop || !ScrolledToBottom)
{
var Ceiling = StickyObscuringElement ? StickyObscuringElement.offsetHeight : 0;
var VisibleArea = GetWindowDim(IsMobile);
VisibleArea.Y -= Ceiling;
var PercentageOfVisibleHeightToGather = 5;
var GatherableHeight = VisibleArea.Y * PercentageOfVisibleHeightToGather / 100;
var BoundingRect = Element.getBoundingClientRect(); var BoundingRect = Element.getBoundingClientRect();
var Height = BoundingRect.height;
var ElementTop = BoundingRect.top - Ceiling; var PercentageOfInView = 89;
var ElementBottom = ElementTop + BoundingRect.height; var GatherableHeight = Height * (1 - PercentageOfInView / 100);
var ScrollY = BoundingRect.top;
var UpperProtrusion = -ElementTop;
var LowerProtrusion = ElementBottom - VisibleArea.Y;
var DesiredScroll = null;
var YOffsetFromPage = getElementYOffsetFromPage(Element); var YOffsetFromPage = getElementYOffsetFromPage(Element);
if(IsInRangeEx(0, UpperProtrusion, GatherableHeight)) var DesiredScroll = null;
if(ScrollY < 0)
{ {
if(!ScrolledToBottom) ScrollY = ~ScrollY;
if(ScrollY <= GatherableHeight)
{ {
DesiredScroll = YOffsetFromPage - Ceiling; DesiredScroll = YOffsetFromPage;
} }
} }
else if(IsInRangeEx(0, LowerProtrusion, GatherableHeight))
{
if(!ScrolledToTop)
{
if(IsInRangeEx(0, UpperProtrusion + LowerProtrusion, GatherableHeight))
{
DesiredScroll = YOffsetFromPage - Ceiling;
}
else else
{
var LowerProtrusion = ScrollY - (window.innerHeight - Height);
if(LowerProtrusion > 0 && LowerProtrusion <= GatherableHeight)
{ {
DesiredScroll = ScrollPos + LowerProtrusion; DesiredScroll = ScrollPos + LowerProtrusion;
} }
} }
}
if(DesiredScroll !== null && DesiredScroll != ScrollPos) if(DesiredScroll !== null)
{ {
window.scrollTo({ window.scrollTo({
top: DesiredScroll, top: DesiredScroll,
behavior: "smooth" behavior: "smooth"
}); });
} }
}
}
} }
function function
InitScrollEventListener(Element, IsMobile, StickyObscuringElement) InitScrollEventListener(Element)
{ {
window.addEventListener('scroll', function() { window.addEventListener('scroll', function() {
if(ScrollTriggeredInternally) if(ScrollCondition == undefined || ScrollCondition == true)
{
ScrollTriggeredInternally = false;
}
else if(ScrollCondition == undefined || ScrollCondition == true)
{ {
LastScrollYPos = window.scrollY; LastScrollYPos = window.scrollY;
if (!ScrollTicking) { if (!ScrollTicking) {
window.requestAnimationFrame(function() { window.requestAnimationFrame(function() {
clearTimeout(ScrollerFunction); clearTimeout(ScrollerFunction);
ScrollerFunction = setTimeout(ScrollTo, 2000, Element, LastScrollYPos, IsMobile, StickyObscuringElement); ScrollerFunction = setTimeout(ScrollTo, 2000, Element, LastScrollYPos);
ScrollTicking = false; ScrollTicking = false;
}); });
@ -431,18 +344,32 @@ function getElementYOffsetFromPage(el) {
function function
MaxWidthOfElement(Element, WindowDim) MaxWidthOfElement(Element, WindowDim)
{ {
// NOTE(matt): This works fine for Elements whose natural max width fills the whole line, i.e. block elements and children
// thereof. To support inline elements, we'll need to mirror MaxHeightOfElement() and may as well roll the two
// functions into one.
var Result = 0; var Result = 0;
var OriginalWidth = Element.style.width; var OriginalWidth = Element.style.width;
Element.style.width = "100%"; Element.style.width = "100%";
var NaturalMax = Element.offsetWidth; var Max = parseInt(window.getComputedStyle(Element).width);
Element.style.width = OriginalWidth; Element.style.width = "unset";
var Default = parseInt(window.getComputedStyle(Element).width);
var InnerWidth = WindowDim ? WindowDim.X : document.documentElement.clientWidth; var InnerWidth = WindowDim ? WindowDim.X : document.body.clientWidth;
Result = Math.min(NaturalMax, InnerWidth);
if(Max > InnerWidth || Max == Default)
{
Result = InnerWidth;
}
else
{
Result = Max;
}
Element.style.width = Result + "px";
if(Element.scrollWidth > Element.clientWidth)
{
Result = Element.clientWidth;
}
Element.style.width = OriginalWidth;
return Result; return Result;
} }
@ -450,48 +377,30 @@ function
MaxHeightOfElement(Element, WindowDim) MaxHeightOfElement(Element, WindowDim)
{ {
var Result = 0; var Result = 0;
var DisplaySettings = [];
for(var i = 0; i < Element.children.length; ++i)
{
var Child = Element.children[i];
DisplaySettings.push(Child.style.display);
Child.style.display = "none";
}
var OriginalHeight = Element.style.height; var OriginalHeight = Element.style.height;
Element.style.height = "100%"; Element.style.height = "100%";
var NaturalMax = Element.offsetHeight; var Max = parseInt(window.getComputedStyle(Element).height);
Element.style.height = OriginalHeight; Element.style.height = "unset";
var Default = parseInt(window.getComputedStyle(Element).height);
var InnerHeight = WindowDim ? WindowDim.Y : document.documentElement.clientHeight; var InnerHeight = WindowDim ? WindowDim.Y : window.innerHeight;
if(NaturalMax > 0)
{ if(Max > InnerHeight || Max == Default)
Result = Math.min(NaturalMax, InnerHeight);
}
else
{ {
Result = InnerHeight; Result = InnerHeight;
} }
else
for(var i = 0; i < Element.children.length; ++i)
{ {
var Child = Element.children[i]; Result = Max;
Child.style.display = DisplaySettings.shift();
} }
return Result; Element.style.height = Result + "px";
} if(Element.scrollHeight > Element.clientHeight)
{
Result = Element.clientHeight;
}
function Element.style.height = OriginalHeight;
MaxDimensionsOfElement(Element, WindowDim)
{
var Result = {
X: null,
Y: null,
};
Result.X = MaxWidthOfElement(Element, WindowDim);
Result.Y = MaxHeightOfElement(Element, WindowDim);
return Result; return Result;
} }
@ -521,94 +430,20 @@ IsOverflowed(Element)
return Element.scrollHeight > Element.clientHeight || Element.scrollWidth > Element.clientWidth; return Element.scrollHeight > Element.clientHeight || Element.scrollWidth > Element.clientWidth;
} }
function
SetHelpUnfocused(Button)
{
Button.firstElementChild.innerText = "¿";
Button.firstElementChild.title = "Keypresses will not pass through to Cinera because focus is currently elsewhere.\n\nTo regain focus, please press Tab / Shift-Tab (multiple times) or click somewhere related to Cinera other than the video, e.g. this button";
}
function
SetHelpFocused(Button)
{
Button.firstElementChild.innerText = "?";
Button.firstElementChild.title = ""
}
function function
BindHelp(Button, DocumentationContainer) BindHelp(Button, DocumentationContainer)
{ {
window.addEventListener("blur", function(){ window.addEventListener("blur", function(){
SetHelpUnfocused(Button); Button.firstElementChild.innerText = "¿";
Button.firstElementChild.title = "Keypresses will not pass through to Cinera because focus is currently elsewhere.\n\nTo regain focus, please press Tab / Shift-Tab (multiple times) or click somewhere related to Cinera other than the video, e.g. this button";
}); });
window.addEventListener("focus", function(){ window.addEventListener("focus", function(){
SetHelpFocused(Button); Button.firstElementChild.innerText = "?";
Button.firstElementChild.title = ""
}); });
Button.addEventListener("click", function() { Button.addEventListener("click", function() {
DocumentationContainer.classList.toggle("visible"); DocumentationContainer.classList.toggle("visible");
}) })
} }
function getBackgroundColourRGB(element) {
var Colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
while((Colour == "transparent" || Colour == "rgba(0, 0, 0, 0)") && element.parentElement && depth <= 4)
{
element = element.parentElement;
Colour = getComputedStyle(element).getPropertyValue("background-color");
++depth;
}
var Staging = Colour.slice(Colour.indexOf("(") + 1, -1).split(", ");
var Result = {
R: parseInt(Staging[0]),
G: parseInt(Staging[1]),
B: parseInt(Staging[2]),
};
return Result;
}
function getBackgroundBrightness(element) {
var Colour = getBackgroundColourRGB(element);
var Result = Math.sqrt(Colour.R * Colour.R * .241 +
Colour.G * Colour.G * .691 +
Colour.B * Colour.B * .068);
return Result;
}
function setTextLightness(textElement)
{
var textHue = textElement.getAttribute("data-hue");
var textSaturation = textElement.getAttribute("data-saturation");
if(textHue && textSaturation)
{
if(getBackgroundBrightness(textElement.parentNode) < 127)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
}
}
}
function setDotLightness(topicDot)
{
var dotHue = topicDot.getAttribute("data-hue");
var dotSaturation = topicDot.getAttribute("data-saturation");
if(dotHue && dotSaturation)
{
if(getBackgroundBrightness(topicDot.parentNode) < 127)
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
topicDot.style.borderColor = ("hsl(" + dotHue + ", " + dotSaturation + ", 47%)");
}
}
}

View File

@ -12,7 +12,6 @@ Nav.Controls.Save = Nav.Controls.Header.querySelector(".cineraMenuItem.save");
Nav.Controls.Help = Nav.Nexus.querySelector(".cineraHelp"); Nav.Controls.Help = Nav.Nexus.querySelector(".cineraHelp");
Nav.Controls.HelpDocumentation = Nav.Controls.Help.querySelector(".help_container"); Nav.Controls.HelpDocumentation = Nav.Controls.Help.querySelector(".help_container");
Nav.GridContainer = Nav.Nexus.querySelector(".cineraIndexGridContainer"); Nav.GridContainer = Nav.Nexus.querySelector(".cineraIndexGridContainer");
Nav.Controls.GridTraversal.Container = Nav.GridContainer.querySelector(".cineraTraversalContainer");
Nav.Controls.GridTraversal.Header = Nav.GridContainer.querySelector(".cineraTraversal"); Nav.Controls.GridTraversal.Header = Nav.GridContainer.querySelector(".cineraTraversal");
Nav.Controls.GridTraversal.Ascend = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.ascension"); Nav.Controls.GridTraversal.Ascend = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.ascension");
Nav.Controls.GridTraversal.Prev = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.prev"); Nav.Controls.GridTraversal.Prev = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.prev");
@ -45,7 +44,6 @@ InitPrototypes(Search.ResultsContainer);
prepareProjects(); prepareProjects();
SyncNavState(); SyncNavState();
FlipClear("scroll");
// //
//// ////
@ -56,7 +54,7 @@ BindGridKeys(Nav.GridSize);
BindControls(); BindControls();
InitResizeEventListener(); InitResizeEventListener();
InitOrientationChangeListener(); InitOrientationChangeListener();
InitScrollEventListener(Nav.GridContainer, CineraProps.IsMobile, Nav.Controls.Header); InitScrollEventListener(Nav.Grid);
// //
//// ////

View File

@ -213,11 +213,11 @@ function prepareToParseIndexFile(project)
mode = "markers"; mode = "markers";
episode.markers = []; episode.markers = [];
} else if (mode == "markers") { } else if (mode == "markers") {
var match = line.match(/"(\d+.\d+)": "(.+)"/); var match = line.match(/"(\d+)": "(.+)"/);
if (match == null) { if (match == null) {
console.log(name, line); console.log(name, line);
} else { } else {
var totalTime = parseFloat(line.slice(1)); var totalTime = parseInt(line.slice(1));
var marker = { var marker = {
totalTime: totalTime, totalTime: totalTime,
prettyTime: markerTime(totalTime), prettyTime: markerTime(totalTime),
@ -292,7 +292,7 @@ function markerTime(totalTime) {
var markTime = "("; var markTime = "(";
var hours = Math.floor(totalTime / 60 / 60); var hours = Math.floor(totalTime / 60 / 60);
var minutes = Math.floor(totalTime / 60) % 60; var minutes = Math.floor(totalTime / 60) % 60;
var seconds = Math.floor(totalTime) % 60; var seconds = totalTime % 60;
if (hours > 0) { if (hours > 0) {
markTime += padTimeComponent(hours) + ":"; markTime += padTimeComponent(hours) + ":";
} }
@ -460,7 +460,7 @@ function runSearch(refresh) {
Search.ResultsSummary.style.display = "none"; Search.ResultsSummary.style.display = "none";
} }
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + Math.floor(totalSeconds)%60 + "s "; var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s ";
Search.ResultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total."; Search.ResultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total.";
} }
@ -553,6 +553,28 @@ function renderResults() {
} }
} }
function IsVisible(el) {
var xPos = 0;
var yPos = 0;
var Height = parseInt(getComputedStyle(el).height);
while (el) {
if (el.tagName == "BODY") {
var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
var yScroll = el.scrollTop || document.documentElement.scrollTop;
xPos += (el.offsetLeft - xScroll + el.clientLeft)
yPos += (el.offsetTop - yScroll + el.clientTop)
} else {
xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPos += (el.offsetTop - el.scrollTop + el.clientTop);
}
el = el.offsetParent;
}
return ((xPos > 0 && xPos < window.innerWidth) && (yPos > 0 && yPos + Height < window.innerHeight));
}
function function
InitQuery(QueryElement) InitQuery(QueryElement)
{ {
@ -566,7 +588,7 @@ InitQuery(QueryElement)
QueryElement.value = decodeURIComponent(initialQuery); QueryElement.value = decodeURIComponent(initialQuery);
} }
if(document.hasFocus() && IsVisible(QueryElement, GetWindowDim(CineraProps.IsMobile))) { QueryElement.focus(); } if(document.hasFocus() && IsVisible(QueryElement)) { QueryElement.focus(); }
} }
function function
@ -673,7 +695,7 @@ var Nav = {
GridRowGap: null, GridRowGap: null,
SortChronological: true, SortChronological: true,
ViewType: view_type.LIST, ViewType: view_type.GRID,
List: null, List: null,
Grid: null, Grid: null,
@ -690,7 +712,6 @@ var Nav = {
HelpKeys: [], HelpKeys: [],
GridTraversal: { GridTraversal: {
Container: null,
Header: null, Header: null,
Ascend: null, Ascend: null,
Prev: null, Prev: null,
@ -921,25 +942,6 @@ InitHelpKeys(HelpDocumentation)
Nav.Controls.HelpKeys = Paragraph.querySelectorAll(".help_key"); Nav.Controls.HelpKeys = Paragraph.querySelectorAll(".help_key");
} }
function
InitView()
{
// NOTE(matt): Nav.ViewType is initialised to view_type.LIST and InitNexus() leaves the List View visible
if(!StateBitIsSet(state_bit.VIEW_LIST))
{
if(GridSizeIsSupported(Nav.GridSize))
{
PickGridView();
}
else
{
// NOTE(matt): Silently swap the state bits, leaving the default List View visible
ClearStateBit(state_bit.VIEW_GRID);
SetStateBit(state_bit.VIEW_LIST);
}
}
}
function function
SyncNavState() SyncNavState()
{ {
@ -950,27 +952,31 @@ SyncNavState()
{ {
if(StateBitIsSet(state_bit.DISABLE_ANIMATIONS)) { ToggleAnimations(); } if(StateBitIsSet(state_bit.DISABLE_ANIMATIONS)) { ToggleAnimations(); }
if(StateBitIsSet(state_bit.SORT_REVERSED)) { Sort(true); } if(StateBitIsSet(state_bit.SORT_REVERSED)) { Sort(true); }
InitView();
// Nav.ViewType is initialised to view_type.GRID, so we needn't do anything if the Nav.State is also on Grid
if(StateBitIsSet(state_bit.VIEW_LIST) || !GridSizeIsSupported(Nav.GridSize))
{
ToggleView();
}
} }
else else
{ {
Nav.Controls.Save.textContent = "Save Settings: ✘"; Nav.Controls.Save.textContent = "Save Settings: ✘";
// Nav.ViewType was initialised to view_type.LIST // Nav.ViewType was initialised to view_type.GRID
if(Nav.ViewType == view_type.GRID && GridSizeIsSupported(Nav.GridSize)) if(!GridSizeIsSupported(Nav.GridSize))
{ {
PickGridView(); ToggleView();
} }
} }
} }
else else
{ {
Nav.State = 0; Nav.State = 0;
switch(Nav.ViewType) SetStateBit(state_bit.VIEW_GRID); // NOTE(matt): Nav.ViewType was initialised to view_type.GRID
if(!GridSizeIsSupported(Nav.GridSize))
{ {
case view_type.LIST: SetStateBit(state_bit.VIEW_LIST); break ToggleView();
case view_type.GRID: SetStateBit(state_bit.VIEW_GRID); break
} }
InitView();
} }
} }
@ -991,11 +997,7 @@ InitTraversalStack()
if(Projects.length === 1) if(Projects.length === 1)
{ {
// NOTE(matt): Automatically descend into the lone project // NOTE(matt): Automatically descend into the lone project
var QueriedProjects = Projects[0].querySelectorAll(":scope > .cineraIndexProject"); Level.Projects = Projects[0].querySelectorAll(":scope > .cineraIndexProject");
if(QueriedProjects.length > 0)
{
Level.Projects = QueriedProjects;
}
Level.Entries = Projects[0].querySelectorAll(":scope > .cineraIndexEntries > div"); Level.Entries = Projects[0].querySelectorAll(":scope > .cineraIndexEntries > div");
} }
else else
@ -2880,18 +2882,13 @@ InitNexus()
// NOTE(matt): We ResetButtonsContainerClone() anyway, but without cycling this classList Safari seems to do transitions // NOTE(matt): We ResetButtonsContainerClone() anyway, but without cycling this classList Safari seems to do transitions
// based on the wrong size grid // based on the wrong size grid
Nav.GridContainer.classList.add("hidden"); Nav.GridContainer.classList.add("hidden");
ResetButtonsContainerClone(); ResetButtonsContainerClone();
Nav.GridContainer.classList.remove("hidden");
switch(Nav.ViewType)
{
case view_type.LIST: { Nav.List.classList.remove("hidden"); ScrollCondition = false; } break;
case view_type.GRID: { Nav.Grid.classList.remove("hidden"); ScrollCondition = true; } break;
}
Nav.Nexus.classList.add("anim"); Nav.Nexus.classList.add("anim");
ScrollCondition = true; // NOTE(matt): Variable in cinera_pre.js, we init with the Grid which is the view we want to auto-scroll
if(CineraProps.IsMobile) if(CineraProps.IsMobile)
{ {
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile); CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
@ -2940,7 +2937,19 @@ ComputeOptimalGridSize()
Y: null, Y: null,
}; };
var WindowDim = GetWindowDim(CineraProps.IsMobile); var WindowDim = {
X: null,
Y: null,
};
if(CineraProps.IsMobile)
{
WindowDim = DeriveReliableWindowDimensions();
}
else
{
WindowDim.X = document.body.clientWidth;
WindowDim.Y = window.innerHeight;
}
var DimReduction = { var DimReduction = {
X: 0, X: 0,
Y: Nav.Controls.Header.offsetHeight, Y: Nav.Controls.Header.offsetHeight,
@ -2948,7 +2957,7 @@ ComputeOptimalGridSize()
Nav.Transition.ButtonsTransitionContainerElement.style = null; Nav.Transition.ButtonsTransitionContainerElement.style = null;
Nav.ButtonsContainer.style = null; Nav.ButtonsContainer.style = null;
Nav.Controls.GridTraversal.Container.style = null; Nav.Controls.GridTraversal.Header.style = null;
Nav.Controls.GridTraversal.Ascend.style = null; Nav.Controls.GridTraversal.Ascend.style = null;
Nav.Controls.GridTraversal.Prev.style = null; Nav.Controls.GridTraversal.Prev.style = null;
Nav.Controls.GridTraversal.Next.style = null; Nav.Controls.GridTraversal.Next.style = null;
@ -2961,21 +2970,20 @@ ComputeOptimalGridSize()
} }
if(CineraProps.IsMobile && (CineraProps.Orientation == orientations.LANDSCAPE_LEFT || CineraProps.Orientation == orientations.LANDSCAPE_RIGHT)) if(CineraProps.IsMobile && (CineraProps.Orientation == orientations.LANDSCAPE_LEFT || CineraProps.Orientation == orientations.LANDSCAPE_RIGHT))
{ {
DimReduction.X += Nav.Controls.GridTraversal.Container.offsetWidth; DimReduction.X += Nav.Controls.GridTraversal.Header.offsetWidth;
} }
else else
{ {
DimReduction.Y += Nav.Controls.GridTraversal.Container.offsetHeight; DimReduction.Y += Nav.Controls.GridTraversal.Header.offsetHeight;
} }
var MaxWidth = MaxWidthOfElement(Nav.Transition.ButtonsTransitionContainerElement, WindowDim) - DimReduction.X;
var MaxHeight = MaxHeightOfElement(Nav.Transition.ButtonsTransitionContainerElement, WindowDim) - DimReduction.Y;
if(GridWasHidden) if(GridWasHidden)
{ {
Nav.GridContainer.classList.add("hidden"); Nav.GridContainer.classList.add("hidden");
} }
var MaxWidth = MaxWidthOfElement(Nav.Nexus, WindowDim) - DimReduction.X;
var MaxHeight = MaxHeightOfElement(Nav.Nexus, WindowDim) - DimReduction.Y;
var BodyStyle = window.getComputedStyle(document.body); var BodyStyle = window.getComputedStyle(document.body);
if(Nav.Nexus.parentNode == document.body) if(Nav.Nexus.parentNode == document.body)
{ {
@ -3009,24 +3017,25 @@ ComputeOptimalGridSize()
SetDim(Nav.Transition.ButtonsTransitionContainerElement, Nav.GridDim.X + "px", Nav.GridDim.Y + "px"); SetDim(Nav.Transition.ButtonsTransitionContainerElement, Nav.GridDim.X + "px", Nav.GridDim.Y + "px");
Nav.Controls.GridTraversal.Container.style.maxWidth = Nav.GridDim.X + "px"; Nav.Controls.GridTraversal.Header.style.maxWidth = Nav.GridDim.X + "px";
Nav.Controls.GridTraversal.Container.style.maxHeight = Nav.GridDim.Y + "px"; Nav.Controls.GridTraversal.Header.style.maxHeight = Nav.GridDim.Y + "px";
var TraversalButtonCount = 3; var TraversalButtonCount = 3;
if(Nav.Controls.GridTraversal.Container.scrollWidth > Nav.Controls.GridTraversal.Container.clientWidth) if(Nav.Controls.GridTraversal.Header.scrollWidth > Nav.Controls.GridTraversal.Header.clientWidth)
{ {
var TraversalButtonDim = Nav.Controls.GridTraversal.Container.clientWidth / TraversalButtonCount; var TraversalButtonDim = Nav.Controls.GridTraversal.Header.clientWidth / TraversalButtonCount;
SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px");
SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px");
SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px");
} }
if(Nav.Controls.GridTraversal.Container.scrollHeight > Nav.Controls.GridTraversal.Container.clientHeight) if(Nav.Controls.GridTraversal.Header.scrollHeight > Nav.Controls.GridTraversal.Header.clientHeight)
{ {
var TraversalButtonDim = Nav.Controls.GridTraversal.Container.clientHeight / TraversalButtonCount; var TraversalButtonDim = Nav.Controls.GridTraversal.Header.clientHeight / TraversalButtonCount;
SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Ascend, TraversalButtonDim + "px", TraversalButtonDim + "px");
SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Prev, TraversalButtonDim + "px", TraversalButtonDim + "px");
SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px"); SetDim(Nav.Controls.GridTraversal.Next, TraversalButtonDim + "px", TraversalButtonDim + "px");
} }
} }
ResetButtonsContainerClone(); // NOTE(matt): This reapplies the sorting Z-rotation ResetButtonsContainerClone(); // NOTE(matt): This reapplies the sorting Z-rotation
@ -3433,45 +3442,13 @@ ScrollToWithOffset(Element, Offset)
} }
function function
PickGridView() ToggleView()
{ {
if(GridSizeIsSupported(Nav.GridSize)) // NOTE(matt): While we only have two views, a toggle will suffice
clearTimeout(ScrollerFunction);
ScrollTicking = false;
if(Nav.ViewType == view_type.GRID)
{ {
Nav.Controls.View.textContent = "View: 𝑛-ary Grid";
Nav.ViewType = view_type.GRID;
if(MaintainingState())
{
ClearStateBit(state_bit.VIEW_LIST);
SetStateBit(state_bit.VIEW_GRID);
}
if(!IsQuery())
{
var TargetLevel = ComputeTargetLevelForViewport();
EmptyTraversalStack();
if(TargetLevel.Projects || TargetLevel.Entries)
{
var ProjectsStack = BuildProjectsStack(TargetLevel);
DeriveTraversalStack(ProjectsStack, TargetLevel);
}
Nav.List.classList.add("hidden");
Nav.GridContainer.classList.remove("hidden");
UpdateButtons();
ScrollToWithOffset(Nav.Controls.GridTraversal.Header, Nav.Controls.Header.offsetHeight);
}
ScrollCondition = true;
}
else
{
// TODO(matt): Inform user that grid view is unavailable
}
}
function
PickListView()
{
Nav.Controls.View.textContent = "View: List"; Nav.Controls.View.textContent = "View: List";
Nav.ViewType = view_type.LIST; Nav.ViewType = view_type.LIST;
if(MaintainingState()) if(MaintainingState())
@ -3499,23 +3476,42 @@ PickListView()
ScrollToWithOffset(Element, Nav.Controls.Header.offsetHeight); ScrollToWithOffset(Element, Nav.Controls.Header.offsetHeight);
} }
} }
ScrollCondition = false;
}
function
ToggleView()
{
// NOTE(matt): While we only have two views, a toggle will suffice.
clearTimeout(ScrollerFunction);
ScrollTicking = false;
if(Nav.ViewType == view_type.GRID)
{
PickListView();
} }
else else
{ {
PickGridView(); if(GridSizeIsSupported(Nav.GridSize))
{
Nav.Controls.View.textContent = "View: Grid";
Nav.ViewType = view_type.GRID;
if(MaintainingState())
{
ClearStateBit(state_bit.VIEW_LIST);
SetStateBit(state_bit.VIEW_GRID);
} }
if(!IsQuery())
{
var TargetLevel = ComputeTargetLevelForViewport();
EmptyTraversalStack();
if(TargetLevel.Projects || TargetLevel.Entries)
{
var ProjectsStack = BuildProjectsStack(TargetLevel);
DeriveTraversalStack(ProjectsStack, TargetLevel);
}
Nav.List.classList.add("hidden");
Nav.GridContainer.classList.remove("hidden");
UpdateButtons();
ScrollToWithOffset(Nav.Controls.GridTraversal.Header, Nav.Controls.Header.offsetHeight);
}
}
else
{
// TODO(matt): Inform user that grid view is unavailable
}
}
ScrollCondition = Nav.ViewType == view_type.GRID;
} }
function function
@ -3817,8 +3813,6 @@ PseudoPushButton(Button)
function function
ResizeFunction() ResizeFunction()
{ {
var OriginalScrollX = scrollX;
var OriginalScrollY = scrollY;
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile); CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
if(CineraProps.IsMobile) if(CineraProps.IsMobile)
{ {
@ -3840,10 +3834,10 @@ ResizeFunction()
} }
else if(Nav.ViewType == view_type.GRID) else if(Nav.ViewType == view_type.GRID)
{ {
PickListView(); ToggleView();
// TODO(matt): Inform user that we've switched to the list view // TODO(matt): Inform user that we've switched to the list view
} }
scroll(OriginalScrollX, OriginalScrollY); ScrollToWithOffset(Nav.Nexus, 0);
} }
UpdateButtons(); UpdateButtons();
} }
@ -3866,7 +3860,7 @@ InitResizeEventListener()
function function
InitOrientationChangeListener() InitOrientationChangeListener()
{ {
screen.orientation.onchange = function() window.onorientationchange = function()
{ {
if(CineraProps.IsMobile) if(CineraProps.IsMobile)
{ {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

5
hmmlib2/.gitignore vendored
View File

@ -1,5 +0,0 @@
/utils/hmmldump
/utils/fuzz/fuzz.gcno
/utils/fuzz/fuzz.gcda
/utils/fuzz/output
/utils/fuzz/fuzz

View File

@ -1,6 +0,0 @@
This is a single-header library for parsing the HMML Format.
See https://git.handmade.network/Annotation-Pushers/Annotation-System/-/wikis/hmmlspec
To use:
1. #include "hmmlib.h" in any files that use the functions / data structures.
2. in **one** of your .c files, #define HMMLIB_IMPLEMENTATION before including it.

View File

@ -1,859 +0,0 @@
#ifndef HMMLIB_H_
#define HMMLIB_H_
#include <stddef.h>
#include <stdio.h>
// Data structures
typedef struct {
char* name;
char* role;
} HMML_Credit;
typedef struct {
char* key;
char* value;
} HMML_VideoCustomMetaData;
typedef struct {
char* stream_platform;
char* project;
char* title;
char* vod_platform;
char* id;
char* output;
char* template;
char* medium;
char* number;
char* cc_lang;
HMML_Credit* credits;
size_t credit_count;
HMML_Credit* uncredits;
size_t uncredit_count;
HMML_VideoCustomMetaData* custom;
size_t custom_count;
} HMML_VideoMetaData;
typedef struct {
char* site;
char* page;
char* url;
char* title;
char* article;
char* author;
char* editor;
char* publisher;
char* isbn;
int offset;
} HMML_Reference;
typedef enum {
HMML_CATEGORY,
HMML_MEMBER,
HMML_PROJECT,
HMML_MARKER_COUNT,
} HMML_MarkerType;
typedef struct {
HMML_MarkerType type;
char* marker;
char* parameter;
char* episode;
int offset;
} HMML_Marker;
typedef struct {
_Bool present;
int id;
char* author;
} HMML_Quote;
typedef struct {
int line;
int h, m, s, ms;
char* text;
char* author;
HMML_Reference* references;
size_t reference_count;
HMML_Marker* markers;
size_t marker_count;
HMML_Quote quote;
} HMML_Timestamp;
typedef struct {
int line;
int col;
char* message;
} HMML_Error;
typedef struct {
_Bool well_formed;
HMML_VideoMetaData metadata;
HMML_Timestamp* timestamps;
size_t timestamp_count;
HMML_Error error;
void* free_list; // implementation detail
} HMML_Output;
// Functions
HMML_Output hmml_parse (const char* string);
void hmml_free (HMML_Output* output);
// Version
extern const struct HMML_Version {
int Major, Minor, Patch;
} hmml_version;
#endif
#ifdef HMMLIB_IMPLEMENTATION
#include <setjmp.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>
#define HSTX(x) x, sizeof(x)-1
#define HSTR(x) (const struct _hmml_str){ HSTX(x) }
#ifndef MALLOC
#define MALLOC malloc
#endif
#ifndef REALLOC
#define REALLOC realloc
#endif
#ifndef countof
#define countof(x) (sizeof(x)/sizeof(*x))
#endif
#define _hmml_debug(...)
//#define _hmml_debug printf
struct _hmml_parser {
HMML_Output out;
const char* mem;
const char* cursor;
jmp_buf err_buf;
uintptr_t* free_list;
int line;
};
struct _hmml_str {
const char* ptr;
size_t len;
};
// memory management boilerplate stuff
static void* _hmml_store_ptr(struct _hmml_parser* p, void* input)
{
uintptr_t* ptr;
if(p->free_list) {
ptr = p->free_list;
if(ptr[1] + 1 == ptr[0]) {
size_t n = ptr[0] << 1;
ptr = REALLOC(ptr, n * sizeof(uintptr_t));
ptr[0] = n;
}
ptr[ptr[1]] = (uintptr_t)input;
ptr[1]++;
} else {
ptr = MALLOC(8 * sizeof(uintptr_t));
ptr[0] = 8;
ptr[1] = 3;
ptr[2] = (uintptr_t)input;
}
p->free_list = ptr;
return input;
}
static char* _hmml_persist_str(struct _hmml_parser* p, const struct _hmml_str str)
{
char* mem = MALLOC(str.len+1);
memcpy(mem, str.ptr, str.len);
mem[str.len] = '\0';
return _hmml_store_ptr(p, mem);
}
static void _hmml_persist_array_fn(struct _hmml_parser* p, void** out, size_t* out_count, void* in, size_t in_size)
{
void* base;
if(!*out) {
base = MALLOC(in_size + sizeof(size_t));
_hmml_store_ptr(p, base);
*(size_t*)base = p->free_list[1]-1;
} else {
base = (char*)(*out) - sizeof(size_t);
base = REALLOC(base, (*out_count + 1) * in_size + sizeof(size_t));
size_t free_list_off = *(size_t*)base;
p->free_list[free_list_off] = (intptr_t)base;
}
*out = (char*)base + sizeof(size_t);
memcpy((char*)*out + (*out_count * in_size), in, in_size);
++(*out_count);
}
#define _hmml_persist_array(p, out, out_count, in) \
_hmml_persist_array_fn((p), (void**)(out), (out_count), &(in), sizeof(in))
// error handling
#define _hmml_err(p, fmt, ...) \
_hmml_err_fn((p), fmt "\n", ##__VA_ARGS__)
__attribute__((noreturn))
static void _hmml_err_fn(struct _hmml_parser* p, const char* fmt, ...)
{
static char error_buf[4096];
va_list va;
va_start(va, fmt);
int n = vsnprintf(error_buf, sizeof(error_buf), fmt, va);
va_end(va);
int line = 1, col = 1;
for(const char* ptr = p->mem; ptr != p->cursor; ++ptr) {
if(*ptr == '\n') {
++line;
col = 1;
} else {
++col;
}
}
p->out.error.message = _hmml_persist_str(p, (struct _hmml_str){ error_buf, n });
p->out.error.line = line;
p->out.error.col = col;
longjmp(p->err_buf, 1);
}
// actual parsing stuff
static void _hmml_skip_ws(struct _hmml_parser* p)
{
for(;;) {
uint8_t c = *p->cursor;
if(c && c <= ' ') {
if(c == '\n') {
++p->line;
}
++p->cursor;
} else {
break;
}
}
}
static _Bool _hmml_str_eq(struct _hmml_str a, struct _hmml_str b)
{
return a.len == b.len && memcmp(a.ptr, b.ptr, a.len) == 0;
}
static _Bool _hmml_unesc(char in, char* out)
{
if(strchr("[]:@~\\\"", in)) {
*out = in;
return 1;
} else {
return 0;
}
}
static char* _hmml_read_attr(struct _hmml_parser* p, char* mem, size_t mem_size, _Bool break_on_punct)
{
const char* src = p->cursor;
char* dst = mem;
if(*src == '"') {
++src;
while(*src && *src != '"' && (size_t)(src - p->cursor) < mem_size) {
char converted;
if(*src == '\\' && _hmml_unesc(src[1], &converted)) {
*dst++ = converted;
src += 2;
} else {
*dst++ = *src++;
}
}
if(*src != '"') {
_hmml_err(p, "Partially quoted attribute");
}
*dst = '\0';
p->cursor = src+1;
} else {
const char* breaks = break_on_punct
? " ]\r\n\t:,'-.#=[\\?!…()\"%"
: " ]\r\n\t"
;
size_t n = strcspn(src, breaks);
if(n >= mem_size) {
_hmml_err(p, "Attribute [%.10s...] too long", p->cursor);
}
memcpy(dst, src, n);
dst += n;
*dst = '\0';
p->cursor += n;
}
return dst;
}
static void _hmml_read_kv(struct _hmml_parser* p, struct _hmml_str* key, struct _hmml_str* val)
{
static char key_memory[64];
static char val_memory[1024];
size_t key_len = strcspn(p->cursor, " \r\n\t=");
if(key_len >= sizeof(key_memory)) {
_hmml_err(p, "Attribute key [%.10s...] too long", p->cursor);
}
memcpy(key_memory, p->cursor, key_len);
key_memory[key_len] = '\0';
p->cursor += key_len;
_hmml_skip_ws(p);
if(*p->cursor != '=') {
_hmml_err(p, "Expected '=', got [%.3s]", p->cursor);
}
++p->cursor;
char* end = _hmml_read_attr(p, val_memory, sizeof(val_memory), 0);
_hmml_debug("read kv [%s] = [%s]\n", key_memory, val_memory);
key->ptr = key_memory;
key->len = key_len;
val->ptr = val_memory;
val->len = end - val_memory;
}
static HMML_Marker _hmml_parse_marker(struct _hmml_parser* p)
{
static char marker_mem[4096];
// the extended markers are inside [ ] and can contain parameters
_Bool extended = *p->cursor == '[';
if(extended) {
++p->cursor;
}
HMML_Marker marker = {
.offset = -1,
};
char c = *p->cursor;
if(c == '~') {
marker.type = HMML_PROJECT;
} else if(c == '@') {
marker.type = HMML_MEMBER;
} else if(c == ':') {
marker.type = HMML_CATEGORY;
} else {
_hmml_err(p, "Unknown marker type");
}
++p->cursor;
char* end = _hmml_read_attr(p, marker_mem, sizeof(marker_mem), !extended);
marker.marker = _hmml_persist_str(p, (struct _hmml_str){ marker_mem, end - marker_mem });
if(extended) {
_hmml_skip_ws(p);
if(*p->cursor == '#') {
++p->cursor;
size_t n = strcspn(p->cursor, " ");
marker.episode = _hmml_persist_str(p, (struct _hmml_str){ p->cursor, n });
p->cursor += n + 1;
}
if(*p->cursor != ']') {
const char* end = p->cursor;
for(;;) {
if(!*end) {
break;
}
char converted;
if(*end == '\\' && _hmml_unesc(end[1], &converted)) {
end += 2;
} else if(*end == ']'){
break;
} else {
++end;
}
}
marker.parameter = _hmml_persist_str(p, (struct _hmml_str){ p->cursor, end - p->cursor });
p->cursor = end;
}
if(*p->cursor != ']') {
_hmml_err(p, "Expected ']'");
}
++p->cursor;
}
return marker;
}
static HMML_Reference _hmml_parse_ref(struct _hmml_parser* p)
{
HMML_Reference ref = {
.offset = -1,
};
struct str_attr {
struct _hmml_str str;
char** dest;
} str_attrs[] = {
{ HSTR("site") , &ref.site },
{ HSTR("page") , &ref.page },
{ HSTR("url") , &ref.url },
{ HSTR("title") , &ref.title },
{ HSTR("article") , &ref.article },
{ HSTR("author") , &ref.author },
{ HSTR("editor") , &ref.editor },
{ HSTR("publisher"), &ref.publisher },
{ HSTR("isbn") , &ref.isbn },
};
for(;;) {
next_attr:
_hmml_skip_ws(p);
if(*p->cursor == ']') {
++p->cursor;
break;
}
struct _hmml_str key, value;
_hmml_read_kv(p, &key, &value);
for(size_t i = 0; i < countof(str_attrs); ++i) {
struct str_attr* s = str_attrs + i;
if(_hmml_str_eq(key, s->str)) {
*s->dest = _hmml_persist_str(p, value);
goto next_attr;
}
}
_hmml_err(p, "Unknown reference attribute");
}
return ref;
}
static void _hmml_parse_timecode(struct _hmml_parser* p, HMML_Timestamp* ts)
{
unsigned int h = 0, m = 0, s = 0, ms = 0;
int offset = 0;
int count = sscanf(p->cursor, "[%u:%u%n", &m, &s, &offset);
if(count < 2) {
_hmml_err(p, "Unable to parse timecode");
}
p->cursor += offset;
char c = *p->cursor;
if(c == ':') {
unsigned int tmp;
offset = 0;
if(sscanf(p->cursor, ":%u%n", &tmp, &offset) != 1 || offset == 0) {
_hmml_err(p, "Unable to parse 3-part timecode");
}
h = m;
m = s;
s = tmp;
p->cursor += offset;
c = *p->cursor;
}
if(c == '.') {
unsigned int tmp;
offset = 0;
int non_number_chars = 2;
int digits_in_100 = 3;
int max_chars_to_parse = non_number_chars + digits_in_100;
if(sscanf(p->cursor, ".%u]%n", &tmp, &offset) != 1 || offset == 0 || offset > max_chars_to_parse) {
_hmml_err(p, "Unable to parse %u.5-part timecode", h ? 3 : 2);
}
for(int i = offset - non_number_chars; i < digits_in_100; ++i) {
tmp *= 10;
}
ms = tmp;
p->cursor += offset;
} else if(c != ']') {
_hmml_err(p, "Unable to parse timecode");
} else {
++p->cursor;
}
if(ms >= 1000) {
_hmml_err(p, "Milliseconds cannot exceed 999");
}
if(s >= 60) {
_hmml_err(p, "Seconds cannot exceed 59");
}
if(m >= 60) {
_hmml_err(p, "Minutes cannot exceed 59");
}
ts->h = h;
ts->m = m;
ts->s = s;
ts->ms = ms;
}
static void _hmml_store_marker(struct _hmml_parser* p, HMML_Timestamp* ts, char** out, char* text_mem, size_t text_mem_size)
{
HMML_Marker m = _hmml_parse_marker(p);
m.offset = (*out) - text_mem;
_hmml_persist_array(p, &ts->markers, &ts->marker_count, m);
const char* marker_text = m.parameter
? m.parameter
: m.marker
;
size_t text_len = strlen(marker_text);
if((*out) + text_len > text_mem + text_mem_size) {\
_hmml_err(p, "Not enough text memory");\
}
memcpy(*out, marker_text, text_len);
*out += text_len;
}
static size_t _hmml_parse_text(struct _hmml_parser* p, HMML_Timestamp* ts)
{
static char text_mem[4096];
char* out = text_mem;
memset(text_mem, 0, sizeof(text_mem));
for(;;) {
size_t n = strcspn(p->cursor, "\\\n\r[]:@~");
char c = p->cursor[n];
if(out + n > text_mem + sizeof(text_mem)) {\
_hmml_err(p, "Not enough text memory");\
}
memcpy(out, p->cursor, n);
p->cursor += n;
out += n;
if(c == '\0') {
_hmml_err(p, "Unexpected EOF");
}
else if(c == ']') {
++p->cursor;
break;
}
else if(c == '\\') {
char converted;
if(_hmml_unesc(p->cursor[1], &converted)) {
*out++ = converted;
p->cursor += 2;
} else {
*out++ = '\\';
p->cursor++;
}
}
else if(c == '\n' || c == '\r') {
++p->cursor;
}
else if(c == '[') {
if(strncmp(p->cursor + 1, "ref", 3) == 0) {
p->cursor += 4;
HMML_Reference ref = _hmml_parse_ref(p);
ref.offset = out - text_mem;
_hmml_persist_array(p, &ts->references, &ts->reference_count, ref);
} else {
_hmml_store_marker(p, ts, &out, text_mem, sizeof(text_mem));
}
}
// it is a @ ~ or : marker without parameters
else {
// if next char is a space, or prev char is not a space*, then it can't be a marker
// * unless it's the first char
if(strchr(" \t\r\n", p->cursor[1]) || !(out == text_mem || strchr(" \t\r\n", p->cursor[-1]))) {
*out++ = c;
++p->cursor;
} else {
_hmml_store_marker(p, ts, &out, text_mem, sizeof(text_mem));
}
}
if((size_t)(out - text_mem) >= sizeof(text_mem)) {
_hmml_err(p, "Not enough text memory");
}
}
// trim trailing whitespace
while(out > text_mem && (uint8_t)(out[-1]) <= ' ') {
out[-1] = '\0';
--out;
}
size_t text_size = out - text_mem;
ts->text = _hmml_persist_str(p, (struct _hmml_str){ text_mem, text_size });
return text_size;
}
static void _hmml_parse_quote(struct _hmml_parser* p, HMML_Timestamp* ts)
{
char member[256];
int id;
int off = 0;
if(sscanf(p->cursor, "[quote %255s %d]%n", member, &id, &off) == 2 && off) {
ts->quote.present = 1;
ts->quote.id = id;
ts->quote.author = _hmml_persist_str(p, (struct _hmml_str){ member, strlen(member) });
} else if(sscanf(p->cursor, "[quote %d]%n", &id, &off) == 1 && off) {
ts->quote.present = 1;
ts->quote.id = id;
} else {
_hmml_err(p, "Unable to parse quote");
}
p->cursor += off;
}
static void _hmml_parse_timestamps(struct _hmml_parser* p)
{
for(;;) {
_hmml_skip_ws(p);
if(*p->cursor == '\0') {
_hmml_err(p, "Unexpected EOF");
}
if(strncmp(p->cursor, "[/video]", 8) == 0) {
break;
}
HMML_Timestamp ts = {
.line = p->line
};
_hmml_parse_timecode(p, &ts);
if(*p->cursor != '[') {
_hmml_err(p, "Expected '['");
}
if(p->cursor[1] == '@') {
HMML_Marker m = _hmml_parse_marker(p);
ts.author = m.marker;
}
++p->cursor;
int text_len = _hmml_parse_text(p, &ts);
if(p->cursor[0] == '[' && p->cursor[1] == ':') {
++p->cursor;
do {
HMML_Marker m = _hmml_parse_marker(p);
_hmml_persist_array(p, &ts.markers, &ts.marker_count, m);
_hmml_skip_ws(p);
if(*p->cursor != ':' && *p->cursor != ']') {
_hmml_err(p, "Unterminated post-text category node");
}
} while(*p->cursor == ':');
++p->cursor;
}
if(p->cursor[0] == '[' && p->cursor[1] == 'q') {
_hmml_parse_quote(p, &ts);
}
// convert all markers to lowercase, fix any out of range offsets
for(size_t i = 0; i < ts.marker_count; ++i) {
HMML_Marker* m = ts.markers + i;
for(char* c = m->marker; *c; ++c) {
if(*c >= 'A' && *c <= 'Z') {
*c = (*c - ('A' - 'a'));
}
}
if(m->offset > text_len) {
m->offset = text_len;
}
}
for(size_t i = 0; i < ts.reference_count; ++i) {
HMML_Reference* ref = ts.references + i;
if(ref->offset > text_len) {
ref->offset = text_len;
}
}
_hmml_persist_array(p, &p->out.timestamps, &p->out.timestamp_count, ts);
}
}
static HMML_Credit _hmml_parse_credit(struct _hmml_parser* p, struct _hmml_str value)
{
HMML_Credit credit = {};
char* colon = strchr(value.ptr, ':');
if(colon) {
*colon = '\0';
credit.name = _hmml_persist_str(p, (struct _hmml_str){ value.ptr, colon - value.ptr });
credit.role = _hmml_persist_str(p, (struct _hmml_str){ colon+1, value.len - ((colon+1) - value.ptr) });
} else {
credit.name = _hmml_persist_str(p, value);
}
return credit;
}
static void _hmml_parse_video(struct _hmml_parser* p)
{
struct str_attr {
struct _hmml_str str;
char** dest;
} str_attrs[] = {
{ HSTR("stream_platform"), &p->out.metadata.stream_platform },
{ HSTR("project") , &p->out.metadata.project },
{ HSTR("title") , &p->out.metadata.title },
{ HSTR("vod_platform") , &p->out.metadata.vod_platform },
{ HSTR("id") , &p->out.metadata.id },
{ HSTR("template") , &p->out.metadata.template },
{ HSTR("medium") , &p->out.metadata.medium },
{ HSTR("number") , &p->out.metadata.number },
{ HSTR("output") , &p->out.metadata.output },
{ HSTR("cc_lang") , &p->out.metadata.cc_lang },
};
for(;;) {
next_attr:
_hmml_skip_ws(p);
if(*p->cursor == ']') {
++p->cursor;
_hmml_parse_timestamps(p);
return;
}
struct _hmml_str key, value;
_hmml_read_kv(p, &key, &value);
for(size_t i = 0; i < countof(str_attrs); ++i) {
struct str_attr* s = str_attrs + i;
if(_hmml_str_eq(key, s->str)) {
*s->dest = _hmml_persist_str(p, value);
goto next_attr;
}
}
if(_hmml_str_eq(key, HSTR("credit"))) {
HMML_Credit credit = _hmml_parse_credit(p, value);
_hmml_persist_array(p, &p->out.metadata.credits, &p->out.metadata.credit_count, credit);
goto next_attr;
}
if(_hmml_str_eq(key, HSTR("uncredit"))) {
HMML_Credit uncredit = _hmml_parse_credit(p, value);
_hmml_persist_array(p, &p->out.metadata.uncredits, &p->out.metadata.uncredit_count, uncredit);
goto next_attr;
}
HMML_VideoCustomMetaData custom = {
.key = _hmml_persist_str(p, key),
.value = _hmml_persist_str(p, value),
};
_hmml_persist_array(p, &p->out.metadata.custom, &p->out.metadata.custom_count, custom);
}
}
HMML_Output hmml_parse(const char* string)
{
struct _hmml_parser p = {
.mem = string,
.cursor = string,
.line = 1,
};
if(setjmp(p.err_buf) == 1) {
// if it returns 1, an error happened
p.out.free_list = p.free_list;
return p.out;
}
const struct _hmml_str prefix = HSTR("[video");
if(strncmp(p.cursor, prefix.ptr, prefix.len)) {
_hmml_err(&p, "Missing initial video tag.");
} else {
p.cursor += prefix.len;
_hmml_parse_video(&p);
}
p.out.free_list = p.free_list;
p.out.well_formed = 1;
return p.out;
}
void hmml_free(HMML_Output* out)
{
if(!out->free_list) {
return;
}
for(uintptr_t i = 2; i < ((uintptr_t*)out->free_list)[1]; ++i) {
free(((void**)out->free_list)[i]);
}
free(out->free_list);
}
const struct HMML_Version hmml_version = {
2, 0, 15
};
#undef HSTX
#undef HSTR
#endif

View File

@ -1,2 +0,0 @@
hmmldump: dump.c ../hmmlib.h stb_sb.h
gcc -Wall -Wextra -I.. -g $< -o $@

View File

@ -1,241 +0,0 @@
#define HMMLIB_IMPLEMENTATION
#include "hmmlib.h"
#include "stb_sb.h"
#include <stdbool.h>
#include <getopt.h>
typedef struct {
char* text;
int* lines;
} Index;
static Index* index_find(Index* base, const char* text)
{
for(size_t i = 0; i < sb_count(base); ++i){
if(strcmp(base[i].text, text) == 0){
return base + i;
}
}
return NULL;
}
void hmml_dump(HMML_Output* hmml, bool extra)
{
if(!hmml){
puts("(null)");
return;
}
if(!hmml->well_formed){
printf("Error:%d:%d %s\n", hmml->error.line, hmml->error.col, hmml->error.message);
return;
}
if(extra) {
puts("Metadata:");
static const char* meta[] = { "stream_platform", "project", "title", "vod_platform", "id", "output", "template", "medium" };
for(size_t i = 0; i < countof(meta); ++i) {
const char* value = ((char**)&hmml->metadata)[i];
printf(" %s = %s\n", meta[i], value);
}
puts(" Credits:");
for(size_t i = 0; i < hmml->metadata.credit_count; ++i) {
HMML_Credit* c = hmml->metadata.credits + i;
printf(" %s [%s]\n", c->name, c->role);
}
puts(" Custom:");
for(size_t i = 0; i < hmml->metadata.custom_count; ++i) {
HMML_VideoCustomMetaData* m = hmml->metadata.custom + i;
printf(" %s = %s\n", m->key, m->value);
}
}
puts("Annotations:");
for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i;
char time_buf[256];
char* tp = time_buf;
if(a->h) {
*tp++ = (a->h%10) + '0';
sprintf(tp, ":%02d:%02d", a->m, a->s);
} else {
sprintf(tp, " %2d:%02d", a->m, a->s);
}
printf("\t%3d [%s] [%s]\n", a->line, time_buf, a->text);
}
Index* authors = NULL;
Index* markers[HMML_MARKER_COUNT] = {};
int max_text_len = 0;
for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i;
if(a->author){
int len = strlen(a->author);
if(len > max_text_len){
max_text_len = len;
}
Index* idx;
if(!(idx = index_find(authors, a->author))){
Index x = { .text = a->author };
sb_push(authors, x);
idx = &sb_last(authors);
}
sb_push(idx->lines, a->line);
}
}
for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i;
for(size_t j = 0; j < a->marker_count; ++j){
int type = a->markers[j].type;
char* text = a->markers[j].marker;
int len = strlen(text);
if(len > max_text_len){
max_text_len = len;
}
Index* idx;
if(!(idx = index_find(markers[type], text))){
Index x = { .text = text };
sb_push(markers[type], x);
idx = &sb_last(markers[type]);
}
sb_push(idx->lines, a->line);
}
}
puts("Authors:");
for(size_t i = 0; i < sb_count(authors); ++i){
printf("\t %*s: ", max_text_len, authors[i].text);
for(size_t j = 0; j < sb_count(authors[i].lines); ++j){
printf("%3d ", authors[i].lines[j]);
}
puts("");
}
static const char* m_tags[HMML_MARKER_COUNT] = { "Categories", "Members", "Projects" };
for(size_t i = 0; i < HMML_MARKER_COUNT; ++i){
printf("%s:\n", m_tags[i]);
for(size_t j = 0; j < sb_count(markers[i]); ++j){
printf("\t %*s: ", max_text_len, markers[i][j].text);
for(size_t k = 0; k < sb_count(markers[i][j].lines); ++k){
printf("%3d ", markers[i][j].lines[k]);
}
puts("");
}
}
static const char* r_tags[] = { "Site", "Page", "URL", "Title", "Article", "Author", "Editor", "Publisher", "ISBN" };
puts("References:");
for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i;
for(size_t j = 0; j < a->reference_count; ++j){
printf("\t%3d ", a->line);
HMML_Reference* r = a->references + j;
for(size_t k = 0; k < countof(r_tags); ++k){
char* item = ((char**)r)[k];
if(item){
printf("[%s = %s] ", r_tags[k], item);
}
}
puts("");
}
}
puts("Quotes:");
for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i;
if(a->quote.present){
if(a->quote.author){
printf("\t%3d [Quote #%d, by %s]", a->line, a->quote.id, a->quote.author);
} else {
printf("\t%3d [Quote #%d]", a->line, a->quote.id);
}
puts("");
}
}
for(size_t i = 0; i < sb_count(authors); ++i){
sb_free(authors[i].lines);
}
sb_free(authors);
for(size_t i = 0; i < HMML_MARKER_COUNT; ++i){
for(size_t j = 0; j < sb_count(markers[i]); ++j){
sb_free(markers[i][j].lines);
}
sb_free(markers[i]);
}
}
void usage(char* argv0, FILE* out)
{
fprintf(out, "Usage: %s [-bx] [file]\n", argv0);
}
int main(int argc, char** argv)
{
bool dump_extra = false;
bool breakpoint = false;
int opt;
while((opt = getopt(argc, argv, "bx")) != -1) {
if(opt == 'x') {
dump_extra = true;
} else if(opt == 'b') {
breakpoint = true;
} else {
usage(argv[0], stderr);
return 1;
}
}
if(optind >= argc){
usage(argv[0], stderr);
return 1;
}
argc -= (optind-1);
argv += (optind-1);
FILE* f = fopen(argv[1], "r");
if(!f) {
perror(argv[1]);
return 1;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
char* mem = malloc(size+1);
mem[size] = 0;
fread(mem, 1, size, f);
fclose(f);
HMML_Output out = hmml_parse(mem);
free(mem);
if(breakpoint) {
asm("int3");
}
hmml_dump(&out, dump_extra);
hmml_free(&out);
}

View File

@ -1,16 +0,0 @@
fuzz: fuzz.c ../../hmmlib.h
afl-gcc -I../.. -g -D_GNU_SOURCE -fprofile-arcs -ftest-coverage $< -o $@
run: fuzz | output
afl-fuzz -i tests -o output ./$<
cov: fuzz | output
afl-cov -d output --coverage-cmd 'cat AFL_FILE | ./fuzz' --code-dir . --enable-branch-coverage --overwrite
output:
mkdir -p $@
clean:
rm -rf fuzz fuzz.gcno fuzz.gcda output
.PHONY: run cov clean

View File

@ -1,20 +0,0 @@
#define HMMLIB_IMPLEMENTATION
#include "hmmlib.h"
int main(int argc, char** argv)
{
char* mem = NULL;
size_t size = 0;
char buf[BUFSIZ];
while(fgets(buf, BUFSIZ, stdin)) {
size_t n = strlen(buf) + 1;
mem = realloc(mem, size + n);
memcpy(mem + size, buf, n);
size += n - 1;
}
HMML_Output out = hmml_parse(mem);
free(mem);
hmml_free(&out);
}

View File

@ -1,11 +0,0 @@
[video member=nothings credit="inso:programmer" annotator=Miblo]
[2:01][Recap and update the TODO list]
[38:58][Continue implementing parse_tag()][:parsing :test]
[41:40][@miblo][Ohhh, right. Yeah, the \[video\] and \[/video\] tags are the only :ones that [:have a s d f] that open-close format. All the [~other] [@tags abc] are "single" tags]
[45:17][Enable parse_tag() to tokenise the \[video\] node][:parsing][quote bob 1]
[3:42:11][@miblo][@nothings2: That's fine, yeah! They are also in: [ref
site="GitLab: Annotation-Pushers / Annotation-Game"
page="projects/nothings/obbg"
url=http://git.handmadedev.org/Annotation-Pushers/Annotation-Game/tree/master/projects/nothings/obbg]]
[3:50:44][Take a break][quote 5]
[/video]

View File

@ -1,3 +0,0 @@
[video credit=inso:programmer]
[2:01][Recap and :update the ~TODO @list]
[/video]

View File

@ -1,25 +0,0 @@
[video member=nothings stream_platform=twitch stream_username=nothings2 project=obbg title="Open Block Building Game Development #32 (1/2)" vod_platform=youtube id=Vm-0ZUVMHHc annotator=Miblo]
[2:01][Recap and update the TODO list]
[10:08][Consult the example YouTube description file and the longest .hmml file][:test1 :test2 :abcd]
[38:58][Continue implementing parse_tag()][:parsing]
[41:40][@miblo][Ohhh, right. Yeah, the \[video\] and \[/video\] tags are the only ones that have that open-close format. All the other tags are "single" tags]
[41:51][Continue writing parse_tag() anyway][:parsing]
[43:10][@miblo][@nothings2: There isn't really, other than the rambling in: [ref
site="GitLab: Annotation-Pushers / Annotation-System / Issues"
page="Handmade Annotation Markup Language (previously MibloMarkup)"
url=http://git.handmadedev.org/Annotation-Pushers/Annotation-System/issues/2]]
[45:17][Enable parse_tag() to tokenise the \[video\] node][:parsing]
[1:51:32][Parse out the embedded \: and \@ tags][:parsing]
[1:57:50][:Run it to see if those two cases work and tweak the username conversion]
[3:15:14][@experior][Add a space after the pound? Or another character]
[3:16:40][Delete the \@ in the text node][:parsing]
[3:42:11][@miblo][@nothings2: That's fine, yeah! They are also in: [ref
site="GitLab: Annotation-Pushers / Annotation-Game"
page="projects/nothings/obbg"
url=http://git.handmadedev.org/Annotation-Pushers/Annotation-Game/tree/master/projects/nothings/obbg]]
[3:47:14][@insofaras][[ref
site="GitHub: nothings/obbg"
page="Pull Request #8: get it building and running on linux by insofaras"
url=https://github.com/nothings/obbg/pull/8/files#diff-90c561ba68b3be193f3378639ef489a0R1831]]
[3:50:44][Take a break]
[/video]

View File

@ -1,59 +0,0 @@
// stb stretchy_buffer.h v1.02 nothings.org/stb
// with custom addtions sb_end, sb_pop, sb_erase
#ifndef STB_STRETCHY_BUFFER_H_INCLUDED
#define STB_STRETCHY_BUFFER_H_INCLUDED
#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES
#define sb_free stb_sb_free
#define sb_push stb_sb_push
#define sb_count stb_sb_count
#define sb_add stb_sb_add
#define sb_last stb_sb_last
#define sb_end stb_sb_end
#define sb_pop stb_sb_pop
#define sb_erase stb_sb_erase
#define sb_each stb_sb_each
#endif
#define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),(a)=0,0 : 0)
#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v))
#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0)
#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)])
#define stb_sb_last(a) ((a)[stb__sbn(a)-1])
#define stb_sb_end(a) ((a) ? (a) + stb__sbn(a) : 0)
#define stb_sb_pop(a) (--stb__sbn(a))
#define stb_sb_erase(a,i) ((a) ? memmove((a)+(i), (a)+(i)+1, sizeof(*(a))*((--stb__sbn(a))-(i))),0 : 0);
#define stb__sbraw(a) ((size_t *) (a) - 2)
#define stb__sbm(a) stb__sbraw(a)[0]
#define stb__sbn(a) stb__sbraw(a)[1]
#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a))
#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0)
#define stb__sbgrow(a,n) ((a) = stb__sbgrowf((a), (n), sizeof(*(a))))
#define stb_sb_each(n,h) for(typeof(h) n = h; n < sb_end(h); ++n)
#include <stdlib.h>
static inline void * stb__sbgrowf(void *arr, int increment, int itemsize)
{
size_t inc_cur = arr ? stb__sbm(arr) + (stb__sbm(arr) >> 1) : 0;
size_t min_needed = stb_sb_count(arr) + increment;
size_t m = inc_cur > min_needed ? inc_cur : min_needed;
size_t *p = (size_t *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(size_t)*2);
if (p) {
if (!arr)
p[1] = 0;
p[0] = m;
return p+2;
} else {
#ifdef STRETCHY_BUFFER_OUT_OF_MEMORY
STRETCHY_BUFFER_OUT_OF_MEMORY ;
#endif
return (void *) (2*sizeof(size_t)); // try to force a NULL pointer exception later
}
}
#endif // STB_STRETCHY_BUFFER_H_INCLUDED