cinera.c: Make @author and :categories searchable
This is a stop-gap gap solution pending CINERA_DB_VERSION 6. It simply augments the .index files, prepending "@author: " to timestamps bearing an author – e.g. audience questions – and appending " [:each :category]" to those bearing topic or medium categorisation.
This commit is contained in:
parent
a67a5ee34b
commit
e5ffec7a55
815
cinera/cinera.c
815
cinera/cinera.c
|
@ -23,7 +23,7 @@ typedef struct
|
||||||
version CINERA_APP_VERSION = {
|
version CINERA_APP_VERSION = {
|
||||||
.Major = 0,
|
.Major = 0,
|
||||||
.Minor = 10,
|
.Minor = 10,
|
||||||
.Patch = 30
|
.Patch = 31
|
||||||
};
|
};
|
||||||
|
|
||||||
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
|
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
|
||||||
|
@ -8350,16 +8350,70 @@ BuildTimestampClass(buffer *TimestampClass, _memory_book(category_info) *LocalTo
|
||||||
CopyStringToBuffer(TimestampClass, "\"");
|
CopyStringToBuffer(TimestampClass, "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
bool
|
||||||
BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *LocalMedia, string DefaultMedium, bool *RequiresCineraJS)
|
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 *FirstLocalTopic = GetPlaceInBook(LocalTopics, 0);
|
||||||
category_info *FirstLocalMedium = GetPlaceInBook(LocalMedia, 0);
|
category_info *FirstLocalMedium = GetPlaceInBook(LocalMedia, 0);
|
||||||
if(!(LocalTopics->ItemCount == 1 && StringsMatch(FirstLocalTopic->Marker, Wrap0("nullTopic"))
|
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\">");
|
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)
|
for(int i = 0; i < LocalTopics->ItemCount; ++i)
|
||||||
{
|
{
|
||||||
category_info *This = GetPlaceInBook(LocalTopics, i);
|
category_info *This = GetPlaceInBook(LocalTopics, i);
|
||||||
|
|
||||||
|
// .index
|
||||||
|
if(SearchEntry)
|
||||||
|
{
|
||||||
|
PushCategorySearchEntry(SearchEntry, Timestamp, This, Underway);
|
||||||
|
}
|
||||||
|
|
||||||
|
// .html
|
||||||
// NOTE(matt): Stack-string
|
// NOTE(matt): Stack-string
|
||||||
char SanitisedMarker[This->Marker.Length + 1];
|
char SanitisedMarker[This->Marker.Length + 1];
|
||||||
CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base);
|
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,
|
CopyStringToBuffer(CategoryIcons,
|
||||||
"></div>");
|
"></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)
|
for(int i = 0; i < LocalMedia->ItemCount; ++i)
|
||||||
{
|
{
|
||||||
category_info *This = GetPlaceInBook(LocalMedia, i);
|
category_info *This = GetPlaceInBook(LocalMedia, i);
|
||||||
|
|
||||||
|
// .index
|
||||||
|
if(SearchEntry)
|
||||||
|
{
|
||||||
|
PushCategorySearchEntry(SearchEntry, Timestamp, This, Underway);
|
||||||
|
}
|
||||||
|
|
||||||
|
// .html
|
||||||
// NOTE(matt): Stack-string
|
// NOTE(matt): Stack-string
|
||||||
char SanitisedMarker[This->Marker.Length + 1];
|
char SanitisedMarker[This->Marker.Length + 1];
|
||||||
CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base);
|
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>");
|
CopyStringToBuffer(CategoryIcons, "</div>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Underway = TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(CategoriesSpan)
|
if(Underway)
|
||||||
{
|
{
|
||||||
|
// .index
|
||||||
|
if(SearchEntry)
|
||||||
|
{
|
||||||
|
CopyStringToBuffer(SearchEntry, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
// .html
|
||||||
CopyStringToBuffer(CategoryIcons, "</span>");
|
CopyStringToBuffer(CategoryIcons, "</span>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9220,21 +9301,6 @@ StripSurroundingSlashes(char *String) // NOTE(matt): For relative paths
|
||||||
return Ptr;
|
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
|
string
|
||||||
StripPWDIndicators(string Path)
|
StripPWDIndicators(string Path)
|
||||||
{
|
{
|
||||||
|
@ -10862,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;
|
return Timecode.Hours == Hours && Timecode.Minutes == Minutes && Timecode.Seconds == Seconds && Timecode.Milliseconds == Milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
rc
|
void
|
||||||
ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, memory_book *Strings,
|
ProcessTimestampCoda(buffer *SearchEntry, index_buffers *IndexBuffers)
|
||||||
menu_buffers *MenuBuffers, index_buffers *IndexBuffers, player_buffers *PlayerBuffers,
|
{
|
||||||
medium *DefaultMedium, speakers *Speakers, string Author,
|
// .index
|
||||||
_memory_book(ref_info) *ReferencesArray,
|
CopyStringToBuffer(SearchEntry, "\"\n");
|
||||||
bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS,
|
|
||||||
int *QuoteIdentifier, int *RefIdentifier,
|
// .html
|
||||||
_memory_book(category_info) *Topics, _memory_book(category_info) *Media,
|
CopyStringToBuffer(&IndexBuffers->Master, "</div>\n"
|
||||||
HMML_Timestamp *Timestamp, v4 *PreviousTimecode)
|
" </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;
|
rc Result = RC_SUCCESS;
|
||||||
|
|
||||||
v4 Timecode = V4(Timestamp->h, Timestamp->m, Timestamp->s, Timestamp->ms);
|
while(*MarkerIndex < Timestamp->marker_count)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
hsl_colour TopicColour = {};
|
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(Result == RC_SUCCESS)
|
||||||
{
|
{
|
||||||
if(!*HasFilterMenu)
|
if(!*HasFilterMenu)
|
||||||
{
|
{
|
||||||
*HasFilterMenu = TRUE;
|
*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);
|
CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -10980,16 +11221,16 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
|
||||||
{
|
{
|
||||||
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
|
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
|
||||||
hsl_colour Colour;
|
hsl_colour Colour;
|
||||||
StringToColourHash(&Colour, Wrap0(Timestamp->markers[MarkerIndex].marker));
|
StringToColourHash(&Colour, Wrap0(Timestamp->markers[*MarkerIndex].marker));
|
||||||
CopyStringToBuffer(&IndexBuffers->Text,
|
CopyStringToBuffer(&IndexBuffers->Text,
|
||||||
"<span class=\"%s\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</span>",
|
"<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,
|
Colour.Hue, Colour.Saturation,
|
||||||
(int)StringLength(Readable), InPtr);
|
(int)StringLength(Readable), InPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
InPtr += StringLength(Readable);
|
InPtr += StringLength(Readable);
|
||||||
++MarkerIndex;
|
++*MarkerIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Result == RC_SUCCESS)
|
if(Result == RC_SUCCESS)
|
||||||
|
@ -11030,7 +11271,7 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
|
||||||
IndexingError(Filepath, Timestamp->line, S_ERROR,
|
IndexingError(Filepath, Timestamp->line, S_ERROR,
|
||||||
"Cannot process new combination of reference info\n"
|
"Cannot process new combination of reference info\n"
|
||||||
"\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"
|
"mentioning the ref node you want to write and how you want it to\n"
|
||||||
"appear in the references menu",
|
"appear in the references menu",
|
||||||
0);
|
0);
|
||||||
|
@ -11040,8 +11281,8 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
|
||||||
break; // NOTE(matt): Out of the while()
|
break; // NOTE(matt): Out of the while()
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyStringToBuffer(&IndexBuffers->Data, "%s%s", !HasReference ? " data-ref=\"" : "," , This->ID);
|
CopyStringToBuffer(&IndexBuffers->Data, "%s%s", !*HasReference ? " data-ref=\"" : "," , This->ID);
|
||||||
HasReference = TRUE;
|
*HasReference = TRUE;
|
||||||
CopyStringToBuffer(&IndexBuffers->Text, "<sup>%s%d</sup>",
|
CopyStringToBuffer(&IndexBuffers->Text, "<sup>%s%d</sup>",
|
||||||
RefIndex > 0 && Timestamp->references[RefIndex].offset == Timestamp->references[RefIndex-1].offset ? "," : "",
|
RefIndex > 0 && Timestamp->references[RefIndex].offset == Timestamp->references[RefIndex-1].offset ? "," : "",
|
||||||
*RefIdentifier);
|
*RefIdentifier);
|
||||||
|
@ -11084,217 +11325,171 @@ ProcessTimestamp(buffers *CollationBuffers, neighbourhood *N, string Filepath, m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Result == RC_SUCCESS)
|
return Result;
|
||||||
{
|
|
||||||
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)
|
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)
|
||||||
{
|
{
|
||||||
CopyStringToBuffer(&IndexBuffers->Data, " data-ref=\"&#%d;", *QuoteIdentifier);
|
speaker *Speaker = GetSpeaker(&Speakers->Speakers, Author);
|
||||||
}
|
|
||||||
else
|
if(!IsCategorisedAFK(Timestamp))
|
||||||
{
|
{
|
||||||
CopyStringToBuffer(&IndexBuffers->Data, ",&#%d;", *QuoteIdentifier);
|
// 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, "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
HasQuote = TRUE;
|
CopyStringToBuffer(&IndexBuffers->Text,
|
||||||
|
">%.*s</span>: ", (int)DisplayName.Length, DisplayName.Base);
|
||||||
|
|
||||||
bool ShouldFetchQuotes = FALSE;
|
Speaker->Seen = TRUE;
|
||||||
if(Config->CacheDir.Length == 0 || time(0) - LastQuoteFetch > 60*60)
|
|
||||||
{
|
|
||||||
ShouldFetchQuotes = TRUE;
|
|
||||||
}
|
}
|
||||||
|
else if(Author.Length > 0)
|
||||||
|
{
|
||||||
|
// .index
|
||||||
|
CopyStringToBuffer(SearchEntry, "@");
|
||||||
|
CopyStringToBufferNoFormat(SearchEntry, Author);
|
||||||
|
CopyStringToBuffer(SearchEntry, ": ");
|
||||||
|
|
||||||
if(!Speaker && Speakers->Speakers.ItemCount > 0)
|
// .html
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
if(!*HasFilterMenu)
|
if(!*HasFilterMenu)
|
||||||
{
|
{
|
||||||
*HasFilterMenu = TRUE;
|
*HasFilterMenu = TRUE;
|
||||||
}
|
}
|
||||||
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Timestamp->markers[MarkerIndex].marker), &TopicColour);
|
InsertCategory(Topics, LocalTopics, Media, LocalMedia, Wrap0("authored"), NULL);
|
||||||
++MarkerIndex;
|
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)
|
||||||
{
|
{
|
||||||
break;
|
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))
|
||||||
|
{
|
||||||
|
*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(Result == RC_SUCCESS)
|
||||||
{
|
{
|
||||||
if(LocalTopics.ItemCount == 0)
|
Result = ProcessTimestampCategories(N,
|
||||||
{
|
&CollationBuffers->SearchEntry, IndexBuffers,
|
||||||
hsl_colour TopicColour = {};
|
DefaultMedium,
|
||||||
Result = GenerateTopicColours(N, Wrap0("nullTopic"), &TopicColour);
|
HasFilterMenu,
|
||||||
if(Result == RC_SUCCESS)
|
RequiresCineraJS,
|
||||||
{
|
Topics, &LocalTopics,
|
||||||
InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic"), &TopicColour);
|
Media, &LocalMedia,
|
||||||
}
|
Timestamp, Timecode,
|
||||||
|
&MarkerIndex, HasQuote, HasReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Result == RC_SUCCESS)
|
if(Result == RC_SUCCESS)
|
||||||
{
|
{
|
||||||
if(LocalMedia.ItemCount == 0)
|
ProcessTimestampCoda(&CollationBuffers->SearchEntry, IndexBuffers);
|
||||||
{
|
|
||||||
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");
|
|
||||||
|
|
||||||
CopyLandmarkedBuffer(&PlayerBuffers->Main, &IndexBuffers->Master, 0, PAGE_PLAYER);
|
CopyLandmarkedBuffer(&PlayerBuffers->Main, &IndexBuffers->Master, 0, PAGE_PLAYER);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FreeBook(&LocalTopics);
|
FreeBook(&LocalTopics);
|
||||||
FreeBook(&LocalMedia);
|
FreeBook(&LocalMedia);
|
||||||
|
@ -18313,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.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.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()
|
CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue