cinera.c: Distinguish speakers from chat comments
It treats co-hosts and guests differently from chat commenters, styling and categorising annotations for them such that their contributions don't come under the "Chat comment" medium Also do some essentially cosmetic code compression of the marker cases and other things cinera_player_pre.js: Make the credits menu initially focus the host's person if they have no support, rather than the first credited person who has support
This commit is contained in:
parent
14afdc044d
commit
1efd808783
418
cinera/cinera.c
418
cinera/cinera.c
|
@ -14,7 +14,7 @@ typedef struct
|
||||||
version CINERA_APP_VERSION = {
|
version CINERA_APP_VERSION = {
|
||||||
.Major = 0,
|
.Major = 0,
|
||||||
.Minor = 5,
|
.Minor = 5,
|
||||||
.Patch = 41
|
.Patch = 42
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(matt): Copy in the DB 3 stuff from cinera_working.c
|
// TODO(matt): Copy in the DB 3 stuff from cinera_working.c
|
||||||
|
@ -716,6 +716,18 @@ StringsDiffer(char *A, char *B) // NOTE(matt): Two null-terminated strings
|
||||||
return *A - *B;
|
return *A - *B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
StringsDifferCaseInsensitive(char *A, char *B) // NOTE(matt): Two null-terminated strings
|
||||||
|
{
|
||||||
|
while(*A && *B &&
|
||||||
|
((*A >= 'A' && *A <= 'Z') ? *A + ('a' - 'A') : *A) ==
|
||||||
|
((*B >= 'A' && *B <= 'Z') ? *B + ('a' - 'A') : *B))
|
||||||
|
{
|
||||||
|
++A, ++B;
|
||||||
|
}
|
||||||
|
return *A - *B;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
StringsDifferT(char *A, // NOTE(matt): Null-terminated string
|
StringsDifferT(char *A, // NOTE(matt): Null-terminated string
|
||||||
char *B, // NOTE(matt): Not null-terminated string (e.g. one mid-buffer)
|
char *B, // NOTE(matt): Not null-terminated string (e.g. one mid-buffer)
|
||||||
|
@ -1146,7 +1158,7 @@ CharToColour(char Char)
|
||||||
return Colour;
|
return Colour;
|
||||||
}
|
}
|
||||||
|
|
||||||
hsl_colour *
|
void
|
||||||
StringToColourHash(hsl_colour *Colour, char *String)
|
StringToColourHash(hsl_colour *Colour, char *String)
|
||||||
{
|
{
|
||||||
Colour->Hue = 0;
|
Colour->Hue = 0;
|
||||||
|
@ -1162,7 +1174,6 @@ StringToColourHash(hsl_colour *Colour, char *String)
|
||||||
|
|
||||||
Colour->Hue = Colour->Hue % 360;
|
Colour->Hue = Colour->Hue % 360;
|
||||||
Colour->Saturation = Colour->Saturation % 26 + 74;
|
Colour->Saturation = Colour->Saturation % 26 + 74;
|
||||||
return(Colour);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char *
|
char *
|
||||||
|
@ -1242,6 +1253,20 @@ ConstructURLPrefix(buffer *URLPrefix, int IncludeType, int PageType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char Abbreviation[32];
|
||||||
|
hsl_colour Colour;
|
||||||
|
credential_info *Credential;
|
||||||
|
bool Seen;
|
||||||
|
} speaker;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
speaker Speaker[16];
|
||||||
|
int Count;
|
||||||
|
} speakers;
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
CreditsError_NoHost,
|
CreditsError_NoHost,
|
||||||
|
@ -1250,13 +1275,18 @@ enum
|
||||||
} credits_errors;
|
} credits_errors;
|
||||||
|
|
||||||
int
|
int
|
||||||
SearchCredentials(buffer *CreditsMenu, bool *HasCreditsMenu, char *Person, char *Role)
|
SearchCredentials(buffer *CreditsMenu, bool *HasCreditsMenu, char *Person, char *Role, speakers *Speakers)
|
||||||
{
|
{
|
||||||
bool Found = FALSE;
|
bool Found = FALSE;
|
||||||
for(int CredentialIndex = 0; CredentialIndex < ArrayCount(Credentials); ++CredentialIndex)
|
for(int CredentialIndex = 0; CredentialIndex < ArrayCount(Credentials); ++CredentialIndex)
|
||||||
{
|
{
|
||||||
if(!StringsDiffer(Person, Credentials[CredentialIndex].Username))
|
if(!StringsDiffer(Person, Credentials[CredentialIndex].Username))
|
||||||
{
|
{
|
||||||
|
if(Speakers)
|
||||||
|
{
|
||||||
|
Speakers->Speaker[Speakers->Count].Credential = &Credentials[CredentialIndex];
|
||||||
|
++Speakers->Count;
|
||||||
|
}
|
||||||
Found = TRUE;
|
Found = TRUE;
|
||||||
if(*HasCreditsMenu == FALSE)
|
if(*HasCreditsMenu == FALSE)
|
||||||
{
|
{
|
||||||
|
@ -1310,41 +1340,159 @@ SearchCredentials(buffer *CreditsMenu, bool *HasCreditsMenu, char *Person, char
|
||||||
" </span>\n");
|
" </span>\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Found ? 0 : CreditsError_NoCredentials;
|
return Found ? RC_SUCCESS : CreditsError_NoCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ClearString(char *String)
|
||||||
|
{
|
||||||
|
while(*String)
|
||||||
|
{
|
||||||
|
*String++ = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InitialString(char *Dest, char *Src)
|
||||||
|
{
|
||||||
|
ClearString(Dest);
|
||||||
|
*Dest++ = *Src++;
|
||||||
|
while(*Src++)
|
||||||
|
{
|
||||||
|
if(*Src == ' ')
|
||||||
|
{
|
||||||
|
++Src;
|
||||||
|
if(*Src)
|
||||||
|
{
|
||||||
|
*Dest++ = *Src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GetFirstSubstring(char *Dest, char *Src)
|
||||||
|
{
|
||||||
|
ClearString(Dest);
|
||||||
|
while(*Src && *Src != ' ')
|
||||||
|
{
|
||||||
|
*Dest++ = *Src++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InitialAndGetFinalString(char *Dest, char *Src)
|
||||||
|
{
|
||||||
|
ClearString(Dest);
|
||||||
|
int SrcLength = StringLength(Src);
|
||||||
|
char *SrcPtr = Src + SrcLength - 1;
|
||||||
|
while(SrcPtr > Src && *SrcPtr != ' ')
|
||||||
|
{
|
||||||
|
--SrcPtr;
|
||||||
|
}
|
||||||
|
if(*SrcPtr == ' ' && SrcPtr - Src < SrcLength - 1)
|
||||||
|
{
|
||||||
|
++SrcPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Src < SrcPtr)
|
||||||
|
{
|
||||||
|
*Dest++ = *Src++;
|
||||||
|
*Dest++ = '.';
|
||||||
|
*Dest++ = ' ';
|
||||||
|
|
||||||
|
while(Src < SrcPtr - 1)
|
||||||
|
{
|
||||||
|
if(*Src == ' ')
|
||||||
|
{
|
||||||
|
++Src;
|
||||||
|
if(*Src)
|
||||||
|
{
|
||||||
|
*Dest++ = *Src;
|
||||||
|
*Dest++ = '.';
|
||||||
|
*Dest++ = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++Src;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyString(Dest, SrcPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
AbbreviationsClash(speakers *Speakers)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Speakers->Count; ++i)
|
||||||
|
{
|
||||||
|
for(int j = i + 1; j < Speakers->Count; ++j)
|
||||||
|
{
|
||||||
|
if(!StringsDiffer(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[j].Abbreviation))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
SortAndAbbreviateSpeakers(speakers *Speakers)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Speakers->Count; ++i)
|
||||||
|
{
|
||||||
|
for(int j = i + 1; j < Speakers->Count; ++j)
|
||||||
|
{
|
||||||
|
if(StringsDiffer(Speakers->Speaker[i].Credential->Username, Speakers->Speaker[j].Credential->Username) > 0)
|
||||||
|
{
|
||||||
|
credential_info *Temp = Speakers->Speaker[j].Credential;
|
||||||
|
Speakers->Speaker[j].Credential = Speakers->Speaker[i].Credential;
|
||||||
|
Speakers->Speaker[i].Credential = Temp;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < Speakers->Count; ++i)
|
||||||
|
{
|
||||||
|
StringToColourHash(&Speakers->Speaker[i].Colour, Speakers->Speaker[i].Credential->Username);
|
||||||
|
InitialString(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Credential->CreditedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Attempt = 0;
|
||||||
|
while(AbbreviationsClash(Speakers))
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Speakers->Count; ++i)
|
||||||
|
{
|
||||||
|
switch(Attempt)
|
||||||
|
{
|
||||||
|
case 0: GetFirstSubstring(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Credential->CreditedName); break;
|
||||||
|
case 1: InitialAndGetFinalString(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Credential->CreditedName); break;
|
||||||
|
case 2: ClearCopyStringNoFormat(Speakers->Speaker[i].Abbreviation, sizeof(Speakers->Speaker[i].Abbreviation), Speakers->Speaker[i].Credential->Username); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++Attempt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
BuildCredits(buffer *CreditsMenu, bool *HasCreditsMenu, HMML_VideoMetaData *Metadata)
|
BuildCredits(buffer *CreditsMenu, bool *HasCreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speakers)
|
||||||
// TODO(matt): Make this take the Credentials, once we are parsing them from a config
|
// TODO(matt): Make this take the Credentials, once we are parsing them from a config
|
||||||
{
|
{
|
||||||
if(Metadata->member)
|
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->member, "Host", Speakers) == CreditsError_NoCredentials)
|
||||||
{
|
{
|
||||||
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->member, "Host"))
|
printf("No credentials for member %s. Please contact matt@handmadedev.org with their:\n"
|
||||||
{
|
" Full name\n"
|
||||||
printf("No credentials for member %s. Please contact matt@handmadedev.org with their:\n"
|
" Homepage URL (optional)\n"
|
||||||
" Full name\n"
|
" Financial support info, e.g. Patreon URL (optional)\n", Metadata->member);
|
||||||
" Homepage URL (optional)\n"
|
return CreditsError_NoCredentials;
|
||||||
" Financial support info, e.g. Patreon URL (optional)\n", Metadata->member);
|
|
||||||
return CreditsError_NoCredentials;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(*HasCreditsMenu == TRUE)
|
|
||||||
{
|
|
||||||
CopyStringToBuffer(CreditsMenu,
|
|
||||||
" </div>\n"
|
|
||||||
" </div>\n");
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Missing \"member\" in the [video] node\n");
|
|
||||||
return CreditsError_NoHost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Metadata->co_host_count > 0)
|
if(Metadata->co_host_count > 0)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < Metadata->co_host_count; ++i)
|
for(int i = 0; i < Metadata->co_host_count; ++i)
|
||||||
{
|
{
|
||||||
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->co_hosts[i], "Co-host"))
|
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->co_hosts[i], "Co-host", Speakers) == CreditsError_NoCredentials)
|
||||||
{
|
{
|
||||||
printf("No credentials for co-host %s. Please contact matt@handmadedev.org with their:\n"
|
printf("No credentials for co-host %s. Please contact matt@handmadedev.org with their:\n"
|
||||||
" Full name\n"
|
" Full name\n"
|
||||||
|
@ -1359,7 +1507,7 @@ BuildCredits(buffer *CreditsMenu, bool *HasCreditsMenu, HMML_VideoMetaData *Meta
|
||||||
{
|
{
|
||||||
for(int i = 0; i < Metadata->guest_count; ++i)
|
for(int i = 0; i < Metadata->guest_count; ++i)
|
||||||
{
|
{
|
||||||
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->guests[i], "Guest"))
|
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->guests[i], "Guest", Speakers) == CreditsError_NoCredentials)
|
||||||
{
|
{
|
||||||
printf("No credentials for guest %s. Please contact matt@handmadedev.org with their:\n"
|
printf("No credentials for guest %s. Please contact matt@handmadedev.org with their:\n"
|
||||||
" Full name\n"
|
" Full name\n"
|
||||||
|
@ -1370,11 +1518,16 @@ BuildCredits(buffer *CreditsMenu, bool *HasCreditsMenu, HMML_VideoMetaData *Meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Speakers->Count > 1)
|
||||||
|
{
|
||||||
|
SortAndAbbreviateSpeakers(Speakers);
|
||||||
|
}
|
||||||
|
|
||||||
if(Metadata->annotator_count > 0)
|
if(Metadata->annotator_count > 0)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < Metadata->annotator_count; ++i)
|
for(int i = 0; i < Metadata->annotator_count; ++i)
|
||||||
{
|
{
|
||||||
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->annotators[i], "Annotator"))
|
if(SearchCredentials(CreditsMenu, HasCreditsMenu, Metadata->annotators[i], "Annotator", 0) == CreditsError_NoCredentials)
|
||||||
{
|
{
|
||||||
printf("No credentials for annotator %s. Please contact matt@handmadedev.org with their:\n"
|
printf("No credentials for annotator %s. Please contact matt@handmadedev.org with their:\n"
|
||||||
" Full name\n"
|
" Full name\n"
|
||||||
|
@ -2713,6 +2866,32 @@ typedef struct
|
||||||
short int PrevIndex, ThisIndex, NextIndex;
|
short int PrevIndex, ThisIndex, NextIndex;
|
||||||
} neighbourhood;
|
} neighbourhood;
|
||||||
|
|
||||||
|
int
|
||||||
|
LinearSearchForSpeaker(speakers Speakers, char *Username)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Speakers.Count; ++i)
|
||||||
|
{
|
||||||
|
if(!StringsDifferCaseInsensitive(Speakers.Speaker[i].Credential->Username, Username))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
IsCategorisedAFK(HMML_Annotation Anno)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < Anno.marker_count; ++i)
|
||||||
|
{
|
||||||
|
if(!StringsDiffer(Anno.markers[i].marker, "afk"))
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filename, neighbourhood *N)
|
HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filename, neighbourhood *N)
|
||||||
{
|
{
|
||||||
|
@ -3019,7 +3198,8 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
|
||||||
CopyStringToBuffer(&CollationBuffers->Player,
|
CopyStringToBuffer(&CollationBuffers->Player,
|
||||||
" <div class=\"markers\">\n");
|
" <div class=\"markers\">\n");
|
||||||
|
|
||||||
switch(BuildCredits(&CreditsMenu, &HasCreditsMenu, &HMML.metadata))
|
speakers Speakers = { 0 };
|
||||||
|
switch(BuildCredits(&CreditsMenu, &HasCreditsMenu, &HMML.metadata, &Speakers))
|
||||||
{
|
{
|
||||||
case CreditsError_NoHost:
|
case CreditsError_NoHost:
|
||||||
case CreditsError_NoAnnotator:
|
case CreditsError_NoAnnotator:
|
||||||
|
@ -3114,35 +3294,45 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
|
||||||
CopyStringToBuffer(&AnnotationClass,
|
CopyStringToBuffer(&AnnotationClass,
|
||||||
" class=\"marker");
|
" class=\"marker");
|
||||||
|
|
||||||
if(Anno->author)
|
if((Anno->author || Speakers.Count > 1) && !IsCategorisedAFK(*Anno))
|
||||||
{
|
{
|
||||||
if(!HasFilterMenu)
|
int SpeakerIndex;
|
||||||
{
|
if(Anno->author && (SpeakerIndex = LinearSearchForSpeaker(Speakers, Anno->author)) == -1)
|
||||||
HasFilterMenu = TRUE;
|
|
||||||
}
|
|
||||||
InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, "authored");
|
|
||||||
hsl_colour AuthorColour;
|
|
||||||
StringToColourHash(&AuthorColour, Anno->author);
|
|
||||||
if(Config.Edition == EDITION_NETWORK)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s:%d - TODO(matt): Implement author hoverbox\n", __FILE__, __LINE__);
|
|
||||||
// NOTE(matt): We should get instructions on how to get this info in the config
|
|
||||||
CopyStringToBuffer(&Text,
|
|
||||||
"<a class=\"author\" href=\"https://handmade.network/m/%s\" target=\"blank\" style=\"color: hsl(%d, %d%%, %d%%); text-decoration: none\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</a> ",
|
|
||||||
Anno->author,
|
|
||||||
AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness,
|
|
||||||
AuthorColour.Hue, AuthorColour.Saturation,
|
|
||||||
Anno->author);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
|
if(!HasFilterMenu)
|
||||||
|
{
|
||||||
|
HasFilterMenu = TRUE;
|
||||||
|
}
|
||||||
|
InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, "authored");
|
||||||
|
hsl_colour AuthorColour;
|
||||||
|
StringToColourHash(&AuthorColour, Anno->author);
|
||||||
|
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
|
||||||
CopyStringToBuffer(&Text,
|
CopyStringToBuffer(&Text,
|
||||||
"<span class=\"author\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span> ",
|
"<span class=\"author\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span> ",
|
||||||
AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness,
|
AuthorColour.Hue, AuthorColour.Saturation, AuthorColour.Lightness,
|
||||||
AuthorColour.Hue, AuthorColour.Saturation,
|
AuthorColour.Hue, AuthorColour.Saturation,
|
||||||
Anno->author);
|
Anno->author);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(!Anno->author)
|
||||||
|
{
|
||||||
|
SpeakerIndex = LinearSearchForSpeaker(Speakers, HMML.metadata.member);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyStringToBuffer(&Text,
|
||||||
|
"<span class=\"author\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span>: ",
|
||||||
|
Speakers.Speaker[SpeakerIndex].Colour.Hue,
|
||||||
|
Speakers.Speaker[SpeakerIndex].Colour.Saturation,
|
||||||
|
Speakers.Speaker[SpeakerIndex].Colour.Lightness,
|
||||||
|
|
||||||
|
Speakers.Speaker[SpeakerIndex].Colour.Hue,
|
||||||
|
Speakers.Speaker[SpeakerIndex].Colour.Saturation,
|
||||||
|
|
||||||
|
Speakers.Speaker[SpeakerIndex].Seen == FALSE ? Speakers.Speaker[SpeakerIndex].Credential->CreditedName : Speakers.Speaker[SpeakerIndex].Abbreviation);
|
||||||
|
|
||||||
|
Speakers.Speaker[SpeakerIndex].Seen = TRUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char *InPtr = Anno->text;
|
char *InPtr = Anno->text;
|
||||||
|
@ -3153,85 +3343,48 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen
|
||||||
if(MarkerIndex < Anno->marker_count &&
|
if(MarkerIndex < Anno->marker_count &&
|
||||||
InPtr - Anno->text == Anno->markers[MarkerIndex].offset)
|
InPtr - Anno->text == Anno->markers[MarkerIndex].offset)
|
||||||
{
|
{
|
||||||
// TODO(matt): Consider switching on the Anno->markers[MarkerIndex].type and 100% ensuring this is all correct
|
|
||||||
// I wonder if HMML_CATEGORY should do InPtr += StringLength(Readable); like the others, and also whether HMML_MEMBER and HMML_PROJECT could be
|
|
||||||
// identical, except only for their class ("member" and "project" respectively)
|
|
||||||
// Pretty goddamn sure we can totally compress these cases, but let's do it tomorrow when we're fresh
|
|
||||||
char *Readable = Anno->markers[MarkerIndex].parameter
|
char *Readable = Anno->markers[MarkerIndex].parameter
|
||||||
? Anno->markers[MarkerIndex].parameter
|
? Anno->markers[MarkerIndex].parameter
|
||||||
: Anno->markers[MarkerIndex].marker;
|
: Anno->markers[MarkerIndex].marker;
|
||||||
if(Anno->markers[MarkerIndex].type == HMML_MEMBER)
|
switch(Anno->markers[MarkerIndex].type)
|
||||||
{
|
{
|
||||||
hsl_colour MemberColour;
|
case HMML_MEMBER:
|
||||||
StringToColourHash(&MemberColour, Anno->markers[MarkerIndex].marker);
|
case HMML_PROJECT:
|
||||||
if(Config.Edition == EDITION_NETWORK)
|
{
|
||||||
{
|
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
|
||||||
fprintf(stderr, "%s:%d - TODO(matt): Implement member hoverbox\n", __FILE__, __LINE__);
|
hsl_colour Colour;
|
||||||
// NOTE(matt): We should get instructions on how to get this info in the config
|
StringToColourHash(&Colour, Anno->markers[MarkerIndex].marker);
|
||||||
CopyStringToBuffer(&Text,
|
CopyStringToBuffer(&Text,
|
||||||
"<a class=\"member\" href=\"https://handmade.network/m/%s\" target=\"blank\" style=\"color: hsl(%d, %d%%, %d%%); text-decoration: none\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</a>",
|
"<span class=\"%s\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span>",
|
||||||
Anno->markers[MarkerIndex].marker,
|
Anno->markers[MarkerIndex].type == HMML_MEMBER ? "member" : "project",
|
||||||
MemberColour.Hue, MemberColour.Saturation, MemberColour.Lightness,
|
Colour.Hue, Colour.Saturation, Colour.Lightness,
|
||||||
MemberColour.Hue, MemberColour.Saturation,
|
Colour.Hue, Colour.Saturation,
|
||||||
StringLength(Readable), InPtr);
|
Readable);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CopyStringToBuffer(&Text,
|
|
||||||
"<span class=\"member\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%.*s</span>",
|
|
||||||
MemberColour.Hue, MemberColour.Saturation, MemberColour.Lightness,
|
|
||||||
MemberColour.Hue, MemberColour.Saturation,
|
|
||||||
StringLength(Readable), InPtr);
|
|
||||||
}
|
|
||||||
|
|
||||||
InPtr += StringLength(Readable);
|
} break;
|
||||||
++MarkerIndex;
|
case HMML_CATEGORY:
|
||||||
}
|
{
|
||||||
else if(Anno->markers[MarkerIndex].type == HMML_PROJECT)
|
switch(GenerateTopicColours(Anno->markers[MarkerIndex].marker))
|
||||||
{
|
{
|
||||||
hsl_colour ProjectColour;
|
case RC_SUCCESS:
|
||||||
StringToColourHash(&ProjectColour, Anno->markers[MarkerIndex].marker);
|
case RC_NOOP:
|
||||||
if(Config.Edition == EDITION_NETWORK)
|
break;
|
||||||
{
|
case RC_ERROR_FILE:
|
||||||
fprintf(stderr, "%s:%d - TODO(matt): Implement project hoverbox\n", __FILE__, __LINE__);
|
case RC_ERROR_MEMORY:
|
||||||
// NOTE(matt): We should get instructions on how to get this info in the config
|
hmml_free(&HMML);
|
||||||
CopyStringToBuffer(&Text,
|
return RC_ERROR_FATAL;
|
||||||
"<a class=\"project\" href=\"https://%s.handmade.network/\" target=\"blank\" style=\"color: hsl(%d, %d%%, %d%%); text-decoration: none\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</a>",
|
};
|
||||||
Anno->markers[MarkerIndex].marker,
|
if(!HasFilterMenu)
|
||||||
ProjectColour.Hue, ProjectColour.Saturation, ProjectColour.Lightness,
|
{
|
||||||
ProjectColour.Hue, ProjectColour.Saturation,
|
HasFilterMenu = TRUE;
|
||||||
Readable);
|
}
|
||||||
}
|
InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Anno->markers[MarkerIndex].marker);
|
||||||
else
|
CopyStringToBuffer(&Text, Readable);
|
||||||
{
|
} break;
|
||||||
CopyStringToBuffer(&Text,
|
case HMML_MARKER_COUNT: break;
|
||||||
"<span class=\"project\" style=\"color: hsl(%d, %d%%, %d%%);\" data-hue=\"%d\" data-saturation=\"%d%%\">%s</span>",
|
|
||||||
ProjectColour.Hue, ProjectColour.Saturation, ProjectColour.Lightness,
|
|
||||||
ProjectColour.Hue, ProjectColour.Saturation,
|
|
||||||
Readable);
|
|
||||||
}
|
|
||||||
InPtr += StringLength(Readable);
|
|
||||||
++MarkerIndex;
|
|
||||||
}
|
|
||||||
else if(Anno->markers[MarkerIndex].type == HMML_CATEGORY)
|
|
||||||
{
|
|
||||||
switch(GenerateTopicColours(Anno->markers[MarkerIndex].marker))
|
|
||||||
{
|
|
||||||
case RC_SUCCESS:
|
|
||||||
case RC_NOOP:
|
|
||||||
break;
|
|
||||||
case RC_ERROR_FILE:
|
|
||||||
case RC_ERROR_MEMORY:
|
|
||||||
hmml_free(&HMML);
|
|
||||||
return RC_ERROR_FATAL;
|
|
||||||
};
|
|
||||||
if(!HasFilterMenu)
|
|
||||||
{
|
|
||||||
HasFilterMenu = TRUE;
|
|
||||||
}
|
|
||||||
InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Anno->markers[MarkerIndex].marker);
|
|
||||||
++MarkerIndex;
|
|
||||||
}
|
}
|
||||||
|
InPtr += StringLength(Readable);
|
||||||
|
++MarkerIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
while(RefIndex < Anno->reference_count &&
|
while(RefIndex < Anno->reference_count &&
|
||||||
|
@ -3356,14 +3509,9 @@ AppendedIdentifier:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Anno->references[RefIndex].offset == Anno->references[RefIndex-1].offset)
|
CopyStringToBuffer(&Text, "<sup style=\"vertical-align: super;\">%s%d</sup>",
|
||||||
{
|
Anno->references[RefIndex].offset == Anno->references[RefIndex-1].offset ? "," : "",
|
||||||
CopyStringToBuffer(&Text, "<sup style=\"vertical-align: super;\">,%d</sup>", RefIdentifier);
|
RefIdentifier);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CopyStringToBuffer(&Text, "<sup style=\"vertical-align: super;\">%d</sup>", RefIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
++RefIndex;
|
++RefIndex;
|
||||||
++RefIdentifier;
|
++RefIdentifier;
|
||||||
|
@ -3375,29 +3523,25 @@ AppendedIdentifier:
|
||||||
{
|
{
|
||||||
case '<':
|
case '<':
|
||||||
CopyStringToBuffer(&Text, "<");
|
CopyStringToBuffer(&Text, "<");
|
||||||
InPtr++;
|
|
||||||
break;
|
break;
|
||||||
case '>':
|
case '>':
|
||||||
CopyStringToBuffer(&Text, ">");
|
CopyStringToBuffer(&Text, ">");
|
||||||
InPtr++;
|
|
||||||
break;
|
break;
|
||||||
case '&':
|
case '&':
|
||||||
CopyStringToBuffer(&Text, "&");
|
CopyStringToBuffer(&Text, "&");
|
||||||
InPtr++;
|
|
||||||
break;
|
break;
|
||||||
case '\"':
|
case '\"':
|
||||||
CopyStringToBuffer(&Text, """);
|
CopyStringToBuffer(&Text, """);
|
||||||
InPtr++;
|
|
||||||
break;
|
break;
|
||||||
case '\'':
|
case '\'':
|
||||||
CopyStringToBuffer(&Text, "'");
|
CopyStringToBuffer(&Text, "'");
|
||||||
InPtr++;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
*Text.Ptr++ = *InPtr++;
|
*Text.Ptr++ = *InPtr;
|
||||||
*Text.Ptr = '\0';
|
*Text.Ptr = '\0';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
++InPtr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -388,7 +388,7 @@ function toggleMenuVisibility(element) {
|
||||||
{
|
{
|
||||||
if(!lastFocusedCreditItem)
|
if(!lastFocusedCreditItem)
|
||||||
{
|
{
|
||||||
if(element.querySelectorAll(".credit .support")[0])
|
if(element.querySelectorAll(".credit .person")[0].nextElementSibling)
|
||||||
{
|
{
|
||||||
lastFocusedCreditItem = element.querySelectorAll(".credit .support")[0];
|
lastFocusedCreditItem = element.querySelectorAll(".credit .support")[0];
|
||||||
focusedElement = lastFocusedCreditItem;
|
focusedElement = lastFocusedCreditItem;
|
||||||
|
|
Loading…
Reference in New Issue