Compare commits

...

105 Commits

Author SHA1 Message Date
Matt Mascarenhas 14dafa4abe cinera.c: Fix stack-use-after-return segfault
The ClashResolver, or a memory_book variable therein, triggered a
stack-use-after-return segfault. Fixed by initialising the ClashResolver
in main() and passing it to InitClashResolver() by pointer, rather than
initialising it in InitClashResolver().
2024-03-12 13:03:08 +00:00
Matt Mascarenhas 213bb2f882 cinera.c: Fix colouring of topic dots
The colour computed for topic dots and put into cinera_topics.css in HSL
format has its lightness value modified depending on whether it's on a
dark or light background. Web browsers do not tell us an element's
computed colour in HSL format, but in RGB, so we would need to convert
it from RGB to HSL when setting the lightness. Previously, this
conversion was busted, calculating too small a value for the saturation.

This commit obviates the need for any RGB→HSL conversion by writing the
hue and saturation values as attributes to the elements, which the
function responsible for setting the lightness may use directly.
2024-02-21 20:52:34 +00:00
Matt Mascarenhas 77aec74483 cinera_player_post.js: Fix fullscreen handling
It is possible to bring our player out of fullscreen mode without using
the provided views menu or keyboard shortcut, e.g. by pressing Escape.
Doing so leaves our state in the SUPERTHEATRE view, which omits
SUPERtheatre mode itself from the views menu. This commit fixes this by
switching our state to the THEATRE view.

Thanks to Aske Bisgaard Vammen for the report.
2024-01-30 15:41:30 +00:00
Matt Mascarenhas 6852d06e04 cinera_player_pre.js: Unset focused menu
When hiding any menu, set this.MenusFocused.MenuID = menu_id.UNSET, to
let hovering over the markers focus them after hiding the link menu.
2023-03-25 01:51:40 +00:00
Matt Mascarenhas 52d6d989f8 cinera_player_pre.js: Fix scoping of "this" 2023-03-25 01:31:13 +00:00
Matt Mascarenhas df93674bf7 cinera: Increase precision to milliseconds
This commit simply adds millisecond precision of timecodes. It includes
small changes to hmmlib.h, cinera.c and the frontend JS files.
2023-03-25 00:04:34 +00:00
Matt Mascarenhas 026585e50b cinera: Fix mobile scrolling and centring
This commit fixes spurious scrolling in DeriveReliableWindowDimensions()
on mobile. It also fixes centring of Vimeo videos on mobile.

In addition, it replaces some deprecated JavaScript:
•   window.orientation → screen.orientation.type
•   window.onorientationchange → screen.orientation.onchange
2023-03-21 19:34:24 +00:00
Matt Mascarenhas 9d5f0f9146 cinera_player_pre.js: Fix scoping of "this" 2023-03-18 01:54:42 +00:00
Matt Mascarenhas a7694d4c3b cinera: Improve keyboard controls and scrolling
This commit adds keyboard navigation of the indices, documented in the
"Help" text box. It also improves scrolling of menus to follow progress
through the video, with the ability to centre the scrolling around a
range of references. Finally in this UI work, it enables the keyboard
and mouse to work more cooperatively.

Other changes:

• Added a "Clear" so the player's initial sizing happens invisibly.
• Fixed getBackgroundColourRGB() to handle both rgb() and rgba().
• Deduplicated code, including spurious querySelectorAll() calls.
• Moved global variables into the Player object.
2023-03-18 01:27:05 +00:00
Matt Mascarenhas c9bf96c7aa cinera_player_pre.js: Improve sizing and scrolling
This commit fixes the sizing of the player's menus to not protrude below
the player.

It also adds scrolling of the references menu to the first menu item
cited in the newly-current timestamp, and fixes scrolling of the markers
container when switching to a timestamp after the user has scrolled the
container away from its default position.
2023-02-20 20:29:16 +00:00
Matt Mascarenhas 44a5008aa7 cinera: Resolve output location clashes
This commit adds support for tracking and resolving clashes of output
locations. Previously open-ended clash dependency chains could passively
resolve across multiple Cinera invocations (undesirable, but possible);
whereas closed-loop dependency chains could not resolve without the user
manually resolving them by moving one clash to a temporary location (not
acceptable). This new support handles such clashes in one fell swoop.

Other smaller changes in this commit:

•   Change message_control to use memory_book
•   Fix deletion of old .index files when reorganising projects
•   Fix deletion of moved entries when subdividing projects
2023-02-10 23:12:21 +00:00
Matt Mascarenhas 6576a91d11 cinera: Support message deduplication
This commit adds support for deduplication of messages emitted by
PrintEdit(). It's a first pass, only supporting single-line messages and
ones no larger than 512 bytes.

It also fixes StringContains() to skip strings shorter than the search
substring, and emits a more sensible error on empty "output" value.
2023-01-13 20:31:44 +00:00
Matt Mascarenhas 1280142d45 cinera.c: Clean up string copy functions
This commit replaces ClearCopyStringNoFormat() with
ClearCopyStringNoFormatOrTerminate(). All calls to that function
passed an implicitly 0-terminated string as the destination, meaning
that we don't need to 0-terminate them and, in not doing, we free up a
byte to store data.

It also deduplicates code as CopyBytes(), and does a little bounds
checking before actually trying to copy.
2023-01-13 12:01:26 +00:00
Matt Mascarenhas 6b09247cd2 cinera.c: Enable default closed captions
This commit adds support for configurably enabling closed captions by
default. Set the "default_cc_lang" in a config file, or "cc_lang" in the
[video] node of an .hmml file. Once set, videos which contain captions
for this langage will be initialised with these captions enabled.

New config setting:
    default_cc_lang

New [video] node field:
    cc_lang
2023-01-11 19:24:41 +00:00
Matt Mascarenhas 554f7393ff cinera_search_pre.js: Fix scrolling bug
This commit fixes ResizeFunction() to scroll back to the original Y
position, rather than the nexus (i.e. the grid or list), after resizing
the grid as a result of the window having been resized.
2023-01-10 12:39:06 +00:00
Matt Mascarenhas e8ed2f0143 cinera.c: Fix buffer overflow copying "output"
This is a hotfix made just to allow using a max-length "output" value.
Next commit should fix buffer overflows for all copies to implicitly
null-terminated destinations.
2022-12-15 20:23:30 +00:00
Matt Mascarenhas ac4b155e73 cinera.c: Add Donorbox as default support platform 2022-12-06 18:22:06 +00:00
Matt Mascarenhas 0f15957cb5 cinera.c: Abbreviate names better
This commit makes the auto-derivation of name abbreviations code handle
quoted nicknames and lower-cased surname prefixes (e.g. du Pré). It also
upstreams the abbreviation to config parse time, and introduces config
fields in the person scope to allow overwriting the auto-derived ones.

Bug fixes:
    •   Fix null pointer dereference in InsertProjectIntoDB(), due to a
        rogue WriteFromByteToPointer() call left in when removing the
        _TopLevel() database modification functions in v0.10.14
    •   Fix config file locking by closing all config files on detecting
        a config file change, even if we had no prior working config
    •   Increase IncludesSearch buffer size from 2 to 4KB

New config fields in the person scope:
    •   abbrev_initial
    •   abbrev_given_or_nickname
    •   abbrev_dotted_initial_and_surname
2022-11-25 21:12:59 +00:00
Matt Mascarenhas 68d1469212 cinera.c: Add Ko-fi as a default support platform 2022-10-13 20:21:48 +01:00
Matt Mascarenhas f321a6b6bf Update README.md 2022-09-23 18:39:01 +01:00
Matt Mascarenhas 6ea5cb6d22 Update README.md 2022-09-23 17:47:12 +01:00
Matt Mascarenhas 0825cbb5ff Update README.md 2022-09-23 17:42:18 +01:00
Matt Mascarenhas 6e9c7edd1d Update README.md 2022-09-23 17:39:07 +01:00
Matt Mascarenhas b8f143e350 cinera: Add database structure specification
This commit aims to generalise database structures such that any block
may be traversed without the need for specific functions for each type.
Concretely, it replaces functions like SkipAsset() and SkipProject()
with a generic SkipHeaderedSectionRecursively(). The change also paves
the way for the plethora of new block types due in CINERA_DB_VERSION 6.
2022-09-19 17:22:19 +01:00
Matt Mascarenhas f60cf3087f cinera: Lock database and config files
This commit aims to help Cinera instances coordinate themselves by
throwing an error upon being instructed to use a database or config
file currently in use by another instance.
2022-09-16 16:10:19 +01:00
Matt Mascarenhas 1cf703e346 cinera: Fully handle auto-numbering
•   Renumber affected entries when inserting / deleting an entry of an
    auto-numbered project
•   Prohibit an entry from sharing an "output" with another entry
•   Prohibit an entry's "output" from containing a slash
•   Fix entry HTML deletion to use OutputLocation, not BaseFilename
2022-08-30 19:15:44 +01:00
Matt Mascarenhas 26e304f62c cinera: Add IRC unfurl tags and GitHub sponsors
New config field:
    instance_title
2022-08-23 22:36:20 +01:00
Matt Mascarenhas 8dffa513cc cinera: Add tag __CINERA_PROJECT_LINEAGE _ 2022-08-21 19:15:10 +01:00
Matt Mascarenhas e17c3aa78c cinera.c: Handle overlong "output" attribute 2022-08-03 22:56:24 +01:00
Matt Mascarenhas 84a35b9ece cinera: Add numbering and fix sizes
New config options:
    numbering_filename_prefix (string)
        Defaults to $project
    numbering_method
        auto
        filename_derived (default)
        hmml_specified
    numbering_start (number)
        May be used to 0-index the set
    numbering_unit (renamed from "unit")
    numbering_zero_pad (boolean)

This commit also fixes the size of the "?" help button and category dots
when the wider site uses box-sizing: border-box
2022-08-01 20:58:55 +01:00
Matt Mascarenhas ef9937e95b hmmlib.h: Add "number" parameter to [video] node 2022-08-01 20:33:38 +01:00
Matt Mascarenhas 652d8f186f Revert "Introduce threading and clean some code"
This reverts commit f7a6df994f.
2022-07-21 20:04:50 +01:00
Matt Mascarenhas 91c33c580b cinera.c: Add a break to a switch case 2022-07-21 19:50:18 +01:00
Matt Mascarenhas f7a6df994f Introduce threading and clean some code
This is an unversioned interim commit with unfinalised code.

The threading code establishes job queues.
The code cleanup removes various _TopLevel functions pertaining to
database operations.
2022-05-20 17:32:28 +01:00
Matt Mascarenhas b6fb755d29 cinera.css: Set margin on search page links
This commit enables Google's Mobile Usability checker to proceed beyond
its quick initial validation, after finding the previous fixes failed.
2021-10-08 15:18:23 +01:00
Matt Mascarenhas d036145b7f cinera.css: Let tall menus scroll 2021-10-06 17:38:18 +01:00
Matt Mascarenhas 60615322dd cinera.css: Apply min-height more broadly
This commit enables Google's Mobile Usability checker to proceed beyond
its quick initial validation.
2021-10-06 17:12:26 +01:00
Matt Mascarenhas 2deb244cce cinera.css: Set min-height on search page links
This commit tries to fix a "Clickable elements too close together"
error:
https://support.google.com/webmasters/answer/9063469#touch_elements_too_close
2021-10-06 16:38:21 +01:00
Matt Mascarenhas 4dc034ecc3 cinera.c: Use html_title in Welcome / End markers 2021-10-06 15:45:19 +01:00
Matt Mascarenhas ee788b0c30 cinera_player_pre.js: Fix YouTube initialisation
Initialise the player if the YouTube API loads before us
2021-09-01 16:38:23 +01:00
Matt Mascarenhas c839e2ed85 cinera.c: Add "direct" video support
Fixes:
• Rename Annotation → Timestamp in cinera_player_*.js
• Redo VideoIsPrivate() to only return once, at the end
2021-07-07 16:33:54 +01:00
Matt Mascarenhas 012d1608a0 cinera.c: Mention "vimeo" in the help text
Also remove HMMLIB_MAJOR_VERSION #defined out code
2021-06-28 13:32:15 +01:00
Matt Mascarenhas 97806c2c9c cinera_player_post.js: s/setTime/setTimeThenPlay
Thanks to @nairou on twitter for the report
2021-06-27 19:13:18 +01:00
Matt Mascarenhas dd33bb49b3 cinera_player_pre.js: Add Vimeo support
Fixes

• Fix fullscreen
• Pause whenever / however following a reference or credit URL
2021-06-23 15:13:41 +01:00
Matt Mascarenhas e39a09c0ad cinera: Code clean-up
• Make vod_platform an enum
• Unionise config_int_pair and config_bool_pair in config_pair
2021-06-17 13:32:53 +01:00
Matt Mascarenhas 744515dac4 cinera_clear.js: Fix the placement of the clear 2021-06-11 17:39:48 +01:00
Matt Mascarenhas 209979f63a cinera.js: Add a "Clear" element
To use it, source the cinera_clear.js file at the position in the HTML
file to be obscured while further processing occurs. (Make sure not to
"defer" it.)

After processing is complete, call FlipClear()
2021-06-11 16:38:38 +01:00
Matt Mascarenhas 12b06812bf cinera.js: Improve style
•   Do not modify visibility of list view during resize
•   Stabilise player sizing across fullscreen toggle and resize
2021-06-11 14:03:13 +01:00
Matt Mascarenhas 373195069a cinera.css: Use rem unit for font-size 2021-06-09 16:30:06 +01:00
Matt Mascarenhas 07795b7cbd cinera.c: Fix segfault on partial hmmlib1 credit 2021-06-07 13:19:03 +01:00
Matt Mascarenhas b0501d20ac cinera.c: Fix sizeof() on a string literal 2021-05-31 22:39:25 +01:00
Matt Mascarenhas af1bff4218 cinera: s/StringLength()/sizeof()-1 on string lits 2021-05-31 21:01:34 +01:00
Matt Mascarenhas 63ab9d5bc2 cinera: Remove memory_book_type 2021-05-31 20:48:58 +01:00
Matt Mascarenhas 9cdb3b96d9 Revert cinera.c: Make ResolvePath() use realpath()
This reverts commit 61fa4e4808.
2021-05-31 20:35:18 +01:00
Matt Mascarenhas 61fa4e4808 cinera.c: Make ResolvePath() use realpath() 2021-05-31 20:10:04 +01:00
Matt Mascarenhas cf51ba24e3 cinera.c: Fix role-related bugs
•   Fix segfault due to unconfigured role
•   Add credit scope to the root scope's TypeSpec, permitting
        credit = "$owner" { role = "host"; }
    to be absorbed by all projects
•   Make "unfound role" errors WaitForInput()
2021-05-31 19:20:52 +01:00
Matt Mascarenhas a6a9653306 hmmlib2 2.0.12: Clamp ref->offset to text_len 2021-05-31 18:06:32 +01:00
Matt Mascarenhas c104500020 hmmlib2 2.0.11: Remove static from compound lit
This permits compilation with older versions of gcc
2021-05-30 23:46:37 +01:00
Matt Mascarenhas 0d88c24db6 cinera: Upgrade to hmmlib2
Features:
    User-configurable roles and credits
    Handle SIGINT to quit cleanly and avoid database corruption

Fixes:
    Filesystem event monitoring handles directory creation / deletion
    Fixed buffer overflow when trying to curl in a non-existent quote
2021-05-30 19:39:47 +01:00
Matt Mascarenhas 65fe93fb57 hmmlib2 2.0.10: Rename annotation to timestamp 2021-05-30 18:42:02 +01:00
Matt Mascarenhas b20192e445 hmmlib2 2.0.9: Add ()"% as break_on_punct chars 2021-05-27 16:11:12 +01:00
Matt Mascarenhas 9a31a1b10c hmmlib2 2.0.8: Align marker rules with hmmlib1
Make extended markers - those within [] - only break on whitespace
Add '!' and '…' as break_on_punct chars
2021-05-24 21:31:45 +01:00
Matt Mascarenhas d1440a7ac6 hmmlib2 2.0.7: Add uncredit attribute 2021-05-24 21:06:40 +01:00
Matt Mascarenhas 0f67d4e658 hmmlib2 2.0.6: Make '?' a break_on_punct char 2021-05-23 22:45:59 +01:00
Alex Baines 00fc6c0ae5 hmmlib2 2.0.5: post-text marker fixes
they don't have parameters, and there can be multiple
2021-05-21 00:50:07 +01:00
Alex Baines c0c3558473 hmmlib2: remove stream_username / member 2021-05-11 23:03:22 +01:00
Alex Baines 3f47e62a7e hmmlib2: allow marker as first thing in text node 2021-05-09 11:52:16 +01:00
Alex Baines c911e393d5 hmmlib2: v2.0.2 fixing issues with code204/284/440
also allow / in markers
2021-05-08 23:30:59 +01:00
Matt Mascarenhas 05b7883033 cinera_search_pre.js: Default to the List View
Also fix the 𝑛-ary Grid traversal PrevAscends case when the search page
contains only one project at the top-level.
2021-04-12 18:00:45 +01:00
Alex Baines e4c2ae4ffa remove branch condition that could never be false
fuzzer gets 100% branch coverage now, no crashes/hangs
2021-04-07 18:50:35 +01:00
Alex Baines 1d916415c1 hmmlib2: consecutive marker/ref bug fix
see riscy004 34:28
2021-04-04 20:57:50 +01:00
Alex Baines 678be54e51 hmmlib2: hmmldump -x option for extra output, afl fuzz testing 2021-04-04 20:39:35 +01:00
Alex Baines 1993e9f1dd hmmlib2: re-instsate "offset" for marker/ref + fixes
fixes:
    - indentation
    - parsing bug for marker with both episode + parameter
2021-04-04 13:07:50 +01:00
Alex Baines 968437d263 hmmlib2 single-header possibly working 2021-04-03 15:42:32 +01:00
Alex Baines 67c4942b60 add unfinished hmmlib rewrite "hmmlib2" 2021-03-07 21:59:43 +00:00
Matt Mascarenhas 828fff7a7a cinera.web: Prevent auto-scroll to hidden elements
The search page could auto-scroll to the top when the grid is not
displayed. This commit fixes that problem by ensuring that the grid
element has a width and height before seeking to auto-scroll to it.

Thanks to Leonardo Serafim Eid for the report
2021-02-16 02:52:27 +00:00
Matt Mascarenhas 8283e2a9e5 cinera_player_pre.js: Remove spurious console.log 2021-02-13 00:16:36 +00:00
Matt Mascarenhas 22633794b5 cinera_pre.js: Fix GetOrSetRule() 2021-02-13 00:12:19 +00:00
Matt Mascarenhas 0cfa87ba84 cinera_pre.js: Handle null StyleSheet.href 2021-02-13 00:00:38 +00:00
Matt Mascarenhas 806c9feba8 cinera.web: Fix sizing and CSS rule setting
• Max{Height,Width}OfElement() should now produce saner values
• Video can now resize upon orientation change on mobile
• Search grid now sizes to the correct area
• CSS rule setting only tries to touch local stylesheets
2021-02-12 23:48:02 +00:00
Matt Mascarenhas b6fff3cc24 cinera_search_post.js: Fix auto-scrolling
Pass CineraProps.IsMobile to InitScrollEventListener()
2021-02-10 23:50:43 +00:00
Matt Mascarenhas 20d7e82a66 cinera_pre.js: Harden GetRule() 2021-02-10 23:33:32 +00:00
Matt Mascarenhas 33f6186aa1 cinera.web: Fix player sizing and auto-scrolling
• Video size may be based on the height if the width-based is too tall
• Auto-scrolling improvements:
    • It now cannot oscillate
    • It doesn't happen at all if scrolled to the very top / bottom

Thanks to @Freak_ on freenode for the report
2021-02-10 22:41:46 +00:00
Matt Mascarenhas 2d26562301 cinera_pre.js: Equalise auto-scroll gatherability 2021-02-07 02:01:38 +00:00
Matt Mascarenhas 4ba03f9338 cinera_pre.js: Add sticky-awareness to auto-scroll
This lets the grid view auto-scroll out from under the control bar.
2021-02-07 01:53:05 +00:00
Matt Mascarenhas 7e80017434 cinera.css: Circular traversal buttons 2021-02-05 20:54:29 +00:00
Matt Mascarenhas 29df4df8a4 cinera_search.js: Better traversal button sizing 2021-02-05 20:15:40 +00:00
Matt Mascarenhas 5857a30eaf cinera_search.js: Pin ↑ button to top in landscape 2021-02-05 19:49:00 +00:00
Matt Mascarenhas 0a9d51b0d7 cinera.web: Better fitting and effiency
Better text fitting
Account for the scrollbar width in our max width considerations
Don't bother to compute the grid size merely when toggling the view
2021-02-04 23:19:56 +00:00
Matt Mascarenhas 50dbc7b5c3 cinera.js: Base dims on window.inner{Width/Height} 2021-02-04 01:22:09 +00:00
Matt Mascarenhas e7d70ccb3a cinera_search_pre.js: Compute grid on view toggle 2021-02-04 00:46:43 +00:00
Matt Mascarenhas f519a0977f cinera_search_pre.js: Fix WindowDim declaration 2021-02-04 00:33:17 +00:00
Matt Mascarenhas 545938d766 cinera.web: Fix issues (on mobile) with new layout
Both pages:
    Derive reliable window dimensions ourselves
    Set the orientation from the window dimensions
    Use "box-sizing: content-box" on the help keys, to fix sizing

Search page:
    Support irregular (i.e. non-square) grids
    General sizing fixes
    Fix text fitting when toggling back from List to Grid view
    Fix text fitting when textNode would overflow container
    Correctly compute optimal grid size after orientation change
    Rename .text to .cineraText to avoid CSS selector clash
    Only add click events for the main set of buttons (not its clone)

Player page:
    Size the video and timestamps bar more sensibly
2021-02-04 00:13:55 +00:00
Matt Mascarenhas 2cf8739a60 cinera.web: Fix issues on desktop with new layout
Search page:
    Manually size the grid buttons, and head / tail items

Player page:
    Confine long menus to the player height
    Confine the help documentation to the window size
2021-01-27 21:58:56 +00:00
Matt Mascarenhas eccd02cc71 cinera.css: Fix menu bar padding
Workaround qutebrowser's (QtWebEngine) inability to handle a list of
selectors in the :not() selector
2021-01-25 18:52:38 +00:00
Matt Mascarenhas 3945ac883c cinera v0.8.0: Mobile-friendly layout
Major features:
    Search page: Subdivision grid layout
    Player page: Device orientation-specific layout

Fixes:
    Strip slashes of the URLs: base_url, etc.
    Fix SnipeChecksumAndCloseFile() to not null-terminate the checksum string
    Fix hover background colouration of medium icons of current timestamp
    Fix DeriveLineageWithoutOriginOfProject() to call InitBookOfPointers()
    Fix SortAndAbbreviateSpeakers() to use the person's ID if Name is blank
    More directly display the "unit", if set, in the search results
    Fix VideoIsPrivate() to find the apparently relocated privacyStatus
    Make VideoIsPrivate() default to TRUE for non-youtube
    Output as keywords all topics that do not match "nullTopic"

Deleted asset:
    cinera_search.js

New assets:
    cinera_search_pre.js
    cinera_search_post.js
2021-01-25 18:09:30 +00:00
Matt Mascarenhas 6eeb588adf cinera.c: Gracefully handle database non-creation 2020-06-24 17:34:41 +01:00
Matt Mascarenhas b3470e0f48 cinera.c: Gracefully handle unset env variables 2020-06-24 16:22:50 +01:00
Matt Mascarenhas 2748687839 cinera.c: Sanitise Memory
Fixes / Improvements:

•   Switch all growable arrays to use memory_book, rather than realloc()
•   Lift a bunch of hardcoded string lengths and item counts
•   malloc() the MemoryArena rather than calloc(), thus saving 3 MiB
•   Reorganise HMMLToBuffers() to return from one place, the end
•   Print indexing errors in the same style as config errors / warnings

Diagnoses:

•   Identified sources of "non-freed" memory usage and marked them +MEM.
    This may aid future work on further reducing memory / cycle usage.

New config settings:

•   suppress_prompts boolean
2020-06-24 13:29:18 +01:00
Matt Mascarenhas 6da970d48c cinera.c: Fix leak in GenerateTopicColours() 2020-06-03 18:04:39 +01:00
Matt Mascarenhas 0959fa2774 cinera.c: Fix segfault and event handling
• Segfault was due to a read access violation on an unset entry pointer,
  which in turn was due to stale neighbourhood data. To fix it we simply
  reset the neighbourhood when starting to delete an entry. Additionally
  we now check that those entry pointers are set before accessing them.

• Event handling of the trio of events triggered when vim saves a file.
  We now read in a second set of events while processing the first if we
  were on the verge of processing a deletion. If we get any more events,
  we continue to squash those ones if possible, to always end up having
  seen the entire trio of events associated with a file save, and then
  process it as an insertion / reinsertion, not a deletion.

• Sort the asset landmarks by their offset.

• Change GenerateTopicColours() to initially open cinera_topics.css as
  "r" to search it for the incoming topic, and only if that topic is
  absent reopen it as "a+", thus triggering an IN_CLOSE_WRITE event.
2020-05-30 18:12:54 +01:00
Matt Mascarenhas 7edcecfd40 cinera.c: InitScopeBooks() in PrintHelpConfig() 2020-05-25 01:29:14 +01:00
Matt Mascarenhas bc6f21f71d Make project navigation menus use the right theme
It was just a typo. HMMLToBuffers() passed the global config pointer,
rather than the passed in one, to GenerateNavigation().

Also switch the entire config over to use memory_book style allocation.
This saves us from having to offset pointers when memory gets realloc'd.
2020-05-25 00:26:13 +01:00
Matt Mascarenhas 8aa67a386a Fix the 3-way filter associating
Add the data-searchLocation attribute to the search results
2020-05-17 23:00:12 +01:00
Matt Mascarenhas ce9a0e7635 Prevent file clashes
Config parsing now errors-out when it detects clashes between:
    base_dir and search_location, and global_search_dir
    base_dir and player_location

This prevents us from generating search / project pages of multiple
projects (incl. global_search_dir) at the same location, which would
screw up the asset landmarks.
2020-05-17 22:50:28 +01:00
25 changed files with 18484 additions and 8721 deletions

202
README.md
View File

@ -5,23 +5,26 @@ deployment
### Install the dependencies
1. flex (for hmmlib)
2. curl (for cinera)
1. curl
### Download, and prepare the parser
### Download, and copy the parser into place
1. `git clone git@gitssh.handmade.network:Annotation-Pushers/Annotation-System.git`
2. `cd Annotation-System/hmmlib`
3. `make`
4. `cp hmml.a hmmlib.h ../cinera/`
5. `cd ../cinera/`
1. `git clone https://git.handmade.network/Annotation-Pushers/Annotation-System.git`
2. `cd Annotation-System/cinera`
3. `cp ../hmmlib2/hmmlib.h .`
Note: For each parser update, remember to make and copy it into place.
Note: For each parser update, remember to copy it into place.
### Build
1. `$SHELL cinera.c`
### Configure
cinera -h
This documents the configuration file format and default settings.
### Configure the server
If you enforce a strict Content Security Policy and X-Frame-Options in your
@ -38,56 +41,24 @@ and [Hardening your HTTP response headers](https://scotthelme.co.uk/hardening-yo
### Run
#### 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
cinera -c /path/to/config.conf
#### Templates
*Search Template*
*(Global) Search Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_
- `<!-- __CINERA_SEARCH__ -->` _the table of contents and search functionality_
*Player Template*
- `<!-- __CINERA_INCLUDES__ -->` _to put inside your own `<head></head>`_
- `<!-- __CINERA_MENUS__ -->`
- `<!-- __CINERA_PLAYER__ -->`
- `<!-- __CINERA_SCRIPT__ -->` _must come after `<!-- __CINERA_MENUS__ -->` and `<!-- __CINERA_PLAYER__ -->`_
*Optional tags available for use in your Player Template*
- `<!-- __CINERA_TITLE__ -->`
- `<!-- __CINERA_VIDEO_ID__ -->`
- `<!-- __CINERA_VOD_PLATFORM__ -->`
*Other tags available for use in either template*
*Other tags available for use in any template*
- Asset tags:
- `<!-- __CINERA_ASSET__ path.ext -->`
General purpose tag that outputs the URL of the specified asset
@ -96,28 +67,50 @@ directory. Typical operation will involve setting these flags:
General purpose tag that outputs the URL of the specified asset
relative to the Images Directory (-i)
- `<!-- __CINERA_CSS__ path.ext -->`
Convenience tag that outputs a <link rel="stylesheet"...> node
Convenience tag that outputs a `<link rel="stylesheet"...>` node
for the specified asset relative to the CSS Directory (-c), for
use inside your <head> block
use inside your `<head>` block
- `<!-- __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),
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
asset file relative to its specified type directory (generic, CSS, image
or JS), including the "../" directory, and paths containing spaces must
be surrounded with double-quotes (\-escapable if the quoted path itself
contains double-quotes).
All these asset tags additionally perform revving, appending a query
string (-Q) and the file's checksum to the URL. Changes to a file
trigger a rehash and edit of all HTML pages citing this asset.
All these asset tags additionally perform versioning, appending a query string
(-Q) and the file's checksum to the URL. Changes to a file trigger a rehash and
edit of all HTML pages citing this asset.
- Navigation / Menu tags:
- `<!-- __CINERA_NAV__ -->`
This menu will contain only the current project, its siblings and
subprojects of both the aforementioned
- `<!-- __CINERA_GLOBAL_NAV__ -->`
This menu will contain every project in the configuration
These navigation tags additionally take one parameter, e.g.
`<!-- __CINERA_NAV__ horizontal -->`:
- `dropdown`
A dropdown menu, that opens on hover
- `horizontal`
More-or-less the same as `dropdown`, but always open
- `plain`
A straightforward `<ul>` for you to style how you like
- `<!-- __CINERA_PROJECT__ -->`
The project's `html_title` if configured, otherwise its `title`
- `<!-- __CINERA_PROJECT_PLAIN__ -->`
The project's `title`
- `<!-- __CINERA_PROJECT_ID__ -->`
- `<!-- __CINERA_PROJECT_LINEAGE__ -->`
The IDs of all projects from the root to the current project, separated
by a spaced-slash
- `<!-- __CINERA_SEARCH_URL__ -->`
- `<!-- __CINERA_THEME__ -->`
- `<!-- __CINERA_URL__ -->` _Only really usable if BaseURL is set (-B)_
- `<!-- __CINERA_URL__ -->`
- `<!-- __CINERA_CUSTOM0__ -->`
- `<!-- __CINERA_CUSTOM1__ -->`
- `<!-- __CINERA_CUSTOM2__ -->`
@ -135,106 +128,15 @@ invalid, _Cinera_ will tell you what's wrong.
#### Arguments
Usage: ./cinera [option(s)] filename(s)
Usage: ./cinera [option(s)]
Options:
Paths: (advisedly universal, but may be set per-(sub)project as required)
-r <assets root directory>
Override default assets root directory (".")
-R <assets root URL>
Override default assets root URL ("")
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)
-c <config file path>
Set the main config file path
Defaults to: $XDG_CONFIG_HOME/cinera/cinera.conf
-0
Dry-run mode. Parse and print the config, but do not modify the
filesystem
-e
Display (examine) database and exit
-v

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ nav.cineraNavDropdown .cineraNavTitle {
nav.cineraNavDropdown ul.cineraNavHorizontal {
display: none;
width: 100%;
z-index: 8;
z-index: 4096;
position: absolute;
}
@ -85,12 +85,124 @@ ul.cineraNavPlain li.current > a {
/* Index */
#cineraIndexControl {
#cineraIndex
{
display: flex;
margin: 16px auto;
max-width: 1024px;
flex-direction: column;
}
#cineraIndex #cineraIndexControl
{
display: flex;
position: -webkit-sticky;
position: sticky;
top: 0px;
z-index: 64;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
#cineraIndex .cineraIndexGridContainer
{
display: flex;
flex-direction: column;
align-items: center;
padding: 8px;
justify-content: center;
}
#cineraIndex .cineraIndexGridContainer.Portrait
{
flex-direction: column-reverse;
}
#cineraIndex .cineraIndexGridContainer.Landscape.Left
{
flex-direction: row-reverse;
}
#cineraIndex .cineraIndexGridContainer.Landscape.Right
{
flex-direction: row;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal
{
flex-direction: column;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal .cineraButton.prev
{
order: 1;
}
#cineraIndex .cineraIndexGridContainer.Landscape .cineraTraversal .cineraButton.next
{
order: 2;
}
#cineraIndex.anim .cineraTraversal,
#cineraIndex.anim .cineraTraversal * p
{
transition: transform .32s;
}
#cineraIndex .cineraTraversalContainer
{
overflow: hidden;
}
#cineraIndex .cineraTraversal
{
display: flex;
justify-content: center;
transform: rotate(0deg);
}
#cineraIndex.reversed .cineraTraversal
{
transform: rotate(180deg);
}
#cineraIndex .cineraTraversalContainer .cineraButton
{
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
padding: 2px;
height: 42px;
width: 42px;
}
#cineraIndex .cineraTraversal * p
{
transform: rotate(0deg);
}
#cineraIndex.reversed .cineraTraversal .ascension p
{
transform: rotate(-180deg);
}
#cineraIndex.reversed .cineraTraversal .prev p,
#cineraIndex.reversed .cineraTraversal .next p
{
transform: rotate(-360deg);
}
#cineraIndex .cineraTraversal .next.ascension p
{
transform: rotate(-45deg);
}
#cineraIndex.reversed .cineraTraversal .next.ascension p
{
transform: rotate(-315deg);
}
.cineraFilterProject .cineraText,
@ -99,15 +211,32 @@ ul.cineraNavPlain li.current > a {
display: flex;
}
.cineraIndexFilter {
.cineraMenu.cineraMenuTitle {
padding: 10px;
cursor: default;
}
.cineraIndexFilter .filter_container
.cineraMenuContainer
{
display: none;
position: absolute;
top: 100%;
z-index: 64;
cursor: pointer;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
border: 1px solid;
border-top: none;
}
.cineraMenuContainer.visible
{
display: block;
max-height: 88vh;
overflow-y: auto;
}
.cineraFilterProject,
@ -120,17 +249,16 @@ ul.cineraNavPlain li.current > a {
}
.cineraIndexProject .cineraProjectTitle,
.cineraFilterProject
.cineraMenuItem
{
font-weight: bold;
font-size: 12px;
font-size: 1rem;
}
.cineraQueryContainer {
flex-grow: 1;
padding-left: 16px;
display: flex;
flex-direction: horizontal;
margin: auto;
}
@ -141,19 +269,23 @@ ul.cineraNavPlain li.current > a {
}
.cineraQueryContainer .inputContainer {
display: flex;
flex-grow: 1;
position: relative;
padding: .5em;
}
.cineraQueryContainer #query {
width: 100%;
width: 0px;
flex-grow: 1;
}
.cineraQueryContainer .inputContainer .spinner {
position: absolute;
top: 2px;
right: 5px;
color: black;
font-size: .5rem;
color: #000000;
height: 100%;
display: none;
}
@ -163,7 +295,7 @@ ul.cineraNavPlain li.current > a {
}
#cineraResults,
#cineraIndex {
#cineraIndexList {
margin: 0 auto;
max-width: 800px;
}
@ -173,22 +305,104 @@ ul.cineraNavPlain li.current > a {
flex-flow: column;
}
#cineraIndexControl #cineraIndexSort {
padding: 4px;
#cineraIndexList .cineraIndexEntries {
display: flex;
flex-flow: column;
width: 100%;
}
#cineraIndexControl #cineraIndexSort,
#cineraIndexControl .cineraIndexFilter .filter_container {
cursor: pointer;
#cineraIndex #cineraIndexGrid
{
display: flex;
overflow: hidden;
position: relative;
perspective-origin: center;
-webkit-perspective-origin: center;
}
#cineraIndex #cineraIndexGrid .cineraButtons
{
flex-shrink: 0;
display: grid;
grid-gap: 2px;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
#cineraIndex .cineraButton
{
border: 1px solid;
text-align: center;
font-size: 1rem;
font-weight: bold;
flex-direction: column;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
#cineraIndex .cineraIndexEntries {
#cineraIndex #cineraIndexGrid .cineraButton.subdivision
{
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;
flex-flow: column;
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 {
@ -199,11 +413,11 @@ ul.cineraNavPlain li.current > a {
box-sizing: border-box;
display: block;
flex-shrink: 0;
font-size: 12px;
font-size: .75rem;
line-height: 16px;
padding: 5px;
vertical-align: top;
width: 200px;
max-width: 200px;
}
.cineraIndexEntries div a {
@ -243,36 +457,6 @@ ul.cineraNavPlain li.current > a {
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 / Structure */
@ -299,7 +483,14 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu {
position: relative;
align-self: center;
min-height: 1em;
min-width: 1em;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.cineraMenus > .menu.filter.responsible,
@ -312,107 +503,117 @@ ul.cineraNavPlain li.current > a {
animation-iteration-count: 1;
}
.cineraMenus .help .help_container .help_key {
font-family: Inconsolata;
font-size: 16px;
border: 1px solid;
display: inline-block;
background-color: #111; /* Per project */
border-radius: 4px;
height: 16px;
width: 16px;
padding: 4px;
line-height: 16px;
margin: 2px;
}
.cineraMenus .help .help_container .help_key.word {
width: auto;
}
.cineraMenus .help .help_container .help_key.modifer {
margin-right: 0;
}
.cineraMenus .help .help_container .help_key.unavailable,
.cineraMenus .help .help_container .help_text.unavailable,
.cineraMenus .help .help_container h2 .unavailable {
opacity: 0.32;
}
.cineraMenus .help .help_container .key_block {
display: inline-flex;
align-items: flex-end;
flex-direction: row;
margin: 8px;
}
.cineraMenus .help .help_container .help_text {
margin: 0 8px 0 2px;
}
.cineraMenus .help .help_container h1 {
display: inline;
margin-left: 4px;
}
.cineraMenus .help .help_container h1:after {
content: "\a";
}
.cineraMenus .help .help_container h2 {
font-size: 16px;
margin-bottom: 8px;
}
.cineraMenus .help .help_container .help_paragraph {
display: inline-flex;
align-items: center;
}
.cineraMenus > .help {
.cineraMenus > .cineraHelp,
.cineraHelp {
cursor: pointer;
box-sizing: content-box;
border: 1px solid;
border-radius: 4px;
height: 6px;
height: .5rem;
width: .5rem;
padding: 4px;
width: 6px;
margin: 2px;
font-size: 12px;
font-size: .75rem;
font-weight: bold;
line-height: 6px;
line-height: .5rem;
text-align: center;
z-index: 64;
}
.cineraMenus .help .help_container .help_grid {
display: inline-flex;
flex-direction: column;
}
.cineraMenus .help .help_container {
background-color: black; /* Per project */
color: #EEE; /* Per project */
.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;
right: 612px;
top: 42px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 97%;
overflow-y: auto;
}
.cineraMenus .help .help_container.visible {
.cineraHelp .help_container.visible {
display: block;
}
#cineraIndexControl .cineraIndexFilter .filter_container
{
.cineraHelp .help_container .help_custom_index {
background-color: #159 !important;
}
.cineraHelp .help_container h2 .help_title_key {
padding: .2em;
border: 1px solid;
border-top: none;
border-radius: 4px;
}
.cineraHelp .help_container .help_key {
box-sizing: content-box;
font-family: Inconsolata;
font-size: 1rem;
border: 1px solid;
display: inline-block;
background-color: #111111; /* Per project */
border-radius: 4px;
min-height: 1em;
min-width: 1em;
padding: 4px;
line-height: 16px;
margin: 2px;
}
.cineraHelp .help_container .help_key.word {
width: auto;
}
.cineraHelp .help_container .help_key.modifer {
margin-right: 0;
}
.cineraHelp .help_container .help_key.unavailable,
.cineraHelp .help_container .help_text.unavailable,
.cineraHelp .help_container h2 .unavailable {
opacity: 0.32;
}
.cineraHelp .help_container .key_block {
display: inline-flex;
align-items: flex-end;
flex-direction: row;
margin: 8px;
}
.cineraHelp .help_container .help_text {
margin: 0 8px 0 2px;
}
.cineraHelp .help_container h1 {
display: inline;
margin-left: 4px;
}
.cineraHelp .help_container h1:after {
content: "\a";
}
.cineraHelp .help_container h2 {
font-size: 1rem;
margin-bottom: 8px;
}
.cineraHelp .help_container .help_paragraph {
display: inline-flex;
align-items: center;
}
.cineraHelp .help_container .help_grid {
display: inline-flex;
flex-direction: column;
}
.cineraMenus > .menu .quotes_container,
@ -423,18 +624,17 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu .credits_container {
border: 1px solid;
border-top: none;
display: none;
/* TODO(matt): Set the height to the player's height */
max-height: 512px;
z-index: -1; /* NOTE(matt): Using "display: none" to hide them proved problematic for scrolling non-visible menus */
overflow-y: auto;
position: absolute;
right: 0;
top: 100%;
z-index: 1;
}
.cineraMenus > .menu .refs,
.cineraMenus > .menu .link_container {
.cineraMenus > .menu .filter_container,
.cineraMenus > .menu .link_container,
.cineraMenus > .menu .credits_container {
width: 350px;
}
@ -442,16 +642,13 @@ ul.cineraNavPlain li.current > a {
overflow-x: hidden;
}
.cineraMenus > .menu .filter_container {
min-width: 350px;
}
.cineraMenus > .menu .credits_container {
min-width: 240px;
.cineraMenus > .menu .sizing {
z-index: -1;
display: block;
}
.cineraMenus > .menu .visible {
display: block;
z-index: 8;
}
.cineraMenus > .menu > .refs .ref {
@ -484,7 +681,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .refs .ref .timecode,
.cineraMenus > .menu > .filter_container .filter_mode,
.cineraMenus > .menu > .link_container #cineraLinkMode {
font-size: 12px;
font-size: .75rem;
}
.cineraMenus > .menu > .filter_container .filter_mode,
@ -514,7 +711,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .refs .ref .quote_byline,
.cineraMenus > .menu > .filter_container .filter_title,
.cineraMenus > .menu > .credits_container .credit .role {
font-size: 10px;
font-size: .64rem;
}
.cineraMenus > .menu > .refs .ref .source,
@ -549,6 +746,11 @@ ul.cineraNavPlain li.current > a {
margin-right: 4px;
}
.cineraMenus > .menu > .filter_container .filter_header {
position: sticky;
top: 0;
}
.cineraMenus > .menu > .filter_container .filter_mode,
.cineraMenus > .menu > .link_container #cineraLinkMode {
cursor: pointer;
@ -568,11 +770,13 @@ ul.cineraNavPlain li.current > a {
text-align: center;
}
.cineraMenus > .menu > .filter_container .filter_header .filter_titles,
.cineraMenus > .menu > .filter_container .filters {
display: flex;
flex-flow: row nowrap;
}
.cineraMenus > .menu > .filter_container .filter_header .filter_titles > *,
.cineraMenus > .menu > .filter_container .filters > * {
width: 50%;
flex-grow: 1;
@ -638,8 +842,16 @@ ul.cineraNavPlain li.current > a {
overflow: hidden;
}
.cineraPlayerContainer .video_container .direct_video {
display: flex;
}
.cineraPlayerContainer .video_container .direct_video video {
outline: none;
}
.cineraPlayerContainer .markers_container {
flex-shrink: 0;
flex-shrink: 1;
overflow-y: scroll;
position: relative;
}
@ -647,10 +859,20 @@ ul.cineraNavPlain li.current > a {
.cineraPlayerContainer .markers_container > .episodeMarker {
text-decoration: none;
display: flex;
font-size: 11px;
font-size: .75rem;
font-weight: bold;
}
.cineraPlayerContainer {
background-color: #000000;
}
.cineraPlayerContainer .video_container {
display: flex;
justify-content: center;
align-self: center;
}
.cineraPlayerContainer .markers_container > a.episodeMarker {
cursor: pointer;
}
@ -665,7 +887,7 @@ ul.cineraNavPlain li.current > a {
}
.cineraPlayerContainer .markers_container > .episodeMarker div:not(:nth-of-type(2)) {
font-size: 15px;
font-size: .9rem;
font-weight: normal;
}
@ -683,7 +905,7 @@ ul.cineraNavPlain li.current > a {
border-bottom: 1px solid;
position: relative;
cursor: pointer;
max-height: 320px;
max-height: 320px; /* NOTE(matt): Required for the transition */
transition: max-height .32s;
}
@ -706,10 +928,10 @@ ul.cineraNavPlain li.current > a {
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
display: inline-block;
font-size: 14px;
font-size: .8rem;
}
.cineraPlayerContainer .markers_container > .markers .marker.skip {
.cinera:not(.mobile) .cineraPlayerContainer .markers_container > .markers .marker.skip {
max-height: 0;
transition: max-height .32s;
overflow: hidden;
@ -745,7 +967,7 @@ ul.cineraNavPlain li.current > a {
}
.cineraPlayerContainer .markers_container > .markers .marker .timecode {
font-size: 9px;
font-size: .6rem;
font-style: normal;
padding-right: 8px;
position: relative;
@ -763,6 +985,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .filter_container .filter_content .category,
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category {
box-sizing: content-box;
border-radius: 50%;
height: 5px;
width: 5px;
@ -781,97 +1004,124 @@ ul.cineraNavPlain li.current > a {
margin-right: 8px;
}
@media (max-width: 720px), (max-height: 512px)
{
.cineraMenus .episode_name,
.cineraMenus > .menu > .view,
.cineraMenus > .menu > .views_container .view,
.cineraMenus > .help,
.markers_container > .episodeMarker div:nth-child(2),
.markers_container > .episodeMarker div:nth-child(3),
.markers_container > .markers .marker:not(.current) {
display: none;
}
/* NOTE(matt): Mobile Style */
.cineraMenus {
justify-content: center;
}
.cineraMenus .menu .quotes_container,
.cineraMenus .menu .references_container,
.cineraMenus .menu .filter_container,
.cineraMenus .menu .link_container,
.cineraMenus .menu .credits_container {
position: fixed;
left: 0;
margin: auto;
max-height: 80%;
max-width: 97%;
}
.cineraPlayerContainer {
display: flex;
flex-direction: column;
}
.cineraPlayerContainer .markers_container {
display: flex;
flex-flow: row;
overflow-y: auto;
}
.cineraPlayerContainer .markers_container > .episodeMarker {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
width: 456px;
}
@media (max-width: 580px)
{
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
width: 320px;
}
}
@media (max-width: 450px)
{
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent {
width: 256px;
}
}
@media (max-width: 375px)
{
.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;
}
#cineraIndex.mobile .cineraMenu.ViewSettings .cineraMenuItem,
#cineraIndex.mobile .cineraFilterProject .cineraText,
#cineraIndex.mobile .cineraIndexEntries div a {
display: flex;
box-sizing: border-box;
align-items: center;
min-height: 10mm; /* NOTE(matt): From https://web.dev/accessible-tap-targets/ */
}
#cineraIndex.mobile .cineraMenu.ViewSettings .cineraMenuItem,
#cineraIndex.mobile .cineraFilterProject .cineraText,
#cineraIndex.mobile .cineraIndexEntries div {
margin: 8px; /* NOTE(matt): From https://web.dev/accessible-tap-targets/ */
}
#cineraIndex.mobile #cineraResults .dayContainer {
flex-direction: column;
}
#cineraIndex.mobile #cineraResults .dayContainer .dayName {
font-weight: bold;
text-align: center;
max-width: 100%;
}
#cineraIndex.mobile #cineraResults .dayContainer .markerList {
max-width: 100%;
}
.cinera.mobile
{
display: flex;
flex-direction: column;
flex-grow: 0;
-webkit-text-size-adjust: none;
}
.cinera.mobile .cineraMenus {
justify-content: center;
display: flex;
flex-direction: row;
position: relative;
border: 0;
}
.cinera.mobile .cineraMenus,
.cinera.mobile .player_container {
flex-grow: 0;
}
.cinera.mobile .cineraMenus .episode_name,
.cinera.mobile .cineraMenus > .menu > .view,
.cinera.mobile .cineraMenus > .menu > .views_container .view,
.cinera.mobile .cineraHelp,
#cineraIndex.mobile .cineraHelp,
.cinera.mobile .markers_container > .episodeMarker div:nth-child(2),
.cinera.mobile .markers_container > .episodeMarker div:nth-child(3),
.cinera.mobile .markers_container > .markers .marker:not(.current) {
display: none;
}
.cinera.mobile .cineraMenus .menu {
position: static;
}
.cinera.mobile .cineraMenus .menu .quotes_container,
.cinera.mobile .cineraMenus .menu .references_container,
.cinera.mobile .cineraMenus .menu .filter_container,
.cinera.mobile .cineraMenus .menu .link_container,
.cinera.mobile .cineraMenus .menu .credits_container {
box-sizing: border-box;
position: absolute;
}
.cinera.mobile .cineraPlayerContainer {
display: flex;
flex-direction: column;
}
.cinera.mobile .cineraPlayerContainer .markers_container {
display: flex;
flex-flow: row;
overflow-y: hidden;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker {
width: 32px;
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .markers {
flex-grow: 1;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev,
.cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next {
border: 0;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.first,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.prev {
border-right: 3px double;
}
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.last,
.cinera.mobile .cineraPlayerContainer .markers_container > .episodeMarker.next {
border-left: 3px double;
}
/* Mobile Style End */
/* CUSTOM PAGE STYLE */
/*
@ -889,4 +1139,4 @@ Open menu: &#9662; ▾
Open link in new tab: &#10555; or &10559; or &8599; or &#11127;
Play from timecode: &#9205; (or, if &#9656;)
Playable from timecode: &#9657;
*/
*/

23
cinera/cinera_clear.js Normal file
View File

@ -0,0 +1,23 @@
// 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,137 +1,6 @@
var originalTextContent = {
TitleQuotes: null,
TitleReferences: null,
TitleCredits: null,
EpisodePrev: null,
EpisodeNext: null,
};
var baseURL = location.hash ? (location.toString().substr(0, location.toString().length - location.hash.length)) : location;
var menuState = [];
var titleBar = document.querySelector(".cineraMenus");
var quotesMenu = titleBar.querySelector(".quotes_container");
if(quotesMenu)
{
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 = {
var CineraProps = {
C: null,
V: views.REGULAR,
Z: null,
@ -142,132 +11,48 @@ var cineraProps = {
H: null,
mH: null,
P: null,
D: devices.DESKTOP,
Display: null,
FlexDirection: null,
JustifyContent: null,
O: null,
IsMobile: IsMobile(),
ScrollX: null,
ScrollY: null,
VODPlatform: null,
};
CineraProps.O = GetRealOrientation(orientations.LANDSCAPE_LEFT, CineraProps.IsMobile);
var MobileCineraContentRuleSelector = ".cinera.mobile .cineraPlayerContainer .markers_container > .markers .marker .cineraContent";
var MobileCineraContentRule = GetOrSetRule(MobileCineraContentRuleSelector);
var MenuContainerRuleSelector = ".cineraMenus > .menu .quotes_container, .cineraMenus > .menu .references_container, .cineraMenus > .menu .filter_container, .cineraMenus > .menu .views_container, .cineraMenus > .menu .link_container, .cineraMenus > .menu .credits_container";
var MenuContainerRule = GetOrSetRule(MenuContainerRuleSelector);
var cinera = document.querySelector(".cinera");
var player = new Player(cinera, onRefChanged);
window.addEventListener("resize", function() {
if(CineraProps.IsMobile)
{
setTimeout(DelayedUpdateSize, 512, player);
}
else
{
player.updateSize();
}
});
screen.orientation.onchange = function() {
if(CineraProps.IsMobile)
{
setTimeout(DelayedUpdateSize, 512, player);
}
else
{
player.updateSize();
}
};
var viewsMenu = titleBar.querySelector(".views");
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 viewItems = viewsMenu.querySelectorAll(".view");
for(var i = 0; i < viewItems.length; ++i)
{
viewItems[i].addEventListener("click", function(ev) {
switch(this.getAttribute("data-id"))
{
case "regular":
case "theatre":
{
toggleTheatreMode();
} break;
case "super":
{
toggleSuperTheatreMode();
} break;
}
});
}
}
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)
{
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";
});
window.addEventListener("focus", function(){
helpButton.firstElementChild.innerText = "?";
helpButton.firstElementChild.title = ""
});
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) {
var key = ev.key;
if(ev.getModifierState("Shift") && key == " ")
@ -275,532 +60,17 @@ document.addEventListener("keydown", function(ev) {
key = "capitalSpace";
}
if(!ev.getModifierState("Control") && handleKey(key) == true && focusedElement)
if(!ev.getModifierState("Control") && player.handleKey(key) == true && player.MenusFocused.Item)
{
ev.preventDefault();
}
});
for(var i = 0; i < sourceMenus.length; ++i)
{
sourceMenus[i].addEventListener("mouseenter", function(ev) {
handleMouseOverMenu(this, ev.type);
})
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;
document.addEventListener("fullscreenchange", function() {
if(!document.fullscreenElement && CineraProps.V == views.SUPERTHEATRE)
{
CineraProps.V = views.THEATRE;
localStorage.setItem(player.cineraViewStorageItem, views.THEATRE);
player.updateSize();
}
return gotKey;
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,115 @@
var orientations = {
PORTRAIT: 0,
LANDSCAPE_LEFT: 90,
LANDSCAPE_RIGHT: -90,
};
function
DeriveReliableWindowDimensions()
{
// https://www.howtocreate.co.uk/tutorials/javascript/browserwindow
var Result = {
X: null,
Y: null,
};
var ScrollPosX = window.scrollX;
var ScrollPosY = window.scrollY;
var DisplaySettings = [];
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
DisplaySettings.push(Child.style.display);
Child.style.display = "none";
}
var Element = document.createElement("div");
Element.style.position = "fixed";
Element.style.top = 0;
Element.style.right = 0;
Element.style.bottom = 0;
Element.style.left = 0;
Element.style.zOffset = -1;
Element.style.opacity = 0;
var ElementInPlace = document.body.appendChild(Element);
Result.X = ElementInPlace.offsetWidth;
Result.Y = ElementInPlace.offsetHeight;
ElementInPlace.remove();
for(var i = 0; i < document.body.children.length; ++i)
{
var Child = document.body.children[i];
Child.style.display = DisplaySettings.shift();
}
ScrollTriggeredInternally = true;
window.scroll(ScrollPosX, ScrollPosY);
return Result;
}
function
GetWindowDim(IsMobile)
{
var Result = {
X: null,
Y: null,
};
if(IsMobile)
{
Result = DeriveReliableWindowDimensions();
}
else
{
Result.X = document.documentElement.clientWidth;
Result.Y = document.documentElement.clientHeight;
}
return Result;
}
function IsVisible(Element, WindowDim) {
var BoundingRect = Element.getBoundingClientRect();
return ((BoundingRect.top >= 0 && BoundingRect.top <= WindowDim.Y) ||
(BoundingRect.bottom >= 0 && BoundingRect.bottom <= WindowDim.Y) ||
(BoundingRect.top < 0 && BoundingRect.bottom > WindowDim.Y))
&& ((BoundingRect.left >= 0 && BoundingRect.left <= WindowDim.X) ||
(BoundingRect.right >= 0 && BoundingRect.right <= WindowDim.X) ||
(BoundingRect.left < 0 && BoundingRect.right > WindowDim.X));
}
function
GetRealOrientation(PreferredLandscape, IsMobile)
{
var Result = screen.orientation.angle;
var WindowDim = GetWindowDim(IsMobile);
if(WindowDim.Y > WindowDim.X)
{
Result = orientations.PORTRAIT;
}
else if(Result == undefined || Result == orientations.PORTRAIT)
{
Result = PreferredLandscape;
}
return Result;
}
var DebugConsoleMessageCount = 0;
function Say(Message)
{
var DebugConsole = document.getElementById("debug-console");
if(DebugConsole)
{
DebugConsole.textContent += DebugConsoleMessageCount++ + ": " + Message + "\n";
DebugConsole.scrollTo(
{
top: DebugConsole.scrollHeight,
behavior: "smooth"
});
}
}
function getBackgroundBrightness(element) {
var colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
@ -80,14 +192,21 @@ function enableSprite(Element)
function disableSprite(Element)
{
if(Element.classList.contains("cineraSprite"))
if(Element.classList.contains("focused"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-disabled") + "px";
focusSprite(Element);
}
for(var i = 0; i < Element.childElementCount; ++i)
else
{
disableSprite(Element.children[i]);
if(Element.classList.contains("cineraSprite"))
{
setSpriteLightness(Element);
Element.style.backgroundPositionY = Element.getAttribute("data-y-disabled") + "px";
}
for(var i = 0; i < Element.childElementCount; ++i)
{
disableSprite(Element.children[i]);
}
}
}
@ -110,3 +229,386 @@ 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%)");
}
}
}

View File

@ -1,641 +0,0 @@
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;
var searchLocation = ProjectElement.attributes.getNamedItem("data-searchLocation").value;
var playerLocation = ProjectElement.attributes.getNamedItem("data-playerLocation").value;
for(var i = 0; i < projects.length; ++i)
{
var ThisProject = projects[i];
if(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
{
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;
var searchLocation = ProjectElement.attributes.getNamedItem("data-searchLocation").value;
var playerLocation = ProjectElement.attributes.getNamedItem("data-playerLocation").value;
for(var i = 0; i < projects.length; ++i)
{
var ThisProject = projects[i];
if(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
{
ThisProject.filteredOut = false;
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "flex";
enableSprite(ThisProject.entriesContainer.parentElement);
}
}
}
}
function hideProjectSearchResults(baseURL, searchLocation, playerLocation)
{
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;
var resultSearchLocation = cineraResultsProjects[i].attributes.getNamedItem("data-searchLocation").value;
var resultPlayerLocation = cineraResultsProjects[i].attributes.getNamedItem("data-playerLocation").value;
if(baseURL === resultBaseURL && searchLocation === resultSearchLocation && playerLocation === resultPlayerLocation)
{
cineraResultsProjects[i].style.display = "none";
return;
}
}
}
}
function showProjectSearchResults(baseURL, searchLocation, playerLocation)
{
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;
var resultSearchLocation = cineraResultsProjects[i].attributes.getNamedItem("data-searchLocation").value;
var resultPlayerLocation = cineraResultsProjects[i].attributes.getNamedItem("data-playerLocation").value;
if(baseURL === resultBaseURL && searchLocation === resultSearchLocation && playerLocation === resultPlayerLocation)
{
cineraResultsProjects[i].style.display = "flex";
return;
}
}
}
}
function toggleEntriesOfProjectAndChildren(ProjectFilterElement)
{
var baseURL = ProjectFilterElement.attributes.getNamedItem("data-baseURL").value;
var searchLocation = ProjectFilterElement.attributes.getNamedItem("data-searchLocation").value;
var playerLocation = ProjectFilterElement.attributes.getNamedItem("data-playerLocation").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(baseURL === ThisProject.baseURL && searchLocation === ThisProject.searchLocation && playerLocation === ThisProject.playerLocation)
{
if(shouldShow)
{
ThisProject.filteredOut = false;
enableSprite(ThisProject.projectTitleElement.parentElement);
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "flex";
}
showProjectSearchResults(ThisProject.baseURL, ThisProject.searchLocation, ThisProject.playerLocation);
}
else
{
ThisProject.filteredOut = true;
disableSprite(ThisProject.projectTitleElement.parentElement);
if(ThisProject.entriesContainer != null)
{
ThisProject.entriesContainer.style.display = "none";
}
hideProjectSearchResults(ThisProject.baseURL, ThisProject.searchLocation, ThisProject.playerLocation);
}
}
}
var indexChildFilterProjects = ProjectFilterElement.querySelectorAll(".cineraFilterProject");
for(var j = 0; j < indexChildFilterProjects.length; ++j)
{
var ThisElement = indexChildFilterProjects[j];
var baseURL = ThisElement.attributes.getNamedItem("data-baseURL").value;
var searchLocation = ThisElement.attributes.getNamedItem("data-searchLocation").value;
var playerLocation = ThisElement.attributes.getNamedItem("data-playerLocation").value;
if(shouldShow)
{
showEntriesOfProject(ThisElement);
showProjectSearchResults(baseURL, searchLocation, playerLocation);
}
else
{
hideEntriesOfProject(ThisElement);
hideProjectSearchResults(baseURL, searchLocation, playerLocation);
}
}
}
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 searchLocation = projectsContainer[i].attributes.getNamedItem("data-searchLocation").value;
var playerLocation = projectsContainer[i].attributes.getNamedItem("data-playerLocation").value;
var theme = projectsContainer[i].classList.item(1);
projects[i] =
{
baseURL: baseURL,
searchLocation: searchLocation,
playerLocation: playerLocation,
playerURLPrefix: (baseURL ? baseURL + "/" : "") + (playerLocation ? playerLocation + "/" : ""),
indexLocation: (baseURL ? baseURL + "/" : "") + (searchLocation ? searchLocation + "/" : "") + 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);
projectContainer.setAttribute("data-playerLocation", projects[i].playerLocation);
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

@ -0,0 +1,67 @@
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();
//
////

3882
cinera/cinera_search_pre.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

5
hmmlib2/.gitignore vendored Normal file
View File

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

6
hmmlib2/README.TXT Normal file
View File

@ -0,0 +1,6 @@
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.

859
hmmlib2/hmmlib.h Normal file
View File

@ -0,0 +1,859 @@
#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

2
hmmlib2/utils/Makefile Normal file
View File

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

241
hmmlib2/utils/dump.c Normal file
View File

@ -0,0 +1,241 @@
#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

@ -0,0 +1,16 @@
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

20
hmmlib2/utils/fuzz/fuzz.c Normal file
View File

@ -0,0 +1,20 @@
#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

@ -0,0 +1,11 @@
[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

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

View File

@ -0,0 +1,25 @@
[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]

59
hmmlib2/utils/stb_sb.h Normal file
View File

@ -0,0 +1,59 @@
// 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