From 84a35b9ece93fa730ec52d241242272548c85f2f Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Mon, 1 Aug 2022 20:58:55 +0100 Subject: [PATCH] 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 --- cinera/cinera.c | 302 ++++++++++++++++++++++++++++++++--------- cinera/cinera.css | 8 +- cinera/cinera_config.c | 94 ++++++++++--- 3 files changed, 317 insertions(+), 87 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index 1d7e0ab..1102e6e 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 10, - .Patch = 7 + .Patch = 8 }; #include // 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; diff --git a/cinera/cinera.css b/cinera/cinera.css index 5bdf2f3..0b7a053 100644 --- a/cinera/cinera.css +++ b/cinera/cinera.css @@ -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; diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index 684b294..e3f37bb 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -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);