cinera: Add numbering and fix sizes

New config options:
    numbering_filename_prefix (string)
        Defaults to $project
    numbering_method
        auto
        filename_derived (default)
        hmml_specified
    numbering_start (number)
        May be used to 0-index the set
    numbering_unit (renamed from "unit")
    numbering_zero_pad (boolean)

This commit also fixes the size of the "?" help button and category dots
when the wider site uses box-sizing: border-box
This commit is contained in:
Matt Mascarenhas 2022-08-01 20:58:55 +01:00
parent ef9937e95b
commit 84a35b9ece
3 changed files with 317 additions and 87 deletions

View File

@ -23,7 +23,7 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 10,
.Patch = 7
.Patch = 8
};
#include <stdarg.h> // NOTE(matt): varargs
@ -113,6 +113,7 @@ clock_t TIMING_START;
#define MAX_BASE_FILENAME_LENGTH 32
#define MAX_ENTRY_OUTPUT_LENGTH MAX_BASE_FILENAME_LENGTH
#define MAX_TITLE_LENGTH 128
#define MAX_NUMBER_LENGTH 16
#define MAX_ASSET_FILENAME_LENGTH 64
@ -1372,12 +1373,10 @@ files for the purpose of hashing them."
{ "cache_dir", "Internal directory (i.e. no access from the wider internet) where we store errors.log and quotes retrieved from insobot." },
{ "cohost",
"The ID of a person (see also person) who cohosts a project. They will then appear in the credits menu of each entry in the project. Note that setting a cohost in the configuration \
file credits this person for the entire project. There is no way to \"uncredit\" people in a HMML file. If a person ought not be credited for the whole project, just add them in \
video node of the entries for which they should be credited."
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file."
},
{ "credit", "The ID of a person (see also person) who contributed to a project. They will then appear in the credits menu of each entry in the project. Note that setting a credit in the configuration \
file credits this person for the entire project. There is no way to \"uncredit\" people in a HMML file. If a person ought not be credited for the whole project, just add them in \
video node of the entries for which they should be credited." },
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file." },
{ "css_path", "Path relative to assets_root_dir and assets_root_url where CSS files are located." },
{ "db_location", "Absolute file path where the database file resides. If you run multiple instances of Cinera on the same machine, please ensure this db_location differs between them." },
{ "default_medium", "The ID of a medium (see also medium) which will be the default for the project. May be overridden by setting the medium in the video node of an HMML file." },
@ -1391,8 +1390,7 @@ video node of the entries for which they should be credited." },
{ "global_theme", "The theme used for global pages, e.g. search page. As with all themes, its name forms the file path of a CSS file containing the style as follows: cinera__${theme}.css" },
{ "guest",
"The ID of a person (see also person) who guests in a project. They will then appear in the credits menu of each entry in the project. Note that setting a guest in the configuration \
file credits this person for the entire project. There is no way to \"uncredit\" people in a HMML file. If a person ought not be credited for the whole project, just add them in \
video node of the entries for which they should be credited."
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file."
},
{ "hidden", "Hidden media are filtered off by default in the player. For example, we may create an \"afk\" medium to tag portions of videos where the host is \"Away from \
Keyboard\". These portions, filtered off, will be skipped to save the viewer sitting through them." },
@ -1416,8 +1414,7 @@ publicly, then we can set ignore_privacy to \"true\" and save the resources othe
{ "include", "The path - either absolute or relative to the containing config file - of a file to be included in the configuration." },
{ "indexer",
"The ID of a person (see also person) who indexes a project. They will then appear in the credits menu of each entry in the project. Note that setting an indexer in the configuration \
file credits this person for the entire project. There is no way to \"uncredit\" people in a HMML file. If a person ought not be credited for the whole project, just add them in \
video node of the entries for which they should be credited."
file credits this person for the entire project, but indexers may \"uncredit\" people in the [video] node of a HMML file."
},
{ "js_path", "Path relative to assets_root_dir and assets_root_url where JavaScript files are located." },
{ "lineage", 0, 0, "A slash-separated string of all project IDs from the top of the family tree to the present project." },
@ -1434,11 +1431,13 @@ default categories are assumed to be topics) or a medium, and both use the same
"The name of the role as it appears in the credits menu."
},
{ "non-speaking", "We try to abbreviate the names of speakers to cite them more concisely in the timestamps. Set this to \"true\" to prevent abbreviating the names of people in non-speaking roles, and so avoid erroneous clashes." },
{ "numbering_scheme",
"Possible numbering schemes: \"calendrical\", \"linear\", \"seasonal\". Only \"linear\" (the default) is treated specially. We assume that .hmml file names take the form: \
\"$project$episode_number.hmml\". Under the \"linear\" scheme, Cinera tries to derive each entry's number in its project by skipping past the project ID, then replacing all underscores with full \
stops. This derived number is then used in the search results to denote the entry."
},
{ "numbering_filename_prefix", "This works in conjunction with the \"filename_derived\" numbering_method. We take the base filename, strip this prefix (default \"$project\") from the start and use the rest as the number." },
{ "numbering_method", "Possible numbering methods: \"auto\" (see also numbering_start and numbering_zero_pad), \"filename_derived\" (see also numbering_filename_prefix), \"hmml_specified\"." },
{ "numbering_scheme", "Possible numbering schemes: \"calendrical\", \"linear\" (the default), \"seasonal\". Currently this setting does nothing." },
{ "numbering_start", "This works in conjunction with the \"auto\" numbering scheme. The number (default 1) denotes the displayed position of the project's first entry recorded in Cinera. Setting it to 0 makes the entries 0-indexed." },
{ "numbering_unit", "This is a freely-configurable string - e.g. \"Day\", \"Session\", \"Episode\", \"Meeting\" - which is written on the search page, \
preceding the derived number of each entry. If the unit is not set, then the entries will not be numbered." },
{ "numbering_zero_pad", "This works in conjunction with the \"auto\" numbering scheme. It zero-pads the entries' numbers so that they all, from the smallest to largest, contain the same number of digits." },
{ "origin", 0, 0, "The ID of the project in our branch of the family tree which has no parent." },
{ "owner",
"The ID of the person (see also person) who owns the project. There may only be one owner, and they will appear in the credits menu as the host of each entry.",
@ -1492,8 +1491,6 @@ fill the slots left vacant by positioned roles in the order in which they are co
{ "title_list_prefix", "Currently not implemented, probably to be removed." },
{ "title_list_suffix", "Currently not implemented, probably to be removed."} ,
{ "title_suffix", "Currently not implemented, probably to be removed." },
{ "unit", "This works in conjunction with the numbering_scheme. It is a freely-configurable string - e.g. \"Day\", \"Session\", \"Episode\", \"Meeting\" - which is written on the search page, \
preceding the derived number of each entry. If the unit is not set, then the entries will not be numbered." },
{ "vod_platform", "Possible VOD platforms: \"direct\" (for .webm, .mp4, etc. files), \"vimeo\", \"youtube\"." },
{ "url", "The URL where viewers may support the person, e.g. their page on a crowd funding site, the \"pledge\" page on their own website." },
};
@ -1544,7 +1541,12 @@ typedef enum
IDENT_MEDIUM,
IDENT_NAME,
IDENT_NON_SPEAKING,
IDENT_NUMBERING_FILENAME_PREFIX,
IDENT_NUMBERING_METHOD,
IDENT_NUMBERING_SCHEME,
IDENT_NUMBERING_START,
IDENT_NUMBERING_UNIT,
IDENT_NUMBERING_ZERO_PAD,
IDENT_ORIGIN,
IDENT_OWNER,
IDENT_PERSON,
@ -1571,7 +1573,6 @@ typedef enum
IDENT_TITLE_LIST_PREFIX,
IDENT_TITLE_LIST_SUFFIX,
IDENT_TITLE_SUFFIX,
IDENT_UNIT,
IDENT_VOD_PLATFORM,
IDENT_URL,
IDENT_COUNT,
@ -2181,6 +2182,59 @@ GetNumberingSchemeFromString(string *Filename, token *T)
return NS_COUNT;
}
char *NumberingMethodStrings[] =
{
"auto",
"filename_derived",
"hmml_specified",
};
typedef enum
{
NM_AUTO,
NM_FILENAME_DERIVED,
NM_HMML_SPECIFIED,
NM_COUNT
} numbering_method;
numbering_method
GetNumberingMethodFromString(string *Filename, token *T)
{
for(int i = 0; i < NM_COUNT; ++i)
{
if(!StringsDifferLv0(T->Content, NumberingMethodStrings[i])) { return i; }
}
ConfigError(Filename, T->LineNumber, S_ERROR, "Unknown numbering method: ", &T->Content);
fprintf(stderr, " Valid numbering methods:\n");
for(int i = 0; i < NM_COUNT; ++i)
{
fprintf(stderr, " %s\n", NumberingMethodStrings[i]);
}
return NM_COUNT;
}
typedef struct
{
uint32_t StartingNumber;
bool ZeroPadded;
} numbering_auto_params;
typedef struct
{
string Prefix;
} numbering_filename_derived_params;
typedef struct
{
string Unit;
numbering_method Method;
numbering_scheme Scheme;
numbering_auto_params Auto;
numbering_filename_derived_params FilenameDerived;
} numbering;
bool
GetBoolFromString(string *Filename, token *T)
{
@ -2599,7 +2653,8 @@ typedef struct
unsigned short int Size;
link_insertion_offsets LinkOffsets;
enum32(special_asset_index) ArtIndex;
char Reserved[46];
char Number[MAX_NUMBER_LENGTH];
char Reserved[30];
} db_entry5;
typedef struct db_block_assets5
@ -3018,8 +3073,9 @@ string
TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd)
{
string Result = S;
Result.Length -= (CharsFromStart + CharsFromEnd);
Result.Base += CharsFromStart;
int OriginalLength = Result.Length;
Result.Length -= MIN(OriginalLength, (CharsFromStart + CharsFromEnd));
Result.Base += MIN(OriginalLength, CharsFromStart);
return Result;
}
@ -3241,6 +3297,8 @@ LogEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitl
}
}
#define SLASH 1
#define NULLTERM 1
typedef struct
{
buffer IncludesSearch;
@ -3249,27 +3307,27 @@ typedef struct
buffer IncludesPlayer;
buffer Player;
char Custom0[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom1[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom2[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom3[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom4[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom5[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom6[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom7[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom8[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom9[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom10[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom11[MAX_CUSTOM_SNIPPET_SHORT_LENGTH];
char Custom12[MAX_CUSTOM_SNIPPET_LONG_LENGTH];
char Custom13[MAX_CUSTOM_SNIPPET_LONG_LENGTH];
char Custom14[MAX_CUSTOM_SNIPPET_LONG_LENGTH];
char Custom15[MAX_CUSTOM_SNIPPET_LONG_LENGTH];
char Custom0[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom1[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom2[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom3[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom4[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom5[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom6[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom7[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom8[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom9[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom10[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom11[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + NULLTERM];
char Custom12[MAX_CUSTOM_SNIPPET_LONG_LENGTH + NULLTERM];
char Custom13[MAX_CUSTOM_SNIPPET_LONG_LENGTH + NULLTERM];
char Custom14[MAX_CUSTOM_SNIPPET_LONG_LENGTH + NULLTERM];
char Custom15[MAX_CUSTOM_SNIPPET_LONG_LENGTH + NULLTERM];
char Title[MAX_TITLE_LENGTH];
char URLSearch[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH];
char URLPlayer[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH];
char VideoID[MAX_VOD_ID_LENGTH];
char Title[MAX_TITLE_LENGTH + NULLTERM];
char URLSearch[MAX_BASE_URL_LENGTH + SLASH + MAX_RELATIVE_PAGE_LOCATION_LENGTH + NULLTERM];
char URLPlayer[MAX_BASE_URL_LENGTH + SLASH + MAX_RELATIVE_PAGE_LOCATION_LENGTH + SLASH + MAX_BASE_FILENAME_LENGTH + NULLTERM];
char VideoID[MAX_VOD_ID_LENGTH + NULLTERM];
vod_platform VODPlatform;
} buffers;
@ -3876,6 +3934,32 @@ AppendUint32ToBuffer(buffer *B, uint32_t I)
CopyStringToBufferNoFormat(B, Wrap0(Temp));
}
void
AppendInt64ToBuffer(buffer *B, int64_t I)
{
int Digits = DigitsInInt(&I);
uint64_t BytePosition = B->Ptr - B->Location;
B->Size = B->Ptr - B->Location + Digits + 1;
B->Location = realloc(B->Location, B->Size);
B->Ptr = B->Location + BytePosition;
char Temp[Digits + 1];
sprintf(Temp, "%li", I);
CopyStringToBufferNoFormat(B, Wrap0(Temp));
}
void
AppendUint64ToBuffer(buffer *B, uint64_t I)
{
int Digits = DigitsInUint(&I);
uint64_t BytePosition = B->Ptr - B->Location;
B->Size = B->Ptr - B->Location + Digits + 1;
B->Location = realloc(B->Location, B->Size);
B->Ptr = B->Location + BytePosition;
char Temp[Digits + 1];
sprintf(Temp, "%li", I);
CopyStringToBufferNoFormat(B, Wrap0(Temp));
}
uint64_t
AmpersandEncodedStringLength(string S)
{
@ -8829,6 +8913,7 @@ PrintProjectAndChildren(db_header_project *P, typography T)
string EntryHMMLBaseFilename = Wrap0i(E->HMMLBaseFilename, sizeof(E->HMMLBaseFilename));
string EntryOutputLocation = Wrap0i(E->OutputLocation, sizeof(E->OutputLocation));
string EntryTitle = Wrap0i(E->Title, sizeof(E->Title));
string Number = Wrap0i(E->Number, sizeof(E->Number));
fprintf(stderr, "\n"
"%s%s%s%s%s ", T.UpperLeftCorner,
@ -8838,6 +8923,7 @@ PrintProjectAndChildren(db_header_project *P, typography T)
Colourise(CS_BLACK_BOLD); fprintf(stderr, "[%d] ", EntryIndex); Colourise(CS_END);
PrintStringC(CS_GREEN_BOLD, EntryHMMLBaseFilename);
fprintf(stderr, "\n%s%s", T.Vertical, T.Margin); PrintC(CS_YELLOW_BOLD, "OutputLocation"); fprintf(stderr, ": "); PrintStringC(CS_GREEN_BOLD, EntryOutputLocation);
fprintf(stderr, "\n%s%s", T.Vertical, T.Margin); PrintC(CS_YELLOW_BOLD, "Number"); fprintf(stderr, ": "); PrintStringC(CS_GREEN_BOLD, Number);
fprintf(stderr, "\n%s%s", T.Vertical, T.Margin); PrintC(CS_YELLOW_BOLD, "Title"); fprintf(stderr, ": "); PrintStringC(CS_GREEN_BOLD, EntryTitle);
fprintf(stderr, "\n%s%s", T.Vertical, T.Margin); PrintC(CS_YELLOW_BOLD, "Size"); fprintf(stderr, ": ");
@ -9960,6 +10046,56 @@ GetNumberFromHMMLBaseFilename(string ProjectID, string HMMLBaseFilename)
return Result;
}
typedef struct
{
pair_type Type;
union
{
string String;
int64_t int64_t;
bool bool;
};
} typed_value;
typedef struct
{
typed_value Value;
uint64_t ZeroPadding;
} numbering_info;
numbering_info
GetEntryNumbering(project *P, string HMMLBaseFilename, string SpecifiedNumber, uint64_t EntryIndex, uint64_t EntryCount)
{
// TODO(matt): Handle P->Numbering.Scheme
numbering_info Result = {};
switch(P->Numbering.Method)
{
case NM_AUTO:
{
Result.Value.Type = PT_INT64;
uint64_t ThisNumber = EntryIndex + P->Numbering.Auto.StartingNumber;
uint64_t HighestNumber = EntryCount + P->Numbering.Auto.StartingNumber - 1;
Result.Value.int64_t = ThisNumber;
if(P->Numbering.Auto.ZeroPadded)
{
Result.ZeroPadding = DigitsInInt(&HighestNumber) - DigitsInInt(&ThisNumber);
}
} break;
case NM_FILENAME_DERIVED:
{
Result.Value.Type = PT_STRING;
Result.Value.String = TrimString(HMMLBaseFilename, P->Numbering.FilenameDerived.Prefix.Length, 0);
} break;
case NM_HMML_SPECIFIED:
{
Result.Value.Type = PT_STRING;
Result.Value.String = SpecifiedNumber;
} break;
default: break;
}
return Result;
}
string
BestTitleForHTML(project *Project)
{
@ -9981,7 +10117,7 @@ CopyProjectEndStringToBuffer(buffer *B, string ProjectTitle)
}
rc
HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N)
HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N, uint64_t EntryCount)
{
MEM_TEST_TOP("HMMLToBuffers");
rc Result = RC_SUCCESS;
@ -10147,6 +10283,24 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
Result = RC_ERROR_HMML;
}
if(!HMML.metadata.number)
{
if(CurrentProject->Numbering.Method == NM_HMML_SPECIFIED)
{
IndexingError(FilepathL, 0, S_ERROR, "No number set in the [video] node", 0);
Result = RC_ERROR_HMML;
}
}
else if(StringLength(HMML.metadata.number) > sizeof(N->WorkingThis.Number))
{
IndexingErrorSizing(&FilepathL, 0, "number", Wrap0(HMML.metadata.number), MAX_NUMBER_LENGTH);
Result = RC_ERROR_HMML;
}
else
{
ClearCopyStringNoFormat(N->WorkingThis.Number, sizeof(N->WorkingThis.Number), Wrap0(HMML.metadata.number));
}
string ProjectTitle = BestTitleForHTML(CurrentProject);
// TODO(matt): Handle the art and art_variants once .hmml supports them
@ -10321,8 +10475,25 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF
if(Result == RC_SUCCESS)
{
CopyStringToBuffer(&CollationBuffers->SearchEntry, "location: \"%.*s\"\n", (int)OutputLocation.Length, OutputLocation.Base);
string Number = GetNumberFromHMMLBaseFilename(Wrap0i(N->Project->ID, sizeof(N->Project->ID)), BaseFilename);
CopyStringToBuffer(&CollationBuffers->SearchEntry, "number: \"%.*s\"\n", (int)Number.Length, Number.Base);
CopyStringToBuffer(&CollationBuffers->SearchEntry, "number: \"");
numbering_info Number = GetEntryNumbering(CurrentProject, BaseFilename, Wrap0i(N->WorkingThis.Number, sizeof(N->WorkingThis.Number)), N->ThisIndex, EntryCount);
switch(Number.Value.Type)
{
case PT_STRING:
{
CopyStringToBuffer(&CollationBuffers->SearchEntry, "%.*s", (int)Number.Value.String.Length, Number.Value.String.Base);
} break;
case PT_INT64:
{
for(int i = 0; i < Number.ZeroPadding; ++i)
{
CopyStringToBuffer(&CollationBuffers->SearchEntry, "0");
}
CopyStringToBuffer(&CollationBuffers->SearchEntry, "%li", Number.Value.int64_t);
} break;
default: break;
}
CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n");
CopyStringToBuffer(&CollationBuffers->SearchEntry, "title: \"");
CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0(HMML.metadata.title));
@ -11962,7 +12133,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl
bool VideoIsPrivate = FALSE;
/* */ MEM_TEST_MID("InsertIntoDB()");
/* +MEM */ rc HMMLToBuffersReturn = HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N);
/* +MEM */ rc HMMLToBuffersReturn = HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N, N->Project->EntryCount + (EditType != EDIT_REINSERTION));
/* */ MEM_TEST_MID("InsertIntoDB()");
if(HMMLToBuffersReturn == RC_SUCCESS || HMMLToBuffersReturn == RC_PRIVATE_VIDEO)
{
@ -13284,7 +13455,7 @@ GenerateIndexOfProjectAndChildren(buffer *Index, db_header_project *StoredP, pro
AppendStringToBuffer(Index, Wrap0("\" data-playerLocation=\""));
AppendStringToBuffer(Index, P->PlayerLocation);
AppendStringToBuffer(Index, Wrap0("\" data-unit=\""));
AppendStringToBuffer(Index, P->Unit);
AppendStringToBuffer(Index, P->Numbering.Unit);
AppendStringToBuffer(Index, Wrap0("\">"));
OpenNodeNewLine(Index, &Index->IndentLevel, NODE_DIV, 0);
@ -13318,31 +13489,30 @@ GenerateIndexOfProjectAndChildren(buffer *Index, db_header_project *StoredP, pro
if(*StoredP->Unit)
{
string NumberL = GetNumberFromHMMLBaseFilename(P->ID, Wrap0i(Entry->HMMLBaseFilename, sizeof(Entry->HMMLBaseFilename)));
if(NumberL.Length > 0)
AppendStringToBuffer(Index, P->Numbering.Unit);
AppendStringToBuffer(Index, Wrap0(" "));
numbering_info Number = GetEntryNumbering(P, Wrap0i(Entry->HMMLBaseFilename, sizeof(Entry->HMMLBaseFilename)), Wrap0i(Entry->Number, sizeof(Entry->Number)), i, StoredP->EntryCount);
switch(Number.Value.Type)
{
char Number[NumberL.Length + 1];
ClearCopyStringNoFormat(Number, sizeof(Number), NumberL);
if(CurrentProject->NumberingScheme == NS_LINEAR)
{
for(int i = 0; Number[i]; ++i)
case PT_STRING:
{
if(Number[i] == '_')
AppendStringToBuffer(Index, Number.Value.String);
} break;
case PT_INT64:
{
for(int i = 0; i < Number.ZeroPadding; ++i)
{
Number[i] = '.';
AppendInt32ToBuffer(Index, 0);
}
}
}
AppendStringToBuffer(Index, P->Unit);
AppendStringToBuffer(Index, Wrap0(" "));
AppendStringToBuffer(Index, Wrap0i(Number, sizeof(Number)));
AppendStringToBuffer(Index, Wrap0(": "));
AppendInt64ToBuffer(Index, Number.Value.int64_t);
} break;
default: break;
}
AppendStringToBuffer(Index, Wrap0(": "));
}
// HERE
AppendStringToBufferHTMLSafe(Index, Wrap0i(Entry->Title, sizeof(Entry->Title)));
CloseNode(Index, &Index->IndentLevel, NODE_A);
CloseNode(Index, &Index->IndentLevel, NODE_DIV);
@ -14597,9 +14767,9 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
Modified = TRUE;
}
if(StringsDiffer(CurrentProject->Unit, Wrap0i(N->Project->Unit, sizeof(N->Project->Unit))))
if(StringsDiffer(CurrentProject->Numbering.Unit, Wrap0i(N->Project->Unit, sizeof(N->Project->Unit))))
{
ClearCopyStringNoFormat(N->Project->Unit, sizeof(N->Project->Unit), CurrentProject->Unit);
ClearCopyStringNoFormat(N->Project->Unit, sizeof(N->Project->Unit), CurrentProject->Numbering.Unit);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
@ -14967,7 +15137,7 @@ InitProjectInDBRecursively(project_generations *G, project *P)
CopyStringNoFormat(Project.BaseURL, sizeof(Project.BaseURL), P->BaseURL);
CopyStringNoFormat(Project.SearchLocation, sizeof(Project.SearchLocation), P->SearchLocation);
CopyStringNoFormat(Project.PlayerLocation, sizeof(Project.PlayerLocation), P->PlayerLocation);
CopyStringNoFormat(Project.Unit, sizeof(Project.Unit), P->Unit);
CopyStringNoFormat(Project.Unit, sizeof(Project.Unit), P->Numbering.Unit);
Project.ArtIndex = SAI_UNSET;
Project.IconIndex = SAI_UNSET;
Project.EntryCount = 0;

View File

@ -506,15 +506,16 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .cineraHelp,
.cineraHelp {
cursor: pointer;
box-sizing: content-box;
border: 1px solid;
border-radius: 4px;
height: 6px;
height: .5rem;
width: .5rem;
padding: 4px;
width: 6px;
margin: 2px;
font-size: .75rem;
font-weight: bold;
line-height: 6px;
line-height: .5rem;
text-align: center;
z-index: 64;
}
@ -968,6 +969,7 @@ ul.cineraNavPlain li.current > a {
.cineraMenus > .menu > .filter_container .filter_content .category,
.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category {
box-sizing: content-box;
border-radius: 50%;
height: 5px;
width: 5px;

View File

@ -507,7 +507,6 @@ typedef struct project
string Title;
string HTMLTitle;
string Unit;
string HMMLDir;
string TemplatesDir;
string SearchTemplatePath;
@ -548,7 +547,7 @@ typedef struct project
struct project *Parent;
genre Genre;
numbering_scheme NumberingScheme;
numbering Numbering;
db_project_index Index;
template PlayerTemplate;
@ -905,7 +904,6 @@ InitTypeSpecs(void)
PushTypeSpecField(Root, FT_STRING, IDENT_INDEXER, FALSE);
PushTypeSpecField(Root, FT_STRING, IDENT_JS_PATH, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_LOG_LEVEL, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_OWNER, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
@ -925,7 +923,16 @@ InitTypeSpecs(void)
PushTypeSpecField(Root, FT_STRING, IDENT_TITLE_SUFFIX, TRUE);
//
////
PushTypeSpecField(Root, FT_STRING, IDENT_UNIT, TRUE);
// NOTE(matt): Numbering
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_FILENAME_PREFIX, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_METHOD, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Root, FT_NUMBER, IDENT_NUMBERING_START, TRUE);
PushTypeSpecField(Root, FT_STRING, IDENT_NUMBERING_UNIT, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_NUMBERING_ZERO_PAD, TRUE);
//
PushTypeSpecField(Root, FT_BARE, IDENT_TITLE_LIST_END, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_DENY_BESPOKE_TEMPLATES, TRUE);
PushTypeSpecField(Root, FT_BOOLEAN, IDENT_IGNORE_PRIVACY, TRUE);
@ -983,7 +990,6 @@ InitTypeSpecs(void)
PushTypeSpecField(Project, FT_STRING, IDENT_DEFAULT_MEDIUM, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_GENRE, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_HMML_DIR, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_OWNER, TRUE); // NOTE(matt): Do not remove, because ResolveLocalVariable() recognises it
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_LOCATION, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_PLAYER_TEMPLATE, TRUE);
@ -1013,7 +1019,16 @@ InitTypeSpecs(void)
PushTypeSpecField(Project, FT_STRING, IDENT_TITLE_SUFFIX, TRUE);
//
////
PushTypeSpecField(Project, FT_STRING, IDENT_UNIT, TRUE);
// NOTE(matt): Numbering
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_FILENAME_PREFIX, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_METHOD, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_SCHEME, TRUE);
PushTypeSpecField(Project, FT_NUMBER, IDENT_NUMBERING_START, TRUE);
PushTypeSpecField(Project, FT_STRING, IDENT_NUMBERING_UNIT, TRUE);
PushTypeSpecField(Project, FT_BOOLEAN, IDENT_NUMBERING_ZERO_PAD, TRUE);
//
PushTypeSpecField(Project, FT_BARE, IDENT_TITLE_LIST_END, TRUE);
// NOTE(matt): Modes
@ -2047,9 +2062,16 @@ SetDefaults(scope_tree *Root, memory_book *TypeSpecs)
PushDefaultPair(Root, IDENT_CACHE_DIR, Wrap0("$XDG_CACHE_HOME/cinera"));
PushDefaultPair(Root, IDENT_DB_LOCATION, Wrap0("$XDG_CONFIG_HOME/cinera/cinera.db"));
// NOTE(matt): Numbering
PushDefaultPair(Root, IDENT_NUMBERING_FILENAME_PREFIX, Wrap0("$project"));
PushDefaultIntPair(Root, IDENT_NUMBERING_METHOD, NM_FILENAME_DERIVED);
PushDefaultIntPair(Root, IDENT_NUMBERING_SCHEME, NS_LINEAR);
PushDefaultIntPair(Root, IDENT_NUMBERING_START, 1);
PushDefaultBoolPair(Root, IDENT_NUMBERING_ZERO_PAD, FALSE);
//
// TODO(matt): Consider where the genre setting should apply, project vs entry level
PushDefaultIntPair(Root, IDENT_GENRE, GENRE_VIDEO);
PushDefaultIntPair(Root, IDENT_NUMBERING_SCHEME, NS_LINEAR);
PushDefaultIntPair(Root, IDENT_PRIVACY_CHECK_INTERVAL, DEFAULT_PRIVACY_CHECK_INTERVAL);
PushDefaultIntPair(Root, IDENT_LOG_LEVEL, LOG_ERROR);
}
@ -2151,7 +2173,16 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; }
if(Field->ID == IDENT_NUMBERING_SCHEME)
if(Field->ID == IDENT_NUMBERING_METHOD)
{
numbering_method NumberingMethod = GetNumberingMethodFromString(&Filepath, &Value);
if(NumberingMethod != NM_COUNT)
{
AssignOrPushEnumValue(Parent, &Pair, Field->Singleton, NumberingMethod);
}
else { FreeScopeTree(Tree); return 0; }
}
else if(Field->ID == IDENT_NUMBERING_SCHEME)
{
numbering_scheme NumberingScheme = GetNumberingSchemeFromString(&Filepath, &Value);
if(NumberingScheme != NS_COUNT)
@ -3422,7 +3453,8 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
//case IDENT_TITLE_LIST_PREFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
//case IDENT_TITLE_LIST_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
//case IDENT_TITLE_SUFFIX: { C->Project[C->ProjectCount].HMMLDir = ResolveString(This); } break;
case IDENT_UNIT: { P->Unit = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_NUMBERING_FILENAME_PREFIX: { P->Numbering.FilenameDerived.Prefix = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_NUMBERING_UNIT: { P->Numbering.Unit = ResolveString(C, E, ProjectTree, This, FALSE); } break;
case IDENT_BASE_DIR:
{
P->BaseDir = StripSlashes(ResolveString(C, E, ProjectTree, This, TRUE), P_ABS);
@ -3478,16 +3510,16 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
case IDENT_ART_VARIANTS: { P->ArtVariants = This->int64_t; } break;
case IDENT_ICON_TYPE: { P->IconType = This->int64_t; } break;
case IDENT_ICON_VARIANTS: { P->IconVariants = This->int64_t; } break;
case IDENT_NUMBERING_SCHEME: { P->NumberingScheme = This->int64_t; } break;
case IDENT_NUMBERING_METHOD: { P->Numbering.Method = This->int64_t; } break;
case IDENT_NUMBERING_SCHEME: { P->Numbering.Scheme = This->int64_t; } break;
case IDENT_NUMBERING_START: { P->Numbering.Auto.StartingNumber = This->int64_t; } break;
case IDENT_VOD_PLATFORM: { P->VODPlatform = This->int64_t; } break;
// NOTE(matt): bool
case IDENT_DENY_BESPOKE_TEMPLATES:
{ P->DenyBespokeTemplates = This->bool; } break;
case IDENT_IGNORE_PRIVACY:
{ P->IgnorePrivacy = This->bool; } break;
case IDENT_SINGLE_BROWSER_TAB:
{ P->SingleBrowserTab = This->bool; } break;
case IDENT_DENY_BESPOKE_TEMPLATES: { P->DenyBespokeTemplates = This->bool; } break;
case IDENT_IGNORE_PRIVACY: { P->IgnorePrivacy = This->bool; } break;
case IDENT_NUMBERING_ZERO_PAD: { P->Numbering.Auto.ZeroPadded = This->bool; } break;
case IDENT_SINGLE_BROWSER_TAB: { P->SingleBrowserTab = This->bool; } break;
default: break;
}
@ -4329,6 +4361,16 @@ TypesetPair(typography *T, uint8_t Generation, config_identifier_id Key, string
}
}
void
TypesetNumber(typography *T, uint8_t Generation, config_identifier_id Key, int64_t Value)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
config_pair Pair = { .Key = Key, .int64_t = Value, .Type = PT_INT64 };
bool ShouldFillSyntax = FALSE;
PrintPair(&Pair, T->Delimiter, ShouldFillSyntax, 0, FALSE, FALSE);
}
void
TypesetBool(typography *T, uint8_t Generation, config_identifier_id Key, bool Value)
{
@ -4366,6 +4408,16 @@ TypesetVODPlatform(typography *T, uint8_t Generation, vod_platform P)
}
}
void
TypesetNumberingMethod(typography *T, uint8_t Generation, numbering_method N)
{
CarriageReturn(T, Generation);
fprintf(stderr, "%s", T->Margin);
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_NUMBERING_METHOD].String);
fprintf(stderr, "%s", T-> Delimiter);
PrintC(CS_GREEN_BOLD, NumberingMethodStrings[N]);
}
void
TypesetNumberingScheme(typography *T, uint8_t Generation, numbering_scheme N)
{
@ -4402,7 +4454,6 @@ PrintProject(config *C, project *P, typography *T, int Ancestors, int Indentatio
TypesetPair(T, Generation, IDENT_HTML_TITLE, P->HTMLTitle, AvailableColumns);
TypesetPair(T, Generation, IDENT_DEFAULT_MEDIUM, P->DefaultMedium ? P->DefaultMedium->ID : EmptyString(), AvailableColumns);
TypesetPair(T, Generation, IDENT_UNIT, P->Unit, AvailableColumns);
TypesetPair(T, Generation, IDENT_HMML_DIR, P->HMMLDir, AvailableColumns);
TypesetPair(T, Generation, IDENT_TEMPLATES_DIR, P->TemplatesDir, AvailableColumns);
TypesetPair(T, Generation, IDENT_SEARCH_TEMPLATE, P->SearchTemplatePath, AvailableColumns);
@ -4425,10 +4476,17 @@ PrintProject(config *C, project *P, typography *T, int Ancestors, int Indentatio
TypesetIconType(T, Generation, P->IconType, TRUE, FALSE);
TypesetVariants(T, Generation, IDENT_ICON_VARIANTS, P->IconVariants, AvailableColumns, 0, TRUE, FALSE);
CarriageReturn(T, Generation);
TypesetNumberingMethod(T, Generation, P->Numbering.Method);
TypesetNumberingScheme(T, Generation, P->Numbering.Scheme);
TypesetPair(T, Generation, IDENT_NUMBERING_UNIT, P->Numbering.Unit, AvailableColumns);
TypesetPair(T, Generation, IDENT_NUMBERING_FILENAME_PREFIX, P->Numbering.FilenameDerived.Prefix, AvailableColumns);
TypesetNumber(T, Generation, IDENT_NUMBERING_START, P->Numbering.Auto.StartingNumber);
TypesetBool(T, Generation, IDENT_NUMBERING_ZERO_PAD, P->Numbering.Auto.ZeroPadded);
CarriageReturn(T, Generation);
TypesetGenre(T, Generation, P->Genre);
TypesetVODPlatform(T, Generation, P->VODPlatform);
TypesetNumberingScheme(T, Generation, P->NumberingScheme);
TypesetBool(T, Generation, IDENT_IGNORE_PRIVACY, P->IgnorePrivacy);
TypesetBool(T, Generation, IDENT_SINGLE_BROWSER_TAB, P->SingleBrowserTab);