Improve settings, sidebars
This commit is contained in:
parent
b3cc4a57db
commit
c3309197c0
@ -22,11 +22,11 @@
|
|||||||
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502C2CF791440090B18B /* PostImagesView.swift */; };
|
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502C2CF791440090B18B /* PostImagesView.swift */; };
|
||||||
E218502F2CFAF69C0090B18B /* WebsiteGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */; };
|
E218502F2CFAF69C0090B18B /* WebsiteGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */; };
|
||||||
E21850312CFAF8880090B18B /* Content+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850302CFAF8840090B18B /* Content+Import.swift */; };
|
E21850312CFAF8880090B18B /* Content+Import.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850302CFAF8840090B18B /* Content+Import.swift */; };
|
||||||
E21850332CFAFA2F0090B18B /* WebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850322CFAFA200090B18B /* WebsiteData.swift */; };
|
E21850332CFAFA2F0090B18B /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850322CFAFA200090B18B /* Settings.swift */; };
|
||||||
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* WebsiteDataFile.swift */; };
|
E21850352CFAFA5A0090B18B /* SettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* SettingsFile.swift */; };
|
||||||
E21850372CFCA55F0090B18B /* LocalizedWebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */; };
|
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */; };
|
||||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */; };
|
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */; };
|
||||||
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */; };
|
E218503D2CFCFD910090B18B /* LocalizedPostFeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */; };
|
||||||
E24252012C50E0A40029FF16 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = E24252002C50E0A40029FF16 /* HighlightedTextEditor */; };
|
E24252012C50E0A40029FF16 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = E24252002C50E0A40029FF16 /* HighlightedTextEditor */; };
|
||||||
E24252032C5163CF0029FF16 /* Importer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252022C5163CF0029FF16 /* Importer.swift */; };
|
E24252032C5163CF0029FF16 /* Importer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252022C5163CF0029FF16 /* Importer.swift */; };
|
||||||
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252052C51684E0029FF16 /* GenericMetadata.swift */; };
|
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E24252052C51684E0029FF16 /* GenericMetadata.swift */; };
|
||||||
@ -44,13 +44,25 @@
|
|||||||
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */; };
|
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */; };
|
||||||
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51C2CFF135B00AEF16D /* GenericPage.swift */; };
|
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51C2CFF135B00AEF16D /* GenericPage.swift */; };
|
||||||
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */; };
|
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */; };
|
||||||
E25DA5212CFF1B9300AEF16D /* WebsiteGenerator+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5202CFF1B8900AEF16D /* WebsiteGenerator+Mock.swift */; };
|
|
||||||
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5222CFF6C2600AEF16D /* ImageGenerator.swift */; };
|
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5222CFF6C2600AEF16D /* ImageGenerator.swift */; };
|
||||||
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5242CFF73A600AEF16D /* NSSize+Scaling.swift */; };
|
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5242CFF73A600AEF16D /* NSSize+Scaling.swift */; };
|
||||||
E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5262CFF745200AEF16D /* URL+Extensions.swift */; };
|
E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5262CFF745200AEF16D /* URL+Extensions.swift */; };
|
||||||
E25DA5292CFFBFBB00AEF16D /* ImageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5282CFFBFB800AEF16D /* ImageType.swift */; };
|
E25DA5292CFFBFBB00AEF16D /* ImageType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5282CFFBFB800AEF16D /* ImageType.swift */; };
|
||||||
E25DA52C2CFFC3EC00AEF16D /* SDWebImageAVIFCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52B2CFFC3EC00AEF16D /* SDWebImageAVIFCoder */; };
|
E25DA52C2CFFC3EC00AEF16D /* SDWebImageAVIFCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52B2CFFC3EC00AEF16D /* SDWebImageAVIFCoder */; };
|
||||||
E25DA52F2CFFC91B00AEF16D /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52E2CFFC91B00AEF16D /* SDWebImageWebPCoder */; };
|
E25DA52F2CFFC91B00AEF16D /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52E2CFFC91B00AEF16D /* SDWebImageWebPCoder */; };
|
||||||
|
E25DA5312D003FCB00AEF16D /* SectionedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5302D003FC000AEF16D /* SectionedSettingsView.swift */; };
|
||||||
|
E25DA5342D0041CB00AEF16D /* NavigationBarSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5332D0041CB00AEF16D /* NavigationBarSettingsFile.swift */; };
|
||||||
|
E25DA5362D0041EB00AEF16D /* PostSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */; };
|
||||||
|
E25DA5382D00420E00AEF16D /* LocalizedPostSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */; };
|
||||||
|
E25DA53A2D00424000AEF16D /* LocalizedSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5392D00423F00AEF16D /* LocalizedSettingsFile.swift */; };
|
||||||
|
E25DA53D2D0043E600AEF16D /* LocalizedSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA53C2D0043E200AEF16D /* LocalizedSettings.swift */; };
|
||||||
|
E25DA53F2D00441F00AEF16D /* NavigationBarSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA53E2D00441C00AEF16D /* NavigationBarSettings.swift */; };
|
||||||
|
E25DA5412D00446C00AEF16D /* PostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5402D00446700AEF16D /* PostSettings.swift */; };
|
||||||
|
E25DA5432D0094A900AEF16D /* SettingsSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5422D0094A400AEF16D /* SettingsSidebar.swift */; };
|
||||||
|
E25DA5452D00952E00AEF16D /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5442D00952D00AEF16D /* SettingsSection.swift */; };
|
||||||
|
E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */; };
|
||||||
|
E25DA56F2D00F9A100AEF16D /* PostFeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */; };
|
||||||
|
E25DA5712D01015400AEF16D /* GenerationSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5702D01015400AEF16D /* GenerationSettingsView.swift */; };
|
||||||
E2A21C012CB16A820060935B /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C002CB16A820060935B /* PostView.swift */; };
|
E2A21C012CB16A820060935B /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C002CB16A820060935B /* PostView.swift */; };
|
||||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C022CB16C220060935B /* Environment+Language.swift */; };
|
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C022CB16C220060935B /* Environment+Language.swift */; };
|
||||||
E2A21C052CB1766C0060935B /* LocalizedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C042CB176670060935B /* LocalizedText.swift */; };
|
E2A21C052CB1766C0060935B /* LocalizedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C042CB176670060935B /* LocalizedText.swift */; };
|
||||||
@ -65,7 +77,7 @@
|
|||||||
E2A21C2C2CB2BB250060935B /* PostList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C2B2CB2BB210060935B /* PostList.swift */; };
|
E2A21C2C2CB2BB250060935B /* PostList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C2B2CB2BB210060935B /* PostList.swift */; };
|
||||||
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */; };
|
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */; };
|
||||||
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C312CB5BCAC0060935B /* PageDetailView.swift */; };
|
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C312CB5BCAC0060935B /* PageDetailView.swift */; };
|
||||||
E2A21C362CB9A3D70060935B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C352CB9A3D70060935B /* SettingsView.swift */; };
|
E2A21C362CB9A3D70060935B /* FolderSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C352CB9A3D70060935B /* FolderSettingsView.swift */; };
|
||||||
E2A21C3B2CB9D9A60060935B /* ImageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C3A2CB9D9A50060935B /* ImageResource.swift */; };
|
E2A21C3B2CB9D9A60060935B /* ImageResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C3A2CB9D9A50060935B /* ImageResource.swift */; };
|
||||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */; };
|
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */; };
|
||||||
E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C472CBAF8830060935B /* String+Extensions.swift */; };
|
E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C472CBAF8830060935B /* String+Extensions.swift */; };
|
||||||
@ -119,11 +131,11 @@
|
|||||||
E218502C2CF791440090B18B /* PostImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostImagesView.swift; sourceTree = "<group>"; };
|
E218502C2CF791440090B18B /* PostImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostImagesView.swift; sourceTree = "<group>"; };
|
||||||
E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteGenerator.swift; sourceTree = "<group>"; };
|
E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteGenerator.swift; sourceTree = "<group>"; };
|
||||||
E21850302CFAF8840090B18B /* Content+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Import.swift"; sourceTree = "<group>"; };
|
E21850302CFAF8840090B18B /* Content+Import.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Import.swift"; sourceTree = "<group>"; };
|
||||||
E21850322CFAFA200090B18B /* WebsiteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteData.swift; sourceTree = "<group>"; };
|
E21850322CFAFA200090B18B /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||||
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteDataFile.swift; sourceTree = "<group>"; };
|
E21850342CFAFA570090B18B /* SettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFile.swift; sourceTree = "<group>"; };
|
||||||
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedWebsiteData.swift; sourceTree = "<group>"; };
|
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettings.swift; sourceTree = "<group>"; };
|
||||||
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebsiteData+Mock.swift"; sourceTree = "<group>"; };
|
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebsiteData+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedSettingsView.swift; sourceTree = "<group>"; };
|
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostFeedSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E24252022C5163CF0029FF16 /* Importer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Importer.swift; sourceTree = "<group>"; };
|
E24252022C5163CF0029FF16 /* Importer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Importer.swift; sourceTree = "<group>"; };
|
||||||
E24252052C51684E0029FF16 /* GenericMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericMetadata.swift; sourceTree = "<group>"; };
|
E24252052C51684E0029FF16 /* GenericMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericMetadata.swift; sourceTree = "<group>"; };
|
||||||
E24252072C5168750029FF16 /* GenericMetadata+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GenericMetadata+Localized.swift"; sourceTree = "<group>"; };
|
E24252072C5168750029FF16 /* GenericMetadata+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GenericMetadata+Localized.swift"; sourceTree = "<group>"; };
|
||||||
@ -141,11 +153,23 @@
|
|||||||
E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostFeedPageNavigation.swift; sourceTree = "<group>"; };
|
E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostFeedPageNavigation.swift; sourceTree = "<group>"; };
|
||||||
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPage.swift; sourceTree = "<group>"; };
|
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenericPage.swift; sourceTree = "<group>"; };
|
||||||
E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
|
E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
|
||||||
E25DA5202CFF1B8900AEF16D /* WebsiteGenerator+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebsiteGenerator+Mock.swift"; sourceTree = "<group>"; };
|
|
||||||
E25DA5222CFF6C2600AEF16D /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
|
E25DA5222CFF6C2600AEF16D /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
|
||||||
E25DA5242CFF73A600AEF16D /* NSSize+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSSize+Scaling.swift"; sourceTree = "<group>"; };
|
E25DA5242CFF73A600AEF16D /* NSSize+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSSize+Scaling.swift"; sourceTree = "<group>"; };
|
||||||
E25DA5262CFF745200AEF16D /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
|
E25DA5262CFF745200AEF16D /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
E25DA5282CFFBFB800AEF16D /* ImageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageType.swift; sourceTree = "<group>"; };
|
E25DA5282CFFBFB800AEF16D /* ImageType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageType.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5302D003FC000AEF16D /* SectionedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionedSettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5332D0041CB00AEF16D /* NavigationBarSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSettingsFile.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSettingsFile.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettingsFile.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5392D00423F00AEF16D /* LocalizedSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedSettingsFile.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA53C2D0043E200AEF16D /* LocalizedSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedSettings.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA53E2D00441C00AEF16D /* NavigationBarSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSettings.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5402D00446700AEF16D /* PostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSettings.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5422D0094A400AEF16D /* SettingsSidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSidebar.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5442D00952D00AEF16D /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostFeedSettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5702D01015400AEF16D /* GenerationSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E2A21C002CB16A820060935B /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
E2A21C002CB16A820060935B /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
||||||
E2A21C022CB16C220060935B /* Environment+Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Language.swift"; sourceTree = "<group>"; };
|
E2A21C022CB16C220060935B /* Environment+Language.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Environment+Language.swift"; sourceTree = "<group>"; };
|
||||||
E2A21C042CB176670060935B /* LocalizedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedText.swift; sourceTree = "<group>"; };
|
E2A21C042CB176670060935B /* LocalizedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedText.swift; sourceTree = "<group>"; };
|
||||||
@ -160,7 +184,7 @@
|
|||||||
E2A21C2B2CB2BB210060935B /* PostList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostList.swift; sourceTree = "<group>"; };
|
E2A21C2B2CB2BB210060935B /* PostList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostList.swift; sourceTree = "<group>"; };
|
||||||
E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalCenter.swift; sourceTree = "<group>"; };
|
E2A21C2F2CB490F90060935B /* HorizontalCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalCenter.swift; sourceTree = "<group>"; };
|
||||||
E2A21C312CB5BCAC0060935B /* PageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailView.swift; sourceTree = "<group>"; };
|
E2A21C312CB5BCAC0060935B /* PageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailView.swift; sourceTree = "<group>"; };
|
||||||
E2A21C352CB9A3D70060935B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
E2A21C352CB9A3D70060935B /* FolderSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderSettingsView.swift; sourceTree = "<group>"; };
|
||||||
E2A21C3A2CB9D9A50060935B /* ImageResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageResource.swift; sourceTree = "<group>"; };
|
E2A21C3A2CB9D9A50060935B /* ImageResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageResource.swift; sourceTree = "<group>"; };
|
||||||
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedEntryContent.swift; sourceTree = "<group>"; };
|
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedEntryContent.swift; sourceTree = "<group>"; };
|
||||||
E2A21C472CBAF8830060935B /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
E2A21C472CBAF8830060935B /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
@ -227,7 +251,7 @@
|
|||||||
E25DA5112CFF001900AEF16D /* Model */ = {
|
E25DA5112CFF001900AEF16D /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */,
|
E25DA5322D0041C400AEF16D /* Settings */,
|
||||||
E21850142CEE55D40090B18B /* FileOnDisk.swift */,
|
E21850142CEE55D40090B18B /* FileOnDisk.swift */,
|
||||||
E21850162CEE55FB0090B18B /* FileType.swift */,
|
E21850162CEE55FB0090B18B /* FileType.swift */,
|
||||||
E2A37D102CE537670000979F /* PageFile.swift */,
|
E2A37D102CE537670000979F /* PageFile.swift */,
|
||||||
@ -238,6 +262,30 @@
|
|||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E25DA5322D0041C400AEF16D /* Settings */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */,
|
||||||
|
E25DA5392D00423F00AEF16D /* LocalizedSettingsFile.swift */,
|
||||||
|
E25DA5332D0041CB00AEF16D /* NavigationBarSettingsFile.swift */,
|
||||||
|
E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */,
|
||||||
|
E21850342CFAFA570090B18B /* SettingsFile.swift */,
|
||||||
|
);
|
||||||
|
path = Settings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E25DA5402D00446700AEF16D /* PostSettings.swift */,
|
||||||
|
E25DA53E2D00441C00AEF16D /* NavigationBarSettings.swift */,
|
||||||
|
E25DA53C2D0043E200AEF16D /* LocalizedSettings.swift */,
|
||||||
|
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */,
|
||||||
|
E21850322CFAFA200090B18B /* Settings.swift */,
|
||||||
|
);
|
||||||
|
path = Settings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E2A21C322CB5BCAC0060935B /* Pages */ = {
|
E2A21C322CB5BCAC0060935B /* Pages */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -250,8 +298,14 @@
|
|||||||
E2A21C342CB9A3CA0060935B /* Settings */ = {
|
E2A21C342CB9A3CA0060935B /* Settings */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */,
|
E25DA5442D00952D00AEF16D /* SettingsSection.swift */,
|
||||||
E2A21C352CB9A3D70060935B /* SettingsView.swift */,
|
E25DA5422D0094A400AEF16D /* SettingsSidebar.swift */,
|
||||||
|
E25DA5302D003FC000AEF16D /* SectionedSettingsView.swift */,
|
||||||
|
E2A21C352CB9A3D70060935B /* FolderSettingsView.swift */,
|
||||||
|
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */,
|
||||||
|
E25DA56E2D00F99900AEF16D /* PostFeedSettingsView.swift */,
|
||||||
|
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */,
|
||||||
|
E25DA5702D01015400AEF16D /* GenerationSettingsView.swift */,
|
||||||
);
|
);
|
||||||
path = Settings;
|
path = Settings;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -312,9 +366,8 @@
|
|||||||
E2B85F392C428F020047CD0C /* Model */ = {
|
E2B85F392C428F020047CD0C /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E25DA53B2D0042EA00AEF16D /* Settings */,
|
||||||
E25DA5282CFFBFB800AEF16D /* ImageType.swift */,
|
E25DA5282CFFBFB800AEF16D /* ImageType.swift */,
|
||||||
E21850322CFAFA200090B18B /* WebsiteData.swift */,
|
|
||||||
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */,
|
|
||||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
||||||
E25DA5162CFF00F200AEF16D /* Content+Save.swift */,
|
E25DA5162CFF00F200AEF16D /* Content+Save.swift */,
|
||||||
E25DA5142CFF00B900AEF16D /* Content+Load.swift */,
|
E25DA5142CFF00B900AEF16D /* Content+Load.swift */,
|
||||||
@ -425,8 +478,8 @@
|
|||||||
E2DD04722C276F31003BFF1F /* CHDataManagement */ = {
|
E2DD04722C276F31003BFF1F /* CHDataManagement */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E2A37D0F2CE5375E0000979F /* Storage */,
|
|
||||||
E2DD04732C276F31003BFF1F /* CHDataManagementApp.swift */,
|
E2DD04732C276F31003BFF1F /* CHDataManagementApp.swift */,
|
||||||
|
E2A37D0F2CE5375E0000979F /* Storage */,
|
||||||
E2B85F392C428F020047CD0C /* Model */,
|
E2B85F392C428F020047CD0C /* Model */,
|
||||||
E2B85F462C42C7CA0047CD0C /* Views */,
|
E2B85F462C42C7CA0047CD0C /* Views */,
|
||||||
E2B85F3F2C42946E0047CD0C /* Page Elements */,
|
E2B85F3F2C42946E0047CD0C /* Page Elements */,
|
||||||
@ -443,7 +496,6 @@
|
|||||||
E2DD047C2C276F32003BFF1F /* Preview Content */ = {
|
E2DD047C2C276F32003BFF1F /* Preview Content */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E25DA5202CFF1B8900AEF16D /* WebsiteGenerator+Mock.swift */,
|
|
||||||
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */,
|
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */,
|
||||||
E218500A2CEE02FA0090B18B /* Content+Mock.swift */,
|
E218500A2CEE02FA0090B18B /* Content+Mock.swift */,
|
||||||
E2A37D1A2CEA45530000979F /* Tag+Mock.swift */,
|
E2A37D1A2CEA45530000979F /* Tag+Mock.swift */,
|
||||||
@ -538,16 +590,19 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */,
|
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */,
|
||||||
|
E25DA56F2D00F9A100AEF16D /* PostFeedSettingsView.swift in Sources */,
|
||||||
E2A21C162CB1A3C90060935B /* PostImageGalleryView.swift in Sources */,
|
E2A21C162CB1A3C90060935B /* PostImageGalleryView.swift in Sources */,
|
||||||
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
||||||
E2A21C562CBBF9880060935B /* FlexibleColumnView.swift in Sources */,
|
E2A21C562CBBF9880060935B /* FlexibleColumnView.swift in Sources */,
|
||||||
|
E25DA5412D00446C00AEF16D /* PostSettings.swift in Sources */,
|
||||||
E2A37D192CEA36A90000979F /* LocalizedTag.swift in Sources */,
|
E2A37D192CEA36A90000979F /* LocalizedTag.swift in Sources */,
|
||||||
E25DA5212CFF1B9300AEF16D /* WebsiteGenerator+Mock.swift in Sources */,
|
|
||||||
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */,
|
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */,
|
||||||
E21850312CFAF8880090B18B /* Content+Import.swift in Sources */,
|
E21850312CFAF8880090B18B /* Content+Import.swift in Sources */,
|
||||||
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */,
|
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */,
|
||||||
|
E25DA5452D00952E00AEF16D /* SettingsSection.swift in Sources */,
|
||||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
||||||
E21850252CF38BCE0090B18B /* TextEntrySheet.swift in Sources */,
|
E21850252CF38BCE0090B18B /* TextEntrySheet.swift in Sources */,
|
||||||
|
E25DA53A2D00424000AEF16D /* LocalizedSettingsFile.swift in Sources */,
|
||||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
||||||
E21850172CEE55FC0090B18B /* FileType.swift in Sources */,
|
E21850172CEE55FC0090B18B /* FileType.swift in Sources */,
|
||||||
E2A37D112CE537800000979F /* PageFile.swift in Sources */,
|
E2A37D112CE537800000979F /* PageFile.swift in Sources */,
|
||||||
@ -559,16 +614,19 @@
|
|||||||
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
|
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
|
||||||
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
|
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
|
||||||
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
||||||
|
E25DA53F2D00441F00AEF16D /* NavigationBarSettings.swift in Sources */,
|
||||||
E218502F2CFAF69C0090B18B /* WebsiteGenerator.swift in Sources */,
|
E218502F2CFAF69C0090B18B /* WebsiteGenerator.swift in Sources */,
|
||||||
E2581DF12C7523F400F1F079 /* ImportableTag.swift in Sources */,
|
E2581DF12C7523F400F1F079 /* ImportableTag.swift in Sources */,
|
||||||
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
||||||
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
||||||
|
E25DA5382D00420E00AEF16D /* LocalizedPostSettingsFile.swift in Sources */,
|
||||||
E2B85F3D2C4293F80047CD0C /* PageInFeed.swift in Sources */,
|
E2B85F3D2C4293F80047CD0C /* PageInFeed.swift in Sources */,
|
||||||
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */,
|
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */,
|
||||||
E2A21C2A2CB2AA4F0060935B /* Post+Mock.swift in Sources */,
|
E2A21C2A2CB2AA4F0060935B /* Post+Mock.swift in Sources */,
|
||||||
E24252082C5168750029FF16 /* GenericMetadata+Localized.swift in Sources */,
|
E24252082C5168750029FF16 /* GenericMetadata+Localized.swift in Sources */,
|
||||||
E2581DED2C75202400F1F079 /* Tag.swift in Sources */,
|
E2581DED2C75202400F1F079 /* Tag.swift in Sources */,
|
||||||
E2A21C4F2CBB29E50060935B /* ImageDetailsView.swift in Sources */,
|
E2A21C4F2CBB29E50060935B /* ImageDetailsView.swift in Sources */,
|
||||||
|
E25DA53D2D0043E600AEF16D /* LocalizedSettings.swift in Sources */,
|
||||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
||||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */,
|
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */,
|
||||||
@ -581,27 +639,28 @@
|
|||||||
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */,
|
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */,
|
||||||
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */,
|
E2A21C332CB5BCAC0060935B /* PageDetailView.swift in Sources */,
|
||||||
E2A21C122CB18D560060935B /* DatePickerView.swift in Sources */,
|
E2A21C122CB18D560060935B /* DatePickerView.swift in Sources */,
|
||||||
E21850332CFAFA2F0090B18B /* WebsiteData.swift in Sources */,
|
E21850332CFAFA2F0090B18B /* Settings.swift in Sources */,
|
||||||
E21850192CEE561C0090B18B /* PageOnDisk.swift in Sources */,
|
E21850192CEE561C0090B18B /* PageOnDisk.swift in Sources */,
|
||||||
E2A21C4D2CBB16B50060935B /* ImagesView.swift in Sources */,
|
E2A21C4D2CBB16B50060935B /* ImagesView.swift in Sources */,
|
||||||
E2A21C202CB28ED20060935B /* MockImage.swift in Sources */,
|
E2A21C202CB28ED20060935B /* MockImage.swift in Sources */,
|
||||||
E2A21C2C2CB2BB250060935B /* PostList.swift in Sources */,
|
E2A21C2C2CB2BB250060935B /* PostList.swift in Sources */,
|
||||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||||
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */,
|
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */,
|
||||||
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */,
|
E21850352CFAFA5A0090B18B /* SettingsFile.swift in Sources */,
|
||||||
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */,
|
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */,
|
||||||
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */,
|
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */,
|
||||||
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */,
|
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */,
|
||||||
|
E25DA5432D0094A900AEF16D /* SettingsSidebar.swift in Sources */,
|
||||||
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */,
|
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */,
|
||||||
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */,
|
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */,
|
||||||
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */,
|
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */,
|
||||||
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */,
|
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */,
|
||||||
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */,
|
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */,
|
||||||
E2A21C3B2CB9D9A60060935B /* ImageResource.swift in Sources */,
|
E2A21C3B2CB9D9A60060935B /* ImageResource.swift in Sources */,
|
||||||
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */,
|
E218503D2CFCFD910090B18B /* LocalizedPostFeedSettingsView.swift in Sources */,
|
||||||
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */,
|
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */,
|
||||||
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */,
|
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */,
|
||||||
E21850372CFCA55F0090B18B /* LocalizedWebsiteData.swift in Sources */,
|
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */,
|
||||||
E2DD04742C276F31003BFF1F /* CHDataManagementApp.swift in Sources */,
|
E2DD04742C276F31003BFF1F /* CHDataManagementApp.swift in Sources */,
|
||||||
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */,
|
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */,
|
||||||
E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */,
|
E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */,
|
||||||
@ -611,20 +670,25 @@
|
|||||||
E25DA5292CFFBFBB00AEF16D /* ImageType.swift in Sources */,
|
E25DA5292CFFBFBB00AEF16D /* ImageType.swift in Sources */,
|
||||||
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
||||||
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
||||||
|
E25DA5342D0041CB00AEF16D /* NavigationBarSettingsFile.swift in Sources */,
|
||||||
E2A21C542CBBF87A0060935B /* FilesView.swift in Sources */,
|
E2A21C542CBBF87A0060935B /* FilesView.swift in Sources */,
|
||||||
E2A37D0E2CE527070000979F /* Storage.swift in Sources */,
|
E2A37D0E2CE527070000979F /* Storage.swift in Sources */,
|
||||||
E2E06E002CA4A8F00019C2AF /* Page+Mock.swift in Sources */,
|
E2E06E002CA4A8F00019C2AF /* Page+Mock.swift in Sources */,
|
||||||
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */,
|
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */,
|
||||||
E2B85F572C4BD0BB0047CD0C /* Binding+Extension.swift in Sources */,
|
E2B85F572C4BD0BB0047CD0C /* Binding+Extension.swift in Sources */,
|
||||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
||||||
|
E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */,
|
||||||
E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */,
|
E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */,
|
||||||
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */,
|
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */,
|
||||||
|
E25DA5712D01015400AEF16D /* GenerationSettingsView.swift in Sources */,
|
||||||
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
||||||
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
||||||
|
E25DA5312D003FCB00AEF16D /* SectionedSettingsView.swift in Sources */,
|
||||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
||||||
E2A21C362CB9A3D70060935B /* SettingsView.swift in Sources */,
|
E2A21C362CB9A3D70060935B /* FolderSettingsView.swift in Sources */,
|
||||||
E2A21C012CB16A820060935B /* PostView.swift in Sources */,
|
E2A21C012CB16A820060935B /* PostView.swift in Sources */,
|
||||||
E2A21C052CB1766C0060935B /* LocalizedText.swift in Sources */,
|
E2A21C052CB1766C0060935B /* LocalizedText.swift in Sources */,
|
||||||
|
E25DA5362D0041EB00AEF16D /* PostSettingsFile.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ struct CHDataManagementApp: App {
|
|||||||
FilesView()
|
FilesView()
|
||||||
}
|
}
|
||||||
Tab("Settings", systemImage: SFSymbol.gear.rawValue) {
|
Tab("Settings", systemImage: SFSymbol.gear.rawValue) {
|
||||||
SettingsView()
|
SectionedSettingsView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.language, selectedLanguage)
|
.environment(\.language, selectedLanguage)
|
||||||
@ -54,18 +54,16 @@ struct CHDataManagementApp: App {
|
|||||||
.tag(ContentLanguage.german)
|
.tag(ContentLanguage.german)
|
||||||
}.pickerStyle(.segmented)
|
}.pickerStyle(.segmented)
|
||||||
}
|
}
|
||||||
}
|
ToolbarItem(placement: .primaryAction) {
|
||||||
.onAppear(perform: importOldContent)
|
|
||||||
.onReceive(Timer.publish(every: 60.0, on: .main, in: .common).autoconnect()) { _ in
|
|
||||||
save()
|
|
||||||
}
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .navigation) {
|
|
||||||
Button(action: save) {
|
Button(action: save) {
|
||||||
Text("Save")
|
Text("Save")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear(perform: importOldContent)
|
||||||
|
.onReceive(Timer.publish(every: 60.0, on: .main, in: .common).autoconnect()) { _ in
|
||||||
|
save()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,16 +37,18 @@ extension Content {
|
|||||||
linkPreviewDescription: page.linkPreviewDescription)
|
linkPreviewDescription: page.linkPreviewDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func convert(_ websiteData: LocalizedWebsiteDataFile) -> LocalizedWebsiteData {
|
private func convert(_ settings: LocalizedSettingsFile) -> LocalizedSettings {
|
||||||
.init(title: websiteData.title,
|
.init(navigationBarIconDescription: settings.navigationBarIconDescription,
|
||||||
description: websiteData.description,
|
posts: .init(
|
||||||
iconDescription: websiteData.iconDescription)
|
title: settings.posts.feedTitle,
|
||||||
|
description: settings.posts.feedDescription,
|
||||||
|
feedUrlPrefix: settings.posts.feedUrlPrefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadFromDisk() throws {
|
func loadFromDisk() throws {
|
||||||
let storage = Storage(baseFolder: URL(filePath: contentPath))
|
let storage = Storage(baseFolder: URL(filePath: contentPath))
|
||||||
|
|
||||||
let websiteData = try storage.loadWebsiteData()
|
let settings = try storage.loadSettings()
|
||||||
|
|
||||||
let tagData = try storage.loadAllTags()
|
let tagData = try storage.loadAllTags()
|
||||||
let pagesData = try storage.loadAllPages()
|
let pagesData = try storage.loadAllPages()
|
||||||
@ -104,10 +106,25 @@ extension Content {
|
|||||||
self.images = images.values.sorted { $0.id }
|
self.images = images.values.sorted { $0.id }
|
||||||
self.videos = videos
|
self.videos = videos
|
||||||
self.posts = posts.sorted(ascending: false) { $0.startDate }
|
self.posts = posts.sorted(ascending: false) { $0.startDate }
|
||||||
self.websiteData = WebsiteData(
|
self.settings = makeSettings(settings, tags: tags)
|
||||||
navigationTags: websiteData.navigationTags.map { tags[$0]! },
|
}
|
||||||
german: convert(websiteData.german),
|
|
||||||
english: convert(websiteData.english))
|
private func makeSettings(_ settings: SettingsFile, tags: [String : Tag]) -> Settings {
|
||||||
|
|
||||||
|
let navigationBar = NavigationBarSettings(
|
||||||
|
iconPath: settings.navigationBar.navigationIconPath,
|
||||||
|
tags: settings.navigationBar.navigationTags.map { tags[$0]! })
|
||||||
|
|
||||||
|
let posts = PostSettings(
|
||||||
|
postsPerPage: settings.posts.postsPerPage,
|
||||||
|
contentWidth: settings.posts.contentWidth)
|
||||||
|
|
||||||
|
return Settings(
|
||||||
|
outputDirectoryPath: settings.outputDirectoryPath,
|
||||||
|
navigationBar: navigationBar,
|
||||||
|
posts: posts,
|
||||||
|
german: convert(settings.german),
|
||||||
|
english: convert(settings.english))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag]) -> [String : Page] {
|
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag]) -> [String : Page] {
|
||||||
|
@ -15,7 +15,7 @@ extension Content {
|
|||||||
for tag in tags {
|
for tag in tags {
|
||||||
storage.save(tagMetadata: tag.tagFile, for: tag.id)
|
storage.save(tagMetadata: tag.tagFile, for: tag.id)
|
||||||
}
|
}
|
||||||
storage.save(websiteData: websiteData.dataFile)
|
storage.save(settings: settings.file)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try storage.deletePostFiles(notIn: posts.map { $0.id })
|
try storage.deletePostFiles(notIn: posts.map { $0.id })
|
||||||
@ -113,21 +113,48 @@ private extension LocalizedTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension WebsiteData {
|
private extension NavigationBarSettings {
|
||||||
|
|
||||||
var dataFile: WebsiteDataFile {
|
var file: NavigationBarSettingsFile {
|
||||||
|
.init(navigationIconPath: iconPath,
|
||||||
|
navigationTags: tags.map { $0.id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Settings {
|
||||||
|
|
||||||
|
var file: SettingsFile {
|
||||||
.init(
|
.init(
|
||||||
navigationTags: navigationTags.map { $0.id },
|
outputDirectoryPath: outputDirectoryPath,
|
||||||
german: german.dataFile,
|
navigationBar: navigationBar.file,
|
||||||
english: english.dataFile)
|
posts: posts.file,
|
||||||
|
german: german.file,
|
||||||
|
english: english.file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension LocalizedWebsiteData {
|
private extension PostSettings {
|
||||||
|
|
||||||
var dataFile: LocalizedWebsiteDataFile {
|
var file: PostSettingsFile {
|
||||||
.init(title: title,
|
.init(postsPerPage: postsPerPage,
|
||||||
description: description,
|
contentWidth: contentWidth)
|
||||||
iconDescription: iconDescription)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedSettings {
|
||||||
|
|
||||||
|
var file: LocalizedSettingsFile {
|
||||||
|
.init(navigationBarIconDescription: navigationBarIconDescription,
|
||||||
|
posts: posts.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedPostSettings {
|
||||||
|
|
||||||
|
var file: LocalizedPostSettingsFile {
|
||||||
|
.init(
|
||||||
|
feedTitle: title,
|
||||||
|
feedDescription: description,
|
||||||
|
feedUrlPrefix: feedUrlPrefix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import Combine
|
|||||||
final class Content: ObservableObject {
|
final class Content: ObservableObject {
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var websiteData: WebsiteData
|
var settings: Settings
|
||||||
|
|
||||||
@Published
|
@Published
|
||||||
var posts: [Post]
|
var posts: [Post]
|
||||||
@ -39,7 +39,7 @@ final class Content: ObservableObject {
|
|||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
init(websiteData: WebsiteData,
|
init(settings: Settings,
|
||||||
posts: [Post],
|
posts: [Post],
|
||||||
pages: [Page],
|
pages: [Page],
|
||||||
tags: [Tag],
|
tags: [Tag],
|
||||||
@ -47,7 +47,7 @@ final class Content: ObservableObject {
|
|||||||
files: [FileResource],
|
files: [FileResource],
|
||||||
videos: [String],
|
videos: [String],
|
||||||
storedContentPath: String) {
|
storedContentPath: String) {
|
||||||
self.websiteData = websiteData
|
self.settings = settings
|
||||||
self.posts = posts
|
self.posts = posts
|
||||||
self.pages = pages
|
self.pages = pages
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
@ -69,7 +69,7 @@ final class Content: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
self.storage = Storage(baseFolder: URL(filePath: ""))
|
self.storage = Storage(baseFolder: URL(filePath: ""))
|
||||||
|
|
||||||
self.websiteData = .mock
|
self.settings = .mock
|
||||||
self.posts = []
|
self.posts = []
|
||||||
self.pages = []
|
self.pages = []
|
||||||
self.tags = []
|
self.tags = []
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
final class LocalizedWebsiteData: ObservableObject {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var title: String
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var description: String
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var iconDescription: String
|
|
||||||
|
|
||||||
init(title: String, description: String, iconDescription: String) {
|
|
||||||
self.title = title
|
|
||||||
self.description = description
|
|
||||||
self.iconDescription = iconDescription
|
|
||||||
}
|
|
||||||
}
|
|
19
CHDataManagement/Model/Settings/LocalizedPostSettings.swift
Normal file
19
CHDataManagement/Model/Settings/LocalizedPostSettings.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class LocalizedPostSettings: ObservableObject {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var title: String
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var description: String
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var feedUrlPrefix: String
|
||||||
|
|
||||||
|
init(title: String, description: String, feedUrlPrefix: String) {
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.feedUrlPrefix = feedUrlPrefix
|
||||||
|
}
|
||||||
|
}
|
15
CHDataManagement/Model/Settings/LocalizedSettings.swift
Normal file
15
CHDataManagement/Model/Settings/LocalizedSettings.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class LocalizedSettings: ObservableObject {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var navigationBarIconDescription: String
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var posts: LocalizedPostSettings
|
||||||
|
|
||||||
|
init(navigationBarIconDescription: String, posts: LocalizedPostSettings) {
|
||||||
|
self.navigationBarIconDescription = navigationBarIconDescription
|
||||||
|
self.posts = posts
|
||||||
|
}
|
||||||
|
}
|
17
CHDataManagement/Model/Settings/NavigationBarSettings.swift
Normal file
17
CHDataManagement/Model/Settings/NavigationBarSettings.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class NavigationBarSettings: ObservableObject {
|
||||||
|
|
||||||
|
/// The path to the main icon in the navigation bar
|
||||||
|
@Published
|
||||||
|
var iconPath: String
|
||||||
|
|
||||||
|
/// The tags to show in the navigation bar
|
||||||
|
@Published
|
||||||
|
var tags: [Tag]
|
||||||
|
|
||||||
|
init(iconPath: String, tags: [Tag]) {
|
||||||
|
self.iconPath = iconPath
|
||||||
|
self.tags = tags
|
||||||
|
}
|
||||||
|
}
|
17
CHDataManagement/Model/Settings/PostSettings.swift
Normal file
17
CHDataManagement/Model/Settings/PostSettings.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class PostSettings: ObservableObject {
|
||||||
|
|
||||||
|
/// The number of posts to show in a single page of the news feed
|
||||||
|
@Published
|
||||||
|
var postsPerPage: Int
|
||||||
|
|
||||||
|
/// The maximum width of the main content
|
||||||
|
@Published
|
||||||
|
var contentWidth: CGFloat
|
||||||
|
|
||||||
|
init(postsPerPage: Int, contentWidth: CGFloat) {
|
||||||
|
self.postsPerPage = postsPerPage
|
||||||
|
self.contentWidth = contentWidth
|
||||||
|
}
|
||||||
|
}
|
34
CHDataManagement/Model/Settings/Settings.swift
Normal file
34
CHDataManagement/Model/Settings/Settings.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class Settings: ObservableObject {
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var outputDirectoryPath: String
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var navigationBar: NavigationBarSettings
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var posts: PostSettings
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var german: LocalizedSettings
|
||||||
|
|
||||||
|
@Published
|
||||||
|
var english: LocalizedSettings
|
||||||
|
|
||||||
|
init(outputDirectoryPath: String, navigationBar: NavigationBarSettings, posts: PostSettings, german: LocalizedSettings, english: LocalizedSettings) {
|
||||||
|
self.outputDirectoryPath = outputDirectoryPath
|
||||||
|
self.navigationBar = navigationBar
|
||||||
|
self.posts = posts
|
||||||
|
self.german = german
|
||||||
|
self.english = english
|
||||||
|
}
|
||||||
|
|
||||||
|
func localized(in language: ContentLanguage) -> LocalizedSettings {
|
||||||
|
switch language {
|
||||||
|
case .english: return english
|
||||||
|
case .german: return german
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,26 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
final class WebsiteData: ObservableObject {
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var navigationTags: [Tag]
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var german: LocalizedWebsiteData
|
|
||||||
|
|
||||||
@Published
|
|
||||||
var english: LocalizedWebsiteData
|
|
||||||
|
|
||||||
init(navigationTags: [Tag] = [], german: LocalizedWebsiteData, english: LocalizedWebsiteData) {
|
|
||||||
self.navigationTags = navigationTags
|
|
||||||
self.german = german
|
|
||||||
self.english = english
|
|
||||||
}
|
|
||||||
|
|
||||||
func localized(in language: ContentLanguage) -> LocalizedWebsiteData {
|
|
||||||
switch language {
|
|
||||||
case .english: return english
|
|
||||||
case .german: return german
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,7 +15,7 @@ extension Content {
|
|||||||
private static let dbPath = FileManager.default.documentDirectory.appendingPathComponent("db").path()
|
private static let dbPath = FileManager.default.documentDirectory.appendingPathComponent("db").path()
|
||||||
|
|
||||||
static let mock: Content = Content(
|
static let mock: Content = Content(
|
||||||
websiteData: .mock,
|
settings: .mock,
|
||||||
posts: [.empty, .mock, .fullMock],
|
posts: [.empty, .mock, .fullMock],
|
||||||
pages: [.empty],
|
pages: [.empty],
|
||||||
tags: [.hiking, .mountains, .nature, .sports],
|
tags: [.hiking, .mountains, .nature, .sports],
|
||||||
|
@ -1,25 +1,48 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension WebsiteData {
|
extension Settings {
|
||||||
|
|
||||||
static let mock: WebsiteData = .init(
|
static let mock: Settings = .init(
|
||||||
|
outputDirectoryPath: "/some/path",
|
||||||
|
navigationBar: .init(iconPath: "/some/other/path", tags: []),
|
||||||
|
posts: .mock,
|
||||||
german: .german,
|
german: .german,
|
||||||
english: .english)
|
english: .english)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension LocalizedWebsiteData {
|
extension PostSettings {
|
||||||
|
|
||||||
static var german: LocalizedWebsiteData {
|
static var mock: PostSettings {
|
||||||
|
.init(postsPerPage: 20, contentWidth: 600)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LocalizedSettings {
|
||||||
|
|
||||||
|
static var german: LocalizedSettings {
|
||||||
|
.init(navigationBarIconDescription: "Ein Symbol",
|
||||||
|
posts: .german)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var english: LocalizedSettings {
|
||||||
|
.init(navigationBarIconDescription: "An icon",
|
||||||
|
posts: .english)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LocalizedPostSettings {
|
||||||
|
|
||||||
|
static var german: LocalizedPostSettings {
|
||||||
.init(
|
.init(
|
||||||
title: "Titel",
|
title: "Titel",
|
||||||
description: "Beschreibung",
|
description: "Beschreibung",
|
||||||
iconDescription: "Icon")
|
feedUrlPrefix: "blog")
|
||||||
}
|
}
|
||||||
|
|
||||||
static var english: LocalizedWebsiteData {
|
static var english: LocalizedPostSettings {
|
||||||
.init(
|
.init(
|
||||||
title: "A Title",
|
title: "A Title",
|
||||||
description: "Description",
|
description: "Description",
|
||||||
iconDescription: "Icon")
|
feedUrlPrefix: "feed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension WebsiteGeneratorConfiguration {
|
|
||||||
|
|
||||||
static let english = WebsiteGeneratorConfiguration(
|
|
||||||
language: .english,
|
|
||||||
outputDirectory: URL(fileURLWithPath: ""),
|
|
||||||
postsPerPage: 20,
|
|
||||||
postFeedTitle: "Posts",
|
|
||||||
postFeedDescription: "The most recent posts on christophhagen.de",
|
|
||||||
postFeedUrlPrefix: "feed",
|
|
||||||
navigationIconPath: "/assets/icons/ch.svg",
|
|
||||||
mainContentMaximumWidth: 600)
|
|
||||||
|
|
||||||
static let german = WebsiteGeneratorConfiguration(
|
|
||||||
language: .german,
|
|
||||||
outputDirectory: URL(fileURLWithPath: ""),
|
|
||||||
postsPerPage: 20,
|
|
||||||
postFeedTitle: "Beiträge",
|
|
||||||
postFeedDescription: "Die neusten Beiträge auf christophhagen.de",
|
|
||||||
postFeedUrlPrefix: "beiträge",
|
|
||||||
navigationIconPath: "/assets/icons/ch.svg",
|
|
||||||
mainContentMaximumWidth: 600)
|
|
||||||
}
|
|
@ -49,9 +49,9 @@ final class ImageGenerator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runJobs() -> Bool {
|
func runJobs(callback: (String) -> Void) -> Bool {
|
||||||
for job in jobs {
|
for job in jobs {
|
||||||
print("Generating image \(job.version)")
|
callback("Generating image \(job.version)")
|
||||||
guard generate(job: job) else {
|
guard generate(job: job) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -72,6 +72,10 @@ final class ImageGenerator {
|
|||||||
func generateVersion(for image: String, type: ImageType, maximumWidth: CGFloat, maximumHeight: CGFloat) -> String {
|
func generateVersion(for image: String, type: ImageType, maximumWidth: CGFloat, maximumHeight: CGFloat) -> String {
|
||||||
let version = versionFileName(image: image, type: type, width: maximumWidth, height: maximumHeight)
|
let version = versionFileName(image: image, type: type, width: maximumWidth, height: maximumHeight)
|
||||||
let fullPath = "/" + relativeImageOutputPath + "/" + version
|
let fullPath = "/" + relativeImageOutputPath + "/" + version
|
||||||
|
if exists(version) {
|
||||||
|
hasNowGenerated(version: version, for: image)
|
||||||
|
return fullPath
|
||||||
|
}
|
||||||
if hasPreviouslyGenerated(version: version, for: image), exists(version) {
|
if hasPreviouslyGenerated(version: version, for: image), exists(version) {
|
||||||
// Don't add job again
|
// Don't add job again
|
||||||
return fullPath
|
return fullPath
|
||||||
@ -121,6 +125,7 @@ final class ImageGenerator {
|
|||||||
print("Missing image \(inputPath.path())")
|
print("Missing image \(inputPath.path())")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: Data
|
let data: Data
|
||||||
do {
|
do {
|
||||||
data = try Data(contentsOf: inputPath)
|
data = try Data(contentsOf: inputPath)
|
||||||
@ -163,8 +168,16 @@ final class ImageGenerator {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let result = inOutputImagesFolder { folder in
|
let result = inOutputImagesFolder { folder in
|
||||||
let url = folder.appendingPathComponent(job.version)
|
let url = folder.appendingPathComponent(job.version)
|
||||||
|
if job.type == .avif {
|
||||||
|
let out = url.path()
|
||||||
|
let input = out.replacingOccurrences(of: ".avif", with: ".jpg")
|
||||||
|
print("avifenc -q 70 \(input) \(out)")
|
||||||
|
return true
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
try data.write(to: url)
|
try data.write(to: url)
|
||||||
return true
|
return true
|
||||||
@ -198,19 +211,20 @@ final class ImageGenerator {
|
|||||||
private func create(image: NSBitmapImageRep, type: ImageType, quality: CGFloat) -> Data? {
|
private func create(image: NSBitmapImageRep, type: ImageType, quality: CGFloat) -> Data? {
|
||||||
switch type {
|
switch type {
|
||||||
case .jpg:
|
case .jpg:
|
||||||
return image.representation(using: .jpeg, properties: [.compressionFactor: NSNumber(value: quality)])
|
return image.representation(using: .jpeg, properties: [.compressionFactor: NSNumber(value: 0.6)])
|
||||||
case .png:
|
case .png:
|
||||||
return image.representation(using: .png, properties: [.compressionFactor: NSNumber(value: quality)])
|
return image.representation(using: .png, properties: [.compressionFactor: NSNumber(value: 0.6)])
|
||||||
case .avif:
|
case .avif:
|
||||||
return createAvif(image: image, quality: quality)
|
return createAvif(image: image, quality: 0.7)
|
||||||
case .webp:
|
case .webp:
|
||||||
return createWebp(image: image, quality: quality)
|
return createWebp(image: image, quality: 0.8)
|
||||||
case .gif:
|
case .gif:
|
||||||
return image.representation(using: .gif, properties: [.compressionFactor: NSNumber(value: quality)])
|
return image.representation(using: .gif, properties: [.compressionFactor: NSNumber(value: quality)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createAvif(image: NSBitmapImageRep, quality: CGFloat) -> Data? {
|
private func createAvif(image: NSBitmapImageRep, quality: CGFloat) -> Data? {
|
||||||
|
return Data()
|
||||||
let newImage = NSImage(size: image.size)
|
let newImage = NSImage(size: image.size)
|
||||||
newImage.addRepresentation(image)
|
newImage.addRepresentation(image)
|
||||||
return SDImageAVIFCoder.shared.encodedData(with: newImage, format: .AVIF, options: [.encodeCompressionQuality: quality])
|
return SDImageAVIFCoder.shared.encodedData(with: newImage, format: .AVIF, options: [.encodeCompressionQuality: quality])
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
struct LocalizedPostSettingsFile {
|
||||||
|
|
||||||
|
/// The page title for the post feed
|
||||||
|
let feedTitle: String
|
||||||
|
|
||||||
|
/// The page description for the post feed
|
||||||
|
let feedDescription: String
|
||||||
|
|
||||||
|
/// The path to the feed in the final website, appended with the page number
|
||||||
|
let feedUrlPrefix: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LocalizedPostSettingsFile: Codable { }
|
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
struct LocalizedSettingsFile {
|
||||||
|
|
||||||
|
let navigationBarIconDescription: String
|
||||||
|
|
||||||
|
let posts: LocalizedPostSettingsFile
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LocalizedSettingsFile: Codable {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
struct NavigationBarSettingsFile {
|
||||||
|
|
||||||
|
/// The path to the main icon in the navigation bar
|
||||||
|
let navigationIconPath: String
|
||||||
|
|
||||||
|
/// The tags to show in the navigation bar
|
||||||
|
let navigationTags: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NavigationBarSettingsFile: Codable { }
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PostSettingsFile {
|
||||||
|
|
||||||
|
/// The number of posts to show in a single page of the news feed
|
||||||
|
let postsPerPage: Int
|
||||||
|
|
||||||
|
/// The maximum width of the main content
|
||||||
|
let contentWidth: CGFloat
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PostSettingsFile: Codable { }
|
17
CHDataManagement/Storage/Model/Settings/SettingsFile.swift
Normal file
17
CHDataManagement/Storage/Model/Settings/SettingsFile.swift
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SettingsFile {
|
||||||
|
|
||||||
|
/// The file path to the output directory
|
||||||
|
let outputDirectoryPath: String
|
||||||
|
|
||||||
|
let navigationBar: NavigationBarSettingsFile
|
||||||
|
|
||||||
|
let posts: PostSettingsFile
|
||||||
|
|
||||||
|
let german: LocalizedSettingsFile
|
||||||
|
|
||||||
|
let english: LocalizedSettingsFile
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsFile: Codable { }
|
@ -1,28 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
struct WebsiteDataFile {
|
|
||||||
|
|
||||||
let navigationTags: [String]
|
|
||||||
|
|
||||||
let german: LocalizedWebsiteDataFile
|
|
||||||
|
|
||||||
let english: LocalizedWebsiteDataFile
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WebsiteDataFile: Codable {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LocalizedWebsiteDataFile {
|
|
||||||
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
let description: String
|
|
||||||
|
|
||||||
let iconDescription: String
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedWebsiteDataFile: Codable {
|
|
||||||
|
|
||||||
}
|
|
@ -236,17 +236,17 @@ final class Storage {
|
|||||||
|
|
||||||
// MARK: Website data
|
// MARK: Website data
|
||||||
|
|
||||||
private var websiteDataUrl: URL {
|
private var settingsDataUrl: URL {
|
||||||
baseFolder.appending(path: "website-data.json", directoryHint: .notDirectory)
|
baseFolder.appending(path: "settings.json", directoryHint: .notDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadWebsiteData() throws -> WebsiteDataFile {
|
func loadSettings() throws -> SettingsFile {
|
||||||
try read(at: websiteDataUrl)
|
try read(at: settingsDataUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
func save(websiteData: WebsiteDataFile) -> Bool {
|
func save(settings: SettingsFile) -> Bool {
|
||||||
write(websiteData, type: "Website Data", id: "-", to: websiteDataUrl)
|
write(settings, type: "Settings", id: "-", to: settingsDataUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Image generation data
|
// MARK: Image generation data
|
||||||
|
@ -1,71 +1,61 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct WebsiteGeneratorConfiguration {
|
|
||||||
|
|
||||||
let language: ContentLanguage
|
|
||||||
|
|
||||||
let outputDirectory: URL
|
|
||||||
|
|
||||||
let postsPerPage: Int
|
|
||||||
|
|
||||||
let postFeedTitle: String
|
|
||||||
|
|
||||||
let postFeedDescription: String
|
|
||||||
|
|
||||||
let postFeedUrlPrefix: String
|
|
||||||
|
|
||||||
let navigationIconPath: String
|
|
||||||
|
|
||||||
let mainContentMaximumWidth: CGFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
final class WebsiteGenerator {
|
final class WebsiteGenerator {
|
||||||
|
|
||||||
let language: ContentLanguage
|
let language: ContentLanguage
|
||||||
|
|
||||||
let outputDirectory: URL
|
let localizedSettings: LocalizedSettings
|
||||||
|
|
||||||
let postsPerPage: Int
|
private var outputDirectory: URL {
|
||||||
|
URL(filePath: content.settings.outputDirectoryPath)
|
||||||
|
}
|
||||||
|
|
||||||
let postFeedTitle: String
|
private var postsPerPage: Int {
|
||||||
|
content.settings.posts.postsPerPage
|
||||||
|
}
|
||||||
|
|
||||||
let postFeedDescription: String
|
private var postFeedTitle: String {
|
||||||
|
localizedSettings.posts.title
|
||||||
|
}
|
||||||
|
|
||||||
let postFeedUrlPrefix: String
|
private var postFeedDescription: String {
|
||||||
|
localizedSettings.posts.description
|
||||||
|
}
|
||||||
|
|
||||||
let navigationIconPath: String
|
private var postFeedUrlPrefix: String {
|
||||||
|
localizedSettings.posts.feedUrlPrefix
|
||||||
|
}
|
||||||
|
|
||||||
let mainContentMaximumWidth: CGFloat
|
private var navigationIconPath: String {
|
||||||
|
content.settings.navigationBar.iconPath
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mainContentMaximumWidth: CGFloat {
|
||||||
|
content.settings.posts.contentWidth
|
||||||
|
}
|
||||||
|
|
||||||
private let content: Content
|
private let content: Content
|
||||||
|
|
||||||
private let imageGenerator: ImageGenerator
|
private let imageGenerator: ImageGenerator
|
||||||
|
|
||||||
init(content: Content, configuration: WebsiteGeneratorConfiguration) {
|
init(content: Content, language: ContentLanguage) {
|
||||||
self.language = configuration.language
|
self.language = language
|
||||||
self.outputDirectory = configuration.outputDirectory
|
|
||||||
self.postsPerPage = configuration.postsPerPage
|
|
||||||
self.postFeedTitle = configuration.postFeedTitle
|
|
||||||
self.postFeedDescription = configuration.postFeedDescription
|
|
||||||
self.postFeedUrlPrefix = configuration.postFeedUrlPrefix
|
|
||||||
self.navigationIconPath = configuration.navigationIconPath
|
|
||||||
self.mainContentMaximumWidth = configuration.mainContentMaximumWidth
|
|
||||||
|
|
||||||
self.content = content
|
self.content = content
|
||||||
|
self.localizedSettings = content.settings.localized(in: language)
|
||||||
self.imageGenerator = ImageGenerator(
|
self.imageGenerator = ImageGenerator(
|
||||||
storage: content.storage,
|
storage: content.storage,
|
||||||
inputImageFolder: content.storage.filesFolder,
|
inputImageFolder: content.storage.filesFolder,
|
||||||
relativeImageOutputPath: "images")
|
relativeImageOutputPath: "images")
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateWebsite() -> Bool {
|
func generateWebsite(callback: (String) -> Void) -> Bool {
|
||||||
guard imageGenerator.prepareForGeneration() else {
|
guard imageGenerator.prepareForGeneration() else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guard createPostFeedPages() else {
|
guard createPostFeedPages() else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
guard imageGenerator.runJobs() else {
|
guard imageGenerator.runJobs(callback: callback) else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return imageGenerator.save()
|
return imageGenerator.save()
|
||||||
@ -77,7 +67,9 @@ final class WebsiteGenerator {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
let navBarData = createNavigationBarData()
|
let navBarData = createNavigationBarData(
|
||||||
|
settings: content.settings.navigationBar,
|
||||||
|
iconDescription: localizedSettings.navigationBarIconDescription)
|
||||||
|
|
||||||
let numberOfPages = (totalCount + postsPerPage - 1) / postsPerPage // Round up
|
let numberOfPages = (totalCount + postsPerPage - 1) / postsPerPage // Round up
|
||||||
for pageIndex in 1...numberOfPages {
|
for pageIndex in 1...numberOfPages {
|
||||||
@ -91,15 +83,14 @@ final class WebsiteGenerator {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createNavigationBarData() -> NavigationBarData {
|
private func createNavigationBarData(settings: NavigationBarSettings, iconDescription: String) -> NavigationBarData {
|
||||||
let data = content.websiteData.localized(in: language)
|
let navigationItems: [NavigationBarLink] = settings.tags.map {
|
||||||
let navigationItems: [NavigationBarLink] = content.websiteData.navigationTags.map {
|
|
||||||
let localized = $0.localized(in: language)
|
let localized = $0.localized(in: language)
|
||||||
return .init(text: localized.name, url: localized.urlComponent)
|
return .init(text: localized.name, url: localized.urlComponent)
|
||||||
}
|
}
|
||||||
return NavigationBarData(
|
return NavigationBarData(
|
||||||
navigationIconPath: navigationIconPath,
|
navigationIconPath: navigationIconPath,
|
||||||
iconDescription: data.iconDescription,
|
iconDescription: iconDescription,
|
||||||
navigationItems: navigationItems)
|
navigationItems: navigationItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,32 +3,108 @@ import SwiftUI
|
|||||||
struct PageListView: View {
|
struct PageListView: View {
|
||||||
|
|
||||||
@Environment(\.language)
|
@Environment(\.language)
|
||||||
var language
|
private var language
|
||||||
|
|
||||||
@EnvironmentObject
|
@EnvironmentObject
|
||||||
var content: Content
|
private var content: Content
|
||||||
|
|
||||||
@State
|
@State
|
||||||
var selectedPage: Page?
|
private var selected: Page?
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var showNewPageView = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var newPageId = ""
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var newPageIdIsValid = false
|
||||||
|
|
||||||
|
private let allowedCharactersInPageId = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-")).inverted
|
||||||
|
|
||||||
|
private var cleanPageId: String {
|
||||||
|
newPageId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationSplitView {
|
NavigationSplitView {
|
||||||
List(content.pages, selection: $selectedPage) { page in
|
List(content.pages, selection: $selected) { page in
|
||||||
Text(page.localized(in: language).title)
|
Text(page.localized(in: language).title)
|
||||||
.tag(page)
|
.tag(page)
|
||||||
|
|
||||||
}
|
}
|
||||||
} detail: {
|
.toolbar {
|
||||||
// Detail view when an item is selected
|
ToolbarItem(placement: .primaryAction) {
|
||||||
if let selectedPage {
|
Button(action: { showNewPageView = true }) {
|
||||||
PageDetailView(page: selectedPage)
|
Label("New post", systemSymbol: .plus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationSplitViewColumnWidth(min: 300, ideal: 300, max: 300)
|
||||||
|
} content: {
|
||||||
|
if let selected {
|
||||||
|
PageDetailView(page: selected)
|
||||||
|
.layoutPriority(1)
|
||||||
} else {
|
} else {
|
||||||
// Fallback if no item is selected
|
// Fallback if no item is selected
|
||||||
Text("Select a page to show the content.")
|
Text("Select a page from the list")
|
||||||
.font(.largeTitle)
|
.font(.largeTitle)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
|
} detail: {
|
||||||
|
if let selected {
|
||||||
|
EmptyView()
|
||||||
|
.frame(maxWidth: 350)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
.frame(maxWidth: 350)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.onAppear {
|
||||||
|
if selected == nil {
|
||||||
|
selected = content.pages.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showNewPageView,
|
||||||
|
onDismiss: addNewPage) {
|
||||||
|
TextEntrySheet(
|
||||||
|
title: "Enter the id for the new page",
|
||||||
|
text: $newPageId,
|
||||||
|
isValid: $newPageIdIsValid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func isValid(id: String) -> Bool {
|
||||||
|
let id = cleanPageId
|
||||||
|
guard id != "" else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
guard !content.pages.contains(where: { $0.id == id }) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Only allow alphanumeric characters and hyphens
|
||||||
|
return id.rangeOfCharacter(from: allowedCharactersInPageId) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addNewPage() {
|
||||||
|
let id = cleanPageId
|
||||||
|
guard isValid(id: id) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = Page(
|
||||||
|
id: id,
|
||||||
|
isDraft: true,
|
||||||
|
createdDate: .now,
|
||||||
|
startDate: .now,
|
||||||
|
endDate: nil,
|
||||||
|
german: .init(urlString: "seite",
|
||||||
|
title: "Ein Titel"),
|
||||||
|
english: .init(urlString: "page",
|
||||||
|
title: "A Title"),
|
||||||
|
tags: [])
|
||||||
|
content.pages.insert(page, at: 0)
|
||||||
|
selected = page
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,15 +8,15 @@ struct PostList: View {
|
|||||||
@Environment(\.language)
|
@Environment(\.language)
|
||||||
private var language: ContentLanguage
|
private var language: ContentLanguage
|
||||||
|
|
||||||
@State
|
|
||||||
private var newPostId = ""
|
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var selected: Post? = nil
|
private var selected: Post? = nil
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var showNewPostView = false
|
private var showNewPostView = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var newPostId = ""
|
||||||
|
|
||||||
@State
|
@State
|
||||||
private var newPostIdIsValid = false
|
private var newPostIdIsValid = false
|
||||||
|
|
||||||
@ -32,7 +32,6 @@ struct PostList: View {
|
|||||||
Text(post.localized(in: language).title)
|
Text(post.localized(in: language).title)
|
||||||
.tag(post)
|
.tag(post)
|
||||||
}
|
}
|
||||||
.frame(minWidth: 200)
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
Button(action: { showNewPostView = true }) {
|
Button(action: { showNewPostView = true }) {
|
||||||
@ -40,6 +39,7 @@ struct PostList: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250)
|
||||||
} content: {
|
} content: {
|
||||||
if let selected {
|
if let selected {
|
||||||
PostContentView(post: selected)
|
PostContentView(post: selected)
|
||||||
|
91
CHDataManagement/Views/Settings/FolderSettingsView.swift
Normal file
91
CHDataManagement/Views/Settings/FolderSettingsView.swift
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FolderSettingsView: View {
|
||||||
|
|
||||||
|
@Environment(\.language)
|
||||||
|
private var language
|
||||||
|
|
||||||
|
@AppStorage("contentPath")
|
||||||
|
private var contentPath: String = ""
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var content: Content
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var folderSelection: SecurityScopeBookmark = .contentPath
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Folder Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Select the folders for the app to work.")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
Text("Content Folder")
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.bottom, 1)
|
||||||
|
Text(contentPath)
|
||||||
|
Button(action: selectContentFolder) {
|
||||||
|
Text("Select folder")
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
Text("Output Folder")
|
||||||
|
.font(.headline)
|
||||||
|
.padding(.bottom, 1)
|
||||||
|
Text(content.settings.outputDirectoryPath)
|
||||||
|
Button(action: selectOutputFolder) {
|
||||||
|
Text("Select folder")
|
||||||
|
}
|
||||||
|
.padding(.bottom)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Folder selection
|
||||||
|
|
||||||
|
private func selectContentFolder() {
|
||||||
|
folderSelection = .contentPath
|
||||||
|
guard let url = savePanelUsingOpenPanel() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.contentPath = url.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectOutputFolder() {
|
||||||
|
folderSelection = .outputPath
|
||||||
|
guard let url = savePanelUsingOpenPanel() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.settings.outputDirectoryPath = url.path()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func savePanelUsingOpenPanel() -> URL? {
|
||||||
|
let panel = NSOpenPanel()
|
||||||
|
// Sets up so user can only select a single directory
|
||||||
|
panel.canChooseFiles = false
|
||||||
|
panel.canChooseDirectories = true
|
||||||
|
panel.allowsMultipleSelection = false
|
||||||
|
panel.showsHiddenFiles = false
|
||||||
|
panel.title = "Select Save Directory"
|
||||||
|
panel.prompt = "Select Save Directory"
|
||||||
|
|
||||||
|
let response = panel.runModal()
|
||||||
|
guard response == .OK else {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let url = panel.url else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
content.storage.save(folderUrl: url, in: folderSelection)
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
FolderSettingsView()
|
||||||
|
.environmentObject(Content.mock)
|
||||||
|
.padding()
|
||||||
|
}
|
76
CHDataManagement/Views/Settings/GenerationSettingsView.swift
Normal file
76
CHDataManagement/Views/Settings/GenerationSettingsView.swift
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct GenerationSettingsView: View {
|
||||||
|
|
||||||
|
@Environment(\.language)
|
||||||
|
private var language
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var content: Content
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isGeneratingWebsite = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var generatorText: String = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Website Generation")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Regenerate the website and monitor the output")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
Button(action: generateFeed) {
|
||||||
|
Text("Generate")
|
||||||
|
}
|
||||||
|
.disabled(isGeneratingWebsite)
|
||||||
|
if isGeneratingWebsite {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
|
.frame(height: 25)
|
||||||
|
}
|
||||||
|
Text(generatorText)
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func generateFeed() {
|
||||||
|
guard content.settings.outputDirectoryPath != "" else {
|
||||||
|
print("Invalid output path")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let url = URL(fileURLWithPath: content.settings.outputDirectoryPath)
|
||||||
|
|
||||||
|
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||||
|
print("Missing output folder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isGeneratingWebsite = true
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
let generator = WebsiteGenerator(
|
||||||
|
content: content,
|
||||||
|
language: language)
|
||||||
|
_ = generator.generateWebsite { text in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.generatorText = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isGeneratingWebsite = false
|
||||||
|
self.generatorText = "Generation complete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
GenerationSettingsView()
|
||||||
|
.environmentObject(Content.mock)
|
||||||
|
.padding()
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LocalizedPostFeedSettingsView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var settings: LocalizedPostSettings
|
||||||
|
|
||||||
|
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.")
|
||||||
|
.padding(.bottom)
|
||||||
|
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.")
|
||||||
|
.padding(.bottom)
|
||||||
|
Text("Description")
|
||||||
|
.font(.headline)
|
||||||
|
TextEditor(text: $settings.description)
|
||||||
|
.font(.body)
|
||||||
|
.lineLimit(5, reservesSpace: true)
|
||||||
|
.frame(maxWidth: 400, minHeight: 50, maxHeight: 500)
|
||||||
|
.textEditorStyle(.plain)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
.padding(.leading, 3)
|
||||||
|
.background(Color.gray.opacity(0.1))
|
||||||
|
.cornerRadius(8)
|
||||||
|
Text("The description of all post feed pages.")
|
||||||
|
.padding(.bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
LocalizedPostFeedSettingsView(settings: .english)
|
||||||
|
.padding()
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LocalizedSettingsView: View {
|
|
||||||
|
|
||||||
@ObservedObject
|
|
||||||
var settings: LocalizedWebsiteData
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Title")
|
|
||||||
.font(.headline)
|
|
||||||
TextField("", text: $settings.title)
|
|
||||||
Text("Description")
|
|
||||||
.font(.headline)
|
|
||||||
TextField("", text: $settings.description)
|
|
||||||
Text("Icon description")
|
|
||||||
.font(.headline)
|
|
||||||
TextField("", text: $settings.iconDescription)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
LocalizedSettingsView(settings: .english)
|
|
||||||
}
|
|
@ -0,0 +1,84 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct IconDescriptionView: View {
|
||||||
|
|
||||||
|
@ObservedObject
|
||||||
|
var settings: LocalizedSettings
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
TextField("", text: $settings.navigationBarIconDescription)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NavigationBarSettingsView: View {
|
||||||
|
|
||||||
|
@Environment(\.language)
|
||||||
|
private var language
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var content: Content
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var showTagPicker = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Notification Bar Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Customize the navigation bar for all pages at the top of the website")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
Text("Icon Path")
|
||||||
|
.font(.headline)
|
||||||
|
TextField("", text: $content.settings.navigationBar.iconPath)
|
||||||
|
.textFieldStyle(.roundedBorder)
|
||||||
|
.frame(maxWidth: 300)
|
||||||
|
Text("Specify the path to the icon file with regard to the final website folder.")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
Text("Icon Description")
|
||||||
|
.font(.headline)
|
||||||
|
IconDescriptionView(settings: content.settings.localized(in: language))
|
||||||
|
Text("Provide a description of the icon for screen readers.")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
Text("Visible Tags")
|
||||||
|
.font(.headline)
|
||||||
|
FlowHStack {
|
||||||
|
ForEach(content.settings.navigationBar.tags, id: \.id) { tag in
|
||||||
|
TagView(tag: .init(
|
||||||
|
en: tag.english.name,
|
||||||
|
de: tag.german.name)
|
||||||
|
)
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
}
|
||||||
|
Button(action: { showTagPicker = true }) {
|
||||||
|
Image(systemSymbol: .squareAndPencilCircleFill)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
.frame(height: 22)
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
.background(Circle()
|
||||||
|
.fill(Color.white)
|
||||||
|
.padding(1))
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
Text("Select the tags to show in the navigation bar. The number should be even.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showTagPicker) {
|
||||||
|
TagSelectionView(
|
||||||
|
presented: $showTagPicker,
|
||||||
|
selected: $content.settings.navigationBar.tags,
|
||||||
|
tags: $content.tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
NavigationBarSettingsView()
|
||||||
|
.environmentObject(Content.mock)
|
||||||
|
.padding()
|
||||||
|
}
|
30
CHDataManagement/Views/Settings/PostFeedSettingsView.swift
Normal file
30
CHDataManagement/Views/Settings/PostFeedSettingsView.swift
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct PostFeedSettingsView: View {
|
||||||
|
|
||||||
|
@Environment(\.language)
|
||||||
|
private var language
|
||||||
|
|
||||||
|
@EnvironmentObject
|
||||||
|
private var content: Content
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ScrollView {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Post Feed Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Change the way the posts are displayed")
|
||||||
|
.padding(.bottom, 30)
|
||||||
|
LocalizedPostFeedSettingsView(settings: content.settings.localized(in: language).posts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
PostFeedSettingsView()
|
||||||
|
.environmentObject(Content.mock)
|
||||||
|
.padding()
|
||||||
|
}
|
81
CHDataManagement/Views/Settings/SectionedSettingsView.swift
Normal file
81
CHDataManagement/Views/Settings/SectionedSettingsView.swift
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct SectionedSettingsView: View {
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var selectedSection: SettingsSection? = .generation
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationSplitView {
|
||||||
|
SettingsSidebar(selectedSection: $selectedSection)
|
||||||
|
.frame(minWidth: 200, idealWidth: 200, maxWidth: 200)
|
||||||
|
} detail: {
|
||||||
|
DetailView(section: selectedSection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct DetailView: View {
|
||||||
|
|
||||||
|
let section: SettingsSection?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Group {
|
||||||
|
switch section {
|
||||||
|
case .generation:
|
||||||
|
GenerationSettingsView()
|
||||||
|
case .folders:
|
||||||
|
FolderSettingsView()
|
||||||
|
case .navigationBar:
|
||||||
|
NavigationBarSettingsView()
|
||||||
|
case .postFeed:
|
||||||
|
PostFeedSettingsView()
|
||||||
|
case .none:
|
||||||
|
Text("Select a setting from the sidebar")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
|
.padding()
|
||||||
|
.navigationTitle(section?.rawValue ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct AppearanceView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Appearance Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Customize the look and feel of the app.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotificationsView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Notifications Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Manage your notification preferences.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PrivacyView: View {
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("Privacy Settings")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.bold()
|
||||||
|
Text("Configure your privacy and security settings.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
SectionedSettingsView()
|
||||||
|
}
|
34
CHDataManagement/Views/Settings/SettingsSection.swift
Normal file
34
CHDataManagement/Views/Settings/SettingsSection.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import SFSafeSymbols
|
||||||
|
|
||||||
|
enum SettingsSection: String {
|
||||||
|
|
||||||
|
case generation = "Generation"
|
||||||
|
|
||||||
|
case folders = "Folders"
|
||||||
|
|
||||||
|
case navigationBar = "Navigation Bar"
|
||||||
|
|
||||||
|
case postFeed = "Post Feed"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsSection {
|
||||||
|
|
||||||
|
var icon: SFSymbol {
|
||||||
|
switch self {
|
||||||
|
case .generation: return .arrowTriangle2Circlepath
|
||||||
|
case .folders: return .folder
|
||||||
|
case .navigationBar: return .menubarRectangle
|
||||||
|
case .postFeed: return .rectangleGrid1x2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsSection: CaseIterable {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SettingsSection: Identifiable {
|
||||||
|
|
||||||
|
var id: String { rawValue }
|
||||||
|
}
|
15
CHDataManagement/Views/Settings/SettingsSidebar.swift
Normal file
15
CHDataManagement/Views/Settings/SettingsSidebar.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SFSafeSymbols
|
||||||
|
|
||||||
|
struct SettingsSidebar: View {
|
||||||
|
|
||||||
|
@Binding var selectedSection: SettingsSection?
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List(SettingsSection.allCases, selection: $selectedSection) { item in
|
||||||
|
Label(item.rawValue, systemSymbol: item.icon)
|
||||||
|
.tag(item)
|
||||||
|
}
|
||||||
|
.navigationTitle("Settings")
|
||||||
|
}
|
||||||
|
}
|
@ -1,170 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SettingsView: View {
|
|
||||||
|
|
||||||
@Environment(\.language)
|
|
||||||
var language
|
|
||||||
|
|
||||||
@AppStorage("contentPath")
|
|
||||||
var contentPath: String = ""
|
|
||||||
|
|
||||||
@AppStorage("outputPath")
|
|
||||||
var outputPath: String = ""
|
|
||||||
|
|
||||||
@EnvironmentObject
|
|
||||||
var content: Content
|
|
||||||
|
|
||||||
@State
|
|
||||||
private var folderSelection: SecurityScopeBookmark = .contentPath
|
|
||||||
|
|
||||||
@State
|
|
||||||
private var showTagPicker = false
|
|
||||||
|
|
||||||
@State
|
|
||||||
private var isGeneratingWebsite = false
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ScrollView {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("Content Folder")
|
|
||||||
.font(.headline)
|
|
||||||
TextField("Content Folder", text: $contentPath)
|
|
||||||
Button(action: selectContentFolder) {
|
|
||||||
Text("Select folder")
|
|
||||||
}
|
|
||||||
Text("Output Folder")
|
|
||||||
.font(.headline)
|
|
||||||
TextField("Output Folder", text: $outputPath)
|
|
||||||
Button(action: selectOutputFolder) {
|
|
||||||
Text("Select folder")
|
|
||||||
}
|
|
||||||
Text("Navigation Bar Items")
|
|
||||||
.font(.headline)
|
|
||||||
FlowHStack {
|
|
||||||
ForEach(content.websiteData.navigationTags, id: \.id) { tag in
|
|
||||||
TagView(tag: .init(
|
|
||||||
en: tag.english.name,
|
|
||||||
de: tag.german.name)
|
|
||||||
)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
}
|
|
||||||
Button(action: { showTagPicker = true }) {
|
|
||||||
Image(systemSymbol: .squareAndPencilCircleFill)
|
|
||||||
.resizable()
|
|
||||||
.aspectRatio(1, contentMode: .fit)
|
|
||||||
.frame(height: 22)
|
|
||||||
.foregroundColor(Color.gray)
|
|
||||||
.background(Circle()
|
|
||||||
.fill(Color.white)
|
|
||||||
.padding(1))
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
LocalizedSettingsView(settings: content.websiteData.localized(in: language))
|
|
||||||
Text("Feed")
|
|
||||||
.font(.headline)
|
|
||||||
HStack {
|
|
||||||
Button(action: generateFeed) {
|
|
||||||
Text("Generate")
|
|
||||||
}
|
|
||||||
.disabled(isGeneratingWebsite)
|
|
||||||
if isGeneratingWebsite {
|
|
||||||
ProgressView()
|
|
||||||
.progressViewStyle(.circular)
|
|
||||||
.frame(height: 25)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.sheet(isPresented: $showTagPicker) {
|
|
||||||
TagSelectionView(
|
|
||||||
presented: $showTagPicker,
|
|
||||||
selected: $content.websiteData.navigationTags,
|
|
||||||
tags: $content.tags)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Folder selection
|
|
||||||
|
|
||||||
private func selectContentFolder() {
|
|
||||||
folderSelection = .contentPath
|
|
||||||
guard let url = savePanelUsingOpenPanel() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.contentPath = url.path()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func selectOutputFolder() {
|
|
||||||
folderSelection = .outputPath
|
|
||||||
guard let url = savePanelUsingOpenPanel() else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.outputPath = url.path()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Feed
|
|
||||||
|
|
||||||
private func generateFeed() {
|
|
||||||
guard outputPath != "" else {
|
|
||||||
print("Invalid output path")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let url = URL(fileURLWithPath: outputPath)
|
|
||||||
|
|
||||||
guard FileManager.default.fileExists(atPath: url.path) else {
|
|
||||||
print("Missing output folder")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isGeneratingWebsite = true
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
let generator = WebsiteGenerator(
|
|
||||||
content: content,
|
|
||||||
configuration: configuration)
|
|
||||||
_ = generator.generateWebsite()
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
isGeneratingWebsite = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var configuration: WebsiteGeneratorConfiguration {
|
|
||||||
return .init(
|
|
||||||
language: language,
|
|
||||||
outputDirectory: URL(filePath: outputPath, directoryHint: .isDirectory),
|
|
||||||
postsPerPage: 20,
|
|
||||||
postFeedTitle: "Posts",
|
|
||||||
postFeedDescription: "The most recent posts on christophhagen.de",
|
|
||||||
postFeedUrlPrefix: "feed",
|
|
||||||
navigationIconPath: "/assets/icons/ch.svg",
|
|
||||||
mainContentMaximumWidth: 600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func savePanelUsingOpenPanel() -> URL? {
|
|
||||||
let panel = NSOpenPanel()
|
|
||||||
// Sets up so user can only select a single directory
|
|
||||||
panel.canChooseFiles = false
|
|
||||||
panel.canChooseDirectories = true
|
|
||||||
panel.allowsMultipleSelection = false
|
|
||||||
panel.showsHiddenFiles = false
|
|
||||||
panel.title = "Select Save Directory"
|
|
||||||
panel.prompt = "Select Save Directory"
|
|
||||||
|
|
||||||
let response = panel.runModal()
|
|
||||||
guard response == .OK else {
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
guard let url = panel.url else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
content.storage.save(folderUrl: url, in: folderSelection)
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#Preview {
|
|
||||||
SettingsView()
|
|
||||||
.environmentObject(Content.mock)
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user