Add navigation settings, fix page generation
This commit is contained in:
parent
922ba4ebe7
commit
4d4275e072
@ -85,7 +85,6 @@
|
||||
E25DA5912D023A8400AEF16D /* IntegerField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5902D023A7E00AEF16D /* IntegerField.swift */; };
|
||||
E25DA5932D023B3C00AEF16D /* PageSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5922D023B3600AEF16D /* PageSettingsFile.swift */; };
|
||||
E25DA5952D023BD100AEF16D /* PageSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */; };
|
||||
E25DA5972D023F9F00AEF16D /* ContentPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5962D023F9900AEF16D /* ContentPage.swift */; };
|
||||
E25DA5992D02401E00AEF16D /* PageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5982D02401A00AEF16D /* PageGenerator.swift */; };
|
||||
E25DA59B2D024A2B00AEF16D /* DateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA59A2D024A2900AEF16D /* DateItem.swift */; };
|
||||
E29D31202D0320E70051B7F4 /* ContentLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D311F2D0320E20051B7F4 /* ContentLabels.swift */; };
|
||||
@ -196,6 +195,20 @@
|
||||
E2FE0EF42D1D6D2E002963B7 /* GeneralIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF32D1D6D22002963B7 /* GeneralIcons.swift */; };
|
||||
E2FE0EF62D1D6DF1002963B7 /* Icon.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF52D1D6DEE002963B7 /* Icon.swift */; };
|
||||
E2FE0EF82D1D8110002963B7 /* IconCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF72D1D810C002963B7 /* IconCommandProcessor.swift */; };
|
||||
E2FE0EFA2D25AFBA002963B7 /* PageHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF92D25AFB5002963B7 /* PageHeader.swift */; };
|
||||
E2FE0EFC2D266D22002963B7 /* NavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */; };
|
||||
E2FE0EFE2D266DA5002963B7 /* NavigationSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */; };
|
||||
E2FE0F002D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */; };
|
||||
E2FE0F022D266FCB002963B7 /* LocalizedNavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */; };
|
||||
E2FE0F042D267206002963B7 /* LocalizedNavigationBarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */; };
|
||||
E2FE0F062D267350002963B7 /* TextFieldPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F052D26734E002963B7 /* TextFieldPropertyView.swift */; };
|
||||
E2FE0F092D2689F0002963B7 /* TagPageGeneratorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F082D2689F0002963B7 /* TagPageGeneratorSource.swift */; };
|
||||
E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0A2D2689FF002963B7 /* FeedGeneratorSource.swift */; };
|
||||
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */; };
|
||||
E2FE0F0F2D268D4F002963B7 /* BoxCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */; };
|
||||
E2FE0F112D268E7E002963B7 /* PageCodeProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */; };
|
||||
E2FE0F152D26918F002963B7 /* PageHtmlProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */; };
|
||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -273,7 +286,6 @@
|
||||
E25DA5902D023A7E00AEF16D /* IntegerField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerField.swift; sourceTree = "<group>"; };
|
||||
E25DA5922D023B3600AEF16D /* PageSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettingsDetailView.swift; sourceTree = "<group>"; };
|
||||
E25DA5962D023F9900AEF16D /* ContentPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPage.swift; sourceTree = "<group>"; };
|
||||
E25DA5982D02401A00AEF16D /* PageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageGenerator.swift; sourceTree = "<group>"; };
|
||||
E25DA59A2D024A2900AEF16D /* DateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateItem.swift; sourceTree = "<group>"; };
|
||||
E29D311F2D0320E20051B7F4 /* ContentLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabels.swift; sourceTree = "<group>"; };
|
||||
@ -383,6 +395,20 @@
|
||||
E2FE0EF32D1D6D22002963B7 /* GeneralIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralIcons.swift; sourceTree = "<group>"; };
|
||||
E2FE0EF52D1D6DEE002963B7 /* Icon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icon.swift; sourceTree = "<group>"; };
|
||||
E2FE0EF72D1D810C002963B7 /* IconCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconCommandProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0EF92D25AFB5002963B7 /* PageHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHeader.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationBarSettingsView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F052D26734E002963B7 /* TextFieldPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldPropertyView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F082D2689F0002963B7 /* TagPageGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagPageGeneratorSource.swift; sourceTree = "<group>"; };
|
||||
E2FE0F0A2D2689FF002963B7 /* FeedGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedGeneratorSource.swift; sourceTree = "<group>"; };
|
||||
E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListPageGeneratorSource.swift; sourceTree = "<group>"; };
|
||||
E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxCommandProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageCodeProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHtmlProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageId.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -406,6 +432,7 @@
|
||||
E229901A2D0E3F09009F8D77 /* Item */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */,
|
||||
E229902B2D0F6FC0009F8D77 /* ItemId.swift */,
|
||||
E229901D2D0E4362009F8D77 /* LocalizedItem.swift */,
|
||||
E29D31A22D0CC98B0051B7F4 /* Item.swift */,
|
||||
@ -431,6 +458,8 @@
|
||||
E25DA5322D0041C400AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */,
|
||||
E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */,
|
||||
E29D31972D0C19300051B7F4 /* PathSettingsFile.swift */,
|
||||
E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */,
|
||||
E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */,
|
||||
@ -443,10 +472,12 @@
|
||||
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D31952D0C18690051B7F4 /* PathSettings.swift */,
|
||||
E25DA58E2D02368A00AEF16D /* PageSettings.swift */,
|
||||
E25DA5402D00446700AEF16D /* PostSettings.swift */,
|
||||
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */,
|
||||
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */,
|
||||
E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */,
|
||||
E25DA58E2D02368A00AEF16D /* PageSettings.swift */,
|
||||
E29D31952D0C18690051B7F4 /* PathSettings.swift */,
|
||||
E25DA5402D00446700AEF16D /* PostSettings.swift */,
|
||||
E21850322CFAFA200090B18B /* Settings.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
@ -455,6 +486,7 @@
|
||||
E25DA5782D01C56200AEF16D /* Generator */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F072D2689DC002963B7 /* Post Lists */,
|
||||
E29D31B62D0DAC030051B7F4 /* Page Content */,
|
||||
E22990232D0EDBD0009F8D77 /* HeaderElement.swift */,
|
||||
E29D31842D0AE8EE0051B7F4 /* KnownHeaderElement.swift */,
|
||||
@ -465,7 +497,6 @@
|
||||
E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */,
|
||||
E25DA5982D02401A00AEF16D /* PageGenerator.swift */,
|
||||
E2B85F3C2C4293F80047CD0C /* FeedPageGenerator.swift */,
|
||||
E29D316A2D07488B0051B7F4 /* PostListPageGenerator.swift */,
|
||||
E25DA5842D01C92600AEF16D /* ShorthandMarkdownKey.swift */,
|
||||
E29D31252D0370A50051B7F4 /* VideoOption.swift */,
|
||||
E29D318F2D0B34870051B7F4 /* GenerationAnomaly.swift */,
|
||||
@ -540,6 +571,9 @@
|
||||
E29D31B62D0DAC030051B7F4 /* Page Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */,
|
||||
E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */,
|
||||
E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */,
|
||||
E2FE0EF72D1D810C002963B7 /* IconCommandProcessor.swift */,
|
||||
E2FE0EED2D1C22EF002963B7 /* InlineLinkProcessor.swift */,
|
||||
E29D31BD2D0DB8560051B7F4 /* AudioPlayerCommand.swift */,
|
||||
@ -583,6 +617,7 @@
|
||||
E25DA5442D00952D00AEF16D /* SettingsSection.swift */,
|
||||
E2A21C352CB9A3D70060935B /* PathSettingsView.swift */,
|
||||
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */,
|
||||
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */,
|
||||
E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */,
|
||||
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */,
|
||||
E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */,
|
||||
@ -593,27 +628,28 @@
|
||||
E2A21C372CB9A4F10060935B /* Generic */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D31622D06E95A0051B7F4 /* NavigationIcon.swift */,
|
||||
E29D31332D03B5D30051B7F4 /* IconButton.swift */,
|
||||
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */,
|
||||
E25DA5902D023A7E00AEF16D /* IntegerField.swift */,
|
||||
E218501C2CEE6CB30090B18B /* VerticalCenter.swift */,
|
||||
E2A37D2C2CED2EEE0000979F /* OptionalTextField.swift */,
|
||||
E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */,
|
||||
E2A21C0F2CB18B390060935B /* FlowHStack.swift */,
|
||||
E22990292D0F5A10009F8D77 /* DetailTitle.swift */,
|
||||
E22990392D0F7E44009F8D77 /* GenericPropertyView.swift */,
|
||||
E22990272D0F5967009F8D77 /* IntegerPropertyView.swift */,
|
||||
E22990252D0F5822009F8D77 /* FilePropertyView.swift */,
|
||||
E229902D2D0F7278009F8D77 /* IdPropertyView.swift */,
|
||||
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */,
|
||||
E22990312D0F7678009F8D77 /* DatePropertyView.swift */,
|
||||
E22990332D0F77E4009F8D77 /* PagePropertyView.swift */,
|
||||
E22990352D0F79CC009F8D77 /* OptionalStringPropertyView.swift */,
|
||||
E229903D2D0F8EFD009F8D77 /* StringPropertyView.swift */,
|
||||
E22990372D0F7B2C009F8D77 /* OptionalImagePropertyView.swift */,
|
||||
E229903B2D0F8A74009F8D77 /* OptionalTextFieldPropertyView.swift */,
|
||||
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */,
|
||||
E22990292D0F5A10009F8D77 /* DetailTitle.swift */,
|
||||
E22990252D0F5822009F8D77 /* FilePropertyView.swift */,
|
||||
E2A21C0F2CB18B390060935B /* FlowHStack.swift */,
|
||||
E229903F2D0F95DA009F8D77 /* FolderOnDiskPropertyView.swift */,
|
||||
E22990392D0F7E44009F8D77 /* GenericPropertyView.swift */,
|
||||
E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */,
|
||||
E29D31332D03B5D30051B7F4 /* IconButton.swift */,
|
||||
E229902D2D0F7278009F8D77 /* IdPropertyView.swift */,
|
||||
E25DA5902D023A7E00AEF16D /* IntegerField.swift */,
|
||||
E22990272D0F5967009F8D77 /* IntegerPropertyView.swift */,
|
||||
E29D31622D06E95A0051B7F4 /* NavigationIcon.swift */,
|
||||
E22990372D0F7B2C009F8D77 /* OptionalImagePropertyView.swift */,
|
||||
E22990352D0F79CC009F8D77 /* OptionalStringPropertyView.swift */,
|
||||
E2A37D2C2CED2EEE0000979F /* OptionalTextField.swift */,
|
||||
E229903B2D0F8A74009F8D77 /* OptionalTextFieldPropertyView.swift */,
|
||||
E22990332D0F77E4009F8D77 /* PagePropertyView.swift */,
|
||||
E229903D2D0F8EFD009F8D77 /* StringPropertyView.swift */,
|
||||
E2FE0F052D26734E002963B7 /* TextFieldPropertyView.swift */,
|
||||
E218501C2CEE6CB30090B18B /* VerticalCenter.swift */,
|
||||
);
|
||||
path = Generic;
|
||||
sourceTree = "<group>";
|
||||
@ -686,15 +722,6 @@
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2B85F3E2C4293FF0047CD0C /* Pages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E25DA5962D023F9900AEF16D /* ContentPage.swift */,
|
||||
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */,
|
||||
);
|
||||
path = Pages;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2B85F3F2C42946E0047CD0C /* Page Elements */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -702,12 +729,14 @@
|
||||
E25DA58C2D021BA000AEF16D /* WebsiteImage.swift */,
|
||||
E25DA58A2D020C9200AEF16D /* PageImage.swift */,
|
||||
E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */,
|
||||
E2FE0EF92D25AFB5002963B7 /* PageHeader.swift */,
|
||||
E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */,
|
||||
E2B85F422C4294F60047CD0C /* FeedEntry.swift */,
|
||||
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */,
|
||||
E2A21C272CB29B290060935B /* FeedEntryData.swift */,
|
||||
E2B85F442C429ED60047CD0C /* ImageGallery.swift */,
|
||||
E2B85F402C4294790047CD0C /* PageHead.swift */,
|
||||
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */,
|
||||
);
|
||||
path = "Page Elements";
|
||||
sourceTree = "<group>";
|
||||
@ -785,7 +814,6 @@
|
||||
E2B85F392C428F020047CD0C /* Model */,
|
||||
E2B85F462C42C7CA0047CD0C /* Views */,
|
||||
E2B85F3F2C42946E0047CD0C /* Page Elements */,
|
||||
E2B85F3E2C4293FF0047CD0C /* Pages */,
|
||||
E2DD04792C276F32003BFF1F /* Assets.xcassets */,
|
||||
E2DD047B2C276F32003BFF1F /* CHDataManagement.entitlements */,
|
||||
E2B85F552C4BD0AD0047CD0C /* Extensions */,
|
||||
@ -809,6 +837,17 @@
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2FE0F072D2689DC002963B7 /* Post Lists */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */,
|
||||
E2FE0F0A2D2689FF002963B7 /* FeedGeneratorSource.swift */,
|
||||
E2FE0F082D2689F0002963B7 /* TagPageGeneratorSource.swift */,
|
||||
E29D316A2D07488B0051B7F4 /* PostListPageGenerator.swift */,
|
||||
);
|
||||
path = "Post Lists";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -899,7 +938,9 @@
|
||||
files = (
|
||||
E29D31242D0366860051B7F4 /* TagList.swift in Sources */,
|
||||
E22990282D0F596C009F8D77 /* IntegerPropertyView.swift in Sources */,
|
||||
E2FE0EFE2D266DA5002963B7 /* NavigationSettingsFile.swift in Sources */,
|
||||
E2FE0EE62D15A0B5002963B7 /* GenerationResults.swift in Sources */,
|
||||
E2FE0F152D26918F002963B7 /* PageHtmlProcessor.swift in Sources */,
|
||||
E25DA5772D018B9900AEF16D /* File+Mock.swift in Sources */,
|
||||
E25DA5892D01CBD300AEF16D /* Content+Generation.swift in Sources */,
|
||||
E229904C2D10BE5D009F8D77 /* InitialSetupView.swift in Sources */,
|
||||
@ -909,6 +950,7 @@
|
||||
E29D313B2D04464A0051B7F4 /* LocalizedTagDetailView.swift in Sources */,
|
||||
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
||||
E29D31BA2D0DB5080051B7F4 /* LabelsCommand.swift in Sources */,
|
||||
E2FE0EFC2D266D22002963B7 /* NavigationSettings.swift in Sources */,
|
||||
E29D31692D0702700051B7F4 /* TextFileContentView.swift in Sources */,
|
||||
E25DA5412D00446C00AEF16D /* PostSettings.swift in Sources */,
|
||||
E2A37D192CEA36A90000979F /* LocalizedTag.swift in Sources */,
|
||||
@ -922,6 +964,7 @@
|
||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
||||
E21850172CEE55FC0090B18B /* FileType.swift in Sources */,
|
||||
E2FE0F042D267206002963B7 /* LocalizedNavigationBarSettingsView.swift in Sources */,
|
||||
E29D31B12D0DA5510051B7F4 /* ContentIcon.swift in Sources */,
|
||||
E2A37D112CE537800000979F /* PageFile.swift in Sources */,
|
||||
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
||||
@ -947,7 +990,6 @@
|
||||
E25DA58D2D021BA400AEF16D /* WebsiteImage.swift in Sources */,
|
||||
E25DA5992D02401E00AEF16D /* PageGenerator.swift in Sources */,
|
||||
E25DA5382D00420E00AEF16D /* LocalizedPostSettingsFile.swift in Sources */,
|
||||
E25DA5972D023F9F00AEF16D /* ContentPage.swift in Sources */,
|
||||
E2B85F3D2C4293F80047CD0C /* FeedPageGenerator.swift in Sources */,
|
||||
E229902C2D0F6FC6009F8D77 /* ItemId.swift in Sources */,
|
||||
E25DA5952D023BD100AEF16D /* PageSettingsDetailView.swift in Sources */,
|
||||
@ -962,6 +1004,7 @@
|
||||
E29D31982D0C19340051B7F4 /* PathSettingsFile.swift in Sources */,
|
||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
||||
E2FE0F092D2689F0002963B7 /* TagPageGeneratorSource.swift in Sources */,
|
||||
E22990302D0F75DE009F8D77 /* BoolPropertyView.swift in Sources */,
|
||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */,
|
||||
E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */,
|
||||
@ -977,6 +1020,8 @@
|
||||
E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */,
|
||||
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */,
|
||||
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */,
|
||||
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */,
|
||||
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
|
||||
E2A21C332CB5BCAC0060935B /* PageContentView.swift in Sources */,
|
||||
@ -988,6 +1033,7 @@
|
||||
E25DA57A2D01C64400AEF16D /* PageContentGenerator.swift in Sources */,
|
||||
E2A21C202CB28ED20060935B /* MockImage.swift in Sources */,
|
||||
E2FE0EEE2D1C22F3002963B7 /* InlineLinkProcessor.swift in Sources */,
|
||||
E2FE0F022D266FCB002963B7 /* LocalizedNavigationSettings.swift in Sources */,
|
||||
E29D313F2D04822C0051B7F4 /* AddPostView.swift in Sources */,
|
||||
E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */,
|
||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||
@ -997,6 +1043,7 @@
|
||||
E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */,
|
||||
E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */,
|
||||
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */,
|
||||
E2FE0F062D267350002963B7 /* TextFieldPropertyView.swift in Sources */,
|
||||
E29D31B82D0DAC250051B7F4 /* ButtonCommand.swift in Sources */,
|
||||
E29D31962D0C186E0051B7F4 /* PathSettings.swift in Sources */,
|
||||
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */,
|
||||
@ -1009,6 +1056,7 @@
|
||||
E25DA5732D018AA100AEF16D /* FileContentView.swift in Sources */,
|
||||
E22990222D0ED12E009F8D77 /* TagOverviewFile.swift in Sources */,
|
||||
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */,
|
||||
E2FE0F002D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift in Sources */,
|
||||
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */,
|
||||
E29D31202D0320E70051B7F4 /* ContentLabels.swift in Sources */,
|
||||
E229904A2D10BB90009F8D77 /* SecurityScopeBookmark.swift in Sources */,
|
||||
@ -1024,8 +1072,10 @@
|
||||
E29D31A52D0CD03F0051B7F4 /* FileSelectionView.swift in Sources */,
|
||||
E22990322D0F767B009F8D77 /* DatePropertyView.swift in Sources */,
|
||||
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */,
|
||||
E2FE0EFA2D25AFBA002963B7 /* PageHeader.swift in Sources */,
|
||||
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */,
|
||||
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */,
|
||||
E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */,
|
||||
E2DD04742C276F31003BFF1F /* MainView.swift in Sources */,
|
||||
E29D31452D0488CB0051B7F4 /* SelectedContentView.swift in Sources */,
|
||||
E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */,
|
||||
@ -1038,10 +1088,12 @@
|
||||
E29D31C32D0DBEF20051B7F4 /* Song.swift in Sources */,
|
||||
E229902A2D0F5A14009F8D77 /* DetailTitle.swift in Sources */,
|
||||
E29D31532D0618740051B7F4 /* AddPageView.swift in Sources */,
|
||||
E2FE0F112D268E7E002963B7 /* PageCodeProcessor.swift in Sources */,
|
||||
E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */,
|
||||
E29D31C02D0DB9F20051B7F4 /* AudioPlayerContent.swift in Sources */,
|
||||
E22990192D0E3546009F8D77 /* ItemType.swift in Sources */,
|
||||
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
||||
E2FE0F0F2D268D4F002963B7 /* BoxCommandProcessor.swift in Sources */,
|
||||
E22990482D10B7B7009F8D77 /* StorageAccessError.swift in Sources */,
|
||||
E29D31832D0A43DB0051B7F4 /* RelatedPageLink.swift in Sources */,
|
||||
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
||||
|
@ -2,28 +2,34 @@ import Foundation
|
||||
|
||||
final class FeedPageGenerator {
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let content: Content
|
||||
|
||||
init(content: Content) {
|
||||
init(content: Content, results: PageGenerationResults) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
}
|
||||
|
||||
private func includeSwiper(in headers: inout Set<HeaderElement>) {
|
||||
if let swiperCss = content.settings.posts.swiperCssFile {
|
||||
headers.insert(.css(file: swiperCss, order: HeaderElement.swiperCssFileOrder))
|
||||
results.require(file: swiperCss)
|
||||
}
|
||||
if let swiperJs = content.settings.posts.swiperJsFile {
|
||||
headers.insert(.js(file: swiperJs, defer: true))
|
||||
results.require(file: swiperJs)
|
||||
}
|
||||
}
|
||||
|
||||
func generatePage(language: ContentLanguage,
|
||||
posts: [FeedEntryData],
|
||||
title: String,
|
||||
description: String,
|
||||
description: String?,
|
||||
showTitle: Bool,
|
||||
pageNumber: Int,
|
||||
totalPages: Int) -> String {
|
||||
totalPages: Int,
|
||||
languageButtonUrl: String) -> String {
|
||||
var headers = content.defaultPageHeaders
|
||||
var footer = ""
|
||||
if posts.contains(where: { $0.images.count > 1 }) {
|
||||
@ -32,12 +38,23 @@ final class FeedPageGenerator {
|
||||
footer = swiperInitScript(posts: posts)
|
||||
}
|
||||
|
||||
let page = GenericPage(
|
||||
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
|
||||
let languageButton = NavigationBar.Link(
|
||||
text: language.next.rawValue,
|
||||
url: languageButtonUrl)
|
||||
|
||||
let pageHeader = PageHeader(
|
||||
language: language,
|
||||
title: title,
|
||||
description: description,
|
||||
iconUrl: iconUrl,
|
||||
languageButton: languageButton,
|
||||
links: content.navigationBar(in: language),
|
||||
headers: headers,
|
||||
icons: [])
|
||||
|
||||
let page = GenericPage(
|
||||
header: pageHeader,
|
||||
additionalFooter: footer) { content in
|
||||
if showTitle {
|
||||
content += "<h1>\(title)</h1>"
|
||||
@ -48,7 +65,6 @@ final class FeedPageGenerator {
|
||||
if totalPages > 1 {
|
||||
content += PostFeedPageNavigation(currentPage: pageNumber, numberOfPages: totalPages, language: language).content
|
||||
}
|
||||
|
||||
}
|
||||
return page.content
|
||||
}
|
||||
|
@ -1,12 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
struct LocalizedPageId: Hashable {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let pageId: String
|
||||
}
|
||||
|
||||
final class GenerationResults: ObservableObject {
|
||||
|
||||
/// The files that could not be accessed
|
||||
@ -181,6 +174,10 @@ final class GenerationResults: ObservableObject {
|
||||
func unsaved(_ path: String) {
|
||||
update { self.unsavedOutputFiles.insert(path) }
|
||||
}
|
||||
|
||||
func empty(_ page: LocalizedPageId) {
|
||||
update {self.emptyPages.insert(page) }
|
||||
}
|
||||
}
|
||||
|
||||
private extension Dictionary where Value == Set<ItemId> {
|
||||
|
@ -0,0 +1,24 @@
|
||||
|
||||
struct BoxCommandProcessor: CommandProcessor {
|
||||
|
||||
let commandType: ShorthandMarkdownKey = .box
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
init(content: Content, results: PageGenerationResults) {
|
||||
self.results = results
|
||||
}
|
||||
|
||||
/**
|
||||
Format: ``
|
||||
*/
|
||||
func process(_ arguments: [String], markdown: Substring) -> String {
|
||||
guard arguments.count > 1 else {
|
||||
results.invalid(command: .box, markdown)
|
||||
return ""
|
||||
}
|
||||
let title = arguments[0]
|
||||
let text = arguments.dropFirst().joined(separator: ";")
|
||||
return ContentBox(title: title, text: text).content
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ struct IconCommandProcessor: CommandProcessor {
|
||||
|
||||
let commandType: ShorthandMarkdownKey = .icons
|
||||
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
init(content: Content, results: PageGenerationResults) {
|
||||
|
@ -13,7 +13,7 @@ struct InlineLinkProcessor {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
func handleLink(html: String, markdown: Substring) -> String {
|
||||
func process(html: String, markdown: Substring) -> String {
|
||||
let url = markdown.between("(", and: ")")
|
||||
if url.hasPrefix(pageLinkMarker) {
|
||||
return handleInlinePageLink(url: url, html: html, markdown: markdown)
|
||||
@ -56,7 +56,7 @@ struct InlineLinkProcessor {
|
||||
return markdown.between("[", and: "]")
|
||||
}
|
||||
results.linked(to: tag)
|
||||
let tagPath = content.absoluteUrlToTag(tag, language: language)
|
||||
let tagPath = tag.absoluteUrl(in: language)
|
||||
return html.replacingOccurrences(of: textToChange, with: tagPath)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,25 @@
|
||||
import Splash
|
||||
|
||||
struct PageCodeProcessor {
|
||||
|
||||
private let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
init(results: PageGenerationResults) {
|
||||
self.results = results
|
||||
}
|
||||
|
||||
func process(_ html: String, markdown: Substring) -> String {
|
||||
guard markdown.starts(with: "```swift") else {
|
||||
results.require(header: .codeHightlighting)
|
||||
results.require(footer: codeHighlightFooter)
|
||||
return html // Just use normal code highlighting
|
||||
}
|
||||
// Highlight swift code using Splash
|
||||
let code = markdown.between("```swift", and: "```").trimmed
|
||||
return "<pre><code>" + swift.highlight(code) + "</pre></code>"
|
||||
}
|
||||
}
|
153
CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift
Normal file
153
CHDataManagement/Generator/Page Content/PageHtmlProcessor.swift
Normal file
@ -0,0 +1,153 @@
|
||||
import SwiftSoup
|
||||
|
||||
/**
|
||||
Handles both inline HTML and the external HTML command
|
||||
*/
|
||||
struct PageHtmlProcessor: CommandProcessor {
|
||||
|
||||
let commandType: ShorthandMarkdownKey = .includedHtml
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let content: Content
|
||||
|
||||
init(content: Content, results: PageGenerationResults) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
}
|
||||
|
||||
/**
|
||||
Handle the HTML command
|
||||
|
||||
Format: ``
|
||||
*/
|
||||
func process(_ arguments: [String], markdown: Substring) -> String {
|
||||
guard arguments.count == 1 else {
|
||||
results.invalid(command: .includedHtml, markdown)
|
||||
return ""
|
||||
}
|
||||
let fileId = arguments[0]
|
||||
guard let file = content.file(fileId) else {
|
||||
results.missing(file: fileId, source: "External HTML command")
|
||||
return ""
|
||||
}
|
||||
let content = file.textContent()
|
||||
checkResources(in: content)
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
Handle inline HTML
|
||||
*/
|
||||
func process(_ html: String, markdown: Substring) -> String {
|
||||
checkResources(in: html)
|
||||
return html
|
||||
}
|
||||
|
||||
private func checkResources(in html: String) {
|
||||
let document: Document
|
||||
do {
|
||||
document = try SwiftSoup.parse(html)
|
||||
} catch {
|
||||
results.warning("Failed to parse inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
checkImages(in: document)
|
||||
checkLinks(in: document)
|
||||
checkSourceSets(in: document)
|
||||
}
|
||||
|
||||
private func checkImages(in document: Document) {
|
||||
let srcAttributes: [String]
|
||||
do {
|
||||
let imgElements = try document.select("img")
|
||||
|
||||
srcAttributes = try imgElements.array()
|
||||
.compactMap { try $0.attr("src") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
} catch {
|
||||
results.warning("Failed to check 'src' attributes of <img> elements in inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
for src in srcAttributes {
|
||||
results.warning("Found image in html: \(src)")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkLinks(in document: Document) {
|
||||
let hrefs: [String]
|
||||
do {
|
||||
let linkElements = try document.select("a")
|
||||
|
||||
hrefs = try linkElements.array()
|
||||
.compactMap { try $0.attr("href").trimmed }
|
||||
.filter { !$0.isEmpty }
|
||||
} catch {
|
||||
results.warning("Failed to check 'href' attributes of <a> elements in inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
for url in hrefs {
|
||||
if url.hasPrefix("http://") || url.hasPrefix("https://") {
|
||||
results.externalLink(to: url)
|
||||
} else {
|
||||
results.warning("Relative link in HTML: \(url)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func checkSourceSets(in document: Document) {
|
||||
let sources: [Element]
|
||||
do {
|
||||
sources = try document.select("source").array()
|
||||
} catch {
|
||||
results.warning("Failed to find <source> elements in inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func checkSourceSetAttributes(sources: [Element]) {
|
||||
let srcSets: [String]
|
||||
do {
|
||||
srcSets = try sources
|
||||
.compactMap { try $0.attr("srcset") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
} catch {
|
||||
results.warning("Failed to check 'srcset' attributes of <source> elements in inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
for src in srcSets {
|
||||
results.warning("Found source set in html: \(src)")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkSourceAttributes(sources: [Element]) {
|
||||
let srcAttributes: [String]
|
||||
do {
|
||||
srcAttributes = try sources
|
||||
.compactMap { try $0.attr("src") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
} catch {
|
||||
results.warning("Failed to check 'src' attributes of <source> elements in inline HTML: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
for src in srcAttributes {
|
||||
guard content.isValidIdForFile(src) else {
|
||||
results.warning("Found source in html: \(src)")
|
||||
continue
|
||||
}
|
||||
guard let file = content.file(src) else {
|
||||
results.warning("Found source in html: \(src)")
|
||||
continue
|
||||
}
|
||||
#warning("Either find files by their full path, or replace file id with full path")
|
||||
results.require(file: file)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +1,33 @@
|
||||
import Foundation
|
||||
import Ink
|
||||
import Splash
|
||||
import SwiftSoup
|
||||
|
||||
final class PageContentParser {
|
||||
|
||||
private static let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
private let language: ContentLanguage
|
||||
|
||||
private let content: Content
|
||||
|
||||
private let results: PageGenerationResults
|
||||
|
||||
// MARK: Command handlers
|
||||
|
||||
private let buttonHandler: ButtonCommandProcessor
|
||||
|
||||
private let labelHandler: LabelsCommandProcessor
|
||||
|
||||
private let audioPlayer: AudioPlayerCommandProcessor
|
||||
|
||||
private let icons: IconCommandProcessor
|
||||
|
||||
private let box: BoxCommandProcessor
|
||||
|
||||
private let html: PageHtmlProcessor
|
||||
|
||||
// MARK: Other handlers
|
||||
|
||||
private let inlineLink: InlineLinkProcessor
|
||||
|
||||
private let icons: IconCommandProcessor
|
||||
private let code: PageCodeProcessor
|
||||
|
||||
var largeImageWidth: Int {
|
||||
content.settings.pages.largeImageWidth
|
||||
@ -40,127 +44,25 @@ final class PageContentParser {
|
||||
self.buttonHandler = .init(content: content, results: results)
|
||||
self.labelHandler = .init(content: content, results: results)
|
||||
self.audioPlayer = .init(content: content, results: results)
|
||||
self.inlineLink = .init(content: content, results: results, language: language)
|
||||
self.icons = .init(content: content, results: results)
|
||||
self.box = .init(content: content, results: results)
|
||||
self.html = .init(content: content, results: results)
|
||||
|
||||
self.inlineLink = .init(content: content, results: results, language: language)
|
||||
self.code = .init(results: results)
|
||||
}
|
||||
|
||||
func generatePage(from content: String) -> String {
|
||||
let parser = MarkdownParser(modifiers: [
|
||||
Modifier(target: .images, closure: processMarkdownImage),
|
||||
Modifier(target: .codeBlocks, closure: handleCode),
|
||||
Modifier(target: .links, closure: inlineLink.handleLink),
|
||||
Modifier(target: .html, closure: handleHTML),
|
||||
Modifier(target: .codeBlocks, closure: code.process),
|
||||
Modifier(target: .links, closure: inlineLink.process),
|
||||
Modifier(target: .html, closure: html.process),
|
||||
Modifier(target: .headings, closure: handleHeadlines)
|
||||
])
|
||||
return parser.html(from: content)
|
||||
}
|
||||
|
||||
private func handleCode(html: String, markdown: Substring) -> String {
|
||||
guard markdown.starts(with: "```swift") else {
|
||||
results.require(header: .codeHightlighting)
|
||||
results.require(footer: PageContentParser.codeHighlightFooter)
|
||||
return html // Just use normal code highlighting
|
||||
}
|
||||
// Highlight swift code using Splash
|
||||
let code = markdown.between("```swift", and: "```").trimmed
|
||||
return "<pre><code>" + swift.highlight(code) + "</pre></code>"
|
||||
}
|
||||
|
||||
private func handleHTML(html: String, _: Substring) -> String {
|
||||
findResourcesInHtml(html: html)
|
||||
return html
|
||||
}
|
||||
|
||||
private func findResourcesInHtml(html: String) {
|
||||
findImages(in: html)
|
||||
findLinks(in: html)
|
||||
findSourceSets(in: html)
|
||||
}
|
||||
|
||||
private func findImages(in markdown: String) {
|
||||
do {
|
||||
// Parse the HTML string
|
||||
let document = try SwiftSoup.parse(markdown)
|
||||
|
||||
// Select all 'img' elements
|
||||
let imgElements = try document.select("img")
|
||||
|
||||
// Extract the 'src' attributes from each 'img' element
|
||||
let srcAttributes = try imgElements.array()
|
||||
.compactMap { try $0.attr("src") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
|
||||
for src in srcAttributes {
|
||||
results.warning("Found image in html: \(src)")
|
||||
}
|
||||
} catch {
|
||||
print("Error parsing HTML: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func findLinks(in markdown: String) {
|
||||
do {
|
||||
// Parse the HTML string
|
||||
let document = try SwiftSoup.parse(markdown)
|
||||
|
||||
// Select all 'img' elements
|
||||
let linkElements = try document.select("a")
|
||||
|
||||
// Extract the 'src' attributes from each 'img' element
|
||||
let srcAttributes = try linkElements.array()
|
||||
.compactMap { try $0.attr("href").trimmed }
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
for url in srcAttributes {
|
||||
if url.hasPrefix("http://") || url.hasPrefix("https://") {
|
||||
results.externalLink(to: url)
|
||||
} else {
|
||||
results.warning("Relative link in HTML: \(url)")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Error parsing HTML: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func findSourceSets(in markdown: String) {
|
||||
do {
|
||||
// Parse the HTML string
|
||||
let document = try SwiftSoup.parse(markdown)
|
||||
|
||||
// Select all 'img' elements
|
||||
let linkElements = try document.select("source")
|
||||
|
||||
// Extract the 'src' attributes from each 'img' element
|
||||
let srcsetAttributes = try linkElements.array()
|
||||
.compactMap { try $0.attr("srcset") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
|
||||
for src in srcsetAttributes {
|
||||
results.warning("Found source set in html: \(src)")
|
||||
}
|
||||
|
||||
let srcAttributes = try linkElements.array()
|
||||
.compactMap { try $0.attr("src") }
|
||||
.filter { !$0.trimmed.isEmpty }
|
||||
|
||||
for src in srcAttributes {
|
||||
guard content.isValidIdForFile(src) else {
|
||||
results.warning("Found source in html: \(src)")
|
||||
continue
|
||||
}
|
||||
guard let file = content.file(src) else {
|
||||
results.warning("Found source in html: \(src)")
|
||||
continue
|
||||
}
|
||||
#warning("Either find files by their full path, or replace file id with full path")
|
||||
results.require(file: file)
|
||||
}
|
||||
} catch {
|
||||
print("Error parsing HTML: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Modify headlines by extracting an id from the headline and adding it into the html element
|
||||
|
||||
@ -218,9 +120,9 @@ final class PageContentParser {
|
||||
case .pageLink:
|
||||
return handlePageLink(arguments, markdown: markdown)
|
||||
case .includedHtml:
|
||||
return handleExternalHtml(arguments, markdown: markdown)
|
||||
return self.html.process(arguments, markdown: markdown)
|
||||
case .box:
|
||||
return handleSimpleBox(arguments, markdown: markdown)
|
||||
return box.process(arguments, markdown: markdown)
|
||||
case .model:
|
||||
return handleModel(arguments, markdown: markdown)
|
||||
case .svg:
|
||||
@ -343,37 +245,6 @@ final class PageContentParser {
|
||||
return option
|
||||
}
|
||||
|
||||
/**
|
||||
Format: ``
|
||||
*/
|
||||
private func handleExternalHtml(_ arguments: [String], markdown: Substring) -> String {
|
||||
guard arguments.count == 1 else {
|
||||
results.invalid(command: .includedHtml, markdown)
|
||||
return ""
|
||||
}
|
||||
let fileId = arguments[0]
|
||||
guard let file = content.file(fileId) else {
|
||||
results.missing(file: fileId, source: "External HTML command")
|
||||
return ""
|
||||
}
|
||||
let content = file.textContent()
|
||||
findResourcesInHtml(html: content)
|
||||
return content
|
||||
}
|
||||
|
||||
/**
|
||||
Format: ``
|
||||
*/
|
||||
private func handleSimpleBox(_ arguments: [String], markdown: Substring) -> String {
|
||||
guard arguments.count > 1 else {
|
||||
results.invalid(command: .box, markdown)
|
||||
return ""
|
||||
}
|
||||
let title = arguments[0]
|
||||
let text = arguments.dropFirst().joined(separator: ";")
|
||||
return ContentBox(title: title, text: text).content
|
||||
}
|
||||
|
||||
/**
|
||||
Format: ``
|
||||
*/
|
||||
|
@ -73,6 +73,8 @@ final class PageGenerationResults: ObservableObject {
|
||||
/// The files that could not be saved to the output folder
|
||||
private(set) var unsavedOutputFiles: [String: Set<ItemType>] = [:]
|
||||
|
||||
private(set) var pageIsEmpty: Bool
|
||||
|
||||
init(itemId: ItemId, delegate: GenerationResults) {
|
||||
self.itemId = itemId
|
||||
self.delegate = delegate
|
||||
@ -94,33 +96,7 @@ final class PageGenerationResults: ObservableObject {
|
||||
invalidCommands = []
|
||||
warnings = []
|
||||
unsavedOutputFiles = [:]
|
||||
}
|
||||
|
||||
private init(other: PageGenerationResults) {
|
||||
self.itemId = other.itemId
|
||||
self.delegate = other.delegate
|
||||
inaccessibleFiles = other.inaccessibleFiles
|
||||
unparsableFiles = other.unparsableFiles
|
||||
missingFiles = other.missingFiles
|
||||
missingLinkedFiles = other.missingLinkedFiles
|
||||
missingLinkedTags = other.missingLinkedTags
|
||||
missingLinkedPages = other.missingLinkedPages
|
||||
requiredHeaders = other.requiredHeaders
|
||||
requiredFooters = other.requiredFooters
|
||||
requiredIcons = other.requiredIcons
|
||||
linkedPages = other.linkedPages
|
||||
linkedTags = other.linkedTags
|
||||
externalLinks = other.externalLinks
|
||||
usedFiles = other.usedFiles
|
||||
requiredFiles = other.requiredFiles
|
||||
imagesToGenerate = other.imagesToGenerate
|
||||
invalidCommands = other.invalidCommands
|
||||
warnings = other.warnings
|
||||
unsavedOutputFiles = other.unsavedOutputFiles
|
||||
}
|
||||
|
||||
func copy() -> PageGenerationResults {
|
||||
.init(other: self)
|
||||
pageIsEmpty = false
|
||||
}
|
||||
|
||||
// MARK: Adding entries
|
||||
@ -231,5 +207,11 @@ final class PageGenerationResults: ObservableObject {
|
||||
unsavedOutputFiles[path, default: []].insert(source)
|
||||
delegate.unsaved(path)
|
||||
}
|
||||
|
||||
func markPageAsEmpty() {
|
||||
guard case .page(let page) = itemId.itemType else { return }
|
||||
pageIsEmpty = true
|
||||
delegate.empty(.init(language: itemId.language, pageId: page.id))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,13 +19,32 @@ final class PageGenerator {
|
||||
return result
|
||||
}
|
||||
|
||||
private func makeEmptyPageContent(in language: ContentLanguage) -> String {
|
||||
switch language {
|
||||
case .english:
|
||||
return ContentBox(
|
||||
title: "Content not available",
|
||||
text: "This page is not available yet. Try the German version or check back later.")
|
||||
.content
|
||||
case .german:
|
||||
return ContentBox(
|
||||
title: "Inhalt nicht verfügbar",
|
||||
text: "Diese Seite ist noch nicht verfügbar. Versuche die englische Version oder komm später hierher zurück.")
|
||||
.content
|
||||
}
|
||||
}
|
||||
|
||||
func generate(page: Page, language: ContentLanguage, results: PageGenerationResults) -> String? {
|
||||
let contentGenerator = PageContentParser(
|
||||
content: content,
|
||||
language: language, results: results)
|
||||
|
||||
guard let rawPageContent = content.storage.pageContent(for: page.id, language: language) else {
|
||||
return nil
|
||||
let rawPageContent: String
|
||||
if let existing = content.storage.pageContent(for: page.id, language: language) {
|
||||
rawPageContent = existing
|
||||
} else {
|
||||
rawPageContent = makeEmptyPageContent(in: language)
|
||||
results.markPageAsEmpty()
|
||||
}
|
||||
|
||||
let pageContent = contentGenerator.generatePage(from: rawPageContent)
|
||||
@ -34,27 +53,41 @@ final class PageGenerator {
|
||||
|
||||
let tags: [FeedEntryData.Tag] = page.tags.map { tag in
|
||||
.init(name: tag.localized(in: language).name,
|
||||
url: content.absoluteUrlToTag(tag, language: language))
|
||||
url: tag.absoluteUrl(in: language))
|
||||
}
|
||||
|
||||
let headers = makeHeaders(requiredItems: results.requiredHeaders)
|
||||
results.require(files: headers.compactMap { $0.file })
|
||||
|
||||
let fullPage = ContentPage(
|
||||
language: language,
|
||||
dateString: page.dateText(in: language),
|
||||
title: localized.title,
|
||||
showTitle: !localized.hideTitle,
|
||||
tags: tags,
|
||||
linkTitle: localized.linkPreviewTitle ?? localized.title,
|
||||
description: localized.linkPreviewDescription ?? "",
|
||||
navigationBarLinks: content.navigationBar(in: language),
|
||||
pageContent: pageContent,
|
||||
headers: headers,
|
||||
footers: results.requiredFooters.sorted(),
|
||||
icons: results.requiredIcons)
|
||||
.content
|
||||
let iconUrl = content.settings.navigation.localized(in: language).rootUrl
|
||||
let languageUrl = page.absoluteUrl(in: language.next)
|
||||
let languageButton = NavigationBar.Link(
|
||||
text: language.next.rawValue,
|
||||
url: languageUrl)
|
||||
|
||||
return fullPage
|
||||
let pageHeader = PageHeader(
|
||||
language: language,
|
||||
title: localized.linkPreviewTitle ?? localized.title,
|
||||
description: localized.linkPreviewDescription,
|
||||
iconUrl: iconUrl,
|
||||
languageButton: languageButton,
|
||||
links: content.navigationBar(in: language),
|
||||
headers: headers,
|
||||
icons: results.requiredIcons)
|
||||
|
||||
let fullPage = GenericPage(
|
||||
header: pageHeader,
|
||||
additionalFooter: results.requiredFooters.sorted().joined()) { content in
|
||||
content += "<article>"
|
||||
if !localized.hideTitle {
|
||||
content += "<h3>\(page.dateText(in: language))</h3>"
|
||||
content += "<h1>\(localized.title)</h1>"
|
||||
content += TagList(tags: tags).content
|
||||
}
|
||||
content += pageContent
|
||||
content += "</article>"
|
||||
}
|
||||
|
||||
return fullPage.content
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
|
||||
struct FeedGeneratorSource: PostListPageGeneratorSource {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let content: Content
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
var showTitle: Bool {
|
||||
false
|
||||
}
|
||||
|
||||
var pageTitle: String {
|
||||
content.settings.localized(in: language).title
|
||||
}
|
||||
|
||||
var pageDescription: String {
|
||||
content.settings.localized(in: language).description
|
||||
}
|
||||
|
||||
func pageUrlPrefix(for language: ContentLanguage) -> String {
|
||||
content.settings.localized(in: language).feedUrlPrefix
|
||||
}
|
||||
}
|
@ -2,42 +2,26 @@ import Foundation
|
||||
|
||||
final class PostListPageGenerator {
|
||||
|
||||
private let language: ContentLanguage
|
||||
let source: PostListPageGeneratorSource
|
||||
|
||||
private let content: Content
|
||||
init(source: PostListPageGeneratorSource) {
|
||||
self.source = source
|
||||
}
|
||||
|
||||
private let results: PageGenerationResults
|
||||
|
||||
private let showTitle: Bool
|
||||
|
||||
private let pageTitle: String
|
||||
|
||||
private let pageDescription: String
|
||||
|
||||
/// The url of the page, excluding the extension
|
||||
private let pageUrlPrefix: String
|
||||
|
||||
init(language: ContentLanguage,
|
||||
content: Content,
|
||||
results: PageGenerationResults,
|
||||
showTitle: Bool, pageTitle: String,
|
||||
pageDescription: String,
|
||||
pageUrlPrefix: String) {
|
||||
self.language = language
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.showTitle = showTitle
|
||||
self.pageTitle = pageTitle
|
||||
self.pageDescription = pageDescription
|
||||
self.pageUrlPrefix = pageUrlPrefix
|
||||
private var language: ContentLanguage {
|
||||
source.language
|
||||
}
|
||||
|
||||
private var mainContentMaximumWidth: Int {
|
||||
content.settings.posts.contentWidth
|
||||
source.content.settings.posts.contentWidth
|
||||
}
|
||||
|
||||
private var postsPerPage: Int {
|
||||
content.settings.posts.postsPerPage
|
||||
source.content.settings.posts.postsPerPage
|
||||
}
|
||||
|
||||
private func pageUrl(in language: ContentLanguage, pageNumber: Int) -> String {
|
||||
"\(source.pageUrlPrefix(for: language))/\(pageNumber).html"
|
||||
}
|
||||
|
||||
func createPages(for posts: [Post]) {
|
||||
@ -67,7 +51,7 @@ final class PostListPageGenerator {
|
||||
|
||||
let tags: [FeedEntryData.Tag] = post.tags.filter { $0.isVisible }.map { tag in
|
||||
.init(name: tag.localized(in: language).name,
|
||||
url: content.absoluteUrlToTag(tag, language: language))
|
||||
url: tag.absoluteUrl(in: language))
|
||||
}
|
||||
|
||||
let images = localized.images.map(createFeedImage)
|
||||
@ -82,25 +66,28 @@ final class PostListPageGenerator {
|
||||
images: images)
|
||||
}
|
||||
|
||||
let feedPageGenerator = FeedPageGenerator(content: content)
|
||||
let feedPageGenerator = FeedPageGenerator(content: source.content, results: source.results)
|
||||
|
||||
let languageButtonUrl = pageUrl(in: language.next, pageNumber: pageIndex)
|
||||
|
||||
let fileContent = feedPageGenerator.generatePage(
|
||||
language: language,
|
||||
posts: posts,
|
||||
title: pageTitle,
|
||||
description: pageDescription,
|
||||
showTitle: showTitle,
|
||||
title: source.pageTitle,
|
||||
description: source.pageDescription,
|
||||
showTitle: source.showTitle,
|
||||
pageNumber: pageIndex,
|
||||
totalPages: pageCount)
|
||||
let filePath = "\(pageUrlPrefix)/\(pageIndex).html"
|
||||
totalPages: pageCount,
|
||||
languageButtonUrl: languageButtonUrl)
|
||||
let filePath = pageUrl(in: language, pageNumber: pageIndex)
|
||||
guard save(fileContent, to: filePath) else {
|
||||
results.unsavedOutput(filePath, source: .feed)
|
||||
source.results.unsavedOutput(filePath, source: .feed)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func createFeedImage(for image: FileResource) -> FeedEntryData.Image {
|
||||
results.requireImageSet(for: image, size: mainContentMaximumWidth)
|
||||
source.results.requireImageSet(for: image, size: mainContentMaximumWidth)
|
||||
return .init(
|
||||
rawImagePath: image.absoluteUrl,
|
||||
width: mainContentMaximumWidth,
|
||||
@ -109,6 +96,6 @@ final class PostListPageGenerator {
|
||||
}
|
||||
|
||||
private func save(_ content: String, to relativePath: String) -> Bool {
|
||||
self.content.storage.write(content, to: relativePath)
|
||||
source.content.storage.write(content, to: relativePath)
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
|
||||
protocol PostListPageGeneratorSource {
|
||||
|
||||
var language: ContentLanguage { get }
|
||||
|
||||
var content: Content { get }
|
||||
|
||||
var results: PageGenerationResults { get }
|
||||
|
||||
var showTitle: Bool { get }
|
||||
|
||||
var pageTitle: String { get }
|
||||
|
||||
var pageDescription: String { get }
|
||||
|
||||
func pageUrlPrefix(for language: ContentLanguage) -> String
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
|
||||
struct TagPageGeneratorSource: PostListPageGeneratorSource {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let content: Content
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let tag: Tag
|
||||
|
||||
var showTitle: Bool {
|
||||
true
|
||||
}
|
||||
|
||||
var pageTitle: String {
|
||||
tag.localized(in: language).name
|
||||
}
|
||||
|
||||
var pageDescription: String {
|
||||
tag.localized(in: language).description ?? ""
|
||||
}
|
||||
|
||||
func pageUrlPrefix(for language: ContentLanguage) -> String {
|
||||
tag.absoluteUrl(in: language)
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ enum ShorthandMarkdownKey: String {
|
||||
/// Format: ``
|
||||
case tagLink = "tag"
|
||||
|
||||
/// Additional HTML code include verbatim into the page.
|
||||
/// Additional HTML code included verbatim into the page.
|
||||
/// Format: ``
|
||||
case includedHtml = "html"
|
||||
|
||||
|
@ -5,6 +5,7 @@ import SFSafeSymbols
|
||||
#warning("Fix CV")
|
||||
#warning("Fix endeavor basics (image compare)")
|
||||
|
||||
#warning("Add custom url string to external files (optional)")
|
||||
#warning("Show all warnings on page content")
|
||||
#warning("Button to delete file")
|
||||
#warning("Add link to other language")
|
||||
|
@ -106,20 +106,6 @@ extension Content {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Paths to items
|
||||
|
||||
private func makeCleanAbsolutePath(_ path: String) -> String {
|
||||
("/" + path).replacingOccurrences(of: "//", with: "/")
|
||||
}
|
||||
|
||||
func absoluteUrlPrefixForTag(_ tag: Tag, language: ContentLanguage) -> String {
|
||||
makeCleanAbsolutePath(settings.paths.tagsOutputFolderPath + "/" + tag.localized(in: language).urlComponent)
|
||||
}
|
||||
|
||||
func absoluteUrlToTag(_ tag: Tag, language: ContentLanguage) -> String {
|
||||
absoluteUrlPrefixForTag(tag, language: language) + ".html"
|
||||
}
|
||||
|
||||
// MARK: Find items by id
|
||||
|
||||
func page(_ pageId: String) -> Page? {
|
||||
@ -145,7 +131,7 @@ extension Content {
|
||||
// MARK: Generation input
|
||||
|
||||
func navigationBar(in language: ContentLanguage) -> [NavigationBar.Link] {
|
||||
settings.navigationItems.map {
|
||||
settings.navigation.navigationItems.map {
|
||||
.init(text: $0.title(in: language),
|
||||
url: $0.absoluteUrl(in: language))
|
||||
}
|
||||
@ -197,9 +183,6 @@ extension Content {
|
||||
continue
|
||||
}
|
||||
for language in ContentLanguage.allCases {
|
||||
guard page.hasContent(in: language) else {
|
||||
continue
|
||||
}
|
||||
generateInternal(page, in: language)
|
||||
}
|
||||
}
|
||||
@ -213,14 +196,12 @@ extension Content {
|
||||
for language in ContentLanguage.allCases {
|
||||
guard shouldGenerateWebsite else { return }
|
||||
let results = results.makeResults(for: .feed, in: language)
|
||||
let generator = PostListPageGenerator(
|
||||
let source = FeedGeneratorSource(
|
||||
language: language,
|
||||
content: self,
|
||||
results: results,
|
||||
showTitle: false,
|
||||
pageTitle: settings.localized(in: language).title,
|
||||
pageDescription: settings.localized(in: language).description,
|
||||
pageUrlPrefix: settings.localized(in: language).feedUrlPrefix)
|
||||
results: results)
|
||||
|
||||
let generator = PostListPageGenerator(source: source)
|
||||
generator.createPages(for: posts)
|
||||
}
|
||||
|
||||
@ -249,16 +230,12 @@ extension Content {
|
||||
let posts = posts.filter { $0.tags.contains(tag) }
|
||||
guard posts.count > 0 else { continue }
|
||||
|
||||
let localized = tag.localized(in: language)
|
||||
let urlPrefix = absoluteUrlPrefixForTag(tag, language: language)
|
||||
let generator = PostListPageGenerator(
|
||||
let source = TagPageGeneratorSource(
|
||||
language: language,
|
||||
content: self,
|
||||
results: results,
|
||||
showTitle: true,
|
||||
pageTitle: localized.name,
|
||||
pageDescription: localized.description ?? "",
|
||||
pageUrlPrefix: urlPrefix)
|
||||
tag: tag)
|
||||
let generator = PostListPageGenerator(source: source)
|
||||
generator.createPages(for: posts)
|
||||
}
|
||||
}
|
||||
|
@ -146,52 +146,10 @@ extension Content {
|
||||
self.files = files.values.sorted { $0.id }
|
||||
self.posts = posts.values.sorted(ascending: false) { $0.startDate }
|
||||
self.tagOverview = tagOverview
|
||||
self.settings = makeSettings(settings, tags: tags, pages: pages, files: files, posts: posts)
|
||||
self.settings = .init(file: settings, tags: tags, pages: pages, files: files, posts: posts, tagOverview: tagOverview)
|
||||
print("Content loaded")
|
||||
}
|
||||
|
||||
private func makeSettings(_ settings: SettingsFile,
|
||||
tags: [String : Tag],
|
||||
pages: [String : Page],
|
||||
files: [String : FileResource],
|
||||
posts: [String : Post]) -> Settings {
|
||||
|
||||
#warning("Notify about missing links")
|
||||
let navigationItems: [Item] = settings.navigationItems.compactMap { raw in
|
||||
guard let type = ItemType(rawValue: raw, posts: posts, pages: pages, tags: tags) else {
|
||||
return nil
|
||||
}
|
||||
switch type {
|
||||
case .general:
|
||||
return nil
|
||||
case .post(let post):
|
||||
return post
|
||||
case .feed:
|
||||
return nil // TODO: Provide feed object
|
||||
case .page(let page):
|
||||
return page
|
||||
case .tagPage(let tag):
|
||||
return tag
|
||||
case .tagOverview:
|
||||
return tagOverview
|
||||
}
|
||||
}
|
||||
|
||||
let posts = PostSettings(file: settings.posts, files: files)
|
||||
|
||||
let pages = PageSettings(file: settings.pages, files: files)
|
||||
|
||||
let paths = PathSettings(file: settings.paths)
|
||||
|
||||
return Settings(
|
||||
paths: paths,
|
||||
navigationItems: navigationItems,
|
||||
posts: posts,
|
||||
pages: pages,
|
||||
german: .init(file: settings.german),
|
||||
english: .init(file: settings.english))
|
||||
}
|
||||
|
||||
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag], files: [String : FileResource]) -> [String : Page] {
|
||||
pagesData.reduce(into: [:]) { pages, data in
|
||||
let (pageId, page) = data
|
||||
|
@ -133,37 +133,3 @@ private extension LocalizedTag {
|
||||
originalURL: originalUrl)
|
||||
}
|
||||
}
|
||||
|
||||
extension Settings {
|
||||
|
||||
var file: SettingsFile {
|
||||
.init(
|
||||
paths: paths.file,
|
||||
navigationItems: navigationItems.map { $0.itemType.id },
|
||||
posts: posts.file,
|
||||
pages: pages.file,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
}
|
||||
|
||||
private extension PostSettings {
|
||||
|
||||
var file: PostSettingsFile {
|
||||
.init(postsPerPage: postsPerPage,
|
||||
contentWidth: contentWidth,
|
||||
swiperCssFile: swiperCssFile?.id,
|
||||
swiperJsFile: swiperJsFile?.id,
|
||||
defaultCssFile: defaultCssFile?.id)
|
||||
}
|
||||
}
|
||||
|
||||
private extension LocalizedPostSettings {
|
||||
|
||||
var file: LocalizedPostSettingsFile {
|
||||
.init(
|
||||
feedTitle: title,
|
||||
feedDescription: description,
|
||||
feedUrlPrefix: feedUrlPrefix)
|
||||
}
|
||||
}
|
||||
|
@ -32,3 +32,13 @@ extension ContentLanguage: Comparable {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension ContentLanguage {
|
||||
|
||||
var next: ContentLanguage {
|
||||
switch self {
|
||||
case .english: return .german
|
||||
case .german: return .english
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,6 +67,8 @@ enum FileType: String {
|
||||
|
||||
case js
|
||||
|
||||
case ttf
|
||||
|
||||
// MARK: Text
|
||||
|
||||
case json
|
||||
@ -143,7 +145,7 @@ enum FileType: String {
|
||||
return .video
|
||||
case .mp3, .aac:
|
||||
return .audio
|
||||
case .js, .css:
|
||||
case .js, .css, .ttf:
|
||||
return .asset
|
||||
case .json, .conf, .yaml:
|
||||
return .text
|
||||
@ -195,7 +197,7 @@ enum FileType: String {
|
||||
|
||||
var isAsset: Bool {
|
||||
switch self {
|
||||
case .js, .css:
|
||||
case .js, .css, .ttf:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
25
CHDataManagement/Model/Item/LocalizedPageId.swift
Normal file
25
CHDataManagement/Model/Item/LocalizedPageId.swift
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
struct LocalizedPageId: Hashable {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let pageId: String
|
||||
}
|
||||
|
||||
extension LocalizedPageId: Identifiable {
|
||||
|
||||
var id: String {
|
||||
pageId + "-" + language.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPageId: Comparable {
|
||||
|
||||
static func < (lhs: LocalizedPageId, rhs: LocalizedPageId) -> Bool {
|
||||
lhs.id < rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPageId: Equatable {
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import Foundation
|
||||
|
||||
final class LocalizedNavigationSettings: ObservableObject {
|
||||
|
||||
@Published
|
||||
var rootUrl: String
|
||||
|
||||
init(rootUrl: String) {
|
||||
self.rootUrl = rootUrl
|
||||
}
|
||||
|
||||
init(file: LocalizedNavigationSettingsFile) {
|
||||
self.rootUrl = file.rootUrl
|
||||
}
|
||||
|
||||
var file: LocalizedNavigationSettingsFile {
|
||||
.init(rootUrl: rootUrl)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedNavigationSettings {
|
||||
|
||||
static var `default`: LocalizedNavigationSettings {
|
||||
.init(rootUrl: "/")
|
||||
}
|
||||
}
|
@ -17,9 +17,18 @@ final class LocalizedPostSettings: ObservableObject {
|
||||
self.feedUrlPrefix = feedUrlPrefix
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
init(file: LocalizedPostSettingsFile) {
|
||||
self.title = file.feedTitle
|
||||
self.description = file.feedDescription
|
||||
self.feedUrlPrefix = file.feedUrlPrefix
|
||||
}
|
||||
|
||||
var file: LocalizedPostSettingsFile {
|
||||
.init(
|
||||
feedTitle: title,
|
||||
feedDescription: description,
|
||||
feedUrlPrefix: feedUrlPrefix)
|
||||
}
|
||||
}
|
||||
|
71
CHDataManagement/Model/Settings/NavigationSettings.swift
Normal file
71
CHDataManagement/Model/Settings/NavigationSettings.swift
Normal file
@ -0,0 +1,71 @@
|
||||
import Foundation
|
||||
|
||||
final class NavigationSettings: ObservableObject {
|
||||
|
||||
/// The items to show in the navigation bar
|
||||
@Published
|
||||
var navigationItems: [Item]
|
||||
|
||||
@Published
|
||||
var german: LocalizedNavigationSettings
|
||||
|
||||
@Published
|
||||
var english: LocalizedNavigationSettings
|
||||
|
||||
init(navigationItems: [Item], german: LocalizedNavigationSettings, english: LocalizedNavigationSettings) {
|
||||
self.navigationItems = navigationItems
|
||||
self.german = german
|
||||
self.english = english
|
||||
}
|
||||
|
||||
init(file: NavigationSettingsFile,
|
||||
tags: [String : Tag],
|
||||
pages: [String : Page],
|
||||
files: [String : FileResource],
|
||||
posts: [String : Post],
|
||||
tagOverview: TagOverviewPage?) {
|
||||
|
||||
#warning("Notify about missing links")
|
||||
self.navigationItems = file.navigationItems.compactMap { raw in
|
||||
guard let type = ItemType(rawValue: raw, posts: posts, pages: pages, tags: tags) else {
|
||||
return nil
|
||||
}
|
||||
switch type {
|
||||
case .general:
|
||||
return nil
|
||||
case .post(let post):
|
||||
return post
|
||||
case .feed:
|
||||
return nil // TODO: Provide feed object
|
||||
case .page(let page):
|
||||
return page
|
||||
case .tagPage(let tag):
|
||||
return tag
|
||||
case .tagOverview:
|
||||
return tagOverview
|
||||
}
|
||||
}
|
||||
self.german = LocalizedNavigationSettings(file: file.german)
|
||||
self.english = LocalizedNavigationSettings(file: file.english)
|
||||
}
|
||||
|
||||
var file: NavigationSettingsFile {
|
||||
.init(
|
||||
navigationItems: navigationItems.map { $0.itemType.id },
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigationSettings: LocalizedItem {
|
||||
|
||||
}
|
||||
|
||||
extension NavigationSettings {
|
||||
|
||||
static var `default`: NavigationSettings {
|
||||
.init(navigationItems: [],
|
||||
german: .default,
|
||||
english: .default)
|
||||
}
|
||||
}
|
@ -31,6 +31,8 @@ final class PostSettings: ObservableObject {
|
||||
self.defaultCssFile = defaultCssFile
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
init(file: PostSettingsFile, files: [String : FileResource]) {
|
||||
self.postsPerPage = file.postsPerPage
|
||||
self.contentWidth = file.contentWidth
|
||||
@ -38,4 +40,12 @@ final class PostSettings: ObservableObject {
|
||||
self.swiperJsFile = file.swiperJsFile.map { files[$0] }
|
||||
self.defaultCssFile = file.defaultCssFile.map { files[$0] }
|
||||
}
|
||||
|
||||
var file: PostSettingsFile {
|
||||
.init(postsPerPage: postsPerPage,
|
||||
contentWidth: contentWidth,
|
||||
swiperCssFile: swiperCssFile?.id,
|
||||
swiperJsFile: swiperJsFile?.id,
|
||||
defaultCssFile: defaultCssFile?.id)
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ final class Settings: ObservableObject {
|
||||
|
||||
/// The items to show in the navigation bar
|
||||
@Published
|
||||
var navigationItems: [Item]
|
||||
var navigation: NavigationSettings
|
||||
|
||||
@Published
|
||||
var posts: PostSettings
|
||||
@ -21,9 +21,9 @@ final class Settings: ObservableObject {
|
||||
@Published
|
||||
var english: LocalizedPostSettings
|
||||
|
||||
init(paths: PathSettings, navigationItems: [Item], posts: PostSettings, pages: PageSettings, german: LocalizedPostSettings, english: LocalizedPostSettings) {
|
||||
init(paths: PathSettings, navigation: NavigationSettings, posts: PostSettings, pages: PageSettings, german: LocalizedPostSettings, english: LocalizedPostSettings) {
|
||||
self.paths = paths
|
||||
self.navigationItems = navigationItems
|
||||
self.navigation = navigation
|
||||
self.posts = posts
|
||||
self.pages = pages
|
||||
self.german = german
|
||||
@ -36,4 +36,48 @@ final class Settings: ObservableObject {
|
||||
case .german: return german
|
||||
}
|
||||
}
|
||||
|
||||
init(file: SettingsFile,
|
||||
tags: [String : Tag],
|
||||
pages: [String : Page],
|
||||
files: [String : FileResource],
|
||||
posts: [String : Post],
|
||||
tagOverview: TagOverviewPage?) {
|
||||
|
||||
self.navigation = NavigationSettings(
|
||||
file: file.navigation,
|
||||
tags: tags,
|
||||
pages: pages,
|
||||
files: files,
|
||||
posts: posts,
|
||||
tagOverview: tagOverview)
|
||||
|
||||
self.posts = PostSettings(file: file.posts, files: files)
|
||||
self.pages = PageSettings(file: file.pages, files: files)
|
||||
self.paths = PathSettings(file: file.paths)
|
||||
|
||||
self.german = .init(file: file.german)
|
||||
self.english = .init(file: file.english)
|
||||
}
|
||||
|
||||
var file: SettingsFile {
|
||||
.init(
|
||||
paths: paths.file,
|
||||
navigation: navigation.file,
|
||||
posts: posts.file,
|
||||
pages: pages.file,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
}
|
||||
|
||||
extension Settings {
|
||||
|
||||
static let `default`: Settings = .init(
|
||||
paths: .default,
|
||||
navigation: .default,
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
german: .german,
|
||||
english: .english)
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ struct FeedEntry {
|
||||
}
|
||||
|
||||
var content: String {
|
||||
var result = "<div class='card\(cardLinkClassText)'>"
|
||||
var result = "<article><div class='card\(cardLinkClassText)'>"
|
||||
ImageGallery(id: data.entryId, images: data.images)
|
||||
.addContent(to: &result)
|
||||
|
||||
@ -34,7 +34,7 @@ struct FeedEntry {
|
||||
if let url = data.link {
|
||||
result += "<div class='link-center'><div class='link'>\(url.text)</div></div>"
|
||||
}
|
||||
result += "</div></div>" // Closes card-content and card
|
||||
result += "</div></div></article>" // Closes card-content, card, article
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
28
CHDataManagement/Page Elements/GenericPage.swift
Normal file
28
CHDataManagement/Page Elements/GenericPage.swift
Normal file
@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
|
||||
struct GenericPage {
|
||||
|
||||
let header: PageHeader
|
||||
|
||||
let additionalFooter: String
|
||||
|
||||
let insertedContent: (inout String) -> Void
|
||||
|
||||
init(header: PageHeader,
|
||||
additionalFooter: String,
|
||||
insertedContent: @escaping (inout String) -> Void) {
|
||||
self.header = header
|
||||
self.additionalFooter = additionalFooter
|
||||
self.insertedContent = insertedContent
|
||||
}
|
||||
|
||||
var content: String {
|
||||
var result = ""
|
||||
header.populate(&result) // Opens <html><body><main>
|
||||
insertedContent(&result)
|
||||
result += "</main>" // Close <main>
|
||||
result += additionalFooter
|
||||
result += "</body></html>" // Close <body><html>
|
||||
return result
|
||||
}
|
||||
}
|
@ -12,25 +12,29 @@ struct NavigationBar: HtmlProducer {
|
||||
|
||||
private let links: [Link]
|
||||
|
||||
init(links: [Link]) {
|
||||
private let languageButton: Link
|
||||
|
||||
private let iconUrl: String
|
||||
|
||||
init(links: [Link], languageButton: Link, iconUrl: String) {
|
||||
self.links = links
|
||||
self.languageButton = languageButton
|
||||
self.iconUrl = iconUrl
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
result += "<nav class='navbar'><div class='navbar-fade'></div><div class='nav-center'>"
|
||||
let middleIndex = links.count / 2
|
||||
let leftNavigationItems = links[..<middleIndex]
|
||||
let rightNavigationItems = links[middleIndex...]
|
||||
|
||||
for item in leftNavigationItems {
|
||||
result += "<a class='nav-animate' href='\(item.url)'>\(item.text)</a>"
|
||||
result += "<nav class='navbar'>"
|
||||
result += "<div class='fade'></div>"
|
||||
result += "<div class='centered'>"
|
||||
result += "<a class='icon-link' href='\(iconUrl)'><div class='icon'></div></a>"
|
||||
result += "<div class='buttons'><input type='checkbox' id='menu-toggle'>"
|
||||
result += "<label for='menu-toggle' class='burger'><div></div><div></div><div></div></label>"
|
||||
result += "<ul>"
|
||||
for item in links {
|
||||
result += "<li><a href='\(item.url)'>\(item.text)</a></li>"
|
||||
}
|
||||
|
||||
result += "<a id='nav-image' href='/'><div class='icon-ch'></div></a>"
|
||||
|
||||
for item in rightNavigationItems {
|
||||
result += "<a class='nav-animate' href='\(item.url)'>\(item.text)</a>"
|
||||
}
|
||||
result += "</div></nav>" // Close nav-center, navbar
|
||||
result += "</ul>"
|
||||
result += "<a class='lang-button' href='\(languageButton.url)'>\(languageButton.text)</a>"
|
||||
result += "</div></div></nav>" // Close buttons, centered, navbar
|
||||
}
|
||||
}
|
||||
|
60
CHDataManagement/Page Elements/PageHeader.swift
Normal file
60
CHDataManagement/Page Elements/PageHeader.swift
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
struct PageHeader: HtmlProducer {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let iconUrl: String
|
||||
|
||||
let languageButton: NavigationBar.Link
|
||||
|
||||
let links: [NavigationBar.Link]
|
||||
|
||||
let headers: [HeaderElement]
|
||||
|
||||
let icons: Set<PageIcon>
|
||||
|
||||
init(
|
||||
language: ContentLanguage,
|
||||
title: String,
|
||||
description: String?,
|
||||
iconUrl: String,
|
||||
languageButton: NavigationBar.Link,
|
||||
links: [NavigationBar.Link],
|
||||
headers: Set<HeaderElement>,
|
||||
icons: Set<PageIcon>) {
|
||||
self.language = language
|
||||
self.iconUrl = iconUrl
|
||||
self.languageButton = languageButton
|
||||
self.links = links
|
||||
self.icons = icons
|
||||
|
||||
var headers = headers
|
||||
headers.insert(.title(title))
|
||||
if let description {
|
||||
headers.insert(.description(description))
|
||||
}
|
||||
self.headers = headers.sorted()
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
result += "<!DOCTYPE html><html lang=\"\(language.rawValue)\">"
|
||||
result += PageHead(items: headers).content
|
||||
result += "<body>"
|
||||
result += NavigationBar(links: links, languageButton: languageButton, iconUrl: iconUrl).content
|
||||
result += symbols // Add the svg images required by the page as hidden elements
|
||||
result += "<main>"
|
||||
result += "<div class='navbar-spacer'></div>"
|
||||
}
|
||||
|
||||
private var symbols: String {
|
||||
guard !icons.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
var result = "<div style='display:none'>"
|
||||
for icon in icons {
|
||||
result += icon.icon.content
|
||||
}
|
||||
result += "</div>"
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct ContentPage: HtmlProducer {
|
||||
|
||||
private let linkTitle: String
|
||||
|
||||
private let description: String
|
||||
|
||||
private let language: ContentLanguage
|
||||
|
||||
private let dateString: String
|
||||
|
||||
private let title: String
|
||||
|
||||
private let showTitle: Bool
|
||||
|
||||
private let tags: [FeedEntryData.Tag]
|
||||
|
||||
private let navigationBarLinks: [NavigationBar.Link]
|
||||
|
||||
private let pageContent: String
|
||||
|
||||
private let headers: [HeaderElement]
|
||||
|
||||
private let footers: String
|
||||
|
||||
private let icons: Set<PageIcon>
|
||||
|
||||
init(language: ContentLanguage,
|
||||
dateString: String,
|
||||
title: String,
|
||||
showTitle: Bool,
|
||||
tags: [FeedEntryData.Tag],
|
||||
linkTitle: String,
|
||||
description: String,
|
||||
navigationBarLinks: [NavigationBar.Link],
|
||||
pageContent: String,
|
||||
headers: Set<HeaderElement>,
|
||||
footers: [String], icons: Set<PageIcon>) {
|
||||
self.language = language
|
||||
self.dateString = dateString
|
||||
self.title = title
|
||||
self.showTitle = showTitle
|
||||
self.tags = tags
|
||||
self.linkTitle = linkTitle
|
||||
self.description = description
|
||||
self.navigationBarLinks = navigationBarLinks
|
||||
self.pageContent = pageContent
|
||||
self.headers = headers.union([.title(title), .description(description)]).sorted()
|
||||
self.footers = footers.joined()
|
||||
self.icons = icons
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
// TODO: Add headers and footers from page content
|
||||
result += "<!DOCTYPE html><html lang=\"\(language.rawValue)\">"
|
||||
result += PageHead(items: headers).content
|
||||
result += "<body>"
|
||||
result += NavigationBar(links: navigationBarLinks).content
|
||||
|
||||
result += "<main><article>"
|
||||
result += "<div style=\"height: 70px;\"></div>"
|
||||
if showTitle {
|
||||
result += "<h3>\(dateString)</h3>"
|
||||
result += "<h1>\(title)</h1>"
|
||||
result += TagList(tags: tags).content
|
||||
}
|
||||
result += symbols
|
||||
result += pageContent
|
||||
result += "</article></main>"
|
||||
|
||||
result += footers
|
||||
result += "</body></html>" // Close content
|
||||
}
|
||||
|
||||
private var symbols: String {
|
||||
guard !icons.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
var result = "<div style='display:none'>"
|
||||
for icon in icons {
|
||||
result += icon.icon.content
|
||||
}
|
||||
result += "</div>"
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct GenericPage {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let title: String
|
||||
|
||||
let description: String
|
||||
|
||||
let links: [NavigationBar.Link]
|
||||
|
||||
let headers: [HeaderElement]
|
||||
|
||||
let additionalFooter: String
|
||||
|
||||
let insertedContent: (inout String) -> Void
|
||||
|
||||
init(language: ContentLanguage, title: String, description: String, links: [NavigationBar.Link], headers: Set<HeaderElement>, additionalFooter: String, insertedContent: @escaping (inout String) -> Void) {
|
||||
self.language = language
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.links = links
|
||||
self.headers = headers.union([.title(title), .description(description)]).sorted()
|
||||
self.additionalFooter = additionalFooter
|
||||
self.insertedContent = insertedContent
|
||||
}
|
||||
|
||||
var content: String {
|
||||
#warning("Consolidate this code with ContentPage")
|
||||
var result = ""
|
||||
result += "<!DOCTYPE html><html lang=\"\(language.rawValue)\">"
|
||||
result += PageHead(items: headers).content
|
||||
result += "<body>"
|
||||
result += NavigationBar(links: links).content
|
||||
result += "<div class=\"content\"><div style=\"height: 70px;\"></div>"
|
||||
insertedContent(&result)
|
||||
result += "</div>"
|
||||
result += additionalFooter
|
||||
result += "</body></html>" // Close content
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,16 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
extension Settings {
|
||||
|
||||
static let `default`: Settings = .init(
|
||||
paths: .default,
|
||||
navigationItems: [],
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
german: .german,
|
||||
english: .english)
|
||||
}
|
||||
|
||||
extension PathSettings {
|
||||
|
||||
static var `default`: PathSettings {
|
||||
|
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
|
||||
struct LocalizedNavigationSettingsFile {
|
||||
|
||||
let rootUrl: String
|
||||
}
|
||||
|
||||
extension LocalizedNavigationSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension LocalizedNavigationSettingsFile {
|
||||
|
||||
static var `default`: LocalizedNavigationSettingsFile {
|
||||
.init(rootUrl: "/")
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
|
||||
struct NavigationSettingsFile {
|
||||
|
||||
/// The tags to show in the navigation bar
|
||||
let navigationItems: [String]
|
||||
|
||||
let german: LocalizedNavigationSettingsFile
|
||||
|
||||
let english: LocalizedNavigationSettingsFile
|
||||
}
|
||||
|
||||
extension NavigationSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension NavigationSettingsFile {
|
||||
|
||||
static var `default`: NavigationSettingsFile {
|
||||
.init(
|
||||
navigationItems: [],
|
||||
german: .default,
|
||||
english: .default)
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ struct SettingsFile {
|
||||
let paths: PathSettingsFile
|
||||
|
||||
/// The tags to show in the navigation bar
|
||||
let navigationItems: [String]
|
||||
let navigation: NavigationSettingsFile
|
||||
|
||||
let posts: PostSettingsFile
|
||||
|
||||
@ -23,7 +23,7 @@ extension SettingsFile {
|
||||
static var `default`: SettingsFile {
|
||||
.init(
|
||||
paths: .default,
|
||||
navigationItems: [],
|
||||
navigation: .default,
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
german: .default,
|
||||
|
27
CHDataManagement/Views/Generic/TextFieldPropertyView.swift
Normal file
27
CHDataManagement/Views/Generic/TextFieldPropertyView.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TextFieldPropertyView: View {
|
||||
|
||||
let title: LocalizedStringKey
|
||||
|
||||
@Binding
|
||||
var text: String
|
||||
|
||||
let prompt: String?
|
||||
|
||||
let footer: LocalizedStringKey
|
||||
|
||||
init(title: LocalizedStringKey, text: Binding<String>, prompt: String? = nil, footer: LocalizedStringKey) {
|
||||
self.title = title
|
||||
self._text = text
|
||||
self.prompt = prompt
|
||||
self.footer = footer
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GenericPropertyView(title: title, footer: footer) {
|
||||
DescriptionField(text: $text)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,11 @@ struct GenerationContentView: View {
|
||||
Text("\(content.results.requiredFiles.count) files")
|
||||
}
|
||||
List {
|
||||
Section("Empty pages") {
|
||||
ForEach(content.results.emptyPages.sorted()) { id in
|
||||
Text("\(id.pageId) (\(id.language))")
|
||||
}
|
||||
}
|
||||
Section("Inaccessible files") {
|
||||
ForEach(content.results.inaccessibleFiles.sorted()) { file in
|
||||
Text(file.id)
|
||||
|
@ -0,0 +1,16 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LocalizedNavigationBarSettingsView: View {
|
||||
|
||||
@ObservedObject
|
||||
var settings: LocalizedNavigationSettings
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
StringPropertyView(
|
||||
title: "Root URL",
|
||||
text: $settings.rootUrl,
|
||||
footer: "The url leading to the root page for the language")
|
||||
}
|
||||
}
|
||||
}
|
@ -7,30 +7,20 @@ struct LocalizedPostFeedSettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Title")
|
||||
.font(.headline)
|
||||
TextField("", text: $settings.title)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 400)
|
||||
Text("The title of all post feed pages.")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
StringPropertyView(
|
||||
title: "Title",
|
||||
text: $settings.title,
|
||||
footer: "The title of all post feed pages.")
|
||||
|
||||
Text("URL prefix")
|
||||
.font(.headline)
|
||||
TextField("", text: $settings.feedUrlPrefix)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: 400)
|
||||
Text("The prefix to generate the urls for all post feed pages.")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
StringPropertyView(
|
||||
title: "URL prefix",
|
||||
text: $settings.feedUrlPrefix,
|
||||
footer: "The prefix to generate the urls for all post feed pages.")
|
||||
|
||||
Text("Description")
|
||||
.font(.headline)
|
||||
DescriptionField(text: $settings.description)
|
||||
Text("The description of all post feed pages.")
|
||||
.foregroundStyle(.secondary)
|
||||
.padding(.bottom)
|
||||
TextFieldPropertyView(
|
||||
title: "Description",
|
||||
text: $settings.description,
|
||||
footer: "The description of all post feed pages.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,19 +35,21 @@ struct NavigationBarSettingsView: View {
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
ForEach(content.settings.navigationItems) { tag in
|
||||
ForEach(content.settings.navigation.navigationItems) { tag in
|
||||
TagView(text: tag.title(in: language))
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
Text("Select the tags to show in the navigation bar. The number should be even.")
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
LocalizedNavigationBarSettingsView(settings: content.settings.navigation.localized(in: language))
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.sheet(isPresented: $showItemPicker) {
|
||||
ItemSelectionView(
|
||||
isPresented: $showItemPicker,
|
||||
selectedItems: $content.settings.navigationItems)
|
||||
selectedItems: $content.settings.navigation.navigationItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user