Reorganize saving, generate feed
This commit is contained in:
parent
3c950d47a2
commit
dc7b7a0e90
@ -10,12 +10,9 @@
|
|||||||
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850082CEE01BF0090B18B /* PagePickerView.swift */; };
|
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850082CEE01BF0090B18B /* PagePickerView.swift */; };
|
||||||
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500A2CEE02FA0090B18B /* Content+Mock.swift */; };
|
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500A2CEE02FA0090B18B /* Content+Mock.swift */; };
|
||||||
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500C2CEE07140090B18B /* ColorPalette.swift */; };
|
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500C2CEE07140090B18B /* ColorPalette.swift */; };
|
||||||
E21850112CEE17070090B18B /* Page+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850102CEE17010090B18B /* Page+Storage.swift */; };
|
|
||||||
E21850132CEE541D0090B18B /* Post+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850122CEE541A0090B18B /* Post+Storage.swift */; };
|
|
||||||
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850142CEE55D40090B18B /* FileOnDisk.swift */; };
|
E21850152CEE55D40090B18B /* FileOnDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850142CEE55D40090B18B /* FileOnDisk.swift */; };
|
||||||
E21850172CEE55FC0090B18B /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850162CEE55FB0090B18B /* FileType.swift */; };
|
E21850172CEE55FC0090B18B /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850162CEE55FB0090B18B /* FileType.swift */; };
|
||||||
E21850192CEE561C0090B18B /* PageOnDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850182CEE561B0090B18B /* PageOnDisk.swift */; };
|
E21850192CEE561C0090B18B /* PageOnDisk.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850182CEE561B0090B18B /* PageOnDisk.swift */; };
|
||||||
E218501B2CEE59EC0090B18B /* Tag+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501A2CEE59E80090B18B /* Tag+Storage.swift */; };
|
|
||||||
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501C2CEE6CB30090B18B /* VerticalCenter.swift */; };
|
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501C2CEE6CB30090B18B /* VerticalCenter.swift */; };
|
||||||
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */; };
|
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */; };
|
||||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850222CF10C840090B18B /* TagSelectionView.swift */; };
|
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850222CF10C840090B18B /* TagSelectionView.swift */; };
|
||||||
@ -23,13 +20,12 @@
|
|||||||
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850262CF3B42D0090B18B /* PostDetailView.swift */; };
|
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850262CF3B42D0090B18B /* PostDetailView.swift */; };
|
||||||
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502A2CF790AC0090B18B /* PostContentView.swift */; };
|
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502A2CF790AC0090B18B /* PostContentView.swift */; };
|
||||||
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502C2CF791440090B18B /* PostImagesView.swift */; };
|
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502C2CF791440090B18B /* PostImagesView.swift */; };
|
||||||
E218502F2CFAF69C0090B18B /* Content+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502E2CFAF6990090B18B /* Content+Generate.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 /* WebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850322CFAFA200090B18B /* WebsiteData.swift */; };
|
||||||
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* WebsiteDataFile.swift */; };
|
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* WebsiteDataFile.swift */; };
|
||||||
E21850372CFCA55F0090B18B /* LocalizedWebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */; };
|
E21850372CFCA55F0090B18B /* LocalizedWebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedWebsiteData.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 */; };
|
||||||
E218503B2CFCFBE70090B18B /* WebsiteData+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503A2CFCFBDE0090B18B /* WebsiteData+Storage.swift */; };
|
|
||||||
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */; };
|
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.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 */; };
|
||||||
@ -42,6 +38,13 @@
|
|||||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */; };
|
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */; };
|
||||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.swift */; };
|
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.swift */; };
|
||||||
E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50E2CFDD76B00AEF16D /* ImagesContentView.swift */; };
|
E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50E2CFDD76B00AEF16D /* ImagesContentView.swift */; };
|
||||||
|
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5142CFF00B900AEF16D /* Content+Load.swift */; };
|
||||||
|
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5162CFF00F200AEF16D /* Content+Save.swift */; };
|
||||||
|
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5182CFF035200AEF16D /* Array+Split.swift */; };
|
||||||
|
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */; };
|
||||||
|
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA51C2CFF135B00AEF16D /* GenericPage.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 */; };
|
||||||
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 */; };
|
||||||
@ -82,7 +85,7 @@
|
|||||||
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A9CB7D2C7BCF2A005C89CC /* Page.swift */; };
|
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A9CB7D2C7BCF2A005C89CC /* Page.swift */; };
|
||||||
E2B85F362C426BEE0047CD0C /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2B85F352C426BEE0047CD0C /* SFSafeSymbols */; };
|
E2B85F362C426BEE0047CD0C /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2B85F352C426BEE0047CD0C /* SFSafeSymbols */; };
|
||||||
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3A2C428F0D0047CD0C /* Post.swift */; };
|
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3A2C428F0D0047CD0C /* Post.swift */; };
|
||||||
E2B85F3D2C4293F80047CD0C /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3C2C4293F80047CD0C /* Feed.swift */; };
|
E2B85F3D2C4293F80047CD0C /* PageInFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F3C2C4293F80047CD0C /* PageInFeed.swift */; };
|
||||||
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F402C4294790047CD0C /* PageHead.swift */; };
|
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F402C4294790047CD0C /* PageHead.swift */; };
|
||||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F422C4294F60047CD0C /* FeedEntry.swift */; };
|
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F422C4294F60047CD0C /* FeedEntry.swift */; };
|
||||||
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F442C429ED60047CD0C /* ImageGallery.swift */; };
|
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F442C429ED60047CD0C /* ImageGallery.swift */; };
|
||||||
@ -98,12 +101,9 @@
|
|||||||
E21850082CEE01BF0090B18B /* PagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePickerView.swift; sourceTree = "<group>"; };
|
E21850082CEE01BF0090B18B /* PagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagePickerView.swift; sourceTree = "<group>"; };
|
||||||
E218500A2CEE02FA0090B18B /* Content+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Mock.swift"; sourceTree = "<group>"; };
|
E218500A2CEE02FA0090B18B /* Content+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Mock.swift"; sourceTree = "<group>"; };
|
||||||
E218500C2CEE07140090B18B /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = "<group>"; };
|
E218500C2CEE07140090B18B /* ColorPalette.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = "<group>"; };
|
||||||
E21850102CEE17010090B18B /* Page+Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Storage.swift"; sourceTree = "<group>"; };
|
|
||||||
E21850122CEE541A0090B18B /* Post+Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Post+Storage.swift"; sourceTree = "<group>"; };
|
|
||||||
E21850142CEE55D40090B18B /* FileOnDisk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOnDisk.swift; sourceTree = "<group>"; };
|
E21850142CEE55D40090B18B /* FileOnDisk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOnDisk.swift; sourceTree = "<group>"; };
|
||||||
E21850162CEE55FB0090B18B /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = "<group>"; };
|
E21850162CEE55FB0090B18B /* FileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileType.swift; sourceTree = "<group>"; };
|
||||||
E21850182CEE561B0090B18B /* PageOnDisk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageOnDisk.swift; sourceTree = "<group>"; };
|
E21850182CEE561B0090B18B /* PageOnDisk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageOnDisk.swift; sourceTree = "<group>"; };
|
||||||
E218501A2CEE59E80090B18B /* Tag+Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tag+Storage.swift"; sourceTree = "<group>"; };
|
|
||||||
E218501C2CEE6CB30090B18B /* VerticalCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalCenter.swift; sourceTree = "<group>"; };
|
E218501C2CEE6CB30090B18B /* VerticalCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalCenter.swift; sourceTree = "<group>"; };
|
||||||
E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
|
E218501E2CEE6DAC0090B18B /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = "<group>"; };
|
||||||
E21850222CF10C840090B18B /* TagSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagSelectionView.swift; sourceTree = "<group>"; };
|
E21850222CF10C840090B18B /* TagSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagSelectionView.swift; sourceTree = "<group>"; };
|
||||||
@ -111,13 +111,12 @@
|
|||||||
E21850262CF3B42D0090B18B /* PostDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDetailView.swift; sourceTree = "<group>"; };
|
E21850262CF3B42D0090B18B /* PostDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostDetailView.swift; sourceTree = "<group>"; };
|
||||||
E218502A2CF790AC0090B18B /* PostContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostContentView.swift; sourceTree = "<group>"; };
|
E218502A2CF790AC0090B18B /* PostContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostContentView.swift; sourceTree = "<group>"; };
|
||||||
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 /* Content+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Generate.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 /* WebsiteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteData.swift; sourceTree = "<group>"; };
|
||||||
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteDataFile.swift; sourceTree = "<group>"; };
|
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteDataFile.swift; sourceTree = "<group>"; };
|
||||||
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedWebsiteData.swift; sourceTree = "<group>"; };
|
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedWebsiteData.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>"; };
|
||||||
E218503A2CFCFBDE0090B18B /* WebsiteData+Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebsiteData+Storage.swift"; sourceTree = "<group>"; };
|
|
||||||
E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedSettingsView.swift; sourceTree = "<group>"; };
|
E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedSettingsView.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>"; };
|
||||||
@ -130,6 +129,13 @@
|
|||||||
E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageTagAssignmentView.swift; sourceTree = "<group>"; };
|
E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageTagAssignmentView.swift; sourceTree = "<group>"; };
|
||||||
E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTagAssignmentView.swift; sourceTree = "<group>"; };
|
E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTagAssignmentView.swift; sourceTree = "<group>"; };
|
||||||
E25DA50E2CFDD76B00AEF16D /* ImagesContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesContentView.swift; sourceTree = "<group>"; };
|
E25DA50E2CFDD76B00AEF16D /* ImagesContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesContentView.swift; sourceTree = "<group>"; };
|
||||||
|
E25DA5142CFF00B900AEF16D /* Content+Load.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Load.swift"; sourceTree = "<group>"; };
|
||||||
|
E25DA5162CFF00F200AEF16D /* Content+Save.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Content+Save.swift"; sourceTree = "<group>"; };
|
||||||
|
E25DA5182CFF035200AEF16D /* Array+Split.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Split.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>"; };
|
||||||
|
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>"; };
|
||||||
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>"; };
|
||||||
@ -168,7 +174,7 @@
|
|||||||
E2A37D2C2CED2EEE0000979F /* OptionalTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalTextField.swift; sourceTree = "<group>"; };
|
E2A37D2C2CED2EEE0000979F /* OptionalTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalTextField.swift; sourceTree = "<group>"; };
|
||||||
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = "<group>"; };
|
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Page.swift; sourceTree = "<group>"; };
|
||||||
E2B85F3A2C428F0D0047CD0C /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
E2B85F3A2C428F0D0047CD0C /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
||||||
E2B85F3C2C4293F80047CD0C /* Feed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
E2B85F3C2C4293F80047CD0C /* PageInFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInFeed.swift; sourceTree = "<group>"; };
|
||||||
E2B85F402C4294790047CD0C /* PageHead.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHead.swift; sourceTree = "<group>"; };
|
E2B85F402C4294790047CD0C /* PageHead.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHead.swift; sourceTree = "<group>"; };
|
||||||
E2B85F422C4294F60047CD0C /* FeedEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedEntry.swift; sourceTree = "<group>"; };
|
E2B85F422C4294F60047CD0C /* FeedEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedEntry.swift; sourceTree = "<group>"; };
|
||||||
E2B85F442C429ED60047CD0C /* ImageGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGallery.swift; sourceTree = "<group>"; };
|
E2B85F442C429ED60047CD0C /* ImageGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGallery.swift; sourceTree = "<group>"; };
|
||||||
@ -206,6 +212,20 @@
|
|||||||
path = Import;
|
path = Import;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E25DA5112CFF001900AEF16D /* Model */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */,
|
||||||
|
E21850142CEE55D40090B18B /* FileOnDisk.swift */,
|
||||||
|
E21850162CEE55FB0090B18B /* FileType.swift */,
|
||||||
|
E2A37D102CE537670000979F /* PageFile.swift */,
|
||||||
|
E21850182CEE561B0090B18B /* PageOnDisk.swift */,
|
||||||
|
E2A37D142CE68BEA0000979F /* PostFile.swift */,
|
||||||
|
E2A37D162CE73F170000979F /* TagFile.swift */,
|
||||||
|
);
|
||||||
|
path = Model;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E2A21C322CB5BCAC0060935B /* Pages */ = {
|
E2A21C322CB5BCAC0060935B /* Pages */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -257,14 +277,8 @@
|
|||||||
E2A37D0F2CE5375E0000979F /* Storage */ = {
|
E2A37D0F2CE5375E0000979F /* Storage */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */,
|
E25DA5112CFF001900AEF16D /* Model */,
|
||||||
E21850142CEE55D40090B18B /* FileOnDisk.swift */,
|
|
||||||
E21850162CEE55FB0090B18B /* FileType.swift */,
|
|
||||||
E2A37D102CE537670000979F /* PageFile.swift */,
|
|
||||||
E21850182CEE561B0090B18B /* PageOnDisk.swift */,
|
|
||||||
E2A37D142CE68BEA0000979F /* PostFile.swift */,
|
|
||||||
E2A37D0D2CE527040000979F /* Storage.swift */,
|
E2A37D0D2CE527040000979F /* Storage.swift */,
|
||||||
E2A37D162CE73F170000979F /* TagFile.swift */,
|
|
||||||
);
|
);
|
||||||
path = Storage;
|
path = Storage;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -285,24 +299,22 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E21850322CFAFA200090B18B /* WebsiteData.swift */,
|
E21850322CFAFA200090B18B /* WebsiteData.swift */,
|
||||||
E218503A2CFCFBDE0090B18B /* WebsiteData+Storage.swift */,
|
|
||||||
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */,
|
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */,
|
||||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
||||||
E218502E2CFAF6990090B18B /* Content+Generate.swift */,
|
E25DA5162CFF00F200AEF16D /* Content+Save.swift */,
|
||||||
|
E25DA5142CFF00B900AEF16D /* Content+Load.swift */,
|
||||||
|
E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */,
|
||||||
E21850302CFAF8840090B18B /* Content+Import.swift */,
|
E21850302CFAF8840090B18B /* Content+Import.swift */,
|
||||||
E24252092C52C9260029FF16 /* ContentLanguage.swift */,
|
E24252092C52C9260029FF16 /* ContentLanguage.swift */,
|
||||||
E2A21C502CBBD53C0060935B /* FileResource.swift */,
|
E2A21C502CBBD53C0060935B /* FileResource.swift */,
|
||||||
E2A21C3A2CB9D9A50060935B /* ImageResource.swift */,
|
E2A21C3A2CB9D9A50060935B /* ImageResource.swift */,
|
||||||
E2A21C042CB176670060935B /* LocalizedText.swift */,
|
E2A21C042CB176670060935B /* LocalizedText.swift */,
|
||||||
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */,
|
|
||||||
E21850102CEE17010090B18B /* Page+Storage.swift */,
|
|
||||||
E25A0B882CE4021400F33674 /* LocalizedPage.swift */,
|
E25A0B882CE4021400F33674 /* LocalizedPage.swift */,
|
||||||
E2B85F3A2C428F0D0047CD0C /* Post.swift */,
|
E2B85F3A2C428F0D0047CD0C /* Post.swift */,
|
||||||
E21850122CEE541A0090B18B /* Post+Storage.swift */,
|
|
||||||
E2A37D1C2CEA922A0000979F /* LocalizedPost.swift */,
|
E2A37D1C2CEA922A0000979F /* LocalizedPost.swift */,
|
||||||
E2581DEC2C75202400F1F079 /* Tag.swift */,
|
E2581DEC2C75202400F1F079 /* Tag.swift */,
|
||||||
E218501A2CEE59E80090B18B /* Tag+Storage.swift */,
|
|
||||||
E2A37D182CEA36A40000979F /* LocalizedTag.swift */,
|
E2A37D182CEA36A40000979F /* LocalizedTag.swift */,
|
||||||
|
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -310,7 +322,8 @@
|
|||||||
E2B85F3E2C4293FF0047CD0C /* Pages */ = {
|
E2B85F3E2C4293FF0047CD0C /* Pages */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E2B85F3C2C4293F80047CD0C /* Feed.swift */,
|
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */,
|
||||||
|
E2B85F3C2C4293F80047CD0C /* PageInFeed.swift */,
|
||||||
);
|
);
|
||||||
path = Pages;
|
path = Pages;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -318,6 +331,8 @@
|
|||||||
E2B85F3F2C42946E0047CD0C /* Page Elements */ = {
|
E2B85F3F2C42946E0047CD0C /* Page Elements */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */,
|
||||||
|
E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */,
|
||||||
E2B85F422C4294F60047CD0C /* FeedEntry.swift */,
|
E2B85F422C4294F60047CD0C /* FeedEntry.swift */,
|
||||||
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */,
|
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */,
|
||||||
E2A21C272CB29B290060935B /* FeedEntryData.swift */,
|
E2A21C272CB29B290060935B /* FeedEntryData.swift */,
|
||||||
@ -364,6 +379,7 @@
|
|||||||
E2B85F552C4BD0AD0047CD0C /* Extensions */ = {
|
E2B85F552C4BD0AD0047CD0C /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E25DA5182CFF035200AEF16D /* Array+Split.swift */,
|
||||||
E2A37D202CEA94E80000979F /* Sequence+Sorted.swift */,
|
E2A37D202CEA94E80000979F /* Sequence+Sorted.swift */,
|
||||||
E2A37D1E2CEA94330000979F /* Optional+Extensions.swift */,
|
E2A37D1E2CEA94330000979F /* Optional+Extensions.swift */,
|
||||||
E2A21C472CBAF8830060935B /* String+Extensions.swift */,
|
E2A21C472CBAF8830060935B /* String+Extensions.swift */,
|
||||||
@ -411,6 +427,7 @@
|
|||||||
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 */,
|
||||||
@ -505,8 +522,10 @@
|
|||||||
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
||||||
E2A21C562CBBF9880060935B /* FlexibleColumnView.swift in Sources */,
|
E2A21C562CBBF9880060935B /* FlexibleColumnView.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 */,
|
||||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
||||||
E21850252CF38BCE0090B18B /* TextEntrySheet.swift in Sources */,
|
E21850252CF38BCE0090B18B /* TextEntrySheet.swift in Sources */,
|
||||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
||||||
@ -515,22 +534,22 @@
|
|||||||
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
||||||
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */,
|
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */,
|
||||||
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */,
|
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */,
|
||||||
|
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */,
|
||||||
E2A21C082CB17B870060935B /* TagView.swift in Sources */,
|
E2A21C082CB17B870060935B /* TagView.swift in Sources */,
|
||||||
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 */,
|
||||||
E218502F2CFAF69C0090B18B /* Content+Generate.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 */,
|
||||||
E2B85F3D2C4293F80047CD0C /* Feed.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 */,
|
||||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||||
E218501B2CEE59EC0090B18B /* Tag+Storage.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 */,
|
||||||
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */,
|
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */,
|
||||||
@ -548,15 +567,14 @@
|
|||||||
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 */,
|
||||||
E21850112CEE17070090B18B /* Page+Storage.swift in Sources */,
|
|
||||||
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */,
|
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */,
|
||||||
E218503B2CFCFBE70090B18B /* WebsiteData+Storage.swift in Sources */,
|
|
||||||
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */,
|
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */,
|
||||||
E21850132CEE541D0090B18B /* Post+Storage.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 */,
|
||||||
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 */,
|
||||||
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 /* LocalizedSettingsView.swift in Sources */,
|
||||||
@ -568,6 +586,7 @@
|
|||||||
E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */,
|
E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */,
|
||||||
E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */,
|
E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */,
|
||||||
E2A37D1F2CEA94370000979F /* Optional+Extensions.swift in Sources */,
|
E2A37D1F2CEA94370000979F /* Optional+Extensions.swift in Sources */,
|
||||||
|
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
||||||
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
||||||
E2A21C542CBBF87A0060935B /* FilesView.swift in Sources */,
|
E2A21C542CBBF87A0060935B /* FilesView.swift in Sources */,
|
||||||
E2A37D0E2CE527070000979F /* Storage.swift in Sources */,
|
E2A37D0E2CE527070000979F /* Storage.swift in Sources */,
|
||||||
@ -577,6 +596,7 @@
|
|||||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
||||||
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */,
|
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */,
|
||||||
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
||||||
|
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
||||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
||||||
E2A21C362CB9A3D70060935B /* SettingsView.swift in Sources */,
|
E2A21C362CB9A3D70060935B /* SettingsView.swift in Sources */,
|
||||||
E2A21C012CB16A820060935B /* PostView.swift in Sources */,
|
E2A21C012CB16A820060935B /* PostView.swift in Sources */,
|
||||||
|
9
CHDataManagement/Extensions/Array+Split.swift
Normal file
9
CHDataManagement/Extensions/Array+Split.swift
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
extension Array {
|
||||||
|
|
||||||
|
func split(into size: Int) -> [[Element]] {
|
||||||
|
guard size > 0 else { return [] }
|
||||||
|
return stride(from: 0, to: count, by: size).map {
|
||||||
|
Array(self[$0..<Swift.min($0 + size, count)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension Content {
|
|
||||||
|
|
||||||
func generateFeed(for language: ContentLanguage, bookmarkKey: String) {
|
|
||||||
let posts = posts.map { $0.feedEntry(for: language) }
|
|
||||||
let data = websiteData.localized(in: language)
|
|
||||||
let navigationItems: [FeedNavigationLink] = websiteData.navigationTags.map {
|
|
||||||
let localized = $0.localized(in: language)
|
|
||||||
return .init(text: localized.name, url: localized.urlComponent)
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
|
||||||
|
|
||||||
let feed = Feed(
|
|
||||||
language: language,
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
iconDescription: data.iconDescription,
|
|
||||||
navigationItems: navigationItems,
|
|
||||||
posts: posts)
|
|
||||||
let fileContent = feed.content
|
|
||||||
Content.accessFolderFromBookmark(key: bookmarkKey) { folder in
|
|
||||||
let outputFile = folder.appendingPathComponent("feed.html", isDirectory: false)
|
|
||||||
do {
|
|
||||||
try fileContent
|
|
||||||
.data(using: .utf8)!
|
|
||||||
.write(to: outputFile)
|
|
||||||
} catch {
|
|
||||||
print("Failed to save: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
128
CHDataManagement/Model/Content+Load.swift
Normal file
128
CHDataManagement/Model/Content+Load.swift
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Content {
|
||||||
|
|
||||||
|
private func convert(_ tag: LocalizedTagFile, images: [String : ImageResource]) -> LocalizedTag {
|
||||||
|
LocalizedTag(
|
||||||
|
urlComponent: tag.urlComponent,
|
||||||
|
name: tag.name,
|
||||||
|
subtitle: tag.subtitle,
|
||||||
|
description: tag.description,
|
||||||
|
thumbnail: tag.thumbnail.map { images[$0] },
|
||||||
|
originalUrl: tag.originalURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func convert(_ post: LocalizedPostFile, images: [String : ImageResource]) -> LocalizedPost {
|
||||||
|
LocalizedPost(
|
||||||
|
title: post.title,
|
||||||
|
content: post.content,
|
||||||
|
lastModified: post.lastModifiedDate,
|
||||||
|
images: post.images.compactMap { images[$0] },
|
||||||
|
linkPreviewImage: post.linkPreviewImage.map { images[$0] },
|
||||||
|
linkPreviewTitle: post.linkPreviewTitle,
|
||||||
|
linkPreviewDescription: post.linkPreviewDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func convert(_ page: LocalizedPageFile) -> LocalizedPage {
|
||||||
|
LocalizedPage(
|
||||||
|
urlString: page.url,
|
||||||
|
title: page.title,
|
||||||
|
lastModified: page.lastModifiedDate,
|
||||||
|
originalUrl: page.originalURL,
|
||||||
|
files: Set(page.files),
|
||||||
|
externalFiles: Set(page.externalFiles),
|
||||||
|
requiredFiles: Set(page.requiredFiles),
|
||||||
|
linkPreviewImage: page.linkPreviewImage,
|
||||||
|
linkPreviewTitle: page.linkPreviewTitle,
|
||||||
|
linkPreviewDescription: page.linkPreviewDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func convert(_ websiteData: LocalizedWebsiteDataFile) -> LocalizedWebsiteData {
|
||||||
|
.init(title: websiteData.title,
|
||||||
|
description: websiteData.description,
|
||||||
|
iconDescription: websiteData.iconDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFromDisk() throws {
|
||||||
|
let storage = Storage(baseFolder: URL(filePath: contentPath))
|
||||||
|
|
||||||
|
let websiteData = try storage.loadWebsiteData()
|
||||||
|
|
||||||
|
let tagData = try storage.loadAllTags()
|
||||||
|
let pagesData = try storage.loadAllPages()
|
||||||
|
let postsData = try storage.loadAllPosts()
|
||||||
|
let filesData = try storage.loadAllFiles()
|
||||||
|
|
||||||
|
var images: [String : ImageResource] = [:]
|
||||||
|
var files: [FileResource] = []
|
||||||
|
var videos: [String] = []
|
||||||
|
|
||||||
|
for (file, url) in filesData {
|
||||||
|
let ext = file.components(separatedBy: ".").last!.lowercased()
|
||||||
|
let type = FileType(fileExtension: ext)
|
||||||
|
switch type {
|
||||||
|
case .image:
|
||||||
|
images[file] = ImageResource(uniqueId: file, altText: .init(en: "", de: ""), fileUrl: url)
|
||||||
|
case .file:
|
||||||
|
files.append(FileResource(uniqueId: file, description: ""))
|
||||||
|
case .video:
|
||||||
|
videos.append(file)
|
||||||
|
case .resource:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tagData.reduce(into: [:]) { (tags, data) in
|
||||||
|
tags[data.key] = Tag(
|
||||||
|
isVisible: data.value.isVisible,
|
||||||
|
german: convert(data.value.german, images: images),
|
||||||
|
english: convert(data.value.english, images: images))
|
||||||
|
}
|
||||||
|
|
||||||
|
let pages: [String : Page] = loadPages(pagesData, tags: tags)
|
||||||
|
|
||||||
|
let posts = postsData.map { postId, post in
|
||||||
|
let linkedPage = post.linkedPageId.map { pages[$0] }
|
||||||
|
let german = convert(post.german, images: images)
|
||||||
|
let english = convert(post.english, images: images)
|
||||||
|
|
||||||
|
return Post(
|
||||||
|
id: postId,
|
||||||
|
isDraft: post.isDraft,
|
||||||
|
createdDate: post.createdDate,
|
||||||
|
startDate: post.startDate,
|
||||||
|
endDate: post.endDate,
|
||||||
|
tags: post.tags.map { tags[$0]! },
|
||||||
|
german: german,
|
||||||
|
english: english,
|
||||||
|
linkedPage: linkedPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.tags = tags.values.sorted()
|
||||||
|
self.pages = pages.values.sorted(ascending: false) { $0.startDate }
|
||||||
|
self.files = files.sorted { $0.uniqueId }
|
||||||
|
self.images = images.values.sorted { $0.id }
|
||||||
|
self.videos = videos
|
||||||
|
self.posts = posts.sorted(ascending: false) { $0.startDate }
|
||||||
|
self.websiteData = WebsiteData(
|
||||||
|
navigationTags: websiteData.navigationTags.map { tags[$0]! },
|
||||||
|
german: convert(websiteData.german),
|
||||||
|
english: convert(websiteData.english))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag]) -> [String : Page] {
|
||||||
|
pagesData.reduce(into: [:]) { pages, data in
|
||||||
|
let (pageId, page) = data
|
||||||
|
pages[pageId] = Page(
|
||||||
|
id: pageId,
|
||||||
|
isDraft: page.isDraft,
|
||||||
|
createdDate: page.createdDate,
|
||||||
|
startDate: page.startDate,
|
||||||
|
endDate: page.endDate,
|
||||||
|
german: convert(page.german),
|
||||||
|
english: convert(page.english),
|
||||||
|
tags: page.tags.map { tags[$0]! })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
133
CHDataManagement/Model/Content+Save.swift
Normal file
133
CHDataManagement/Model/Content+Save.swift
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Content {
|
||||||
|
|
||||||
|
func saveToDisk() {
|
||||||
|
//print("Starting save")
|
||||||
|
for page in pages {
|
||||||
|
storage.save(pageMetadata: page.pageFile, for: page.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for post in posts {
|
||||||
|
storage.save(post: post.postFile, for: post.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
for tag in tags {
|
||||||
|
storage.save(tagMetadata: tag.tagFile, for: tag.id)
|
||||||
|
}
|
||||||
|
storage.save(websiteData: websiteData.dataFile)
|
||||||
|
|
||||||
|
do {
|
||||||
|
try storage.deletePostFiles(notIn: posts.map { $0.id })
|
||||||
|
try storage.deletePageFiles(notIn: pages.map { $0.id })
|
||||||
|
try storage.deleteTagFiles(notIn: tags.map { $0.id })
|
||||||
|
let allFiles = files.map { $0.uniqueId } + images.map { $0.id } + videos
|
||||||
|
try storage.deleteFiles(notIn: allFiles)
|
||||||
|
} catch {
|
||||||
|
print("Failed to remove unused files: \(error)")
|
||||||
|
}
|
||||||
|
// TODO: Remove all files that are no longer in use (they belong to deleted items)
|
||||||
|
//print("Finished save")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private extension Page {
|
||||||
|
|
||||||
|
var pageFile: PageFile {
|
||||||
|
.init(isDraft: isDraft,
|
||||||
|
tags: tags.map { $0.id },
|
||||||
|
createdDate: createdDate,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: hasEndDate ? endDate : nil,
|
||||||
|
german: german.pageFile,
|
||||||
|
english: english.pageFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedPage {
|
||||||
|
|
||||||
|
var pageFile: LocalizedPageFile {
|
||||||
|
.init(url: urlString,
|
||||||
|
files: files.sorted(),
|
||||||
|
externalFiles: externalFiles.sorted(),
|
||||||
|
requiredFiles: requiredFiles.sorted(),
|
||||||
|
title: title,
|
||||||
|
linkPreviewImage: linkPreviewImage,
|
||||||
|
linkPreviewTitle: linkPreviewTitle,
|
||||||
|
linkPreviewDescription: linkPreviewDescription,
|
||||||
|
lastModifiedDate: lastModified,
|
||||||
|
originalURL: originalUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private extension Post {
|
||||||
|
|
||||||
|
var postFile: PostFile {
|
||||||
|
.init(
|
||||||
|
isDraft: isDraft,
|
||||||
|
createdDate: createdDate,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: hasEndDate ? endDate : nil,
|
||||||
|
tags: tags.map { $0.id },
|
||||||
|
german: german.postFile,
|
||||||
|
english: english.postFile,
|
||||||
|
linkedPageId: linkedPage?.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedPost {
|
||||||
|
|
||||||
|
var postFile: LocalizedPostFile {
|
||||||
|
.init(images: images.map { $0.id },
|
||||||
|
title: title.nonEmpty,
|
||||||
|
content: content,
|
||||||
|
lastModifiedDate: lastModified,
|
||||||
|
linkPreviewImage: linkPreviewImage?.id,
|
||||||
|
linkPreviewTitle: linkPreviewTitle,
|
||||||
|
linkPreviewDescription: linkPreviewDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private extension Tag {
|
||||||
|
|
||||||
|
var tagFile: TagFile {
|
||||||
|
.init(id: id,
|
||||||
|
isVisible: isVisible,
|
||||||
|
german: german.tagFile,
|
||||||
|
english: english.tagFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedTag {
|
||||||
|
|
||||||
|
var tagFile: LocalizedTagFile {
|
||||||
|
.init(urlComponent: urlComponent,
|
||||||
|
name: name,
|
||||||
|
subtitle: subtitle,
|
||||||
|
description: description,
|
||||||
|
thumbnail: thumbnail?.id,
|
||||||
|
originalURL: originalUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension WebsiteData {
|
||||||
|
|
||||||
|
var dataFile: WebsiteDataFile {
|
||||||
|
.init(
|
||||||
|
navigationTags: navigationTags.map { $0.id },
|
||||||
|
german: german.dataFile,
|
||||||
|
english: english.dataFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension LocalizedWebsiteData {
|
||||||
|
|
||||||
|
var dataFile: LocalizedWebsiteDataFile {
|
||||||
|
.init(title: title,
|
||||||
|
description: description,
|
||||||
|
iconDescription: iconDescription)
|
||||||
|
}
|
||||||
|
}
|
@ -97,159 +97,6 @@ final class Content: ObservableObject {
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func convert(_ tag: LocalizedTagFile, images: [String : ImageResource]) -> LocalizedTag {
|
|
||||||
LocalizedTag(
|
|
||||||
urlComponent: tag.urlComponent,
|
|
||||||
name: tag.name,
|
|
||||||
subtitle: tag.subtitle,
|
|
||||||
description: tag.description,
|
|
||||||
thumbnail: tag.thumbnail.map { images[$0] },
|
|
||||||
originalUrl: tag.originalURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func convert(_ post: LocalizedPostFile, images: [String : ImageResource]) -> LocalizedPost {
|
|
||||||
LocalizedPost(
|
|
||||||
title: post.title,
|
|
||||||
content: post.content,
|
|
||||||
lastModified: post.lastModifiedDate,
|
|
||||||
images: post.images.compactMap { images[$0] },
|
|
||||||
linkPreviewImage: post.linkPreviewImage.map { images[$0] },
|
|
||||||
linkPreviewTitle: post.linkPreviewTitle,
|
|
||||||
linkPreviewDescription: post.linkPreviewDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func convert(_ page: LocalizedPageFile) -> LocalizedPage {
|
|
||||||
LocalizedPage(
|
|
||||||
urlString: page.url,
|
|
||||||
title: page.title,
|
|
||||||
lastModified: page.lastModifiedDate,
|
|
||||||
originalUrl: page.originalURL,
|
|
||||||
files: Set(page.files),
|
|
||||||
externalFiles: Set(page.externalFiles),
|
|
||||||
requiredFiles: Set(page.requiredFiles),
|
|
||||||
linkPreviewImage: page.linkPreviewImage,
|
|
||||||
linkPreviewTitle: page.linkPreviewTitle,
|
|
||||||
linkPreviewDescription: page.linkPreviewDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func convert(_ websiteData: LocalizedWebsiteDataFile) -> LocalizedWebsiteData {
|
|
||||||
.init(title: websiteData.title,
|
|
||||||
description: websiteData.description,
|
|
||||||
iconDescription: websiteData.iconDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFromDisk() throws {
|
|
||||||
let storage = Storage(baseFolder: URL(filePath: contentPath))
|
|
||||||
|
|
||||||
let websiteData = try storage.loadWebsiteData()
|
|
||||||
|
|
||||||
let tagData = try storage.loadAllTags()
|
|
||||||
let pagesData = try storage.loadAllPages()
|
|
||||||
let postsData = try storage.loadAllPosts()
|
|
||||||
let filesData = try storage.loadAllFiles()
|
|
||||||
|
|
||||||
var images: [String : ImageResource] = [:]
|
|
||||||
var files: [FileResource] = []
|
|
||||||
var videos: [String] = []
|
|
||||||
|
|
||||||
for (file, url) in filesData {
|
|
||||||
let ext = file.components(separatedBy: ".").last!.lowercased()
|
|
||||||
let type = FileType(fileExtension: ext)
|
|
||||||
switch type {
|
|
||||||
case .image:
|
|
||||||
images[file] = ImageResource(uniqueId: file, altText: .init(en: "", de: ""), fileUrl: url)
|
|
||||||
case .file:
|
|
||||||
files.append(FileResource(uniqueId: file, description: ""))
|
|
||||||
case .video:
|
|
||||||
videos.append(file)
|
|
||||||
case .resource:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tags = tagData.reduce(into: [:]) { (tags, data) in
|
|
||||||
tags[data.key] = Tag(
|
|
||||||
isVisible: data.value.isVisible,
|
|
||||||
german: convert(data.value.german, images: images),
|
|
||||||
english: convert(data.value.english, images: images))
|
|
||||||
}
|
|
||||||
|
|
||||||
let pages: [String : Page] = loadPages(pagesData, tags: tags)
|
|
||||||
|
|
||||||
let posts = postsData.map { postId, post in
|
|
||||||
let linkedPage = post.linkedPageId.map { pages[$0] }
|
|
||||||
let german = convert(post.german, images: images)
|
|
||||||
let english = convert(post.english, images: images)
|
|
||||||
|
|
||||||
return Post(
|
|
||||||
id: postId,
|
|
||||||
isDraft: post.isDraft,
|
|
||||||
createdDate: post.createdDate,
|
|
||||||
startDate: post.startDate,
|
|
||||||
endDate: post.endDate,
|
|
||||||
tags: post.tags.map { tags[$0]! },
|
|
||||||
german: german,
|
|
||||||
english: english,
|
|
||||||
linkedPage: linkedPage)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.tags = tags.values.sorted()
|
|
||||||
self.pages = pages.values.sorted(ascending: false) { $0.startDate }
|
|
||||||
self.files = files.sorted { $0.uniqueId }
|
|
||||||
self.images = images.values.sorted { $0.id }
|
|
||||||
self.videos = videos
|
|
||||||
self.posts = posts.sorted(ascending: false) { $0.startDate }
|
|
||||||
self.websiteData = WebsiteData(
|
|
||||||
navigationTags: websiteData.navigationTags.map { tags[$0]! },
|
|
||||||
german: convert(websiteData.german),
|
|
||||||
english: convert(websiteData.english))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag]) -> [String : Page] {
|
|
||||||
pagesData.reduce(into: [:]) { pages, data in
|
|
||||||
let (pageId, page) = data
|
|
||||||
pages[pageId] = Page(
|
|
||||||
id: pageId,
|
|
||||||
isDraft: page.isDraft,
|
|
||||||
createdDate: page.createdDate,
|
|
||||||
startDate: page.startDate,
|
|
||||||
endDate: page.endDate,
|
|
||||||
german: convert(page.german),
|
|
||||||
english: convert(page.english),
|
|
||||||
tags: page.tags.map { tags[$0]! })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Saving
|
|
||||||
|
|
||||||
func saveToDisk() {
|
|
||||||
//print("Starting save")
|
|
||||||
for page in pages {
|
|
||||||
storage.save(pageMetadata: page.pageFile, for: page.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
for post in posts {
|
|
||||||
storage.save(post: post.postFile, for: post.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
for tag in tags {
|
|
||||||
storage.save(tagMetadata: tag.tagFile, for: tag.id)
|
|
||||||
}
|
|
||||||
storage.save(websiteData: websiteData.dataFile)
|
|
||||||
|
|
||||||
do {
|
|
||||||
try storage.deletePostFiles(notIn: posts.map { $0.id })
|
|
||||||
try storage.deletePageFiles(notIn: pages.map { $0.id })
|
|
||||||
try storage.deleteTagFiles(notIn: tags.map { $0.id })
|
|
||||||
let allFiles = files.map { $0.uniqueId } + images.map { $0.id } + videos
|
|
||||||
try storage.deleteFiles(notIn: allFiles)
|
|
||||||
} catch {
|
|
||||||
print("Failed to remove unused files: \(error)")
|
|
||||||
}
|
|
||||||
// TODO: Remove all files that are no longer in use (they belong to deleted items)
|
|
||||||
//print("Finished save")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Folder access
|
// MARK: Folder access
|
||||||
|
|
||||||
static func accessFolderFromBookmark(key: String, operation: (URL) -> Void) {
|
static func accessFolderFromBookmark(key: String, operation: (URL) -> Void) {
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension Page {
|
|
||||||
|
|
||||||
var pageFile: PageFile {
|
|
||||||
.init(isDraft: isDraft,
|
|
||||||
tags: tags.map { $0.id },
|
|
||||||
createdDate: createdDate,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: hasEndDate ? endDate : nil,
|
|
||||||
german: german.pageFile,
|
|
||||||
english: english.pageFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedPage {
|
|
||||||
|
|
||||||
var pageFile: LocalizedPageFile {
|
|
||||||
.init(url: urlString,
|
|
||||||
files: files.sorted(),
|
|
||||||
externalFiles: externalFiles.sorted(),
|
|
||||||
requiredFiles: requiredFiles.sorted(),
|
|
||||||
title: title,
|
|
||||||
linkPreviewImage: linkPreviewImage,
|
|
||||||
linkPreviewTitle: linkPreviewTitle,
|
|
||||||
linkPreviewDescription: linkPreviewDescription,
|
|
||||||
lastModifiedDate: lastModified,
|
|
||||||
originalURL: originalUrl)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension Post {
|
|
||||||
|
|
||||||
var postFile: PostFile {
|
|
||||||
.init(
|
|
||||||
isDraft: isDraft,
|
|
||||||
createdDate: createdDate,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: hasEndDate ? endDate : nil,
|
|
||||||
tags: tags.map { $0.id },
|
|
||||||
german: german.postFile,
|
|
||||||
english: english.postFile,
|
|
||||||
linkedPageId: linkedPage?.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedPost {
|
|
||||||
|
|
||||||
var postFile: LocalizedPostFile {
|
|
||||||
.init(images: images.map { $0.id },
|
|
||||||
title: title.nonEmpty,
|
|
||||||
content: content,
|
|
||||||
lastModifiedDate: lastModified,
|
|
||||||
linkPreviewImage: linkPreviewImage?.id,
|
|
||||||
linkPreviewTitle: linkPreviewTitle,
|
|
||||||
linkPreviewDescription: linkPreviewDescription)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension Tag {
|
|
||||||
|
|
||||||
var tagFile: TagFile {
|
|
||||||
.init(id: id,
|
|
||||||
isVisible: isVisible,
|
|
||||||
german: german.tagFile,
|
|
||||||
english: english.tagFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedTag {
|
|
||||||
|
|
||||||
var tagFile: LocalizedTagFile {
|
|
||||||
.init(urlComponent: urlComponent,
|
|
||||||
name: name,
|
|
||||||
subtitle: subtitle,
|
|
||||||
description: description,
|
|
||||||
thumbnail: thumbnail?.id,
|
|
||||||
originalURL: originalUrl)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
extension WebsiteData {
|
|
||||||
|
|
||||||
var dataFile: WebsiteDataFile {
|
|
||||||
.init(
|
|
||||||
navigationTags: navigationTags.map { $0.id },
|
|
||||||
german: german.dataFile,
|
|
||||||
english: english.dataFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension LocalizedWebsiteData {
|
|
||||||
|
|
||||||
var dataFile: LocalizedWebsiteDataFile {
|
|
||||||
.init(title: title,
|
|
||||||
description: description,
|
|
||||||
iconDescription: iconDescription)
|
|
||||||
}
|
|
||||||
}
|
|
102
CHDataManagement/Model/WebsiteGenerator.swift
Normal file
102
CHDataManagement/Model/WebsiteGenerator.swift
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct WebsiteGeneratorConfiguration {
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
let postsPerPage: Int
|
||||||
|
|
||||||
|
let postFeedTitle: String
|
||||||
|
|
||||||
|
let postFeedDescription: String
|
||||||
|
|
||||||
|
let postFeedUrlPrefix: String
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WebsiteGenerator {
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
let content: Content
|
||||||
|
|
||||||
|
let postsPerPage: Int
|
||||||
|
|
||||||
|
let postFeedTitle: String
|
||||||
|
|
||||||
|
let postFeedDescription: String
|
||||||
|
|
||||||
|
let postFeedUrlPrefix: String
|
||||||
|
|
||||||
|
init(content: Content, configuration: WebsiteGeneratorConfiguration) {
|
||||||
|
self.content = content
|
||||||
|
self.language = configuration.language
|
||||||
|
self.postsPerPage = configuration.postsPerPage
|
||||||
|
self.postFeedTitle = configuration.postFeedTitle
|
||||||
|
self.postFeedDescription = configuration.postFeedDescription
|
||||||
|
self.postFeedUrlPrefix = configuration.postFeedUrlPrefix
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateWebsite() {
|
||||||
|
createPostFeedPages()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createPostFeedPages() {
|
||||||
|
let totalCount = content.posts.count
|
||||||
|
guard totalCount > 0 else { return }
|
||||||
|
|
||||||
|
let navBarData = createNavigationBarData()
|
||||||
|
|
||||||
|
let numberOfPages = (totalCount + postsPerPage - 1) / postsPerPage // Round up
|
||||||
|
for pageIndex in 1...numberOfPages {
|
||||||
|
let startIndex = (pageIndex - 1) * postsPerPage
|
||||||
|
let endIndex = min(pageIndex * postsPerPage, totalCount)
|
||||||
|
let postsOnPage = content.posts[startIndex..<endIndex]
|
||||||
|
createPostFeedPage(pageIndex, pageCount: numberOfPages, posts: postsOnPage, bar: navBarData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createNavigationBarData() -> NavigationBarData {
|
||||||
|
let data = content.websiteData.localized(in: language)
|
||||||
|
let navigationItems: [NavigationBarLink] = content.websiteData.navigationTags.map {
|
||||||
|
let localized = $0.localized(in: language)
|
||||||
|
return .init(text: localized.name, url: localized.urlComponent)
|
||||||
|
}
|
||||||
|
return NavigationBarData(
|
||||||
|
navigationIconPath: "/assets/icons/ch.svg",
|
||||||
|
iconDescription: data.iconDescription,
|
||||||
|
navigationItems: navigationItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func createPostFeedPage(_ pageIndex: Int, pageCount: Int, posts: ArraySlice<Post>, bar: NavigationBarData) {
|
||||||
|
let posts = posts.map { $0.feedEntry(for: language) }
|
||||||
|
|
||||||
|
let feed = PageInFeed(
|
||||||
|
language: language,
|
||||||
|
title: postFeedTitle,
|
||||||
|
description: postFeedDescription,
|
||||||
|
navigationBarData: bar,
|
||||||
|
pageNumber: pageIndex,
|
||||||
|
totalPages: pageCount,
|
||||||
|
posts: posts)
|
||||||
|
let fileContent = feed.content
|
||||||
|
|
||||||
|
if pageIndex == 1 {
|
||||||
|
save(fileContent, to: "\(postFeedUrlPrefix).html")
|
||||||
|
} else {
|
||||||
|
save(fileContent, to: "\(postFeedUrlPrefix)-\(pageIndex).html")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func save(_ content: String, to relativePath: String) {
|
||||||
|
Content.accessFolderFromBookmark(key: Storage.outputPathBookmarkKey) { folder in
|
||||||
|
let outputFile = folder.appendingPathComponent(relativePath, isDirectory: false)
|
||||||
|
do {
|
||||||
|
try content
|
||||||
|
.data(using: .utf8)!
|
||||||
|
.write(to: outputFile)
|
||||||
|
} catch {
|
||||||
|
print("Failed to save: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
CHDataManagement/Page Elements/NavigationBar.swift
Normal file
52
CHDataManagement/Page Elements/NavigationBar.swift
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct NavigationBarLink {
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
|
||||||
|
let url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct NavigationBarData {
|
||||||
|
|
||||||
|
let navigationIconPath: String
|
||||||
|
|
||||||
|
let iconDescription: String
|
||||||
|
|
||||||
|
let navigationItems: [NavigationBarLink]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct NavigationBar {
|
||||||
|
|
||||||
|
let data: NavigationBarData
|
||||||
|
|
||||||
|
init(data: NavigationBarData) {
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
private var items: [NavigationBarLink] {
|
||||||
|
data.navigationItems
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: String {
|
||||||
|
var result = "<nav class=\"navbar\"><div class=\"navbar-fade\"></div><div class=\"nav-center\">"
|
||||||
|
let middleIndex = items.count / 2
|
||||||
|
let leftNavigationItems = items[..<middleIndex]
|
||||||
|
let rightNavigationItems = items[middleIndex...]
|
||||||
|
|
||||||
|
for item in leftNavigationItems {
|
||||||
|
result += "<a class=\"nav-animate\" href=\"\(item.url)\">\(item.text)</a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "<a id=\"nav-image\" href=\"/\">"
|
||||||
|
result += "<img class=\"navbar-icon\" src=\"\(data.navigationIconPath)\" alt=\"\(data.iconDescription)\">"
|
||||||
|
|
||||||
|
for item in rightNavigationItems {
|
||||||
|
result += "<a class=\"nav-animate\" href=\"\(item.url)\">\(item.text)</a>"
|
||||||
|
}
|
||||||
|
result += "</div></nav>" // Close nav-center, navbar
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ struct PageHead {
|
|||||||
|
|
||||||
let description: String
|
let description: String
|
||||||
|
|
||||||
|
let additionalHeaders: String
|
||||||
|
|
||||||
var content: String {
|
var content: String {
|
||||||
"""
|
"""
|
||||||
<head>
|
<head>
|
||||||
@ -14,7 +16,7 @@ struct PageHead {
|
|||||||
<title>\(title)</title>
|
<title>\(title)</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" />
|
||||||
<meta name="description" content="\(description)">
|
<meta name="description" content="\(description)">
|
||||||
<link rel="stylesheet" href="/assets/swiper/swiper.css" />
|
\(additionalHeaders)
|
||||||
<link rel="stylesheet" href="/assets/css/style.css" />
|
<link rel="stylesheet" href="/assets/css/style.css" />
|
||||||
</head>
|
</head>
|
||||||
"""
|
"""
|
||||||
|
88
CHDataManagement/Page Elements/PostFeedPageNavigation.swift
Normal file
88
CHDataManagement/Page Elements/PostFeedPageNavigation.swift
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PostFeedPageNavigation {
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
let currentPage: Int
|
||||||
|
|
||||||
|
let numberOfPages: Int
|
||||||
|
|
||||||
|
init(currentPage: Int, numberOfPages: Int, language: ContentLanguage) {
|
||||||
|
self.currentPage = currentPage
|
||||||
|
self.numberOfPages = numberOfPages
|
||||||
|
self.language = language
|
||||||
|
}
|
||||||
|
|
||||||
|
private func pageLink(_ page: Int) -> String {
|
||||||
|
guard page > 1 else { return "href='/feed'" }
|
||||||
|
return "href='/feed-\(page)'"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func previousText() -> String {
|
||||||
|
switch language {
|
||||||
|
case .english:
|
||||||
|
return "Previous"
|
||||||
|
case .german:
|
||||||
|
return "Zurück"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addPreviousButton(to result: inout String) {
|
||||||
|
if currentPage == 1 {
|
||||||
|
// Disable the previous button if we are on the first page
|
||||||
|
result += "<a class='tag prev disabled' href='' aria-label='Previous'>"
|
||||||
|
} else {
|
||||||
|
let link = pageLink(currentPage - 1)
|
||||||
|
result += "<a class='tag prev' \(link) aria-label='Previous'>"
|
||||||
|
}
|
||||||
|
result += "<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>"
|
||||||
|
result += "<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M15 19l-7-7 7-7' />"
|
||||||
|
result += "</svg></a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addNextButton(to result: inout String) {
|
||||||
|
if currentPage == numberOfPages {
|
||||||
|
// Disable the previous button if we are on the first page
|
||||||
|
result += "<a class='tag next disabled' href='' aria-label='Next'>"
|
||||||
|
} else {
|
||||||
|
let link = pageLink(currentPage + 1)
|
||||||
|
result += "<a class='tag next' \(link) aria-label='Next'>"
|
||||||
|
}
|
||||||
|
result += "<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'>"
|
||||||
|
result += "<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5l7 7-7 7' />"
|
||||||
|
result += "</svg></a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addLink(page: Int, to result: inout String) {
|
||||||
|
result += "<a class='tag' \(pageLink(page))>\(page)</a>"
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: String {
|
||||||
|
var result = "<div class='pagination'>"
|
||||||
|
addPreviousButton(to: &result)
|
||||||
|
// Add a maximum of two buttons before the current page
|
||||||
|
if currentPage > 2 {
|
||||||
|
addLink(page: currentPage - 2, to: &result)
|
||||||
|
}
|
||||||
|
if currentPage > 1 {
|
||||||
|
addLink(page: currentPage - 1, to: &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the current page
|
||||||
|
result += "<span class='tag current'>\(currentPage)</span>"
|
||||||
|
|
||||||
|
// Add a maximum of two buttons after the current page
|
||||||
|
if currentPage < numberOfPages {
|
||||||
|
addLink(page: currentPage + 1, to: &result)
|
||||||
|
}
|
||||||
|
if currentPage + 1 < numberOfPages {
|
||||||
|
addLink(page: currentPage + 2, to: &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
addNextButton(to: &result)
|
||||||
|
|
||||||
|
result += "</div>" // Close pagination
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
struct FeedNavigationLink {
|
|
||||||
|
|
||||||
let text: String
|
|
||||||
|
|
||||||
let url: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Feed {
|
|
||||||
|
|
||||||
private let navigationIconPath = "/assets/icons/ch.svg"
|
|
||||||
|
|
||||||
let language: ContentLanguage
|
|
||||||
|
|
||||||
let title: String
|
|
||||||
|
|
||||||
let description: String
|
|
||||||
|
|
||||||
let iconDescription: String
|
|
||||||
|
|
||||||
let navigationItems: [FeedNavigationLink]
|
|
||||||
|
|
||||||
let posts: [FeedEntryData]
|
|
||||||
|
|
||||||
var content: String {
|
|
||||||
#warning("TODO: Split feed into multiple pages")
|
|
||||||
var result = ""
|
|
||||||
result += "<!DOCTYPE html><html lang=\"\(language.rawValue)\">"
|
|
||||||
let head = PageHead(
|
|
||||||
title: title,
|
|
||||||
description: description)
|
|
||||||
result += head.content
|
|
||||||
result += "<body>"
|
|
||||||
addNavbar(to: &result)
|
|
||||||
result += "<div class=\"content\"><div style=\"height: 70px;\"></div>"
|
|
||||||
for post in posts {
|
|
||||||
FeedEntry(data: post)
|
|
||||||
.addContent(to: &result)
|
|
||||||
}
|
|
||||||
|
|
||||||
addSwiperInits(to: &result)
|
|
||||||
result += "</div></body></html>" // Close content
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
#warning("TODO: Set correct navigation links and texts")
|
|
||||||
private func addNavbar(to result: inout String) {
|
|
||||||
result += "<nav class=\"navbar\"><div class=\"navbar-fade\"></div><div class=\"nav-center\">"
|
|
||||||
let middleIndex = navigationItems.count / 2
|
|
||||||
let leftNavigationItems = navigationItems[..<middleIndex]
|
|
||||||
let rightNavigationItems = navigationItems[middleIndex...]
|
|
||||||
|
|
||||||
for item in leftNavigationItems {
|
|
||||||
result += "<a class=\"nav-animate\" href=\"\(item.url)\">\(item.text)</a>"
|
|
||||||
}
|
|
||||||
|
|
||||||
result += "<a id=\"nav-image\" href=\"/\">"
|
|
||||||
result += "<img class=\"navbar-icon\" src=\"\(navigationIconPath)\" alt=\"\(iconDescription)\">"
|
|
||||||
|
|
||||||
for item in rightNavigationItems {
|
|
||||||
result += "<a class=\"nav-animate\" href=\"\(item.url)\">\(item.text)</a>"
|
|
||||||
}
|
|
||||||
result += "</div></nav>" // Close nav-center, navbar
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addSwiperInits(to result: inout String) {
|
|
||||||
if posts.contains(where: { $0.images.count > 1 }) {
|
|
||||||
result += "<script src=\"/assets/swiper/swiper.min.js\"></script><script>"
|
|
||||||
for post in posts {
|
|
||||||
guard post.images.count > 1 else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result += ImageGallery.swiperInit(id: post.entryId)
|
|
||||||
}
|
|
||||||
result += "</script>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
36
CHDataManagement/Pages/GenericPage.swift
Normal file
36
CHDataManagement/Pages/GenericPage.swift
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct GenericPage {
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
let description: String
|
||||||
|
|
||||||
|
let data: NavigationBarData
|
||||||
|
|
||||||
|
let additionalHeaders: String
|
||||||
|
|
||||||
|
let insertedContent: (inout String) -> Void
|
||||||
|
|
||||||
|
init(language: ContentLanguage, title: String, description: String, data: NavigationBarData, additionalHeaders: String, insertedContent: @escaping (inout String) -> Void) {
|
||||||
|
self.language = language
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
self.data = data
|
||||||
|
self.additionalHeaders = additionalHeaders
|
||||||
|
self.insertedContent = insertedContent
|
||||||
|
}
|
||||||
|
var content: String {
|
||||||
|
var result = ""
|
||||||
|
result += "<!DOCTYPE html><html lang=\"\(language.rawValue)\">"
|
||||||
|
result += PageHead(title: title, description: description, additionalHeaders: additionalHeaders).content
|
||||||
|
result += "<body>"
|
||||||
|
result += NavigationBar(data: data).content
|
||||||
|
result += "<div class=\"content\"><div style=\"height: 70px;\"></div>"
|
||||||
|
insertedContent(&result)
|
||||||
|
result += "</div></body></html>" // Close content
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
58
CHDataManagement/Pages/PageInFeed.swift
Normal file
58
CHDataManagement/Pages/PageInFeed.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PageInFeed {
|
||||||
|
|
||||||
|
private let swiperStyleSheetPath = "/assets/swiper/swiper-bundle.min.css"
|
||||||
|
|
||||||
|
private let swiperJsPath = "/assets/swiper/swiper-bundle.min.js"
|
||||||
|
|
||||||
|
let language: ContentLanguage
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
|
||||||
|
let description: String
|
||||||
|
|
||||||
|
let navigationBarData: NavigationBarData
|
||||||
|
|
||||||
|
let pageNumber: Int
|
||||||
|
|
||||||
|
let totalPages: Int
|
||||||
|
|
||||||
|
let posts: [FeedEntryData]
|
||||||
|
|
||||||
|
private var swiperHeader: String {
|
||||||
|
"<link rel='stylesheet' href='\(swiperStyleSheetPath)' />"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var swiperIsNeeded: Bool {
|
||||||
|
posts.contains(where: { $0.images.count > 1 })
|
||||||
|
}
|
||||||
|
|
||||||
|
private var headers: String {
|
||||||
|
swiperIsNeeded ? swiperHeader : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var content: String {
|
||||||
|
GenericPage(language: language, title: title, description: description, data: navigationBarData, additionalHeaders: headers) { content in
|
||||||
|
for post in posts {
|
||||||
|
FeedEntry(data: post)
|
||||||
|
.addContent(to: &content)
|
||||||
|
}
|
||||||
|
content += PostFeedPageNavigation(currentPage: pageNumber, numberOfPages: totalPages, language: language).content
|
||||||
|
if swiperIsNeeded {
|
||||||
|
addSwiperInits(to: &content)
|
||||||
|
}
|
||||||
|
}.content
|
||||||
|
}
|
||||||
|
|
||||||
|
private func addSwiperInits(to result: inout String) {
|
||||||
|
result += "<script src='\(swiperJsPath)'></script><script>"
|
||||||
|
for post in posts {
|
||||||
|
guard post.images.count > 1 else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result += ImageGallery.swiperInit(id: post.entryId)
|
||||||
|
}
|
||||||
|
result += "</script>"
|
||||||
|
}
|
||||||
|
}
|
19
CHDataManagement/Preview Content/WebsiteGenerator+Mock.swift
Normal file
19
CHDataManagement/Preview Content/WebsiteGenerator+Mock.swift
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension WebsiteGeneratorConfiguration {
|
||||||
|
|
||||||
|
static let english = WebsiteGeneratorConfiguration(
|
||||||
|
language: .english,
|
||||||
|
postsPerPage: 20,
|
||||||
|
postFeedTitle: "Posts",
|
||||||
|
postFeedDescription: "The most recent posts on christophhagen.de",
|
||||||
|
postFeedUrlPrefix: "feed")
|
||||||
|
|
||||||
|
static let german = WebsiteGeneratorConfiguration(
|
||||||
|
language: .german,
|
||||||
|
postsPerPage: 20,
|
||||||
|
postFeedTitle: "Beiträge",
|
||||||
|
postFeedDescription: "Die neusten Beiträge auf christophhagen.de",
|
||||||
|
postFeedUrlPrefix: "beiträge")
|
||||||
|
|
||||||
|
}
|
@ -13,6 +13,9 @@ import Foundation
|
|||||||
*/
|
*/
|
||||||
final class Storage {
|
final class Storage {
|
||||||
|
|
||||||
|
static let outputPathBookmarkKey = "outputPathBookmark"
|
||||||
|
static let contentPathBookmarkKey = "contentPathBookmark"
|
||||||
|
|
||||||
private(set) var baseFolder: URL
|
private(set) var baseFolder: URL
|
||||||
|
|
||||||
private let encoder = JSONEncoder()
|
private let encoder = JSONEncoder()
|
||||||
|
@ -23,6 +23,9 @@ struct SettingsView: View {
|
|||||||
@State
|
@State
|
||||||
private var showTagPicker = false
|
private var showTagPicker = false
|
||||||
|
|
||||||
|
@State
|
||||||
|
private var isGeneratingWebsite = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
@ -63,8 +66,16 @@ struct SettingsView: View {
|
|||||||
LocalizedSettingsView(settings: content.websiteData.localized(in: language))
|
LocalizedSettingsView(settings: content.websiteData.localized(in: language))
|
||||||
Text("Feed")
|
Text("Feed")
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
Button(action: generateFeed) {
|
HStack {
|
||||||
Text("Generate")
|
Button(action: generateFeed) {
|
||||||
|
Text("Generate")
|
||||||
|
}
|
||||||
|
if isGeneratingWebsite {
|
||||||
|
ProgressView()
|
||||||
|
.progressViewStyle(.circular)
|
||||||
|
.frame(height: 25)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
@ -86,7 +97,7 @@ struct SettingsView: View {
|
|||||||
private func selectContentFolder() {
|
private func selectContentFolder() {
|
||||||
isSelectingContentFolder = true
|
isSelectingContentFolder = true
|
||||||
//showFileImporter = true
|
//showFileImporter = true
|
||||||
guard let url = savePanelUsingOpenPanel(key: "contentPathBookmark") else {
|
guard let url = savePanelUsingOpenPanel(key: Storage.contentPathBookmarkKey) else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.contentPath = url.path()
|
self.contentPath = url.path()
|
||||||
@ -94,8 +105,7 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
private func selectOutputFolder() {
|
private func selectOutputFolder() {
|
||||||
isSelectingContentFolder = false
|
isSelectingContentFolder = false
|
||||||
//showFileImporter = true
|
guard let url = savePanelUsingOpenPanel(key: Storage.outputPathBookmarkKey) else {
|
||||||
guard let url = savePanelUsingOpenPanel(key: "outputPathBookmark") else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.outputPath = url.path()
|
self.outputPath = url.path()
|
||||||
@ -115,10 +125,10 @@ struct SettingsView: View {
|
|||||||
.replacingOccurrences(of: "file://", with: "")
|
.replacingOccurrences(of: "file://", with: "")
|
||||||
if isSelectingContentFolder {
|
if isSelectingContentFolder {
|
||||||
self.contentPath = path
|
self.contentPath = path
|
||||||
saveSecurityScopedBookmark(folder, key: "contentPathBookmark")
|
saveSecurityScopedBookmark(folder, key: Storage.contentPathBookmarkKey)
|
||||||
} else {
|
} else {
|
||||||
self.outputPath = path
|
self.outputPath = path
|
||||||
saveSecurityScopedBookmark(folder, key: "outputPathBookmark")
|
saveSecurityScopedBookmark(folder, key: Storage.outputPathBookmarkKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,8 +145,23 @@ struct SettingsView: View {
|
|||||||
print("Missing output folder")
|
print("Missing output folder")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
isGeneratingWebsite = true
|
||||||
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
|
let generator = WebsiteGenerator(
|
||||||
|
content: content,
|
||||||
|
configuration: configuration)
|
||||||
|
generator.generateWebsite()
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
isGeneratingWebsite = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content.generateFeed(for: language, bookmarkKey: "outputPathBookmark")
|
private var configuration: WebsiteGeneratorConfiguration {
|
||||||
|
switch language {
|
||||||
|
case .english: return .english
|
||||||
|
case .german: return .german
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func savePanelUsingOpenPanel(key: String) -> URL? {
|
func savePanelUsingOpenPanel(key: String) -> URL? {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user