Compare commits
	
		
			2 Commits
		
	
	
		
			14dafa4abe
			...
			e5ffec7a55
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | e5ffec7a55 | |
|  | a67a5ee34b | 
							
								
								
									
										842
									
								
								cinera/cinera.c
								
								
								
								
							
							
						
						
									
										842
									
								
								cinera/cinera.c
								
								
								
								
							|  | @ -23,7 +23,7 @@ typedef struct | |||
| version CINERA_APP_VERSION = { | ||||
|     .Major = 0, | ||||
|     .Minor = 10, | ||||
|     .Patch = 30 | ||||
|     .Patch = 31 | ||||
| }; | ||||
| 
 | ||||
| #define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
 | ||||
|  | @ -8350,16 +8350,70 @@ BuildTimestampClass(buffer *TimestampClass, _memory_book(category_info) *LocalTo | |||
|     CopyStringToBuffer(TimestampClass, "\""); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *LocalMedia, string DefaultMedium, bool *RequiresCineraJS) | ||||
| bool | ||||
| IsWhitespace(char C) | ||||
| { | ||||
|     bool CategoriesSpan = FALSE; | ||||
|     return (C == ' ' || C == '\t' || C == '\n'); | ||||
| } | ||||
| 
 | ||||
| bool | ||||
| ContainsWhitespace(string S) | ||||
| { | ||||
|     bool Result = FALSE; | ||||
|     for(int i = 0; i < S.Length; ++i) | ||||
|     { | ||||
|         if(IsWhitespace(S.Base[i])) | ||||
|         { | ||||
|             Result = TRUE; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return Result; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| ConsumeWhitespace(buffer *B) | ||||
| { | ||||
|     while(B->Ptr - B->Location < B->Size && IsWhitespace(*B->Ptr)) | ||||
|     { | ||||
|         ++B->Ptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void | ||||
| PushCategorySearchEntry(buffer *SearchEntry, HMML_Timestamp *Timestamp, category_info *Category, bool Underway) | ||||
| { | ||||
|     if(Underway || Timestamp->text[0]) | ||||
|     { | ||||
|         CopyStringToBuffer(SearchEntry, " "); | ||||
|     } | ||||
|     if(!Underway) | ||||
|     { | ||||
|         CopyStringToBuffer(SearchEntry, "["); | ||||
|     } | ||||
| 
 | ||||
|     CopyStringToBuffer(SearchEntry, ":"); | ||||
|     bool NeedsQuoting = ContainsWhitespace(Category->Marker); | ||||
|     if(NeedsQuoting) | ||||
|     { | ||||
|         CopyStringToBuffer(SearchEntry, "\""); | ||||
|     } | ||||
|     CopyStringToBufferNoFormat(SearchEntry, Category->Marker); | ||||
|     if(NeedsQuoting) | ||||
|     { | ||||
|         CopyStringToBuffer(SearchEntry, "\""); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void | ||||
| BuildCategoryIcons(buffer *SearchEntry, buffer *CategoryIcons, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *LocalMedia, string DefaultMedium, bool *RequiresCineraJS, HMML_Timestamp *Timestamp) | ||||
| { | ||||
|     bool Underway = FALSE; | ||||
|     category_info *FirstLocalTopic = GetPlaceInBook(LocalTopics, 0); | ||||
|     category_info *FirstLocalMedium = GetPlaceInBook(LocalMedia, 0); | ||||
|     if(!(LocalTopics->ItemCount == 1 && StringsMatch(FirstLocalTopic->Marker, Wrap0("nullTopic")) | ||||
|             && LocalMedia->ItemCount == 1 && StringsMatch(DefaultMedium, FirstLocalMedium->Marker))) | ||||
|             && LocalMedia->ItemCount == 1 && StringsMatch(FirstLocalMedium->Marker, DefaultMedium))) | ||||
|     { | ||||
|         CategoriesSpan = TRUE; | ||||
|         CopyStringToBuffer(CategoryIcons, "<span class=\"cineraCategories\">"); | ||||
|     } | ||||
| 
 | ||||
|  | @ -8368,6 +8422,14 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi | |||
|         for(int i = 0; i < LocalTopics->ItemCount; ++i) | ||||
|         { | ||||
|             category_info *This = GetPlaceInBook(LocalTopics, i); | ||||
| 
 | ||||
|             // .index
 | ||||
|             if(SearchEntry) | ||||
|             { | ||||
|                 PushCategorySearchEntry(SearchEntry, Timestamp, This, Underway); | ||||
|             } | ||||
| 
 | ||||
|             // .html
 | ||||
|             // NOTE(matt): Stack-string
 | ||||
|             char SanitisedMarker[This->Marker.Length + 1]; | ||||
|             CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); | ||||
|  | @ -8385,14 +8447,24 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi | |||
|             } | ||||
|             CopyStringToBuffer(CategoryIcons, | ||||
|                     "></div>"); | ||||
| 
 | ||||
|             Underway = TRUE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(!(LocalMedia->ItemCount == 1 && StringsMatch(DefaultMedium, FirstLocalMedium->Marker))) | ||||
|     if(!(LocalMedia->ItemCount == 1 && StringsMatch(FirstLocalMedium->Marker, DefaultMedium))) | ||||
|     { | ||||
|         for(int i = 0; i < LocalMedia->ItemCount; ++i) | ||||
|         { | ||||
|             category_info *This = GetPlaceInBook(LocalMedia, i); | ||||
| 
 | ||||
|             // .index
 | ||||
|             if(SearchEntry) | ||||
|             { | ||||
|                 PushCategorySearchEntry(SearchEntry, Timestamp, This, Underway); | ||||
|             } | ||||
| 
 | ||||
|             // .html
 | ||||
|             // NOTE(matt): Stack-string
 | ||||
|             char SanitisedMarker[This->Marker.Length + 1]; | ||||
|             CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); | ||||
|  | @ -8409,11 +8481,20 @@ BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopi | |||
| 
 | ||||
|                 CopyStringToBuffer(CategoryIcons, "</div>"); | ||||
|             } | ||||
| 
 | ||||
|             Underway = TRUE; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(CategoriesSpan) | ||||
|     if(Underway) | ||||
|     { | ||||
|         // .index
 | ||||
|         if(SearchEntry) | ||||
|         { | ||||
|             CopyStringToBuffer(SearchEntry, "]"); | ||||
|         } | ||||
| 
 | ||||
|         // .html
 | ||||
|         CopyStringToBuffer(CategoryIcons, "</span>"); | ||||
|     } | ||||
| } | ||||
|  | @ -8431,15 +8512,10 @@ String0ToInt(char *String) | |||
| } | ||||
| 
 | ||||
| size_t | ||||
| CurlIntoBuffer(char *InPtr, size_t CharLength, size_t Chars, char **OutputPtr) | ||||
| CurlIntoBuffer(char *InPtr, size_t CharLength, size_t Chars, void *OutPtr) | ||||
| { | ||||
|     int Length = CharLength * Chars; | ||||
|     int i; | ||||
|     for(i = 0; InPtr[i] && i < Length; ++i) | ||||
|     { | ||||
|         *((*OutputPtr)++) = InPtr[i]; | ||||
|     } | ||||
|     **OutputPtr = '\0'; | ||||
|     size_t Length = CharLength * Chars; | ||||
|     CopyStringToBufferNoFormat(OutPtr, Wrap0i_(InPtr, Length)); | ||||
|     return Length; | ||||
| }; | ||||
| 
 | ||||
|  | @ -8452,7 +8528,7 @@ CurlQuotes(buffer *QuoteStaging, char *QuotesURL) | |||
| 
 | ||||
|     CURL *curl = curl_easy_init(); | ||||
|     if(curl) { | ||||
|         curl_easy_setopt(curl, CURLOPT_WRITEDATA, &QuoteStaging->Ptr); | ||||
|         curl_easy_setopt(curl, CURLOPT_WRITEDATA, QuoteStaging); | ||||
|         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlIntoBuffer); | ||||
|         curl_easy_setopt(curl, CURLOPT_URL, QuotesURL); | ||||
|         /*      */ MEM_TEST_MID(); | ||||
|  | @ -9225,21 +9301,6 @@ StripSurroundingSlashes(char *String) // NOTE(matt): For relative paths | |||
|     return Ptr; | ||||
| } | ||||
| 
 | ||||
| bool | ||||
| IsWhitespace(char C) | ||||
| { | ||||
|     return (C == ' ' || C == '\t' || C == '\n'); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| ConsumeWhitespace(buffer *B) | ||||
| { | ||||
|     while(B->Ptr - B->Location < B->Size && IsWhitespace(*B->Ptr)) | ||||
|     { | ||||
|         ++B->Ptr; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| string | ||||
| StripPWDIndicators(string Path) | ||||
| { | ||||
|  | @ -10654,8 +10715,8 @@ ClaimMenuIndexAndPlayerBuffers(menu_buffers *MenuBuffers, index_buffers *IndexBu | |||
|     if(ClaimBuffer(&IndexBuffers->Text, BID_INDEX_BUFFERS_TEXT, Kilobytes(4)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
|     if(ClaimBuffer(&IndexBuffers->CategoryIcons, BID_INDEX_BUFFERS_CATEGORY_ICONS, Kilobytes(1)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
| 
 | ||||
|     if(ClaimBuffer(&PlayerBuffers->Menus, BID_PLAYER_BUFFERS_MENUS, Kilobytes(32)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
|     if(ClaimBuffer(&PlayerBuffers->Main, BID_PLAYER_BUFFERS_MAIN, Kilobytes(512)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
|     if(ClaimBuffer(&PlayerBuffers->Menus, BID_PLAYER_BUFFERS_MENUS, Kilobytes(64)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
|     if(ClaimBuffer(&PlayerBuffers->Main, BID_PLAYER_BUFFERS_MAIN, Megabytes(1)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
|     if(ClaimBuffer(&PlayerBuffers->Script, BID_PLAYER_BUFFERS_SCRIPT, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; | ||||
| 
 | ||||
|     return Result; | ||||
|  | @ -10867,113 +10928,288 @@ TimecodeIs(v4 Timecode, int Hours, int Minutes, int Seconds, int Milliseconds) | |||
|     return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds && Timecode.Milliseconds == Milliseconds; | ||||
| } | ||||
| 
 | ||||
| rc | ||||
| ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, memory_book *Strings, | ||||
|         menu_buffers *MenuBuffers, index_buffers *IndexBuffers, player_buffers *PlayerBuffers, | ||||
|         medium *DefaultMedium, speakers *Speakers, string Author, | ||||
|         _memory_book(ref_info) *ReferencesArray, | ||||
|         bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS, | ||||
|         int *QuoteIdentifier, int *RefIdentifier, | ||||
|         _memory_book(category_info) *Topics, _memory_book(category_info) *Media, | ||||
|         HMML_Timestamp *Timestamp, v4 *PreviousTimecode) | ||||
| void | ||||
| ProcessTimestampCoda(buffer *SearchEntry, index_buffers *IndexBuffers) | ||||
| { | ||||
|     // .index
 | ||||
|     CopyStringToBuffer(SearchEntry, "\"\n"); | ||||
| 
 | ||||
|     // .html
 | ||||
|     CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|             "                    </div>\n" | ||||
|             "                </div>\n"); | ||||
| } | ||||
| 
 | ||||
| rc | ||||
| ProcessTimestampCategories(neighbourhood *N, | ||||
|         buffer *SearchEntry, index_buffers *IndexBuffers, | ||||
|         medium *DefaultMedium, | ||||
|         bool *HasFilterMenu, | ||||
|         bool *RequiresCineraJS, | ||||
|         _memory_book(category_info) *Topics, _memory_book(category_info) *LocalTopics, | ||||
|         _memory_book(category_info) *Media, _memory_book(category_info) *LocalMedia, | ||||
|         HMML_Timestamp *Timestamp, v4 Timecode, | ||||
|         int *MarkerIndex, bool HasQuote, bool HasReference) | ||||
| { | ||||
|     MEM_TEST_TOP(); | ||||
|     // TODO(matt): Introduce and use a SystemError() in here
 | ||||
|     rc Result = RC_SUCCESS; | ||||
| 
 | ||||
|     v4 Timecode = V4(Timestamp->h, Timestamp->m, Timestamp->s, Timestamp->ms); | ||||
|     if(TimecodeToDottedSeconds(Timecode) >= TimecodeToDottedSeconds(*PreviousTimecode)) | ||||
|     { | ||||
|         *PreviousTimecode = Timecode; | ||||
| 
 | ||||
|         memory_book LocalTopics = InitBook(sizeof(category_info), 8); | ||||
|         memory_book LocalMedia = InitBook(sizeof(category_info), 8); | ||||
| 
 | ||||
|         quote_info QuoteInfo = { }; | ||||
| 
 | ||||
|         bool HasQuote = FALSE; | ||||
|         bool HasReference = FALSE; | ||||
| 
 | ||||
|         RewindBuffer(&IndexBuffers->Master); | ||||
|         RewindBuffer(&IndexBuffers->Header); | ||||
|         RewindBuffer(&IndexBuffers->Class); | ||||
|         RewindBuffer(&IndexBuffers->Data); | ||||
|         RewindBuffer(&IndexBuffers->Text); | ||||
|         RewindBuffer(&IndexBuffers->CategoryIcons); | ||||
| 
 | ||||
| 
 | ||||
|         CopyStringToBuffer(&IndexBuffers->Header, | ||||
|                 "                <div data-timestamp=\"%.3f\"", | ||||
|                 TimecodeToDottedSeconds(Timecode)); | ||||
| 
 | ||||
|         CopyStringToBuffer(&IndexBuffers->Class, | ||||
|                 " class=\"marker"); | ||||
| 
 | ||||
|         speaker *Speaker = GetSpeaker(&Speakers->Speakers, Author); | ||||
|         if(!IsCategorisedAFK(Timestamp)) | ||||
|         { | ||||
|             // NOTE(matt): I reckon it's fair to only cite the speaker when there are a multiple of them
 | ||||
|             if(Speakers->Speakers.ItemCount > 1 && Speaker && !IsCategorisedAuthored(Timestamp)) | ||||
|             { | ||||
|                 string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Speaker->Person->Abbreviations[Speakers->AbbrevScheme]; | ||||
| 
 | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                         "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\"", | ||||
|                         Speaker->Colour.Hue, | ||||
|                         Speaker->Colour.Saturation); | ||||
| 
 | ||||
|                 if(Speaker->Seen) | ||||
|                 { | ||||
|                     CopyStringToBuffer(&IndexBuffers->Text, " title=\""); | ||||
|                     CopyStringToBufferHTMLSafe(&IndexBuffers->Text, Speaker->Person->Name); | ||||
|                     CopyStringToBuffer(&IndexBuffers->Text, "\""); | ||||
|                 } | ||||
| 
 | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                         ">%.*s</span>: ", (int)DisplayName.Length, DisplayName.Base); | ||||
| 
 | ||||
|                 Speaker->Seen = TRUE; | ||||
|             } | ||||
|             else if(Author.Length > 0) | ||||
|             { | ||||
|                 if(!*HasFilterMenu) | ||||
|                 { | ||||
|                     *HasFilterMenu = TRUE; | ||||
|                 } | ||||
|                 InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored"), NULL); | ||||
|                 hsl_colour AuthorColour; | ||||
|                 StringToColourHash(&AuthorColour, Author); | ||||
|                 // TODO(matt): That EDITION_NETWORK site database API-polling stuff
 | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                         "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</span> ", | ||||
|                         AuthorColour.Hue, AuthorColour.Saturation, | ||||
|                         (int)Author.Length, Author.Base); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         char *InPtr = Timestamp->text; | ||||
| 
 | ||||
|         int MarkerIndex = 0, RefIndex = 0; | ||||
|         while(*InPtr || RefIndex < Timestamp->reference_count) | ||||
|         { | ||||
|             if(MarkerIndex < Timestamp->marker_count && | ||||
|                     InPtr - Timestamp->text == Timestamp->markers[MarkerIndex].offset) | ||||
|             { | ||||
|                 char *Readable = Timestamp->markers[MarkerIndex].parameter | ||||
|                     ? Timestamp->markers[MarkerIndex].parameter | ||||
|                     : Timestamp->markers[MarkerIndex].marker; | ||||
|                 HMML_MarkerType Type = Timestamp->markers[MarkerIndex].type; | ||||
|                 if(Type == HMML_CATEGORY) | ||||
|     while(*MarkerIndex < Timestamp->marker_count) | ||||
|     { | ||||
|         hsl_colour TopicColour = {}; | ||||
|                     Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); | ||||
|         Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[*MarkerIndex].marker), &TopicColour); | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|             if(!*HasFilterMenu) | ||||
|             { | ||||
|                 *HasFilterMenu = TRUE; | ||||
|             } | ||||
|                         InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); | ||||
|             InsertCategory(Topics, LocalTopics, Media, LocalMedia, Wrap0(Timestamp->markers[*MarkerIndex].marker), &TopicColour); | ||||
|             ++*MarkerIndex; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if(Result == RC_SUCCESS) | ||||
|     { | ||||
|         if(LocalTopics->ItemCount == 0) | ||||
|         { | ||||
|             hsl_colour TopicColour = {}; | ||||
|             Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour); | ||||
|             if(Result == RC_SUCCESS) | ||||
|             { | ||||
|                 InsertCategory(Topics, LocalTopics, Media, LocalMedia, Wrap0("nullTopic"), &TopicColour); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|             if(LocalMedia->ItemCount == 0) | ||||
|             { | ||||
|                 InsertCategory(Topics, LocalTopics, Media, LocalMedia, DefaultMedium->ID, NULL); | ||||
|             } | ||||
| 
 | ||||
|             BuildTimestampClass(&IndexBuffers->Class, LocalTopics, LocalMedia, DefaultMedium->ID); | ||||
|             CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Class, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|             if(HasQuote || HasReference) | ||||
|             { | ||||
|                 CopyStringToBuffer(&IndexBuffers->Data, "\""); | ||||
|                 CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Data, 0, PAGE_PLAYER); | ||||
|             } | ||||
|             CopyStringToBuffer(&IndexBuffers->Header, ">\n"); | ||||
| 
 | ||||
|             CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Header, 0, PAGE_PLAYER); | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, | ||||
|                     "                    <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|             CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|             CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|             if(LocalTopics->ItemCount > 0) | ||||
|             { | ||||
|                 BuildCategoryIcons(SearchEntry, &IndexBuffers->Master, LocalTopics, LocalMedia, DefaultMedium->ID, RequiresCineraJS, Timestamp); | ||||
|             } | ||||
| 
 | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|                     "                    <div class=\"progress faded\">\n" | ||||
|                     "                        <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|             CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|             CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|             if(LocalTopics->ItemCount > 0) | ||||
|             { | ||||
|                 BuildCategoryIcons(0, &IndexBuffers->Master, LocalTopics, LocalMedia, DefaultMedium->ID, RequiresCineraJS, Timestamp); | ||||
|             } | ||||
| 
 | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|                     "                    </div>\n" | ||||
|                     "                    <div class=\"progress main\">\n" | ||||
|                     "                        <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|             CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|             CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|             CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|             if(LocalTopics->ItemCount > 0) | ||||
|             { | ||||
|                 BuildCategoryIcons(0, &IndexBuffers->Master, LocalTopics, LocalMedia, DefaultMedium->ID, RequiresCineraJS, Timestamp); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return Result; | ||||
| } | ||||
| 
 | ||||
| rc | ||||
| ProcessTimestampQuoting(buffer *SearchEntry, string Filepath, memory_book *Strings, | ||||
|         menu_buffers *MenuBuffers, index_buffers *IndexBuffers, | ||||
|         speakers *Speakers, speaker *Speaker, string Author, | ||||
|         bool *HasQuoteMenu, int *QuoteIdentifier, | ||||
|         bool *HasQuote, bool HasReference, | ||||
|         HMML_Timestamp *Timestamp, v4 Timecode) | ||||
| { | ||||
|     rc Result = RC_SUCCESS; | ||||
|     if(Timestamp->quote.present) | ||||
|     { | ||||
|         if(!*HasQuoteMenu) | ||||
|         { | ||||
|             CopyStringToBuffer(&MenuBuffers->Quote, | ||||
|                     "            <div class=\"menu quotes\">\n" | ||||
|                     "                <span>Quotes ▼</span>\n" | ||||
|                     "                <div class=\"refs quotes_container\">\n"); | ||||
| 
 | ||||
|             *HasQuoteMenu = TRUE; | ||||
|         } | ||||
| 
 | ||||
|         if(!HasReference) | ||||
|         { | ||||
|             CopyStringToBuffer(&IndexBuffers->Data, " data-ref=\"&#%d;", *QuoteIdentifier); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             CopyStringToBuffer(&IndexBuffers->Data, ",&#%d;", *QuoteIdentifier); | ||||
|         } | ||||
| 
 | ||||
|         *HasQuote = TRUE; | ||||
| 
 | ||||
|         bool ShouldFetchQuotes = FALSE; | ||||
|         if(Config->CacheDir.Length == 0 || time(0) - LastQuoteFetch > 60*60) | ||||
|         { | ||||
|             ShouldFetchQuotes = TRUE; | ||||
|         } | ||||
| 
 | ||||
|         if(!Speaker && Speakers->Speakers.ItemCount > 0) | ||||
|         { | ||||
|             Speaker = GetPlaceInBook(&Speakers->Speakers, 0); | ||||
|         } | ||||
|         string QuoteUsername; | ||||
|         if(Timestamp->quote.author) | ||||
|         { | ||||
|             QuoteUsername = Wrap0(Timestamp->quote.author); | ||||
|         } | ||||
|         else if(Speaker) | ||||
|         { | ||||
|             QuoteUsername = Speaker->Person->QuoteUsername; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             QuoteUsername = Author; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         quote_info QuoteInfo = { }; | ||||
|         /*      */ MEM_TEST_MID(); | ||||
|         /* +MEM */ Result = BuildQuote(Strings, &QuoteInfo, | ||||
|                 QuoteUsername, Timestamp->quote.id, ShouldFetchQuotes); | ||||
|         /*      */ MEM_TEST_MID(); | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|             CopyStringToBuffer(&MenuBuffers->Quote, | ||||
|                     "                    <a target=\"_blank\" data-id=\"&#%d;\" class=\"ref\" href=\"https://dev.abaines.me.uk/quotes/%.*s/%d\">\n" | ||||
|                     "                        <span>\n" | ||||
|                     "                            <span class=\"ref_content\">\n" | ||||
|                     "                                <div class=\"source\">Quote %d</div>\n" | ||||
|                     "                                <div class=\"ref_title\">", | ||||
|                     *QuoteIdentifier, | ||||
|                     (int)QuoteUsername.Length, QuoteUsername.Base, | ||||
|                     Timestamp->quote.id, | ||||
|                     Timestamp->quote.id); | ||||
| 
 | ||||
|             CopyStringToBufferHTMLSafe(&MenuBuffers->Quote, QuoteInfo.Text); | ||||
| 
 | ||||
|             string DateString = UnixTimeToDateString(Strings, QuoteInfo.Date); | ||||
|             CopyStringToBuffer(&MenuBuffers->Quote, "</div>\n" | ||||
|                     "                                <div class=\"quote_byline\">—%.*s, %.*s</div>\n" | ||||
|                     "                            </span>\n" | ||||
|                     "                            <div class=\"ref_indices\">\n" | ||||
|                     "                                <span data-timestamp=\"%.3f\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">", | ||||
|                     (int)QuoteUsername.Length, QuoteUsername.Base, | ||||
|                     (int)DateString.Length, DateString.Base, // TODO(matt): Convert Unixtime to date-string
 | ||||
|                     TimecodeToDottedSeconds(Timecode), | ||||
|                     *QuoteIdentifier); | ||||
|             CopyTimecodeToBuffer(&MenuBuffers->Quote, Timecode); | ||||
|             CopyStringToBuffer(&MenuBuffers->Quote, "</span></span>\n" | ||||
|                     "                            </div>\n" | ||||
|                     "                        </span>\n" | ||||
|                     "                    </a>\n"); | ||||
|             if(!Timestamp->text[0]) | ||||
|             { | ||||
|                 // .index
 | ||||
|                 CopyStringToBuffer(SearchEntry, "\u201C"); | ||||
|                 CopyStringToBufferNoFormat(SearchEntry, QuoteInfo.Text); | ||||
|                 CopyStringToBuffer(SearchEntry, "\u201D"); | ||||
| 
 | ||||
|                 // .html
 | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, "“"); | ||||
|                 CopyStringToBufferHTMLSafe(&IndexBuffers->Text, QuoteInfo.Text); | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, "”"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 // NOTE(matt): Here accounting for ProcessTimestampText() not writing to .index
 | ||||
| 
 | ||||
|                 // .index
 | ||||
|                 CopyStringToBufferNoFormat(SearchEntry, Wrap0(Timestamp->text)); | ||||
|             } | ||||
|             CopyStringToBuffer(&IndexBuffers->Text, "<sup>&#%d;</sup>", *QuoteIdentifier); | ||||
|             ++*QuoteIdentifier; | ||||
|         } | ||||
|         else if(Result == RC_UNFOUND) | ||||
|         { | ||||
|             IndexingQuoteError(&Filepath, Timestamp->line, QuoteUsername, Timestamp->quote.id); | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         // .index
 | ||||
|         CopyStringToBufferNoFormat(SearchEntry, Wrap0(Timestamp->text)); | ||||
|     } | ||||
|     return Result; | ||||
| } | ||||
| 
 | ||||
| rc | ||||
| ProcessTimestampText(neighbourhood *N, string Filepath, memory_book *Strings, | ||||
|         menu_buffers *MenuBuffers, index_buffers *IndexBuffers, | ||||
|         _memory_book(ref_info) *ReferencesArray, | ||||
|         bool *HasReferenceMenu, bool *HasFilterMenu, | ||||
|         int *RefIdentifier, | ||||
|         _memory_book(category_info) *Topics, _memory_book(category_info) *LocalTopics, | ||||
|         _memory_book(category_info) *Media, _memory_book(category_info) *LocalMedia, | ||||
|         HMML_Timestamp *Timestamp, | ||||
|         bool *HasReference, v4 Timecode, int *MarkerIndex) | ||||
| { | ||||
|     // NOTE(matt):  While this function processes the Timestamp->text for the benefit of both the .html and .index sides, it
 | ||||
|     //              only writes out .html, meaning we need to write the .index file elsewhere in ProcessTimestampQuoting().
 | ||||
|     rc Result = RC_SUCCESS; | ||||
| 
 | ||||
|     int RefIndex = 0; | ||||
|     char *InPtr = Timestamp->text; | ||||
|     while(*InPtr || RefIndex < Timestamp->reference_count) | ||||
|     { | ||||
|         if(*MarkerIndex < Timestamp->marker_count && | ||||
|                 InPtr - Timestamp->text == Timestamp->markers[*MarkerIndex].offset) | ||||
|         { | ||||
|             char *Readable = Timestamp->markers[*MarkerIndex].parameter | ||||
|                 ? Timestamp->markers[*MarkerIndex].parameter | ||||
|                 : Timestamp->markers[*MarkerIndex].marker; | ||||
|             HMML_MarkerType Type = Timestamp->markers[*MarkerIndex].type; | ||||
|             if(Type == HMML_CATEGORY) | ||||
|             { | ||||
|                 hsl_colour TopicColour = {}; | ||||
|                 Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[*MarkerIndex].marker), &TopicColour); | ||||
|                 if(Result == RC_SUCCESS) | ||||
|                 { | ||||
|                     if(!*HasFilterMenu) | ||||
|                     { | ||||
|                         *HasFilterMenu = TRUE; | ||||
|                     } | ||||
|                     InsertCategory(Topics, LocalTopics, Media, LocalMedia, Wrap0(Timestamp->markers[*MarkerIndex].marker), &TopicColour); | ||||
|                     CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr); | ||||
|                 } | ||||
|                 else | ||||
|  | @ -10985,16 +11221,16 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m | |||
|             { | ||||
|                 // TODO(matt): That EDITION_NETWORK site database API-polling stuff
 | ||||
|                 hsl_colour Colour; | ||||
|                     StringToColourHash(&Colour, Wrap0(Timestamp->markers[MarkerIndex].marker)); | ||||
|                 StringToColourHash(&Colour, Wrap0(Timestamp->markers[*MarkerIndex].marker)); | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                         "<span class=\"%s\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</span>", | ||||
|                             Timestamp->markers[MarkerIndex].type == HMML_MEMBER ? "member" : "project", | ||||
|                         Timestamp->markers[*MarkerIndex].type == HMML_MEMBER ? "member" : "project", | ||||
|                         Colour.Hue, Colour.Saturation, | ||||
|                         (int)StringLength(Readable), InPtr); | ||||
|             } | ||||
| 
 | ||||
|             InPtr += StringLength(Readable); | ||||
|                 ++MarkerIndex; | ||||
|             ++*MarkerIndex; | ||||
|         } | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|  | @ -11035,7 +11271,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m | |||
|                                 IndexingError(Filepath, Timestamp->line, S_ERROR, | ||||
|                                         "Cannot process new combination of reference info\n" | ||||
|                                         "\n" | ||||
|                                             "Either tweak your timestamp, or contact miblodelcarpio@gmail.com\n" | ||||
|                                         "Either tweak your timestamp, or write to contact@miblo.net\n" | ||||
|                                         "mentioning the ref node you want to write and how you want it to\n" | ||||
|                                         "appear in the references menu", | ||||
|                                         0); | ||||
|  | @ -11045,8 +11281,8 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m | |||
|                     break; // NOTE(matt): Out of the while()
 | ||||
|                 } | ||||
| 
 | ||||
|                     CopyStringToBuffer(&IndexBuffers->Data, "%s%s", !HasReference ? " data-ref=\"" : "," , This->ID); | ||||
|                     HasReference = TRUE; | ||||
|                 CopyStringToBuffer(&IndexBuffers->Data, "%s%s", !*HasReference ? " data-ref=\"" : "," , This->ID); | ||||
|                 *HasReference = TRUE; | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, "<sup>%s%d</sup>", | ||||
|                         RefIndex > 0 && Timestamp->references[RefIndex].offset == Timestamp->references[RefIndex-1].offset ? "," : "", | ||||
|                         *RefIdentifier); | ||||
|  | @ -11089,217 +11325,171 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|             if(Timestamp->quote.present) | ||||
|             { | ||||
|                 if(!*HasQuoteMenu) | ||||
|                 { | ||||
|                     CopyStringToBuffer(&MenuBuffers->Quote, | ||||
|                             "            <div class=\"menu quotes\">\n" | ||||
|                             "                <span>Quotes ▼</span>\n" | ||||
|                             "                <div class=\"refs quotes_container\">\n"); | ||||
|     return Result; | ||||
| } | ||||
| 
 | ||||
|                     *HasQuoteMenu = TRUE; | ||||
| speaker * | ||||
| ProcessTimestampAuthoring(buffer *SearchEntry, index_buffers *IndexBuffers, | ||||
|         speakers *Speakers, string Author, bool *HasFilterMenu, | ||||
|         _memory_book(category_info) *Topics, _memory_book(category_info) *LocalTopics, | ||||
|         _memory_book(category_info) *Media, _memory_book(category_info) *LocalMedia, | ||||
|         HMML_Timestamp *Timestamp) | ||||
| { | ||||
|     speaker *Speaker = GetSpeaker(&Speakers->Speakers, Author); | ||||
| 
 | ||||
|     if(!IsCategorisedAFK(Timestamp)) | ||||
|     { | ||||
|         // NOTE(matt): I reckon it's fair to only cite the speaker when there are a multiple of them
 | ||||
|         if(Speakers->Speakers.ItemCount > 1 && Speaker && !IsCategorisedAuthored(Timestamp)) | ||||
|         { | ||||
|             // .index
 | ||||
|             CopyStringToBufferNoFormat(SearchEntry, Speaker->Person->Name); | ||||
|             CopyStringToBuffer(SearchEntry, ": "); | ||||
| 
 | ||||
|             // .html
 | ||||
|             string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Speaker->Person->Abbreviations[Speakers->AbbrevScheme]; | ||||
| 
 | ||||
|             CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                     "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\"", | ||||
|                     Speaker->Colour.Hue, | ||||
|                     Speaker->Colour.Saturation); | ||||
| 
 | ||||
|             if(Speaker->Seen) | ||||
|             { | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, " title=\""); | ||||
|                 CopyStringToBufferHTMLSafe(&IndexBuffers->Text, Speaker->Person->Name); | ||||
|                 CopyStringToBuffer(&IndexBuffers->Text, "\""); | ||||
|             } | ||||
| 
 | ||||
|                 if(!HasReference) | ||||
|                 { | ||||
|                     CopyStringToBuffer(&IndexBuffers->Data, " data-ref=\"&#%d;", *QuoteIdentifier); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     CopyStringToBuffer(&IndexBuffers->Data, ",&#%d;", *QuoteIdentifier); | ||||
|                 } | ||||
|             CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                     ">%.*s</span>: ", (int)DisplayName.Length, DisplayName.Base); | ||||
| 
 | ||||
|                 HasQuote = TRUE; | ||||
|             Speaker->Seen = TRUE; | ||||
|         } | ||||
|         else if(Author.Length > 0) | ||||
|         { | ||||
|             // .index
 | ||||
|             CopyStringToBuffer(SearchEntry, "@"); | ||||
|             CopyStringToBufferNoFormat(SearchEntry, Author); | ||||
|             CopyStringToBuffer(SearchEntry, ": "); | ||||
| 
 | ||||
|                 bool ShouldFetchQuotes = FALSE; | ||||
|                 if(Config->CacheDir.Length == 0 || time(0) - LastQuoteFetch > 60*60) | ||||
|                 { | ||||
|                     ShouldFetchQuotes = TRUE; | ||||
|                 } | ||||
| 
 | ||||
|                 if(!Speaker && Speakers->Speakers.ItemCount > 0) | ||||
|                 { | ||||
|                     Speaker = GetPlaceInBook(&Speakers->Speakers, 0); | ||||
|                 } | ||||
|                 string QuoteUsername; | ||||
|                 if(Timestamp->quote.author) | ||||
|                 { | ||||
|                     QuoteUsername = Wrap0(Timestamp->quote.author); | ||||
|                 } | ||||
|                 else if(Speaker) | ||||
|                 { | ||||
|                     QuoteUsername = Speaker->Person->QuoteUsername; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     QuoteUsername = Author; | ||||
|                 } | ||||
| 
 | ||||
|                 /*      */ MEM_TEST_MID(); | ||||
|                 /* +MEM */ Result = BuildQuote(Strings, &QuoteInfo, | ||||
|                         QuoteUsername, Timestamp->quote.id, ShouldFetchQuotes); | ||||
|                 /*      */ MEM_TEST_MID(); | ||||
|                 if(Result == RC_SUCCESS) | ||||
|                 { | ||||
|                     CopyStringToBuffer(&MenuBuffers->Quote, | ||||
|                             "                    <a target=\"_blank\" data-id=\"&#%d;\" class=\"ref\" href=\"https://dev.abaines.me.uk/quotes/%.*s/%d\">\n" | ||||
|                             "                        <span>\n" | ||||
|                             "                            <span class=\"ref_content\">\n" | ||||
|                             "                                <div class=\"source\">Quote %d</div>\n" | ||||
|                             "                                <div class=\"ref_title\">", | ||||
|                             *QuoteIdentifier, | ||||
|                             (int)QuoteUsername.Length, QuoteUsername.Base, | ||||
|                             Timestamp->quote.id, | ||||
|                             Timestamp->quote.id); | ||||
| 
 | ||||
|                     CopyStringToBufferHTMLSafe(&MenuBuffers->Quote, QuoteInfo.Text); | ||||
| 
 | ||||
|                     string DateString = UnixTimeToDateString(Strings, QuoteInfo.Date); | ||||
|                     CopyStringToBuffer(&MenuBuffers->Quote, "</div>\n" | ||||
|                             "                                <div class=\"quote_byline\">—%.*s, %.*s</div>\n" | ||||
|                             "                            </span>\n" | ||||
|                             "                            <div class=\"ref_indices\">\n" | ||||
|                             "                                <span data-timestamp=\"%.3f\" class=\"timecode\"><span class=\"ref_index\">[&#%d;]</span><span class=\"time\">", | ||||
|                             (int)QuoteUsername.Length, QuoteUsername.Base, | ||||
|                             (int)DateString.Length, DateString.Base, // TODO(matt): Convert Unixtime to date-string
 | ||||
|                             TimecodeToDottedSeconds(Timecode), | ||||
|                             *QuoteIdentifier); | ||||
|                     CopyTimecodeToBuffer(&MenuBuffers->Quote, Timecode); | ||||
|                     CopyStringToBuffer(&MenuBuffers->Quote, "</span></span>\n" | ||||
|                             "                            </div>\n" | ||||
|                             "                        </span>\n" | ||||
|                             "                    </a>\n"); | ||||
|                     if(!Timestamp->text[0]) | ||||
|                     { | ||||
|                         CopyStringToBuffer(&IndexBuffers->Text, "“"); | ||||
|                         CopyStringToBufferHTMLSafe(&IndexBuffers->Text, QuoteInfo.Text); | ||||
|                         CopyStringToBuffer(&IndexBuffers->Text, "”"); | ||||
|                     } | ||||
|                     CopyStringToBuffer(&IndexBuffers->Text, "<sup>&#%d;</sup>", *QuoteIdentifier); | ||||
|                     ++*QuoteIdentifier; | ||||
|                 } | ||||
|                 else if(Result == RC_UNFOUND) | ||||
|                 { | ||||
|                     IndexingQuoteError(&Filepath, Timestamp->line, QuoteUsername, Timestamp->quote.id); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if(Result == RC_SUCCESS) | ||||
|             { | ||||
|                 CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%.3f\": \"", TimecodeToDottedSeconds(Timecode)); | ||||
|                 if(Timestamp->quote.present && !Timestamp->text[0]) | ||||
|                 { | ||||
|                     CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201C"); | ||||
|                     CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, QuoteInfo.Text); | ||||
|                     CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201D"); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0(Timestamp->text)); | ||||
|                 } | ||||
|                 CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n"); | ||||
| 
 | ||||
|                 while(MarkerIndex < Timestamp->marker_count) | ||||
|                 { | ||||
|                     hsl_colour TopicColour = {}; | ||||
|                     Result = GenerateTopicColours(N, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); | ||||
|                     if(Result == RC_SUCCESS) | ||||
|                     { | ||||
|             // .html
 | ||||
|             if(!*HasFilterMenu) | ||||
|             { | ||||
|                 *HasFilterMenu = TRUE; | ||||
|             } | ||||
|                         InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour); | ||||
|                         ++MarkerIndex; | ||||
|             InsertCategory(Topics, LocalTopics, Media, LocalMedia, Wrap0("authored"), NULL); | ||||
|             hsl_colour AuthorColour; | ||||
|             StringToColourHash(&AuthorColour, Author); | ||||
|             // TODO(matt): That EDITION_NETWORK site database API-polling stuff
 | ||||
|             CopyStringToBuffer(&IndexBuffers->Text, | ||||
|                     "<span class=\"author\" data-hue=\"%d\" data-saturation=\"%d%%\">@%.*s</span> ", | ||||
|                     AuthorColour.Hue, AuthorColour.Saturation, | ||||
|                     (int)Author.Length, Author.Base); | ||||
|         } | ||||
|                     else | ||||
|     } | ||||
| 
 | ||||
|     return Speaker; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| ProcessTimestampTiming(buffer *SearchEntry, index_buffers *IndexBuffers, v4 Timecode) | ||||
| { | ||||
|     float Time = TimecodeToDottedSeconds(Timecode); | ||||
| 
 | ||||
|     // .index
 | ||||
|     CopyStringToBuffer(SearchEntry, "\"%.3f\": \"", Time); | ||||
| 
 | ||||
|     // .html
 | ||||
|     CopyStringToBuffer(&IndexBuffers->Header, | ||||
|             "                <div data-timestamp=\"%.3f\"", Time); | ||||
| } | ||||
| 
 | ||||
| rc | ||||
| ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, memory_book *Strings, | ||||
|         menu_buffers *MenuBuffers, index_buffers *IndexBuffers, player_buffers *PlayerBuffers, | ||||
|         medium *DefaultMedium, speakers *Speakers, string Author, | ||||
|         _memory_book(ref_info) *ReferencesArray, | ||||
|         bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS, | ||||
|         int *QuoteIdentifier, int *RefIdentifier, | ||||
|         _memory_book(category_info) *Topics, _memory_book(category_info) *Media, | ||||
|         HMML_Timestamp *Timestamp, v4 *PreviousTimecode) | ||||
| { | ||||
|     MEM_TEST_TOP(); | ||||
|     // TODO(matt): Introduce and use a SystemError() in here
 | ||||
|     rc Result = RC_SUCCESS; | ||||
| 
 | ||||
|     v4 Timecode = V4(Timestamp->h, Timestamp->m, Timestamp->s, Timestamp->ms); | ||||
|     if(TimecodeToDottedSeconds(Timecode) >= TimecodeToDottedSeconds(*PreviousTimecode)) | ||||
|     { | ||||
|                         break; | ||||
|                     } | ||||
|         *PreviousTimecode = Timecode; | ||||
| 
 | ||||
|         memory_book LocalTopics = InitBook(sizeof(category_info), 8); | ||||
|         memory_book LocalMedia = InitBook(sizeof(category_info), 8); | ||||
| 
 | ||||
|         bool HasQuote = FALSE; | ||||
|         bool HasReference = FALSE; | ||||
| 
 | ||||
|         RewindBuffer(&IndexBuffers->Master); | ||||
|         RewindBuffer(&IndexBuffers->Header); | ||||
|         RewindBuffer(&IndexBuffers->Class); | ||||
|         RewindBuffer(&IndexBuffers->Data); | ||||
|         RewindBuffer(&IndexBuffers->Text); | ||||
|         RewindBuffer(&IndexBuffers->CategoryIcons); | ||||
| 
 | ||||
| 
 | ||||
|         ProcessTimestampTiming(&CollationBuffers->SearchEntry, IndexBuffers, Timecode); | ||||
| 
 | ||||
|         CopyStringToBuffer(&IndexBuffers->Class, | ||||
|                 " class=\"marker"); | ||||
| 
 | ||||
|         speaker *Speaker = ProcessTimestampAuthoring(&CollationBuffers->SearchEntry, IndexBuffers, | ||||
|                 Speakers, Author, HasFilterMenu, | ||||
|                 Topics, &LocalTopics, | ||||
|                 Media, &LocalMedia, | ||||
|                 Timestamp); | ||||
| 
 | ||||
|         int MarkerIndex = 0; | ||||
|         Result = ProcessTimestampText(N, Filepath, Strings, | ||||
|                 MenuBuffers, IndexBuffers, | ||||
|                 ReferencesArray, | ||||
|                 HasReferenceMenu, HasFilterMenu, | ||||
|                 RefIdentifier, | ||||
|                 Topics, &LocalTopics, | ||||
|                 Media, &LocalMedia, | ||||
|                 Timestamp, | ||||
|                 &HasReference, Timecode, &MarkerIndex); | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|             Result = ProcessTimestampQuoting(&CollationBuffers->SearchEntry, Filepath, Strings, | ||||
|                     MenuBuffers, IndexBuffers, | ||||
|                     Speakers, Speaker, Author, | ||||
|                     HasQuoteMenu, QuoteIdentifier, | ||||
|                     &HasQuote, HasReference, | ||||
|                     Timestamp, Timecode); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|                     if(LocalTopics.ItemCount == 0) | ||||
|                     { | ||||
|                         hsl_colour TopicColour = {}; | ||||
|                         Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour); | ||||
|                         if(Result == RC_SUCCESS) | ||||
|                         { | ||||
|                             InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"), &TopicColour); | ||||
|                         } | ||||
|             Result = ProcessTimestampCategories(N, | ||||
|                 &CollationBuffers->SearchEntry, IndexBuffers, | ||||
|                 DefaultMedium, | ||||
|                 HasFilterMenu, | ||||
|                 RequiresCineraJS, | ||||
|                 Topics, &LocalTopics, | ||||
|                 Media, &LocalMedia, | ||||
|                 Timestamp, Timecode, | ||||
|                 &MarkerIndex, HasQuote, HasReference); | ||||
|         } | ||||
| 
 | ||||
|         if(Result == RC_SUCCESS) | ||||
|         { | ||||
|                         if(LocalMedia.ItemCount == 0) | ||||
|                         { | ||||
|                             InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID, NULL); | ||||
|                         } | ||||
| 
 | ||||
|                         BuildTimestampClass(&IndexBuffers->Class, &LocalTopics, &LocalMedia, DefaultMedium->ID); | ||||
|                         CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Class, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|                         if(HasQuote || HasReference) | ||||
|                         { | ||||
|                             CopyStringToBuffer(&IndexBuffers->Data, "\""); | ||||
|                             CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Data, 0, PAGE_PLAYER); | ||||
|                         } | ||||
|                         CopyStringToBuffer(&IndexBuffers->Header, ">\n"); | ||||
| 
 | ||||
|                         CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Header, 0, PAGE_PLAYER); | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, | ||||
|                                 "                    <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|                         CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|                         CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|                         if(LocalTopics.ItemCount > 0) | ||||
|                         { | ||||
|                             BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); | ||||
|                         } | ||||
| 
 | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|                                 "                    <div class=\"progress faded\">\n" | ||||
|                                 "                        <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|                         CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|                         CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|                         if(LocalTopics.ItemCount > 0) | ||||
|                         { | ||||
|                             BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); | ||||
|                         } | ||||
| 
 | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|                                 "                    </div>\n" | ||||
|                                 "                    <div class=\"progress main\">\n" | ||||
|                                 "                        <div class=\"cineraContent\"><span class=\"timecode\">"); | ||||
|                         CopyTimecodeToBuffer(&IndexBuffers->Master, Timecode); | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</span>"); | ||||
| 
 | ||||
|                         CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); | ||||
| 
 | ||||
|                         if(LocalTopics.ItemCount > 0) | ||||
|                         { | ||||
|                             //CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->CategoryIcons, PAGE_PLAYER);
 | ||||
|                             BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); | ||||
|                         } | ||||
| 
 | ||||
|                         CopyStringToBuffer(&IndexBuffers->Master, "</div>\n" | ||||
|                                 "                    </div>\n" | ||||
|                                 "                </div>\n"); | ||||
|             ProcessTimestampCoda(&CollationBuffers->SearchEntry, IndexBuffers); | ||||
| 
 | ||||
|             CopyLandmarkedBuffer(&PlayerBuffers->Main, &IndexBuffers->Master, 0, PAGE_PLAYER); | ||||
|         } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         FreeBook(&LocalTopics); | ||||
|         FreeBook(&LocalMedia); | ||||
|  | @ -18318,7 +18508,7 @@ main(int ArgC, char **Args) | |||
|         if(ClaimBuffer(&CollationBuffers.Player, BID_COLLATION_BUFFERS_PLAYER, Kilobytes(552)) == RC_ARENA_FULL) { Exit(); }; | ||||
| 
 | ||||
|         if(ClaimBuffer(&CollationBuffers.IncludesSearch, BID_COLLATION_BUFFERS_INCLUDES_SEARCH, Kilobytes(4)) == RC_ARENA_FULL) { Exit(); }; | ||||
|         if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(32)) == RC_ARENA_FULL) { Exit(); }; | ||||
|         if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(64)) == RC_ARENA_FULL) { Exit(); }; | ||||
| 
 | ||||
|         CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue