Compare commits

..

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

25 changed files with 9379 additions and 19173 deletions

202
README.md
View File

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

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ nav.cineraNavDropdown .cineraNavTitle {
nav.cineraNavDropdown ul.cineraNavHorizontal { nav.cineraNavDropdown ul.cineraNavHorizontal {
display: none; display: none;
width: 100%; width: 100%;
z-index: 4096; z-index: 8;
position: absolute; position: absolute;
} }
@ -85,124 +85,12 @@ ul.cineraNavPlain li.current > a {
/* Index */ /* Index */
#cineraIndex #cineraIndexControl {
{
display: flex; display: flex;
flex-direction: column; margin: 16px auto;
} max-width: 1024px;
#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; align-items: center;
justify-content: center; padding: 8px;
}
#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, .cineraFilterProject .cineraText,
@ -211,32 +99,15 @@ ul.cineraNavPlain li.current > a {
display: flex; display: flex;
} }
.cineraMenu.cineraMenuTitle { .cineraIndexFilter {
padding: 10px; padding: 10px;
cursor: default;
} }
.cineraMenuContainer .cineraIndexFilter .filter_container
{ {
display: none; display: none;
position: absolute; position: absolute;
top: 100%;
z-index: 64; 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, .cineraFilterProject,
@ -249,16 +120,17 @@ ul.cineraNavPlain li.current > a {
} }
.cineraIndexProject .cineraProjectTitle, .cineraIndexProject .cineraProjectTitle,
.cineraMenuItem .cineraFilterProject
{ {
font-weight: bold; font-weight: bold;
font-size: 1rem; font-size: 12px;
} }
.cineraQueryContainer { .cineraQueryContainer {
flex-grow: 1; flex-grow: 1;
padding-left: 16px; padding-left: 16px;
display: flex; display: flex;
flex-direction: horizontal;
margin: auto; margin: auto;
} }
@ -269,23 +141,19 @@ ul.cineraNavPlain li.current > a {
} }
.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,7 +163,7 @@ ul.cineraNavPlain li.current > a {
} }
#cineraResults, #cineraResults,
#cineraIndexList { #cineraIndex {
margin: 0 auto; margin: 0 auto;
max-width: 800px; max-width: 800px;
} }
@ -305,104 +173,22 @@ ul.cineraNavPlain li.current > a {
flex-flow: column; flex-flow: column;
} }
#cineraIndexList .cineraIndexEntries { #cineraIndexControl #cineraIndexSort {
display: flex; padding: 4px;
flex-flow: column;
width: 100%;
} }
#cineraIndex #cineraIndexGrid #cineraIndexControl #cineraIndexSort,
{ #cineraIndexControl .cineraIndexFilter .filter_container {
display: flex; cursor: pointer;
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,11 +199,11 @@ 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 {
@ -457,6 +243,36 @@ ul.cineraNavPlain li.current > a {
display: none; display: none;
} }
@media (max-width: 720px), (max-height: 512px)
{
#cineraIndexControl {
margin: 4px auto;
}
#cineraIndexControl,
#cineraResultsSummary {
font-size: 64%;
}
#cineraResults .dayContainer {
flex-direction: column;
}
#cineraResults .dayContainer .dayName {
font-weight: bold;
text-align: center;
width: 100%;
}
#cineraResults .dayContainer .markerList {
max-width: 100%;
}
.cineraIndexEntries div a {
font-size: 80%;
}
}
/* Player */ /* Player */
/* Player / Structure */ /* Player / Structure */
@ -483,14 +299,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu { .cineraMenus > .menu {
position: relative; position: relative;
min-height: 1em; align-self: center;
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 +312,109 @@ 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;
}
#cineraIndexControl .cineraIndexFilter .filter_container
{
border: 1px solid;
border-top: none;
}
.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 +423,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 +442,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 {
@ -681,7 +484,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 +514,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 +549,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 +568,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 +638,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 +647,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 +665,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 +683,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 +706,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 +745,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 +763,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,123 +781,96 @@ ul.cineraNavPlain li.current > a {
margin-right: 8px; margin-right: 8px;
} }
/* NOTE(matt): Mobile Style */ @media (max-width: 720px), (max-height: 512px)
#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; .cineraMenus .episode_name,
flex-direction: column; .cineraMenus > .menu > .view,
flex-grow: 0; .cineraMenus > .menu > .views_container .view,
-webkit-text-size-adjust: none; .cineraMenus > .help,
} .markers_container > .episodeMarker div:nth-child(2),
.markers_container > .episodeMarker div:nth-child(3),
.markers_container > .markers .marker:not(.current) {
display: none;
}
.cinera.mobile .cineraMenus { .cineraMenus {
justify-content: center; justify-content: center;
display: flex; }
flex-direction: row;
position: relative;
border: 0;
}
.cinera.mobile .cineraMenus, .cineraMenus .menu .quotes_container,
.cinera.mobile .player_container { .cineraMenus .menu .references_container,
flex-grow: 0; .cineraMenus .menu .filter_container,
} .cineraMenus .menu .link_container,
.cineraMenus .menu .credits_container {
.cinera.mobile .cineraMenus .episode_name, position: fixed;
.cinera.mobile .cineraMenus > .menu > .view, left: 0;
.cinera.mobile .cineraMenus > .menu > .views_container .view, margin: auto;
.cinera.mobile .cineraHelp, max-height: 80%;
#cineraIndex.mobile .cineraHelp, max-width: 97%;
.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 { .cineraPlayerContainer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.cinera.mobile .cineraPlayerContainer .markers_container { .cineraPlayerContainer .markers_container {
display: flex; display: flex;
flex-flow: row; flex-flow: row;
overflow-y: hidden; overflow-y: auto;
} }
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker { .cineraPlayerContainer .markers_container > .episodeMarker {
width: 32px; user-select: none;
user-select: none; -moz-user-select: none;
-moz-user-select: none; -webkit-user-select: none;
-webkit-user-select: none; }
}
.cinera.mobile .cineraPlayerContainer .markers_container > .markers { .cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
flex-grow: 1; width: 456px;
} }
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first, @media (max-width: 580px)
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev, {
.cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker, .cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last, width: 320px;
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next { }
border: 0;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first, }
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev { @media (max-width: 450px)
border-right: 3px double; {
} .cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
width: 256px;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last, }
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next { @media (max-width: 375px)
border-left: 3px double; {
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
width: 180px;
}
}
.cineraPlayerContainer .markers_container > .episodeMarker.first,
.cineraPlayerContainer .markers_container > .episodeMarker.prev,
.cineraPlayerContainer .markers_container > .markers .marker,
.cineraPlayerContainer .markers_container > .episodeMarker.last,
.cineraPlayerContainer .markers_container > .episodeMarker.next {
border: 0;
}
.cineraPlayerContainer .markers_container > .episodeMarker.first,
.cineraPlayerContainer .markers_container > .episodeMarker.prev {
border-right: 3px double;
}
.cineraPlayerContainer .markers_container > .episodeMarker.last,
.cineraPlayerContainer .markers_container > .episodeMarker.next {
border-left: 3px double;
}
} }
/* Mobile Style End */
/* CUSTOM PAGE STYLE */ /* CUSTOM PAGE STYLE */
@ -1139,4 +889,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,137 @@
var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location; var originalTextContent = {
TitleQuotes: null,
TitleReferences: null,
TitleCredits: null,
EpisodePrev: null,
EpisodeNext: null,
};
var CineraProps = { var menuState = [];
var titleBar = document.querySelector(".cineraMenus");
var quotesMenu = titleBar.querySelector(".quotes_container");
if(quotesMenu)
{
originalTextContent.TitleQuotes = quotesMenu.previousElementSibling.textContent;
menuState.push(quotesMenu);
var quoteItems = quotesMenu.querySelectorAll(".ref");
if(quoteItems)
{
for(var i = 0; i < quoteItems.length; ++i)
{
quoteItems[i].addEventListener("mouseenter", function(ev) {
mouseOverQuotes(this);
})
};
}
var quoteTimecodes = quotesMenu.querySelectorAll(".refs .ref .ref_indices .timecode");
for (var i = 0; i < quoteTimecodes.length; ++i) {
quoteTimecodes[i].addEventListener("click", function(ev) {
if (player) {
var time = ev.currentTarget.getAttribute("data-timestamp");
mouseSkipToTimecode(player, time, ev);
}
});
}
var lastFocusedQuote = null;
}
var referencesMenu = titleBar.querySelector(".references_container");
if(referencesMenu)
{
originalTextContent.TitleReferences = referencesMenu.previousElementSibling.textContent;
menuState.push(referencesMenu);
var referenceItems = referencesMenu.querySelectorAll(".ref");
if(referenceItems)
{
for(var i = 0; i < referenceItems.length; ++i)
{
referenceItems[i].addEventListener("mouseenter", function(ev) {
mouseOverReferences(this);
})
};
var lastFocusedReference = null;
var lastFocusedIdentifier = null;
}
var refTimecodes = referencesMenu.querySelectorAll(".refs .ref .ref_indices .timecode");
for (var i = 0; i < refTimecodes.length; ++i) {
refTimecodes[i].addEventListener("click", function(ev) {
if (player) {
var time = ev.currentTarget.getAttribute("data-timestamp");
mouseSkipToTimecode(player, time, ev);
}
});
}
}
if(referencesMenu || quotesMenu)
{
var refSources = titleBar.querySelectorAll(".refs .ref"); // This is for both quotes and refs
for (var i = 0; i < refSources.length; ++i) {
refSources[i].addEventListener("click", function(ev) {
if (player) {
player.pause();
}
});
}
}
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: 0,
THEATRE: 1,
SUPERTHEATRE: 2,
};
var devices = {
DESKTOP: 0,
MOBILE: 1,
};
var cineraProps = {
C: null, C: null,
V: views.REGULAR, V: views.REGULAR,
Z: null, Z: null,
@ -11,48 +142,132 @@ var CineraProps = {
H: null, H: null,
mH: null, mH: null,
P: null, P: null,
Display: null, D: devices.DESKTOP,
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)
{
originalTextContent.TitleCredits = creditsMenu.previousElementSibling.textContent;
menuState.push(creditsMenu);
var lastFocusedCreditItem = null;
var creditItems = creditsMenu.querySelectorAll(".person, .support");
for(var i = 0; i < creditItems.length; ++i)
{ {
player.updateSize(); creditItems[i].addEventListener("mouseenter", function(ev) {
if(this != lastFocusedCreditItem)
{
lastFocusedCreditItem.classList.remove("focused");
unfocusSprite(lastFocusedCreditItem);
if(lastFocusedCreditItem.classList.contains("support"))
{
setSpriteLightness(lastFocusedCreditItem.firstChild);
}
lastFocusedCreditItem = this;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
if(focusedElement.classList.contains("support"))
{
setSpriteLightness(focusedElement.firstChild);
}
}
})
} }
}
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 prevEpisode = playerContainer.querySelector(".episodeMarker.prev");
if(prevEpisode) { originalTextContent.EpisodePrev = prevEpisode.firstChild.textContent; }
var nextEpisode = playerContainer.querySelector(".episodeMarker.next");
if(nextEpisode) { originalTextContent.EpisodeNext = nextEpisode.firstChild.textContent; }
var testMarkers = playerContainer.querySelectorAll(".marker");
var cinera = playerContainer.parentNode;
// NOTE(matt): All the originalTextContent values must be set by this point, because the player's construction may need them
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 +275,532 @@ 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 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);
}
function handleKey(key) {
var gotKey = true;
switch (key) {
case "q": {
if(quotesMenu)
{
toggleMenuVisibility(quotesMenu)
}
} break;
case "r": {
if(referencesMenu)
{
toggleMenuVisibility(referencesMenu)
}
} break;
case "f": {
if(filterMenu)
{
toggleMenuVisibility(filterMenu)
}
} break;
case "y": {
if(linkMenu)
{
toggleMenuVisibility(linkMenu)
}
break;
}
case "c": {
if(creditsMenu)
{
toggleMenuVisibility(creditsMenu)
}
} break;
case "t": {
if(cinera)
{
toggleTheatreMode();
}
} break;
case "T": {
if(cinera)
{
toggleSuperTheatreMode();
}
} break;
case "Enter": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
var time = focusedElement.querySelector(".timecode").getAttribute("data-timestamp");
player.setTime(parseInt(time, 10));
player.play();
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
var time = focusedIdentifier.getAttribute("data-timestamp");
player.setTime(parseInt(time, 10));
player.play();
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.hasAttribute)
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
}
}
else
{
console.log("TODO(matt): Implement me, perhaps?\n");
}
} break;
case "o": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container") ||
focusedElement.parentNode.classList.contains("quotes_container"))
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.hasAttribute("href"))
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
}
}
} break;
case "w": case "k": case "ArrowUp": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
if(focusedElement.previousElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedQuote = focusedElement.previousElementSibling;
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedElement.previousElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedReference = focusedElement.previousElementSibling;
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
}
else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
{
if(focusedElement.previousElementSibling &&
focusedElement.previousElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedCategory = focusedElement.previousElementSibling;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.parentNode.previousElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
if(focusedElement.parentNode.previousElementSibling.querySelector(".support") &&
focusedElement.classList.contains("support"))
{
setSpriteLightness(focusedElement.firstChild);
lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".support");
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
else
{
lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".person");
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
}
}
} break;
case "s": case "j": case "ArrowDown": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
if(focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedQuote = focusedElement.nextElementSibling;
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedReference = focusedElement.nextElementSibling;
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
}
else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
{
if(focusedElement.nextElementSibling &&
focusedElement.nextElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedCategory = focusedElement.nextElementSibling;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.parentNode.nextElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
if(focusedElement.parentNode.nextElementSibling.querySelector(".support") &&
focusedElement.classList.contains("support"))
{
setSpriteLightness(focusedElement.firstChild);
lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".support");
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
else
{
lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".person");
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
}
}
} break;
case "a": case "h": case "ArrowLeft": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedIdentifier.previousElementSibling)
{
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedIdentifier = focusedIdentifier.previousElementSibling;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
else if(focusedIdentifier.parentNode.previousElementSibling.classList.contains("ref_indices"))
{
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedIdentifier = focusedIdentifier.parentNode.previousElementSibling.lastElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
}
else if(focusedElement.classList.contains("filter_content"))
{
if(focusedElement.parentNode.classList.contains("filter_media") &&
focusedElement.parentNode.previousElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedMedium = focusedElement;
if(!lastFocusedTopic)
{
lastFocusedTopic = focusedElement.parentNode.previousElementSibling.children[1];
}
lastFocusedCategory = lastFocusedTopic;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.classList.contains("support"))
{
focusedElement.classList.remove("focused");
console.log(focusedElement);
unfocusSprite(focusedElement);
lastFocusedCreditItem = focusedElement.previousElementSibling;
setSpriteLightness(focusedElement.firstChild);
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
}
} break;
case "d": case "l": case "ArrowRight": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedIdentifier.nextElementSibling)
{
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedIdentifier = focusedIdentifier.nextElementSibling;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
else if(focusedIdentifier.parentNode.nextElementSibling)
{
focusedIdentifier.classList.remove("focused");
unfocusSprite(focusedIdentifier);
lastFocusedIdentifier = focusedIdentifier.parentNode.nextElementSibling.firstElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
focusSprite(focusedIdentifier);
}
}
else if(focusedElement.classList.contains("filter_content"))
{
if(focusedElement.parentNode.classList.contains("filter_topics") &&
focusedElement.parentNode.nextElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedTopic = focusedElement;
if(!lastFocusedMedium)
{
lastFocusedMedium = focusedElement.parentNode.nextElementSibling.children[1];
}
lastFocusedCategory = lastFocusedMedium;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.classList.contains("person") &&
focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
lastFocusedCreditItem = focusedElement.nextElementSibling;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
}
} break;
case "x": case " ": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
filterItemToggle(focusedElement);
if(focusedElement.nextElementSibling &&
focusedElement.nextElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
if(focusedElement.parentNode.classList.contains("filter_topics"))
{
lastFocusedTopic = focusedElement.nextElementSibling;
lastFocusedCategory = lastFocusedTopic;
}
else
{
lastFocusedMedium = focusedElement.nextElementSibling;
lastFocusedCategory = lastFocusedMedium;
}
lastFocusedElement = lastFocusedCategory;
focusedElement = lastFocusedElement;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
} break;
case "X": case "capitalSpace": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
filterItemToggle(focusedElement);
if(focusedElement.previousElementSibling &&
focusedElement.previousElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
unfocusSprite(focusedElement);
if(focusedElement.parentNode.classList.contains("filter_topics"))
{
lastFocusedTopic = focusedElement.previousElementSibling;
lastFocusedCategory = lastFocusedTopic;
}
else
{
lastFocusedMedium = focusedElement.previousElementSibling;
lastFocusedCategory = lastFocusedMedium;
}
lastFocusedElement = lastFocusedCategory;
focusedElement = lastFocusedElement;
focusedElement.classList.add("focused");
focusSprite(focusedElement);
}
}
} break;
case "z": {
toggleFilterOrLinkMode();
} break;
case "v": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
invertFilter(focusedElement)
}
} break;
case "V": {
resetFilter();
} break;
case "?": {
helpDocumentation.classList.toggle("visible");
} break;
case 'N':
case 'J':
case 'S': {
player.jumpToNextMarker();
} break;
case 'P':
case 'K':
case 'W': {
player.jumpToPrevMarker();
} break;
case '[':
case '<': {
if(prevEpisode)
{
location = prevEpisode.href;
}
} break;
case ']':
case '>': {
if(nextEpisode)
{
location = nextEpisode.href;
}
} break;
case 'Y': {
if(cineraLink)
{
if(linkAnnotation == false && player.playing)
{
player.pause();
}
if(linkMenu && !linkMenu.classList.contains("visible"))
{
toggleMenuVisibility(linkMenu);
}
SelectText(cineraLink);
}
}
default: {
gotKey = false;
} break;
} }
}); return gotKey;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,115 +1,3 @@
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) { function getBackgroundBrightness(element) {
var colour = getComputedStyle(element).getPropertyValue("background-color"); var colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0; var depth = 0;
@ -192,21 +80,14 @@ function enableSprite(Element)
function disableSprite(Element) function disableSprite(Element)
{ {
if(Element.classList.contains("focused")) if(Element.classList.contains("cineraSprite"))
{ {
focusSprite(Element); setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-disabled") + "px";
} }
else for(var i = 0; i < Element.childElementCount; ++i)
{ {
if(Element.classList.contains("cineraSprite")) disableSprite(Element.children[i]);
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-disabled") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
disableSprite(Element.children[i]);
}
} }
} }
@ -229,386 +110,3 @@ function unfocusSprite(Element)
} }
} }
} }
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%)");
}
}
}

625
cinera/cinera_search.js Normal file
View File

@ -0,0 +1,625 @@
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 indexControl = document.getElementById("cineraIndexControl");
var indexSort = indexControl.querySelector("#cineraIndexSort");
var indexSortChronological = true;
var filterMenu = indexControl.querySelector(".cineraIndexFilter");
if(filterMenu)
{
var filterContainer = filterMenu.querySelector(".filter_container");
//menuState.push(linkMenu);
filterMenu.addEventListener("mouseenter", function(ev) {
filterContainer.style.display = "block";
});
filterMenu.addEventListener("mouseleave", function(ev) {
filterContainer.style.display = "none";
});
}
function hideEntriesOfProject(ProjectElement)
{
if(!ProjectElement.classList.contains("off"))
{
ProjectElement.classList.add("off");
}
var baseURL = ProjectElement.attributes.getNamedItem("data-baseURL").value;
for(var i = 0; i < projects.length; ++i)
{
var ThisProject = projects[i];
if(ThisProject.baseURL === baseURL)
{
ThisProject.filteredOut = true;
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "none";
disableSprite(ThisProject.entriesContainer.parentElement);
}
}
}
}
function showEntriesOfProject(ProjectElement)
{
if(ProjectElement.classList.contains("off"))
{
ProjectElement.classList.remove("off");
}
var baseURL = ProjectElement.attributes.getNamedItem("data-baseURL").value;
for(var i = 0; i < projects.length; ++i)
{
var ThisProject = projects[i];
if(ThisProject.baseURL === baseURL)
{
ThisProject.filteredOut = false;
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "flex";
enableSprite(ThisProject.entriesContainer.parentElement);
}
}
}
}
function hideProjectSearchResults(baseURL)
{
var cineraResults = document.getElementById("cineraResults");
if(cineraResults)
{
var cineraResultsProjects = cineraResults.querySelectorAll(".projectContainer");
for(var i = 0; i < cineraResultsProjects.length; ++i)
{
var resultBaseURL = cineraResultsProjects[i].attributes.getNamedItem("data-baseURL").value;
if(baseURL === resultBaseURL)
{
cineraResultsProjects[i].style.display = "none";
return;
}
}
}
}
function showProjectSearchResults(baseURL)
{
var cineraResults = document.getElementById("cineraResults");
if(cineraResults)
{
var cineraResultsProjects = cineraResults.querySelectorAll(".projectContainer");
for(var i = 0; i < cineraResultsProjects.length; ++i)
{
var resultBaseURL = cineraResultsProjects[i].attributes.getNamedItem("data-baseURL").value;
if(baseURL === resultBaseURL)
{
cineraResultsProjects[i].style.display = "flex";
return;
}
}
}
}
function toggleEntriesOfProjectAndChildren(ProjectFilterElement)
{
var baseURL = ProjectFilterElement.attributes.getNamedItem("data-baseURL").value;
var shouldShow = ProjectFilterElement.classList.contains("off");
if(shouldShow)
{
ProjectFilterElement.classList.remove("off");
enableSprite(ProjectFilterElement);
}
else
{
ProjectFilterElement.classList.add("off");
disableSprite(ProjectFilterElement);
}
for(var i = 0; i < projects.length; ++i)
{
var ThisProject = projects[i];
if(ThisProject.baseURL === baseURL)
{
if(shouldShow)
{
ThisProject.filteredOut = false;
enableSprite(ThisProject.projectTitleElement.parentElement);
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "flex";
}
showProjectSearchResults(projects[i].baseURL);
}
else
{
ThisProject.filteredOut = true;
disableSprite(ThisProject.projectTitleElement.parentElement);
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "none";
}
hideProjectSearchResults(ThisProject.baseURL);
}
}
}
var indexChildFilterProjects = ProjectFilterElement.querySelectorAll(".cineraFilterProject");
for(var j = 0; j < indexChildFilterProjects.length; ++j)
{
var ThisElement = indexChildFilterProjects[j];
if(shouldShow)
{
var baseURL = ThisElement.attributes.getNamedItem("data-baseURL").value;
showEntriesOfProject(ThisElement);
showProjectSearchResults(baseURL);
}
else
{
var baseURL = ThisElement.attributes.getNamedItem("data-baseURL").value;
hideEntriesOfProject(ThisElement);
hideProjectSearchResults(baseURL);
}
}
}
var indexFilter = indexControl.querySelector(".cineraIndexFilter");
if(indexFilter)
{
var indexFilterProjects = indexFilter.querySelectorAll(".cineraFilterProject");
for(var i = 0; i < indexFilterProjects.length; ++i)
{
indexFilterProjects[i].addEventListener("mouseover", function(ev) {
ev.stopPropagation();
this.classList.add("focused");
focusSprite(this);
});
indexFilterProjects[i].addEventListener("mouseout", function(ev) {
ev.stopPropagation();
this.classList.remove("focused");
unfocusSprite(this);
});
indexFilterProjects[i].addEventListener("click", function(ev) {
ev.stopPropagation();
toggleEntriesOfProjectAndChildren(this);
});
}
}
var resultsSummary = document.getElementById("cineraResultsSummary");
var resultsContainer = document.getElementById("cineraResults");
var indexContainer = document.getElementById("cineraIndex");
var projectsContainer = indexContainer.querySelectorAll(".cineraIndexProject");
var projectContainerPrototype = document.createElement("DIV");
projectContainerPrototype.classList.add("projectContainer");
var dayContainerPrototype = document.createElement("DIV");
dayContainerPrototype.classList.add("dayContainer");
var dayNamePrototype = document.createElement("SPAN");
dayNamePrototype.classList.add("dayName");
dayContainerPrototype.appendChild(dayNamePrototype);
var markerListPrototype = document.createElement("DIV");
markerListPrototype.classList.add("markerList");
dayContainerPrototype.appendChild(markerListPrototype);
var markerPrototype = document.createElement("A");
markerPrototype.classList.add("marker");
if(resultsContainer.getAttribute("data-single") == 0)
{
markerPrototype.setAttribute("target", "_blank");
}
function prepareToParseIndexFile(project)
{
project.xhr.addEventListener("load", function() {
var contents = project.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.name != null && episode.title != null) {
episode.filename = episode.name;
episode.day = getEpisodeName(episode.filename + ".html.md");
episode.dayContainerPrototype = project.dayContainerPrototype;
episode.markerPrototype = markerPrototype;
episode.playerURLPrefix = project.playerURLPrefix;
project.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");
project.parsed = true;
runSearch(true);
});
project.xhr.addEventListener("error", function() {
console.error("Failed to load content");
});
}
var projects = [];
function prepareProjects()
{
for(var i = 0; i < projectsContainer.length; ++i)
{
var ID = projectsContainer[i].attributes.getNamedItem("data-project").value;
var baseURL = projectsContainer[i].attributes.getNamedItem("data-baseURL").value;
var playerLocation = projectsContainer[i].attributes.getNamedItem("data-playerLocation").value;
var theme = projectsContainer[i].classList.item(1);
projects[i] =
{
baseURL: baseURL,
playerURLPrefix: (baseURL ? baseURL + "/" : "") + (playerLocation ? playerLocation + "/" : ""),
indexLocation: (baseURL ? baseURL + "/" : "") + ID + ".index",
projectTitleElement: projectsContainer[i].querySelector(":scope > .cineraProjectTitle"),
entriesContainer: projectsContainer[i].querySelector(":scope > .cineraIndexEntries"),
dayContainerPrototype: dayContainerPrototype.cloneNode(true),
filteredOut: false,
parsed: false,
searched: false,
resultsToRender: [],
resultsIndex: 0,
theme: theme,
episodes: [],
xhr: new XMLHttpRequest(),
}
projects[i].dayContainerPrototype.classList.add(theme);
projects[i].dayContainerPrototype.children[1].classList.add(theme);
document.querySelector(".spinner").classList.add("show");
projects[i].xhr.open("GET", projects[i].indexLocation);
projects[i].xhr.setRequestHeader("Content-Type", "text/plain");
projects[i].xhr.send();
prepareToParseIndexFile(projects[i]);
}
}
prepareProjects();
indexSort.addEventListener("click", function(ev) {
if(indexSortChronological)
{
this.firstChild.nodeValue = "Sort: New to Old ⏷"
for(var i = 0; i < projects.length; ++i)
{
if(projects[i].entriesContainer)
{
projects[i].entriesContainer.style.flexFlow = "column-reverse";
}
}
}
else
{
this.firstChild.nodeValue = "Sort: Old to New ⏶"
for(var i = 0; i < projects.length; ++i)
{
if(projects[i].entriesContainer)
{
projects[i].entriesContainer.style.flexFlow = "column";
}
}
}
indexSortChronological = !indexSortChronological;
runSearch(true);
});
var lastQuery = null;
var markerList = null;
var projectContainer = null;
var resultsMarkerIndex = -1;
var rendering = false;
var highlightPrototype = document.createElement("B");
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 resetProjectsForSearch()
{
for(var i = 0; i < projects.length; ++i)
{
var project = projects[i];
project.searched = false;
project.resultsToRender = [];
}
}
var renderHandle;
function runSearch(refresh) {
var queryStr = document.getElementById("query").value;
if (refresh || lastQuery != queryStr) {
var oldResultsContainer = resultsContainer;
resultsContainer = oldResultsContainer.cloneNode(false);
oldResultsContainer.parentNode.insertBefore(resultsContainer, oldResultsContainer);
oldResultsContainer.remove();
for(var i = 0; i < projects.length; ++i)
{
projects[i].resultsIndex = 0;
}
resultsMarkerIndex = -1;
}
lastQuery = queryStr;
resetProjectsForSearch();
var numEpisodes = 0;
var numMarkers = 0;
var totalSeconds = 0;
// NOTE(matt): Function defined within runSearch() so that we can modify numEpisodes, numMarkers and totalSeconds
function runSearchInterior(resultsToRender, query, episode)
{
var matches = [];
for (var k = 0; k < episode.markers.length; ++k) {
query.lastIndex = 0;
var result = query.exec(episode.markers[k].text);
if (result && result[0].length > 0) {
numMarkers++;
matches.push(episode.markers[k]);
if (k < episode.markers.length-1) {
totalSeconds += episode.markers[k+1].totalTime - episode.markers[k].totalTime;
}
}
}
if (matches.length > 0) {
numEpisodes++;
resultsToRender.push({
query: query,
episode: episode,
matches: matches
});
}
}
if (queryStr && queryStr.length > 0) {
indexContainer.style.display = "none";
resultsSummary.style.display = "block";
var shouldRender = false;
var query = new RegExp(queryStr.replace("(", "\\(").replace(")", "\\)").replace(/\|+/, "\|").replace(/\|$/, "").replace(/(^|[^\\])\\$/, "$1"), "gi");
// Visible
for(var i = 0; i < projects.length; ++i)
{
var project = projects[i];
if(project.parsed && !project.filteredOut && project.episodes.length > 0) {
if(indexSortChronological)
{
for(var j = 0; j < project.episodes.length; ++j) {
var episode = project.episodes[j];
runSearchInterior(project.resultsToRender, query, episode);
}
}
else
{
for(var j = project.episodes.length; j > 0; --j) {
var episode = project.episodes[j - 1];
runSearchInterior(project.resultsToRender, query, episode);
}
}
shouldRender = true;
project.searched = true;
}
}
// Invisible
for(var i = 0; i < projects.length; ++i)
{
var project = projects[i];
if(project.parsed && project.filteredOut && !project.searched && project.episodes.length > 0) {
if(indexSortChronological)
{
for(var j = 0; j < project.episodes.length; ++j) {
var episode = project.episodes[j];
runSearchInterior(project.resultsToRender, query, episode);
}
}
else
{
for(var j = project.episodes.length; j > 0; --j) {
var episode = project.episodes[j - 1];
runSearchInterior(project.resultsToRender, query, episode);
}
}
shouldRender = true;
project.searched = true;
}
}
if(shouldRender)
{
if (rendering) {
clearTimeout(renderHandle);
}
renderResults();
}
}
else
{
indexContainer.style.display = "block";
resultsSummary.style.display = "none";
}
var totalTime = Math.floor(totalSeconds/60/60) + "h " + Math.floor(totalSeconds/60)%60 + "m " + totalSeconds%60 + "s ";
resultsSummary.textContent = "Found: " + numEpisodes + " episodes, " + numMarkers + " markers, " + totalTime + "total.";
}
function renderResults() {
var maxItems = 42;
var numItems = 0;
for(var i = 0; i < projects.length; ++i)
{
var project = projects[i];
if (project.resultsIndex < project.resultsToRender.length) {
rendering = true;
while (numItems < maxItems && project.resultsIndex < project.resultsToRender.length) {
var query = project.resultsToRender[project.resultsIndex].query;
var episode = project.resultsToRender[project.resultsIndex].episode;
var matches = project.resultsToRender[project.resultsIndex].matches;
if (resultsMarkerIndex == -1) {
if(project.resultsIndex == 0 || project.resultsToRender[project.resultsIndex - 1].episode.playerURLPrefix != episode.playerURLPrefix)
{
projectContainer = projectContainerPrototype.cloneNode(true);
for(var i = 0; i < projects.length; ++i)
{
if(projects[i].playerURLPrefix === episode.playerURLPrefix)
{
projectContainer.setAttribute("data-baseURL", projects[i].baseURL);
if(projects[i].filteredOut)
{
projectContainer.style.display = "none";
}
}
}
resultsContainer.appendChild(projectContainer);
}
else
{
projectContainer = resultsContainer.lastElementChild;
}
var dayContainer = episode.dayContainerPrototype.cloneNode(true);
var dayName = dayContainer.children[0];
markerList = dayContainer.children[1];
dayName.textContent = episode.day + ": " + episode.title;
projectContainer.appendChild(dayContainer);
resultsMarkerIndex = 0;
numItems++;
}
while (numItems < maxItems && resultsMarkerIndex < matches.length) {
var match = matches[resultsMarkerIndex];
var marker = episode.markerPrototype.cloneNode(true);
marker.setAttribute("href", episode.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);
numItems++;
resultsMarkerIndex++;
}
if (resultsMarkerIndex == matches.length) {
resultsMarkerIndex = -1;
project.resultsIndex++;
}
}
renderHandle = setTimeout(renderResults, 0);
} else {
rendering = false;
}
}
}
function IsVisible(el) {
var xPos = 0;
var yPos = 0;
var Height = parseInt(getComputedStyle(el).height);
while (el) {
if (el.tagName == "BODY") {
var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
var yScroll = el.scrollTop || document.documentElement.scrollTop;
xPos += (el.offsetLeft - xScroll + el.clientLeft)
yPos += (el.offsetTop - yScroll + el.clientTop)
} else {
xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
yPos += (el.offsetTop - el.scrollTop + el.clientTop);
}
el = el.offsetParent;
}
return ((xPos > 0 && xPos < window.innerWidth) && (yPos > 0 && yPos + Height < window.innerHeight));
}
var queryEl = document.getElementById("query");
if(document.hasFocus() && IsVisible(queryEl)) { queryEl.focus(); }
queryEl.addEventListener("input", function(ev) {
history.replaceState(null, null, "#" + encodeURIComponent(queryEl.value));
runSearch();
});
runSearch();
// Testing

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

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