Compare commits

..

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

32 changed files with 7637 additions and 39610 deletions

250
README.md
View File

@ -3,28 +3,24 @@ deployment
## Cinera ## Cinera
### Install the dependencies ### Download, and prepare the parser
1. `git clone git@gitssh.handmade.network:Annotation-Pushers/Annotation-System.git`
2. `cd Annotation-System/hmmlib`
3. `make`
4. `cp hmml.a hmmlib.h ../cinera/`
5. `cd ../cinera/`
Note: For each parser update, remember to make and copy it into place.
### Install the dependency
1. curl 1. curl
### Download, and copy the parser into place
1. `git clone https://git.handmade.network/Annotation-Pushers/Annotation-System.git`
2. `cd Annotation-System/cinera`
3. `cp ../hmmlib2/hmmlib.h .`
Note: For each parser update, remember to 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,104 +37,174 @@ 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 Root Directory, path shallower than or equal to the CSS, Images and JS
directories
-R Root URL, corresponding to the Root Directory (optional if the Output
Base Directory resides in 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 Index Template Location, relative to Template Directory
-y Player Template Location, relative to Template Directory
#### Templates #### Templates
*(Global) Search Template* *Index Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_ - `<!-- __CINERA_INCLUDES__ -->` _the necessary `.css` and `.js` files_
- `<!-- __CINERA_SEARCH__ -->` _the table of contents and search functionality_ - `<!-- __CINERA_INDEX__ -->` _the table of contents, and search functionality_
*Player Template* *Player Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_ - `<!-- __CINERA_INCLUDES__ -->` _the necessary `.css` and `.js` files, and
- `<!-- __CINERA_PLAYER__ -->` charset setting_
- `<!-- __CINERA_MENUS__ -->` _ _the menu bar that typically appears above the
player in my samples_
- `<!-- __CINERA_PLAYER__ -->` _the player_
- `<!-- __CINERA_SCRIPT__ -->` _the filter state objects and `.js` file, which
must come after both the MENUS and PLAYER tags_
*Optional tags available for use in your Player Template* *Optional tags available for use in your Player Template*
- `<!-- __CINERA_TITLE__ -->` - `<!-- __CINERA_TITLE__ -->` _the day / episode name, intended to be used
- `<!-- __CINERA_VIDEO_ID__ -->` inside your own `<title>` element, but may be used wherever and as many times
- `<!-- __CINERA_VOD_PLATFORM__ -->` as you want on your page_
- `<!-- __CINERA_VIDEO_ID__ -->` _the unique identifer of the video as provided
by the VoD platform_
*Other tags available for use in any template* *Other available tags*
- Asset tags: - `<!-- __CINERA_PROJECT__ -->` _the full name of the project_
- `<!-- __CINERA_ASSET__ path.ext -->` - `<!-- __CINERA_URL__ -->` _the URL where we have derived the page will be
General purpose tag that outputs the URL of the specified asset publically accessibly, only really usable if BaseURL is set (-B)_
relative to the Asset Root URL (-R)
- `<!-- __CINERA_IMAGE__ path.ext -->`
General purpose tag that outputs the URL of the specified asset
relative to the Images Directory (-i)
- `<!-- __CINERA_CSS__ path.ext -->`
Convenience tag that outputs a `<link rel="stylesheet"...>` node
for the specified asset relative to the CSS Directory (-c), for
use inside your `<head>` block
- `<!-- __CINERA_JS__ path.ext -->`
Convenience tag that outputs a `<script type="text/javascript"...>`
node for the specified asset relative to the JS Directory (-j),
for use wherever a `<script>` node is valid
The path.ext in these tags supports parent directories to locate the
asset file relative to its specified type directory (generic, CSS, image
or JS), including the "../" directory, and paths containing spaces must
be surrounded with double-quotes (\-escapable if the quoted path itself
contains double-quotes).
All these asset tags additionally perform versioning, appending a query string
(-Q) and the file's checksum to the URL. Changes to a file 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__ -->`
The project's `html_title` if configured, otherwise its `title`
- `<!-- __CINERA_PROJECT_PLAIN__ -->`
The project's `title`
- `<!-- __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_THEME__ -->`
- `<!-- __CINERA_URL__ -->`
- `<!-- __CINERA_CUSTOM0__ -->` - `<!-- __CINERA_CUSTOM0__ -->`
- `<!-- __CINERA_CUSTOM1__ -->` - `<!-- __CINERA_CUSTOM1__ -->`
- `<!-- __CINERA_CUSTOM2__ -->` - `<!-- __CINERA_CUSTOM2__ -->`
- ⋮
- `<!-- __CINERA_CUSTOM15__ -->` - `<!-- __CINERA_CUSTOM15__ -->`
Freeform buffers for small snippets of localised information, e.g. a _Freeform buffers for small snippets of localised information, e.g. a single
single `<a>` element or perhaps a `<!-- comment -->` `<a>` element or perhaps a `<!-- comment -->` They correspond to the custom0
They correspond to the custom0 to custom15 attributes in the [video] to custom15 attributes in the [video] node in your .hmml files 0 to 11 may
node in your .hmml files hold up to 255 characters 12 to 15 may hold up to 1023 characters_
0 to 11 may hold up to 255 characters
12 to 15 may hold up to 1023 characters
Feel free to play with templates to your heart's content. If you do anything Feel free to play with templates to your heart's content. If you do anything
invalid, _Cinera_ will tell you what's wrong. 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 <root directory>
Defaults to: $XDG_CONFIG_HOME/cinera/cinera.conf Override default root directory (".")
-0 -R <root URL>
Dry-run mode. Parse and print the config, but do not modify the Override default 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
Project Settings:
-p <project ID>
Set the project ID, equal to the "project" field in the HMML files
NOTE: Setting the project ID triggers PROJECT EDITION
-m <default medium>
Override default default medium ("programming")
Known project defaults:
bitwise: programming
book: research
pcalc: programming
riscy: programming
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
-q
Quit after syncing with annotation files in project input directory
UNSUPPORTED: This is likely to be removed in the future
Project Input Paths
-d <annotations directory>
Override default annotations directory (".")
-t <templates directory>
Override default templates directory (".")
-x <index template location>
Set index 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 <index location>
Override default index 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")
-e -e
Display (examine) database and exit Display (examine) index file and exit
-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
-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)
-v -v
Display version and exit Display version and exit
-h -h

File diff suppressed because it is too large Load Diff

View File

@ -1,291 +1,31 @@
/* Generic Navigation */
/* Generic Navigation end */
/* Structure */
/* Horizontal */
ul.cineraNavHorizontal,
ul.cineraNavHorizontal ul {
display: flex;
flex-grow: 1;
flex-flow: row wrap;
border: 0;
margin: 0;
padding: 0;
}
ul.cineraNavHorizontal li {
display: flex;
flex-direction: column;
flex-grow: 1;
}
ul.cineraNavHorizontal {
border-right: 1px solid;
}
ul.cineraNavHorizontal a {
flex-grow: 1;
padding: 8px;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px solid;
border-bottom: 1px solid;
}
/* Horizontal end */
/* Dropdown */
nav.cineraNavDropdown {
display: flex;
flex-direction: column;
}
nav.cineraNavDropdown .cineraNavTitle {
text-align: center;
padding: 8px 0;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.cineraPositioner {
position: relative;
}
nav.cineraNavDropdown ul.cineraNavHorizontal {
display: none;
width: 100%;
z-index: 4096;
position: absolute;
}
nav.cineraNavDropdown ul.cineraNavHorizontal.visible {
display: flex;
}
/* Dropdown end */
/* Structure end */
/* Typography */
ul.cineraNavHorizontal a,
ul.cineraNavPlain a {
text-decoration: none;
}
/* Plain */
ul.cineraNavPlain li.current > a {
font-weight: bold;
}
/* Plain end */
/* Typography end */
/* Index */ /* Index */
#cineraIndex
{
display: flex;
flex-direction: column;
}
#cineraIndex #cineraIndexControl
{
display: flex;
position: -webkit-sticky;
position: sticky;
top: 0px;
z-index: 64;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
#cineraIndex .cineraIndexGridContainer
{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
#cineraIndex .cineraIndexGridContainer.Portrait
{
flex-direction: column-reverse;
}
#cineraIndex .cineraIndexGridContainer.Landscape.Left
{
flex-direction: row-reverse;
}
#cineraIndex .cineraIndexGridContainer.Landscape.Right
{
flex-direction: row;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal
{
flex-direction: column;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal .cineraButton.prev
{
order: 1;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal .cineraButton.next
{
order: 2;
}
#cineraIndex.anim .cineraTraversal,
#cineraIndex.anim .cineraTraversal * p
{
transition: transform .32s;
}
#cineraIndex .cineraTraversalContainer
{
overflow: hidden;
}
#cineraIndex .cineraTraversal
{
display: flex;
justify-content: center;
transform: rotate(0deg);
}
#cineraIndex.reversed .cineraTraversal
{
transform: rotate(180deg);
}
#cineraIndex .cineraTraversalContainer .cineraButton
{
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
padding: 2px;
height: 42px;
width: 42px;
}
#cineraIndex .cineraTraversal * p
{
transform: rotate(0deg);
}
#cineraIndex.reversed .cineraTraversal .ascension p
{
transform: rotate(-180deg);
}
#cineraIndex.reversed .cineraTraversal .prev p,
#cineraIndex.reversed .cineraTraversal .next p
{
transform: rotate(-360deg);
}
#cineraIndex .cineraTraversal .next.ascension p
{
transform: rotate(-45deg);
}
#cineraIndex.reversed .cineraTraversal .next.ascension p
{
transform: rotate(-315deg);
}
.cineraFilterProject .cineraText,
.cineraProjectTitle
{
display: flex;
}
.cineraMenu.cineraMenuTitle {
padding: 10px;
cursor: default;
}
.cineraMenuContainer
{
display: none;
position: absolute;
top: 100%;
z-index: 64;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
border: 1px solid;
border-top: none;
}
.cineraMenuContainer.visible
{
display: block;
max-height: 88vh;
overflow-y: auto;
}
.cineraFilterProject,
.cineraIndexProject > .cineraIndexProject {
padding-left: 1em;
}
.cineraIndexProject {
margin-top: 4px;
}
.cineraIndexProject .cineraProjectTitle,
.cineraMenuItem
{
font-weight: bold;
font-size: 1rem;
}
.cineraQueryContainer { .cineraQueryContainer {
flex-grow: 1; max-width: 1024px;
padding-left: 16px; margin: 15px auto;
display: flex; display: flex;
margin: auto; flex-direction: horizontal;
} }
.cineraQueryContainer label { .cineraQueryContainer label {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
margin: auto;
} }
.cineraQueryContainer .inputContainer { .cineraQueryContainer .inputContainer {
display: flex;
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;
padding: .5em;
} }
.cineraQueryContainer #query { .cineraQueryContainer #query {
width: 0px; width: 100%;
flex-grow: 1;
} }
.cineraQueryContainer .inputContainer .spinner { .cineraQueryContainer .inputContainer .spinner {
position: absolute; position: absolute;
top: 2px; top: 2px;
right: 5px; right: 5px;
font-size: .5rem; color: black;
color: #000000;
height: 100%; height: 100%;
display: none; display: none;
} }
@ -295,114 +35,25 @@ ul.cineraNavPlain li.current > a {
} }
#cineraResults, #cineraResults,
#cineraIndexList { #cineraIndex {
margin: 0 auto; margin: 0 auto;
max-width: 800px; max-width: 800px;
} }
#cineraResults .projectContainer { #cineraIndex #cineraIndexSort {
display: flex; margin-bottom: 8px;
flex-flow: column; display: inline-block;
} padding: 5px;
cursor: pointer;
#cineraIndexList .cineraIndexEntries {
display: flex;
flex-flow: column;
width: 100%;
}
#cineraIndex #cineraIndexGrid
{
display: flex;
overflow: hidden;
position: relative;
perspective-origin: center;
-webkit-perspective-origin: center;
}
#cineraIndex #cineraIndexGrid .cineraButtons
{
flex-shrink: 0;
display: grid;
grid-gap: 2px;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
#cineraIndex .cineraButton
{
border: 1px solid;
text-align: center;
font-size: 1rem;
font-weight: bold;
flex-direction: column;
user-select: none; user-select: none;
-moz-user-select: none; -moz-user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
} }
#cineraIndex #cineraIndexGrid .cineraButton.subdivision #cineraIndex #cineraIndexEntries {
{
box-sizing: border-box;
display: flex;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
overflow: hidden;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .head-item,
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .tail-item
{
display: flex; display: flex;
flex-flow: column;
width: 100%; width: 100%;
height: 50%;
overflow: hidden;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .head-item p,
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .tail-item p
{
border: 0;
margin: 0;
padding: 0;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision.leaf
{
justify-content: center;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision.leaf a
{
text-decoration: none;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision.leaf a:hover {
cursor: default;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .head-item,
#cineraIndex.reversed #cineraIndexGrid .cineraButton.subdivision .tail-item
{
align-items: flex-start;
justify-content: flex-start;
text-align: left;
}
#cineraIndex #cineraIndexGrid .cineraButton.subdivision .tail-item,
#cineraIndex.reversed #cineraIndexGrid .cineraButton.subdivision .head-item
{
align-items: flex-end;
justify-content: flex-end;
text-align: right;
}
#cineraIndexList.hidden,
#cineraIndex .cineraIndexGridContainer.hidden
{
display: none;
} }
#cineraResults .dayContainer { #cineraResults .dayContainer {
@ -413,21 +64,21 @@ 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;
max-width: 200px; width: 200px;
} }
.cineraIndexEntries div a { #cineraIndexEntries div a {
display: block; display: block;
padding: 5px; padding: 5px;
text-decoration: none; text-decoration: none;
} }
.cineraIndexEntries div a::before { #cineraIndexEntries div a::before {
content: "\2713"; content: "";
margin: 0 4px; margin: 0 4px;
} }
@ -454,12 +105,11 @@ ul.cineraNavPlain li.current > a {
#cineraResultsSummary { #cineraResultsSummary {
margin: 10px 0; margin: 10px 0;
text-align: center; text-align: center;
display: none;
} }
/* Player */ /* Player */
/* Player / Structure */ /* Structure */
.cineraMenus, .cineraMenus,
.cineraMenus > .menu > .refs .ref, .cineraMenus > .menu > .refs .ref,
@ -483,14 +133,6 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu { .cineraMenus > .menu {
position: relative; position: relative;
min-height: 1em;
min-width: 1em;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
} }
.cineraMenus > .menu.filter.responsible, .cineraMenus > .menu.filter.responsible,
@ -503,119 +145,103 @@ ul.cineraNavPlain li.current > a {
animation-iteration-count: 1; animation-iteration-count: 1;
} }
.cineraMenus > .cineraHelp, .cineraMenus .help .help_container .help_key {
.cineraHelp {
cursor: pointer;
box-sizing: content-box;
border: 1px solid;
border-radius: 4px;
height: .5rem;
width: .5rem;
padding: 4px;
margin: 2px;
font-size: .75rem;
font-weight: bold;
line-height: .5rem;
text-align: center;
z-index: 64;
}
.cineraHelp .help_container {
background-color: #000000; /* Per project */
color: #EEEEEE; /* Per project */
display: none;
font-weight: normal;
line-height: 12px;
opacity: 0.9;
padding: 8px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 97%;
overflow-y: auto;
}
.cineraHelp .help_container.visible {
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 {
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: #111; /* Per project */
border-radius: 4px; border-radius: 4px;
min-height: 1em; height: 16px;
min-width: 1em; width: 16px;
padding: 4px; padding: 4px;
line-height: 16px; line-height: 16px;
margin: 2px; margin: 2px;
} }
.cineraHelp .help_container .help_key.word { .cineraMenus .help .help_container .help_key.word {
width: auto; width: auto;
} }
.cineraHelp .help_container .help_key.modifer { .cineraMenus .help .help_container .help_key.modifer {
margin-right: 0; margin-right: 0;
} }
.cineraHelp .help_container .help_key.unavailable, .cineraMenus .help .help_container .help_key.unavailable,
.cineraHelp .help_container .help_text.unavailable, .cineraMenus .help .help_container .help_text.unavailable,
.cineraHelp .help_container h2 .unavailable { .cineraMenus .help .help_container h2 .unavailable {
opacity: 0.32; opacity: 0.32;
} }
.cineraHelp .help_container .key_block { .cineraMenus .help .help_container .key_block {
display: inline-flex; display: inline-flex;
align-items: flex-end; align-items: flex-end;
flex-direction: row; flex-direction: row;
margin: 8px; margin: 8px;
} }
.cineraHelp .help_container .help_text { .cineraMenus .help .help_container .help_text {
margin: 0 8px 0 2px; margin: 0 8px 0 2px;
} }
.cineraHelp .help_container h1 { .cineraMenus .help .help_container h1 {
display: inline; display: inline;
margin-left: 4px; margin-left: 4px;
} }
.cineraHelp .help_container h1:after { .cineraMenus .help .help_container h1:after {
content: "\a"; content: "\a";
} }
.cineraHelp .help_container h2 { .cineraMenus .help .help_container h2 {
font-size: 1rem; font-size: 16px;
margin-bottom: 8px; margin-bottom: 8px;
} }
.cineraHelp .help_container .help_paragraph { .cineraMenus .help .help_container .help_paragraph {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
} }
.cineraHelp .help_container .help_grid { .cineraMenus > .help {
cursor: pointer;
border: 1px solid;
border-radius: 4px;
height: 6px;
padding: 4px;
width: 6px;
margin: 2px;
font-size: 12px;
font-weight: bold;
line-height: 6px;
text-align: center;
z-index: 64;
}
.cineraMenus .help .help_container .help_grid {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
} }
.cineraMenus .help .help_container {
background-color: black; /* Per project */
color: #EEE; /* Per project */
display: none;
font-weight: normal;
line-height: 12px;
opacity: 0.9;
padding: 8px;
position: fixed;
right: 612px;
top: 42px;
}
.cineraMenus .help .help_container.visible {
display: block;
}
.cineraMenus > .menu .quotes_container, .cineraMenus > .menu .quotes_container,
.cineraMenus > .menu .references_container, .cineraMenus > .menu .references_container,
.cineraMenus > .menu .filter_container, .cineraMenus > .menu .filter_container,
@ -624,17 +250,18 @@ 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;
/* TODO(matt): Set the height to the player's height */
max-height: 512px;
overflow-y: auto; overflow-y: auto;
position: absolute; position: absolute;
right: 0; right: 0;
top: 100%; top: 100%;
z-index: 1;
} }
.cineraMenus > .menu .refs, .cineraMenus > .menu .refs,
.cineraMenus > .menu .filter_container, .cineraMenus > .menu .link_container {
.cineraMenus > .menu .link_container,
.cineraMenus > .menu .credits_container {
width: 350px; width: 350px;
} }
@ -642,13 +269,16 @@ ul.cineraNavPlain li.current > a {
overflow-x: hidden; overflow-x: hidden;
} }
.cineraMenus > .menu .sizing { .cineraMenus > .menu .filter_container {
z-index: -1; min-width: 350px;
display: block; }
.cineraMenus > .menu .credits_container {
min-width: 240px;
} }
.cineraMenus > .menu .visible { .cineraMenus > .menu .visible {
z-index: 8; display: block;
} }
.cineraMenus > .menu > .refs .ref { .cineraMenus > .menu > .refs .ref {
@ -673,6 +303,11 @@ ul.cineraNavPlain li.current > a {
padding: 16px; padding: 16px;
} }
.cineraMenus > .menu > .credits_container .credit .support .support_icon {
height: 16px;
width: 16px;
}
.cineraMenus > .menu > .refs .ref:last-child, .cineraMenus > .menu > .refs .ref:last-child,
.cineraMenus > .menu > .credits_container .credit:last-child { .cineraMenus > .menu > .credits_container .credit:last-child {
border: none; border: none;
@ -681,7 +316,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 +346,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 +381,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 +400,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,16 +470,8 @@ 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: 0;
overflow-y: scroll; overflow-y: scroll;
position: relative; position: relative;
} }
@ -859,20 +479,10 @@ 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;
} }
.cineraPlayerContainer {
background-color: #000000;
}
.cineraPlayerContainer .video_container {
display: flex;
justify-content: center;
align-self: center;
}
.cineraPlayerContainer .markers_container > a.episodeMarker { .cineraPlayerContainer .markers_container > a.episodeMarker {
cursor: pointer; cursor: pointer;
} }
@ -887,7 +497,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;
} }
@ -905,7 +515,7 @@ ul.cineraNavPlain li.current > a {
border-bottom: 1px solid; border-bottom: 1px solid;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
max-height: 320px; /* NOTE(matt): Required for the transition */ max-height: 320px;
transition: max-height .32s; transition: max-height .32s;
} }
@ -928,10 +538,10 @@ 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 { .cineraPlayerContainer .markers_container > .markers .marker.skip {
max-height: 0; max-height: 0;
transition: max-height .32s; transition: max-height .32s;
overflow: hidden; overflow: hidden;
@ -967,7 +577,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 +595,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;
@ -1004,124 +613,6 @@ ul.cineraNavPlain li.current > a {
margin-right: 8px; margin-right: 8px;
} }
/* 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 {
flex-direction: column;
}
#cineraIndex.mobile #cineraResults .dayContainer .dayName {
font-weight: bold;
text-align: center;
max-width: 100%;
}
#cineraIndex.mobile #cineraResults .dayContainer .markerList {
max-width: 100%;
}
.cinera.mobile
{
display: flex;
flex-direction: column;
flex-grow: 0;
-webkit-text-size-adjust: none;
}
.cinera.mobile .cineraMenus {
justify-content: center;
display: flex;
flex-direction: row;
position: relative;
border: 0;
}
.cinera.mobile .cineraMenus,
.cinera.mobile .player_container {
flex-grow: 0;
}
.cinera.mobile .cineraMenus .episode_name,
.cinera.mobile .cineraMenus > .menu > .view,
.cinera.mobile .cineraMenus > .menu > .views_container .view,
.cinera.mobile .cineraHelp,
#cineraIndex.mobile .cineraHelp,
.cinera.mobile .markers_container > .episodeMarker div:nth-child(2),
.cinera.mobile .markers_container > .episodeMarker div:nth-child(3),
.cinera.mobile .markers_container > .markers .marker:not(.current) {
display: none;
}
.cinera.mobile .cineraMenus .menu {
position: static;
}
.cinera.mobile .cineraMenus .menu .quotes_container,
.cinera.mobile .cineraMenus .menu .references_container,
.cinera.mobile .cineraMenus .menu .filter_container,
.cinera.mobile .cineraMenus .menu .link_container,
.cinera.mobile .cineraMenus .menu .credits_container {
box-sizing: border-box;
position: absolute;
}
.cinera.mobile .cineraPlayerContainer {
display: flex;
flex-direction: column;
}
.cinera.mobile .cineraPlayerContainer .markers_container {
display: flex;
flex-flow: row;
overflow-y: hidden;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker {
width: 32px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .markers {
flex-grow: 1;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev,
.cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next {
border: 0;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev {
border-right: 3px double;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next {
border-left: 3px double;
}
/* Mobile Style End */
/* CUSTOM PAGE STYLE */ /* CUSTOM PAGE STYLE */
/* /*
@ -1139,4 +630,4 @@ Open menu: &#9662; ▾
Open link in new tab: &#10555; or &10559; or &8599; or &#11127; Open link in new tab: &#10555; or &10559; or &8599; or &#11127;
Play from timecode: &#9205; (or, if &#9656;) Play from timecode: &#9205; (or, if &#9656;)
Playable from timecode: &#9657; Playable from timecode: &#9657;
*/ */

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,6 +1,121 @@
var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location; var menuState = [];
var titleBar = document.querySelector(".cineraMenus");
var quotesMenu = titleBar.querySelector(".quotes_container");
if(quotesMenu)
{
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;
}
var CineraProps = { var referencesMenu = titleBar.querySelector(".references_container");
if(referencesMenu)
{
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();
}
});
}
}
var 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) {
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) {
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") };
}
}
}
var views = {
REGULAR: 1,
THEATRE: 2,
SUPERTHEATRE: 3,
};
var cineraProps = {
C: null, C: null,
V: views.REGULAR, V: views.REGULAR,
Z: null, Z: null,
@ -10,49 +125,132 @@ var CineraProps = {
mW: null, mW: null,
H: null, H: null,
mH: null, mH: null,
P: null, P: null
Display: null,
FlexDirection: null,
JustifyContent: null,
O: null,
IsMobile: IsMobile(),
ScrollX: null,
ScrollY: null,
VODPlatform: null,
}; };
CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
var MobileCineraContentRuleSelector = ".cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker .cineraContent"; var viewsMenu = titleBar.querySelector(".views");
var MobileCineraContentRule = GetOrSetRule(MobileCineraContentRuleSelector); if(viewsMenu)
{
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 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 viewItems = viewsMenu.querySelectorAll(".view");
var MenuContainerRule = GetOrSetRule(MenuContainerRuleSelector); for(var i = 0; i < viewItems.length; ++i)
var cinera = document.querySelector(".cinera");
var player = new Player(cinera, onRefChanged);
window.addEventListener("resize", function() {
if(CineraProps.IsMobile)
{ {
setTimeout(DelayedUpdateSize, 512, player); viewItems[i].addEventListener("click", function(ev) {
switch(this.getAttribute("data-id"))
{
case "regular":
case "theatre":
{
toggleTheatreMode();
} break;
case "super":
{
toggleSuperTheatreMode();
} break;
}
});
} }
else }
var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location;
var 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) {
toggleLinkMode(linkMode, link);
});
link.addEventListener("click", function(ev) {
CopyToClipboard(link);
toggleMenuVisibility(linkMenu);
});
}
var creditsMenu = titleBar.querySelector(".credits_container");
if(creditsMenu)
{
menuState.push(creditsMenu);
var lastFocusedCreditItem = null;
var creditItems = creditsMenu.querySelectorAll(".person, .support");
for(var i = 0; i < creditItems.length; ++i)
{ {
player.updateSize(); creditItems[i].addEventListener("mouseenter", function(ev) {
if(this != lastFocusedCreditItem)
{
lastFocusedCreditItem.classList.remove("focused");
if(lastFocusedCreditItem.classList.contains("support"))
{
setIconLightness(lastFocusedCreditItem.firstChild);
}
lastFocusedCreditItem = this;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
if(focusedElement.classList.contains("support"))
{
setIconLightness(focusedElement.firstChild);
}
}
})
} }
var supportIcons = creditsMenu.querySelectorAll(".support_icon");
{
for(var i = 0; i < supportIcons.length; ++i)
{
supportIcons[i].style.backgroundImage = "url(\"" + supportIcons[i].getAttribute("data-sprite") + "\")";
setIconLightness(supportIcons[i]);
}
}
}
var sourceMenus = titleBar.querySelectorAll(".menu");
var helpButton = titleBar.querySelector(".help");
window.addEventListener("blur", function(){
helpButton.firstElementChild.innerText = "¿";
helpButton.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";
}); });
screen.orientation.onchange = function() { window.addEventListener("focus", function(){
if(CineraProps.IsMobile) helpButton.firstElementChild.innerText = "?";
{ helpButton.firstElementChild.title = ""
setTimeout(DelayedUpdateSize, 512, player); });
}
else
{
player.updateSize();
}
};
var helpDocumentation = helpButton.querySelector(".help_container");
helpButton.addEventListener("click", function(ev) {
handleMouseOverMenu(this, ev.type);
})
var focusedElement = null;
var focusedIdentifier = null;
var playerContainer = document.querySelector(".cineraPlayerContainer")
var cinera = playerContainer.parentNode;
var player = new Player(playerContainer, onRefChanged);
var cineraViewStorageItem = "cineraView";
if(viewsMenu && localStorage.getItem(cineraViewStorageItem))
{
toggleTheatreMode();
}
window.addEventListener("resize", function() { player.updateSize(); });
document.addEventListener("keydown", function(ev) { document.addEventListener("keydown", function(ev) {
var key = ev.key; var key = ev.key;
if(ev.getModifierState("Shift") && key == " ") if(ev.getModifierState("Shift") && key == " ")
@ -60,17 +258,44 @@ 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; handleMouseOverMenu(this, ev.type);
localStorage.setItem(player.cineraViewStorageItem, views.THEATRE); })
player.updateSize(); sourceMenus[i].addEventListener("mouseleave", function(ev) {
} handleMouseOverMenu(this, ev.type);
}); })
};
var prevEpisode = playerContainer.querySelector(".episodeMarker.prev");
var nextEpisode = playerContainer.querySelector(".episodeMarker.next");
var testMarkers = playerContainer.querySelectorAll(".marker");
var colouredItems = playerContainer.querySelectorAll(".author, .member, .project");
for(i = 0; i < colouredItems.length; ++i)
{
setTextLightness(colouredItems[i]);
}
var topicDots = document.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

@ -1,86 +0,0 @@
var cineraDropdownNavigation = document.getElementsByClassName("cineraNavDropdown");
for(var i = 0; i < cineraDropdownNavigation.length; ++i)
{
var cineraFamily = cineraDropdownNavigation[i].getElementsByClassName("cineraNavHorizontal")[0];
cineraDropdownNavigation[i].addEventListener("click", function() {
if(cineraFamily.classList.contains("visible"))
{
cineraFamily.classList.remove("visible");
}
else
{
cineraFamily.classList.add("visible");
}
});
cineraDropdownNavigation[i].addEventListener("mouseenter", function() {
cineraFamily.classList.add("visible");
});
cineraDropdownNavigation[i].addEventListener("mouseleave", function() {
cineraFamily.classList.remove("visible");
});
}
var Sprites = document.getElementsByClassName("cineraSprite");
for(var i = 0; i < Sprites.length; ++i)
{
var This = Sprites[i];
var TileX = This.getAttribute("data-tile-width");
var TileY = This.getAttribute("data-tile-height");
var AspectRatio = TileX / TileY;
// TODO(matt): Nail down the desiredness situation. Perhaps respond to:
// width / min-width / height / min-height set in a CSS file
// flexbox layout, if possible
// NOTE(matt): These values are "decoupled" here, to facilitate handling of sizes other than the original
// We'll probably need some way of checking the desired and original of both the X and Y, and pick which one on
// which to base the computation of the other
var DesiredX = TileX;
var DesiredY = DesiredX / AspectRatio;
var Proportion = DesiredX / TileX;
//
////
// NOTE(matt): Size the container and its background image
//
This.style.width = DesiredX + "px";
This.style.height = DesiredY + "px";
var SpriteWidth = This.getAttribute("data-sprite-width");
var SpriteHeight = This.getAttribute("data-sprite-height");
This.style.backgroundSize = SpriteWidth * Proportion + "px " + SpriteHeight * Proportion + "px";
//
////
// NOTE(matt): Pick the tile
//
setSpriteLightness(This);
if(This.classList.contains("dark"))
{
This.style.backgroundPositionX = This.getAttribute("data-x-dark") + "px";
}
if(elementIsFocused(This))
{
This.style.backgroundPositionY = This.getAttribute("data-y-focused") + "px";
}
if(This.classList.contains("off"))
{
This.style.backgroundPositionY = This.getAttribute("data-y-disabled") + "px";
}
else
{
This.style.backgroundPositionY = This.getAttribute("data-y-normal") + "px";
}
//
////
// NOTE(matt): Finally apply the background image
var URL = This.getAttribute("data-src");
This.style.backgroundImage = "url('" + URL + "')";
}

View File

@ -1,614 +0,0 @@
var orientations = {
PORTRAIT: 0,
LANDSCAPE_LEFT: 90,
LANDSCAPE_RIGHT: -90,
};
function
DeriveReliableWindowDimensions()
{
// https://www.howtocreate.co.uk/tutorials/javascript/browserwindow
var Result = {
X: null,
Y: null,
};
var ScrollPosX = window.scrollX;
var ScrollPosY = window.scrollY;
var DisplaySettings = [];
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
DisplaySettings.push(Child.style.display);
Child.style.display = "none";
}
var Element = document.createElement("div");
Element.style.position = "fixed";
Element.style.top = 0;
Element.style.right = 0;
Element.style.bottom = 0;
Element.style.left = 0;
Element.style.zOffset = -1;
Element.style.opacity = 0;
var ElementInPlace = document.body.appendChild(Element);
Result.X = ElementInPlace.offsetWidth;
Result.Y = ElementInPlace.offsetHeight;
ElementInPlace.remove();
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
Child.style.display = DisplaySettings.shift();
}
ScrollTriggeredInternally = true;
window.scroll(ScrollPosX, ScrollPosY);
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
GetRealOrientation(PreferredLandscape, IsMobile)
{
var Result = screen.orientation.angle;
var WindowDim = GetWindowDim(IsMobile);
if(WindowDim.Y > WindowDim.X)
{
Result = orientations.PORTRAIT;
}
else if(Result == undefined || Result == orientations.PORTRAIT)
{
Result = PreferredLandscape;
}
return Result;
}
var DebugConsoleMessageCount = 0;
function Say(Message)
{
var DebugConsole = document.getElementById("debug-console");
if(DebugConsole)
{
DebugConsole.textContent += DebugConsoleMessageCount++ + ": " + Message + "\n";
DebugConsole.scrollTo(
{
top: DebugConsole.scrollHeight,
behavior: "smooth"
});
}
}
function getBackgroundBrightness(element) {
var colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
while((colour == "transparent" || colour == "rgba(0, 0, 0, 0)") && depth <= 4)
{
element = element.parentNode;
colour = getComputedStyle(element).getPropertyValue("background-color");
++depth;
}
var rgb = colour.slice(4, -1).split(", ");
var result = Math.sqrt(rgb[0] * rgb[0] * .241 +
rgb[1] * rgb[1] * .691 +
rgb[2] * rgb[2] * .068);
return result;
}
function setSpriteLightness(spriteElement)
{
if(getBackgroundBrightness(spriteElement) < 127)
{
spriteElement.classList.add("dark");
}
else
{
spriteElement.classList.remove("dark");
}
}
function elementIsFocused(Element)
{
var Result = false;
if(Element.classList.contains("focused"))
{
Result = true;
}
while(Element.parent)
{
Element = Element.parent;
if(Element.classList.contains("focused"))
{
Result = true;
break;
}
}
return Result;
}
function focusSprite(Element)
{
if(Element.classList.contains("cineraSprite"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-focused") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
focusSprite(Element.children[i]);
}
}
function enableSprite(Element)
{
if(Element.classList.contains("focused"))
{
focusSprite(Element);
}
else
{
if(Element.classList.contains("cineraSprite"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-normal") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
enableSprite(Element.children[i]);
}
}
}
function disableSprite(Element)
{
if(Element.classList.contains("focused"))
{
focusSprite(Element);
}
else
{
if(Element.classList.contains("cineraSprite"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-disabled") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
disableSprite(Element.children[i]);
}
}
}
function unfocusSprite(Element)
{
if(Element.classList.contains("off"))
{
disableSprite(Element);
}
else
{
if(Element.classList.contains("cineraSprite"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-normal") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
unfocusSprite(Element.children[i]);
}
}
}
function IsMobile() {
// NOTE(matt): From https://medium.com/simplejs/detect-the-users-device-type-with-a-simple-javascript-check-4fc656b735e1
var Identifier = navigator.userAgent||navigator.vendor||window.opera;
var Result = (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(Identifier)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(Identifier.substr(0,4)));
return Result;
};
function GetRule(SelectorText)
{
// NOTE(matt): Modifying CSS style
// From https://stackoverflow.com/a/566445
// https://usefulangle.com/post/39/adding-css-to-stylesheet-with-javascript
var Result = undefined;
var StyleSheets = document.styleSheets;
var cssRuleCode = document.all ? 'rules' : 'cssRules'; // account for IE and FF
for(var StyleSheetIndex = StyleSheets.length - 1; StyleSheetIndex >= 0; --StyleSheetIndex)
{
var ThisSheet = StyleSheets[StyleSheetIndex];
if(ThisSheet.href !== null && ThisSheet.href.includes(location.hostname))
{
var Rules = ThisSheet[cssRuleCode];
for(var RuleIndex = Rules.length - 1; RuleIndex >= 0; --RuleIndex)
{
var ThisRule = Rules[RuleIndex];
if(SelectorText === ThisRule.selectorText)
{
Result = ThisRule;
break;
}
}
if(Result !== undefined) { break; }
}
}
return Result;
}
function
GetRulesOfStyleSheetIndex(Index)
{
var Result = undefined;
var StyleSheets = document.styleSheets;
var cssRuleCode = document.all ? 'rules' : 'cssRules'; // account for IE and FF
var StyleSheet = StyleSheets[Index];
if(StyleSheet)
{
Result = StyleSheet[cssRuleCode];
}
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
GetOrSetRule(SelectorText)
{
var Result = GetRule(SelectorText);
if(Result === undefined)
{
var StyleSheet = GetLocalStyleSheet();
if(StyleSheet)
{
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];
}
}
return Result;
}
function IsInRangeEx(Min, N, Max)
{
return N > Min && N < Max;
}
/* Auto-scrolling */
var ScrollTriggeredInternally = false;
var LastScrollYPos = 0;
var ScrollTicking = false;
var ScrollerFunction;
var ScrollCondition;
function ScrollTo(Element, ScrollPos, IsMobile, StickyObscuringElement) {
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 ElementTop = BoundingRect.top - Ceiling;
var ElementBottom = ElementTop + BoundingRect.height;
var UpperProtrusion = -ElementTop;
var LowerProtrusion = ElementBottom - VisibleArea.Y;
var DesiredScroll = null;
var YOffsetFromPage = getElementYOffsetFromPage(Element);
if(IsInRangeEx(0, UpperProtrusion, GatherableHeight))
{
if(!ScrolledToBottom)
{
DesiredScroll = YOffsetFromPage - Ceiling;
}
}
else if(IsInRangeEx(0, LowerProtrusion, GatherableHeight))
{
if(!ScrolledToTop)
{
if(IsInRangeEx(0, UpperProtrusion + LowerProtrusion, GatherableHeight))
{
DesiredScroll = YOffsetFromPage - Ceiling;
}
else
{
DesiredScroll = ScrollPos + LowerProtrusion;
}
}
}
if(DesiredScroll !== null && DesiredScroll != ScrollPos)
{
window.scrollTo({
top: DesiredScroll,
behavior: "smooth"
});
}
}
}
}
function
InitScrollEventListener(Element, IsMobile, StickyObscuringElement)
{
window.addEventListener('scroll', function() {
if(ScrollTriggeredInternally)
{
ScrollTriggeredInternally = false;
}
else if(ScrollCondition == undefined || ScrollCondition == true)
{
LastScrollYPos = window.scrollY;
if (!ScrollTicking) {
window.requestAnimationFrame(function() {
clearTimeout(ScrollerFunction);
ScrollerFunction = setTimeout(ScrollTo, 2000, Element, LastScrollYPos, IsMobile, StickyObscuringElement);
ScrollTicking = false;
});
ScrollTicking = true;
}
}
});
}
/* /Auto-scrolling */
function getElementXOffsetFromPage(el) {
var left = 0;
do {
left += el.offsetLeft;
} while (el = el.offsetParent);
return left;
}
function getElementYOffsetFromPage(el) {
var top = 0;
do {
top += el.offsetTop;
} while (el = el.offsetParent);
return top;
}
function
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 OriginalWidth = Element.style.width;
Element.style.width = "100%";
var NaturalMax = Element.offsetWidth;
Element.style.width = OriginalWidth;
var InnerWidth = WindowDim ? WindowDim.X : document.documentElement.clientWidth;
Result = Math.min(NaturalMax, InnerWidth);
return Result;
}
function
MaxHeightOfElement(Element, WindowDim)
{
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;
Element.style.height = "100%";
var NaturalMax = Element.offsetHeight;
Element.style.height = OriginalHeight;
var InnerHeight = WindowDim ? WindowDim.Y : document.documentElement.clientHeight;
if(NaturalMax > 0)
{
Result = Math.min(NaturalMax, InnerHeight);
}
else
{
Result = InnerHeight;
}
for(var i = 0; i < Element.children.length; ++i)
{
var Child = Element.children[i];
Child.style.display = DisplaySettings.shift();
}
return Result;
}
function
MaxDimensionsOfElement(Element, WindowDim)
{
var Result = {
X: null,
Y: null,
};
Result.X = MaxWidthOfElement(Element, WindowDim);
Result.Y = MaxHeightOfElement(Element, WindowDim);
return Result;
}
function
Clamp(EndA, N, EndB)
{
var Min = EndA < EndB ? EndA : EndB;
var Max = EndA > EndB ? EndA : EndB;
return N < Min ? Min : N > Max ? Max : N;
}
function
Clamp01(N)
{
return N < 0 ? 0 : N > 1 ? 1 : N;
}
function
Lerp(A, t, B)
{
return (1-t)*A + t*B;
}
function
IsOverflowed(Element)
{
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
BindHelp(Button, DocumentationContainer)
{
window.addEventListener("blur", function(){
SetHelpUnfocused(Button);
});
window.addEventListener("focus", function(){
SetHelpFocused(Button);
});
Button.addEventListener("click", function() {
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%)");
}
}
}

267
cinera/cinera_search.js Normal file
View File

@ -0,0 +1,267 @@
document.body.style.overflowY = "scroll";
if (location.hash && location.hash.length > 0) {
var initialQuery = location.hash;
if (initialQuery[0] == "#") {
initialQuery = initialQuery.slice(1);
}
document.getElementById("query").value = decodeURIComponent(initialQuery);
}
var indexContainer = document.getElementById("cineraIndex");
var projectID = indexContainer.attributes.getNamedItem("data-project").value;
var theme = indexContainer.classList.item(0);
var baseURL = indexContainer.attributes.getNamedItem("data-baseURL").value;
var playerLocation = indexContainer.attributes.getNamedItem("data-playerLocation").value;
var indexSort = indexContainer.querySelector("#cineraIndexSort");
var indexEntries = indexContainer.querySelector("#cineraIndexEntries");
var indexSortChronological = true;
indexSort.addEventListener("click", function(ev) {
if(indexSortChronological)
{
this.firstChild.nodeValue = "Sort: New to Old ⏷"
indexEntries.style.flexFlow = "column-reverse";
}
else
{
this.firstChild.nodeValue = "Sort: Old to New ⏶"
indexEntries.style.flexFlow = "column";
}
indexSortChronological = !indexSortChronological;
});
var lastQuery = null;
var resultsToRender = [];
var resultsIndex = 0;
var resultsMarkerIndex = 0;
var resultsContainer = document.getElementById("cineraResults");
var rendering = false;
var dayContainerPrototype = document.createElement("DIV");
dayContainerPrototype.classList.add("dayContainer");
dayContainerPrototype.classList.add(theme);
var dayNamePrototype = document.createElement("SPAN");
dayNamePrototype.classList.add("dayName");
dayContainerPrototype.appendChild(dayNamePrototype);
var markerListPrototype = document.createElement("DIV");
markerListPrototype.classList.add("markerList");
markerListPrototype.classList.add(theme);
dayContainerPrototype.appendChild(markerListPrototype);
var markerPrototype = document.createElement("A");
markerPrototype.classList.add("marker");
markerPrototype.setAttribute("target", "_blank");
var highlightPrototype = document.createElement("B");
var episodes = [];
function getEpisodeName(filename) {
var day = filename;
var dayParts = day.match(/([a-zA-Z_-]+)([0-9]+)?([a-zA-Z]+)?/);
day = dayParts[1].slice(0, 1).toUpperCase() + dayParts[1].slice(1) + (dayParts[2] ? " " + dayParts[2] : "") + (dayParts[3] ? " " + dayParts[3].toUpperCase() : "");
return day;
}
function markerTime(totalTime) {
var markTime = "(";
var hours = Math.floor(totalTime / 60 / 60);
var minutes = Math.floor(totalTime / 60) % 60;
var seconds = totalTime % 60;
if (hours > 0) {
markTime += padTimeComponent(hours) + ":";
}
markTime += padTimeComponent(minutes) + ":" + padTimeComponent(seconds) + ")";
return markTime;
}
function padTimeComponent(component) {
return (component < 10 ? "0" + component : component);
}
function runSearch() {
var queryStr = document.getElementById("query").value;
if (lastQuery != queryStr) {
var oldResultsContainer = resultsContainer;
resultsContainer = oldResultsContainer.cloneNode(false);
oldResultsContainer.parentNode.insertBefore(resultsContainer, oldResultsContainer);
oldResultsContainer.remove();
resultsIndex = 0;
resultsMarkerIndex = 0;
}
lastQuery = queryStr;
resultsToRender = [];
var numEpisodes = 0;
var numMarkers = 0;
var totalSeconds = 0;
if (queryStr && queryStr.length > 0) {
indexContainer.style.display = "none";
if (episodes.length > 0) {
var query = new RegExp(queryStr.replace("(", "\\(").replace(")", "\\)").replace(/\|+/, "\|").replace(/\|$/, "").replace(/(^|[^\\])\\$/, "$1"), "gi");
for (var i = 0; i < episodes.length; ++i) {
var episode = episodes[i];
var matches = [];
for (var j = 0; j < episode.markers.length; ++j) {
query.lastIndex = 0;
var result = query.exec(episode.markers[j].text);
if (result && result[0].length > 0) {
numMarkers++;
matches.push(episode.markers[j]);
if (j < episode.markers.length-1) {
totalSeconds += episode.markers[j+1].totalTime - episode.markers[j].totalTime;
}
}
}
if (matches.length > 0) {
numEpisodes++;
resultsToRender.push({
query: query,
episode: episode,
matches: matches
});
}
}
if (!rendering) {
renderResults();
}
} else {
document.querySelector(".spinner").classList.add("show");
}
}
else
{
indexContainer.style.display = "block";
}
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s ";
document.getElementById("cineraResultsSummary").textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total.";
}
function renderMatches(renderStart) {
var query = resultsToRender[resultsIndex].query;
var episode = resultsToRender[resultsIndex].episode;
var matches = resultsToRender[resultsIndex].matches;
var markerList = null;
if (resultsMarkerIndex == 0) {
var dayContainer = dayContainerPrototype.cloneNode(true);
var dayName = dayContainer.children[0];
markerList = dayContainer.children[1];
dayName.textContent = episode.day + ": " + episode.title;
resultsContainer.appendChild(dayContainer);
} else {
markerList = document.querySelector("#cineraResults > .dayContainer:nth-child(" + (resultsIndex+1) + ") .markerList");
}
do {
var match = matches[resultsMarkerIndex];
var marker = markerPrototype.cloneNode();
var playerURLPrefix = (baseURL ? baseURL + "/" : "") + (playerLocation ? playerLocation + "/" : "");
marker.setAttribute("href", playerURLPrefix + episode.filename.replace(/"/g, "") + "/#" + match.totalTime);
query.lastIndex = 0;
var cursor = 0;
var text = match.text;
var result = null;
marker.appendChild(document.createTextNode(match.prettyTime + " "));
while (result = query.exec(text)) {
if (result.index > cursor) {
marker.appendChild(document.createTextNode(text.slice(cursor, result.index)));
}
var highlightEl = highlightPrototype.cloneNode();
highlightEl.textContent = result[0];
marker.appendChild(highlightEl);
cursor = result.index + result[0].length;
}
if (cursor < text.length) {
marker.appendChild(document.createTextNode(text.slice(cursor, text.length)));
}
markerList.appendChild(marker);
resultsMarkerIndex++;
} while (resultsMarkerIndex < matches.length && performance.now() - renderStart < 1);
return resultsMarkerIndex == matches.length;
}
function renderResults() {
if (resultsIndex < resultsToRender.length) {
rendering = true;
var renderStart = performance.now();
while (resultsIndex < resultsToRender.length && performance.now() - renderStart < 1) {
var done = renderMatches(renderStart);
if (done) {
resultsMarkerIndex = 0;
resultsIndex++;
}
}
requestAnimationFrame(renderResults);
} else {
rendering = false;
}
}
var queryEl = document.getElementById("query")
queryEl.addEventListener("input", function(ev) {
history.replaceState(null, null, "#" + encodeURIComponent(queryEl.value));
runSearch();
});
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", function() {
var contents = xhr.response;
var lines = contents.split("\n");
var mode = "none";
var episode = null;
for (var i = 0; i < lines.length; ++i) {
var line = lines[i];
if (line.trim().length == 0) { continue; }
if (line == "---") {
if (episode != null) {
episode.filename = episode.name;
episode.day = getEpisodeName(episode.filename + ".html.md");
episodes.push(episode);
}
episode = {};
mode = "none";
} else if (line.startsWith("name:")) {
episode.name = line.slice(6);
} else if (line.startsWith("title:")) {
episode.title = line.slice(7).trim().slice(1, -1);
} else if (line.startsWith("markers")) {
mode = "markers";
episode.markers = [];
} else if (mode == "markers") {
var match = line.match(/"(\d+)": "(.+)"/);
if (match == null) {
console.log(name, line);
} else {
var totalTime = parseInt(line.slice(1));
var marker = {
totalTime: totalTime,
prettyTime: markerTime(totalTime),
text: match[2].replace(/\\"/g, "\"")
}
episode.markers.push(marker);
}
}
}
document.querySelector(".spinner").classList.remove("show");
runSearch();
});
xhr.addEventListener("error", function() {
console.error("Failed to load content");
});
var indexLocation = (baseURL ? baseURL + "/" : "") + projectID + ".index";
xhr.open("GET", indexLocation);
xhr.setRequestHeader("Content-Type", "text/plain");
xhr.send();
runSearch();

View File

@ -1,67 +0,0 @@
document.body.style.overflowY = "scroll";
CineraProps.Orientation = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
// Element Selection
//
Nav.Nexus = document.getElementById("cineraIndex");
Nav.Controls.Header = document.getElementById("cineraIndexControl");
Nav.Controls.Sort = Nav.Controls.Header.querySelector(".cineraMenuItem.sort");
Nav.Controls.View = Nav.Controls.Header.querySelector(".cineraMenuItem.view");
Nav.Controls.Anim = Nav.Controls.Header.querySelector(".cineraMenuItem.anim");
Nav.Controls.Save = Nav.Controls.Header.querySelector(".cineraMenuItem.save");
Nav.Controls.Help = Nav.Nexus.querySelector(".cineraHelp");
Nav.Controls.HelpDocumentation = Nav.Controls.Help.querySelector(".help_container");
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.Ascend = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.ascension");
Nav.Controls.GridTraversal.Prev = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.prev");
Nav.Controls.GridTraversal.Next = Nav.Controls.GridTraversal.Header.querySelector(".cineraButton.next");
Search.QueryElement = document.getElementById("query");
Search.ResultsSummary = document.getElementById("cineraResultsSummary");
Search.ResultsContainer = document.getElementById("cineraResults");
Search.IndexContainer = document.getElementById("cineraIndexList");
Search.ProjectsContainer = Search.IndexContainer.querySelectorAll(".cineraIndexProject");
//
///
// NOTE(matt): Initialisation
//
if(CineraProps.IsMobile)
{
Nav.Nexus.classList.add("mobile");
}
InitTraversalStack();
InitNexus();
InitHelpKeys(Nav.Controls.HelpDocumentation);
Nav.GridSize = ComputeOptimalGridSize();
SetHelpKeyAvailability(Nav.GridSize);
InitButtons(); // NOTE(matt): Also does "keydown" listeners, needed before UpdateButtons()
UpdateButtons();
InitQuery(Search.QueryElement);
InitPrototypes(Search.ResultsContainer);
prepareProjects();
SyncNavState();
FlipClear("scroll");
//
////
// NOTE(matt): Listeners
//
BindControlKeys();
BindGridKeys(Nav.GridSize);
BindControls();
InitResizeEventListener();
InitOrientationChangeListener();
InitScrollEventListener(Nav.GridContainer, CineraProps.IsMobile, Nav.Controls.Header);
//
////
// NOTE(matt): On-load Execution
//
runSearch();
//
////

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 893 B

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 801 B

After

Width:  |  Height:  |  Size: 855 B

File diff suppressed because it is too large Load Diff

View File

@ -30,9 +30,6 @@ typedef struct {
char* custom[HMML_CUSTOM_ATTR_COUNT]; char* custom[HMML_CUSTOM_ATTR_COUNT];
long publication_date;
char* output;
} HMML_VideoMetaData; } HMML_VideoMetaData;
typedef struct { typedef struct {

View File

@ -5,7 +5,7 @@
#include "stb_sb.h" #include "stb_sb.h"
#include "hmmlib.h" #include "hmmlib.h"
const struct HMML_Version hmml_version = { 0, 4, 0 }; const struct HMML_Version hmml_version = { 0, 3, 0 };
typedef struct { typedef struct {
int line; int line;
@ -58,10 +58,10 @@
yyextra->first = false;\ yyextra->first = false;\
} while(0) } while(0)
#define UNQUOTE_LEN(_attr, _len) ({ \ #define UNQUOTE(_attr) ({ \
typeof(_attr) attr = (_attr); \ typeof(_attr) attr = (_attr); \
typeof(_len) len = (_len); \ size_t len = strlen(attr); \
for(char* c = attr; c < attr+len; ++c){\ for(char* c = attr; *c; ++c){\
if(*c == '\\'){ \ if(*c == '\\'){ \
CHECKESCAPE(c[1]); \ CHECKESCAPE(c[1]); \
memmove(c, c+1, len-(c-attr)); \ memmove(c, c+1, len-(c-attr)); \
@ -71,8 +71,6 @@
attr; \ attr; \
}) })
#define UNQUOTE(_attr) UNQUOTE_LEN(_attr, strlen(attr))
%} %}
%option reentrant %option reentrant
@ -93,7 +91,6 @@ RB \]
%s VIDEO %s VIDEO
%s V_ATTR %s V_ATTR
%s V2_ATTR %s V2_ATTR
%s V3_ATTR
%s ANNOTATION %s ANNOTATION
%s TEXT_START %s TEXT_START
%s TEXT %s TEXT
@ -143,8 +140,6 @@ RB \]
<VIDEO>co\-host{S}= { yyextra->attr = V_(co_hosts); BEGIN(V2_ATTR); } <VIDEO>co\-host{S}= { yyextra->attr = V_(co_hosts); BEGIN(V2_ATTR); }
<VIDEO>guest{S}= { yyextra->attr = V_(guests); BEGIN(V2_ATTR); } <VIDEO>guest{S}= { yyextra->attr = V_(guests); BEGIN(V2_ATTR); }
<VIDEO>annotator{S}= { yyextra->attr = V_(annotators); BEGIN(V2_ATTR); } <VIDEO>annotator{S}= { yyextra->attr = V_(annotators); BEGIN(V2_ATTR); }
<VIDEO>publication_date{S}= { yyextra->attr = V_(publication_date); BEGIN(V3_ATTR); }
<VIDEO>output{S}= { yyextra->attr = V_(output); BEGIN(V_ATTR); }
<VIDEO>\] { BEGIN(ANNOTATION); }; <VIDEO>\] { BEGIN(ANNOTATION); };
<VIDEO>. { HMML_ERR("Unknown attribute in video tag, beginning with '%s'.", yytext); } <VIDEO>. { HMML_ERR("Unknown attribute in video tag, beginning with '%s'.", yytext); }
@ -158,11 +153,6 @@ RB \]
<V2_ATTR>{ATTR_QUOTED} { sb_push(*(char***)yyextra->attr, UNQUOTE(strndup(yytext+1, yyleng-2))); BEGIN(VIDEO); } <V2_ATTR>{ATTR_QUOTED} { sb_push(*(char***)yyextra->attr, UNQUOTE(strndup(yytext+1, yyleng-2))); BEGIN(VIDEO); }
<V2_ATTR>\] { yyless(0); BEGIN(VIDEO); } <V2_ATTR>\] { yyless(0); BEGIN(VIDEO); }
<V3_ATTR>{S} { BEGIN(VIDEO); }
<V3_ATTR>{ATTR_SIMPLE} { *(long*)yyextra->attr = atol(yytext); BEGIN(VIDEO); }
<V3_ATTR>{ATTR_QUOTED} { *(long*)yyextra->attr = atol(UNQUOTE_LEN(yytext+1, yyleng-2)); BEGIN(VIDEO); }
<V3_ATTR>\] { yyless(0); BEGIN(VIDEO); }
<ANNOTATION>{TIMECODE}{LB}@ { NEWANNO(); yyextra->an.time = strndup(yytext+1, yyleng-4); BEGIN(AUTHOR); } <ANNOTATION>{TIMECODE}{LB}@ { NEWANNO(); yyextra->an.time = strndup(yytext+1, yyleng-4); BEGIN(AUTHOR); }
<ANNOTATION>{TIMECODE} { NEWANNO(); yyextra->an.time = strndup(yytext+1, yyleng-2); BEGIN(TEXT_START); } <ANNOTATION>{TIMECODE} { NEWANNO(); yyextra->an.time = strndup(yytext+1, yyleng-2); BEGIN(TEXT_START); }
<ANNOTATION>{BAD_TIMECODE} { HMML_ERR("Timecode %s out of range.", yytext); } <ANNOTATION>{BAD_TIMECODE} { HMML_ERR("Timecode %s out of range.", yytext); }
@ -350,7 +340,6 @@ void hmml_free(HMML_Output* hmml){
free(hmml->metadata.id); free(hmml->metadata.id);
free(hmml->metadata.template); free(hmml->metadata.template);
free(hmml->metadata.medium); free(hmml->metadata.medium);
free(hmml->metadata.output);
for(size_t i = 0; i < HMML_CUSTOM_ATTR_COUNT; ++i){ for(size_t i = 0; i < HMML_CUSTOM_ATTR_COUNT; ++i){
free(hmml->metadata.custom[i]); free(hmml->metadata.custom[i]);
@ -394,6 +383,7 @@ void hmml_dump(HMML_Output* hmml){
return; return;
} }
puts("Annotations:"); puts("Annotations:");
for(size_t i = 0; i < hmml->annotation_count; ++i){ for(size_t i = 0; i < hmml->annotation_count; ++i){
HMML_Annotation* a = hmml->annotations + i; HMML_Annotation* a = hmml->annotations + i;

View File

@ -3,9 +3,6 @@ all: hmml.a example
hmml.a: hmmlib.o hmml.a: hmmlib.o
ar rcs $@ $< ar rcs $@ $<
hmmlib.c: hmmlib.l
lex -o $@ $<
hmmlib.o: hmmlib.c hmmlib.o: hmmlib.c
gcc -std=gnu99 -D_GNU_SOURCE -g -c $< -o $@ gcc -std=gnu99 -D_GNU_SOURCE -g -c $< -o $@
@ -13,6 +10,6 @@ example: example.c hmml.a
gcc -std=gnu99 -g $^ -o $@ gcc -std=gnu99 -g $^ -o $@
clean: clean:
$(RM) hmml.a hmmlib.o hmmlib.c example $(RM) hmml.a hmmlib.o example
.PHONY: all clean .PHONY: clean

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