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 */; };
|
||||
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218500A2CEE02FA0090B18B /* Content+Mock.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 */; };
|
||||
E21850172CEE55FC0090B18B /* FileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850162CEE55FB0090B18B /* FileType.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 */; };
|
||||
E218501F2CEE6DAC0090B18B /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218501E2CEE6DAC0090B18B /* ImagePickerView.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 */; };
|
||||
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502A2CF790AC0090B18B /* PostContentView.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 */; };
|
||||
E21850332CFAFA2F0090B18B /* WebsiteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850322CFAFA200090B18B /* WebsiteData.swift */; };
|
||||
E21850352CFAFA5A0090B18B /* WebsiteDataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* WebsiteDataFile.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 */; };
|
||||
E218503B2CFCFBE70090B18B /* WebsiteData+Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503A2CFCFBDE0090B18B /* WebsiteData+Storage.swift */; };
|
||||
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedSettingsView.swift */; };
|
||||
E24252012C50E0A40029FF16 /* HighlightedTextEditor in Frameworks */ = {isa = PBXBuildFile; productRef = E24252002C50E0A40029FF16 /* HighlightedTextEditor */; };
|
||||
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 */; };
|
||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.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 */; };
|
||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C022CB16C220060935B /* Environment+Language.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 */; };
|
||||
E2B85F362C426BEE0047CD0C /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2B85F352C426BEE0047CD0C /* SFSafeSymbols */; };
|
||||
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 */; };
|
||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B85F422C4294F60047CD0C /* FeedEntry.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -111,13 +111,12 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -130,6 +129,13 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -168,7 +174,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -206,6 +212,20 @@
|
||||
path = Import;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -257,14 +277,8 @@
|
||||
E2A37D0F2CE5375E0000979F /* Storage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E21850342CFAFA570090B18B /* WebsiteDataFile.swift */,
|
||||
E21850142CEE55D40090B18B /* FileOnDisk.swift */,
|
||||
E21850162CEE55FB0090B18B /* FileType.swift */,
|
||||
E2A37D102CE537670000979F /* PageFile.swift */,
|
||||
E21850182CEE561B0090B18B /* PageOnDisk.swift */,
|
||||
E2A37D142CE68BEA0000979F /* PostFile.swift */,
|
||||
E25DA5112CFF001900AEF16D /* Model */,
|
||||
E2A37D0D2CE527040000979F /* Storage.swift */,
|
||||
E2A37D162CE73F170000979F /* TagFile.swift */,
|
||||
);
|
||||
path = Storage;
|
||||
sourceTree = "<group>";
|
||||
@ -285,24 +299,22 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E21850322CFAFA200090B18B /* WebsiteData.swift */,
|
||||
E218503A2CFCFBDE0090B18B /* WebsiteData+Storage.swift */,
|
||||
E21850362CFCA5580090B18B /* LocalizedWebsiteData.swift */,
|
||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
||||
E218502E2CFAF6990090B18B /* Content+Generate.swift */,
|
||||
E25DA5162CFF00F200AEF16D /* Content+Save.swift */,
|
||||
E25DA5142CFF00B900AEF16D /* Content+Load.swift */,
|
||||
E218502E2CFAF6990090B18B /* WebsiteGenerator.swift */,
|
||||
E21850302CFAF8840090B18B /* Content+Import.swift */,
|
||||
E24252092C52C9260029FF16 /* ContentLanguage.swift */,
|
||||
E2A21C502CBBD53C0060935B /* FileResource.swift */,
|
||||
E2A21C3A2CB9D9A50060935B /* ImageResource.swift */,
|
||||
E2A21C042CB176670060935B /* LocalizedText.swift */,
|
||||
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */,
|
||||
E21850102CEE17010090B18B /* Page+Storage.swift */,
|
||||
E25A0B882CE4021400F33674 /* LocalizedPage.swift */,
|
||||
E2B85F3A2C428F0D0047CD0C /* Post.swift */,
|
||||
E21850122CEE541A0090B18B /* Post+Storage.swift */,
|
||||
E2A37D1C2CEA922A0000979F /* LocalizedPost.swift */,
|
||||
E2581DEC2C75202400F1F079 /* Tag.swift */,
|
||||
E218501A2CEE59E80090B18B /* Tag+Storage.swift */,
|
||||
E2A37D182CEA36A40000979F /* LocalizedTag.swift */,
|
||||
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -310,7 +322,8 @@
|
||||
E2B85F3E2C4293FF0047CD0C /* Pages */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2B85F3C2C4293F80047CD0C /* Feed.swift */,
|
||||
E25DA51C2CFF135B00AEF16D /* GenericPage.swift */,
|
||||
E2B85F3C2C4293F80047CD0C /* PageInFeed.swift */,
|
||||
);
|
||||
path = Pages;
|
||||
sourceTree = "<group>";
|
||||
@ -318,6 +331,8 @@
|
||||
E2B85F3F2C42946E0047CD0C /* Page Elements */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E25DA51E2CFF15C100AEF16D /* NavigationBar.swift */,
|
||||
E25DA51A2CFF08AF00AEF16D /* PostFeedPageNavigation.swift */,
|
||||
E2B85F422C4294F60047CD0C /* FeedEntry.swift */,
|
||||
E2A21C452CBAE2E50060935B /* FeedEntryContent.swift */,
|
||||
E2A21C272CB29B290060935B /* FeedEntryData.swift */,
|
||||
@ -364,6 +379,7 @@
|
||||
E2B85F552C4BD0AD0047CD0C /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E25DA5182CFF035200AEF16D /* Array+Split.swift */,
|
||||
E2A37D202CEA94E80000979F /* Sequence+Sorted.swift */,
|
||||
E2A37D1E2CEA94330000979F /* Optional+Extensions.swift */,
|
||||
E2A21C472CBAF8830060935B /* String+Extensions.swift */,
|
||||
@ -411,6 +427,7 @@
|
||||
E2DD047C2C276F32003BFF1F /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E25DA5202CFF1B8900AEF16D /* WebsiteGenerator+Mock.swift */,
|
||||
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */,
|
||||
E218500A2CEE02FA0090B18B /* Content+Mock.swift */,
|
||||
E2A37D1A2CEA45530000979F /* Tag+Mock.swift */,
|
||||
@ -505,8 +522,10 @@
|
||||
E2A37D212CEA94EC0000979F /* Sequence+Sorted.swift in Sources */,
|
||||
E2A21C562CBBF9880060935B /* FlexibleColumnView.swift in Sources */,
|
||||
E2A37D192CEA36A90000979F /* LocalizedTag.swift in Sources */,
|
||||
E25DA5212CFF1B9300AEF16D /* WebsiteGenerator+Mock.swift in Sources */,
|
||||
E24252062C51684E0029FF16 /* GenericMetadata.swift in Sources */,
|
||||
E21850312CFAF8880090B18B /* Content+Import.swift in Sources */,
|
||||
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */,
|
||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
||||
E21850252CF38BCE0090B18B /* TextEntrySheet.swift in Sources */,
|
||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
||||
@ -515,22 +534,22 @@
|
||||
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
||||
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */,
|
||||
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */,
|
||||
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */,
|
||||
E2A21C082CB17B870060935B /* TagView.swift in Sources */,
|
||||
E2A37D2D2CED2EF10000979F /* OptionalTextField.swift in Sources */,
|
||||
E2A37D2B2CED2CC30000979F /* TagDetailView.swift in Sources */,
|
||||
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
||||
E218502F2CFAF69C0090B18B /* Content+Generate.swift in Sources */,
|
||||
E218502F2CFAF69C0090B18B /* WebsiteGenerator.swift in Sources */,
|
||||
E2581DF12C7523F400F1F079 /* ImportableTag.swift in Sources */,
|
||||
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
||||
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
||||
E2B85F3D2C4293F80047CD0C /* Feed.swift in Sources */,
|
||||
E2B85F3D2C4293F80047CD0C /* PageInFeed.swift in Sources */,
|
||||
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */,
|
||||
E2A21C2A2CB2AA4F0060935B /* Post+Mock.swift in Sources */,
|
||||
E24252082C5168750029FF16 /* GenericMetadata+Localized.swift in Sources */,
|
||||
E2581DED2C75202400F1F079 /* Tag.swift in Sources */,
|
||||
E2A21C4F2CBB29E50060935B /* ImageDetailsView.swift in Sources */,
|
||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||
E218501B2CEE59EC0090B18B /* Tag+Storage.swift in Sources */,
|
||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */,
|
||||
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */,
|
||||
@ -548,15 +567,14 @@
|
||||
E2A21C202CB28ED20060935B /* MockImage.swift in Sources */,
|
||||
E2A21C2C2CB2BB250060935B /* PostList.swift in Sources */,
|
||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||
E21850112CEE17070090B18B /* Page+Storage.swift in Sources */,
|
||||
E218500D2CEE07180090B18B /* ColorPalette.swift in Sources */,
|
||||
E218503B2CFCFBE70090B18B /* WebsiteData+Storage.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 */,
|
||||
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */,
|
||||
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */,
|
||||
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */,
|
||||
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */,
|
||||
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */,
|
||||
E2A21C3B2CB9D9A60060935B /* ImageResource.swift in Sources */,
|
||||
E218503D2CFCFD910090B18B /* LocalizedSettingsView.swift in Sources */,
|
||||
@ -568,6 +586,7 @@
|
||||
E25DA50F2CFDD76B00AEF16D /* ImagesContentView.swift in Sources */,
|
||||
E2A21C482CBAF88B0060935B /* String+Extensions.swift in Sources */,
|
||||
E2A37D1F2CEA94370000979F /* Optional+Extensions.swift in Sources */,
|
||||
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
||||
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */,
|
||||
E2A21C542CBBF87A0060935B /* FilesView.swift in Sources */,
|
||||
E2A37D0E2CE527070000979F /* Storage.swift in Sources */,
|
||||
@ -577,6 +596,7 @@
|
||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
||||
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */,
|
||||
E2A21C0E2CB189DC0060935B /* Color+RGB.swift in Sources */,
|
||||
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
||||
E2A21C362CB9A3D70060935B /* SettingsView.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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 additionalHeaders: String
|
||||
|
||||
var content: String {
|
||||
"""
|
||||
<head>
|
||||
@ -14,7 +16,7 @@ struct PageHead {
|
||||
<title>\(title)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1" />
|
||||
<meta name="description" content="\(description)">
|
||||
<link rel="stylesheet" href="/assets/swiper/swiper.css" />
|
||||
\(additionalHeaders)
|
||||
<link rel="stylesheet" href="/assets/css/style.css" />
|
||||
</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 {
|
||||
|
||||
static let outputPathBookmarkKey = "outputPathBookmark"
|
||||
static let contentPathBookmarkKey = "contentPathBookmark"
|
||||
|
||||
private(set) var baseFolder: URL
|
||||
|
||||
private let encoder = JSONEncoder()
|
||||
@ -252,7 +255,7 @@ final class Storage {
|
||||
private func deleteFiles(in folder: URL, notIn fileSet: Set<String>) throws {
|
||||
let filesToDelete = try files(in: folder)
|
||||
.filter { !fileSet.contains($0.lastPathComponent) }
|
||||
|
||||
|
||||
for file in filesToDelete {
|
||||
try fm.removeItem(at: file)
|
||||
print("Deleted \(file.path())")
|
||||
|
@ -23,6 +23,9 @@ struct SettingsView: View {
|
||||
@State
|
||||
private var showTagPicker = false
|
||||
|
||||
@State
|
||||
private var isGeneratingWebsite = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
@ -63,8 +66,16 @@ struct SettingsView: View {
|
||||
LocalizedSettingsView(settings: content.websiteData.localized(in: language))
|
||||
Text("Feed")
|
||||
.font(.headline)
|
||||
Button(action: generateFeed) {
|
||||
Text("Generate")
|
||||
HStack {
|
||||
Button(action: generateFeed) {
|
||||
Text("Generate")
|
||||
}
|
||||
if isGeneratingWebsite {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.frame(height: 25)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@ -86,7 +97,7 @@ struct SettingsView: View {
|
||||
private func selectContentFolder() {
|
||||
isSelectingContentFolder = true
|
||||
//showFileImporter = true
|
||||
guard let url = savePanelUsingOpenPanel(key: "contentPathBookmark") else {
|
||||
guard let url = savePanelUsingOpenPanel(key: Storage.contentPathBookmarkKey) else {
|
||||
return
|
||||
}
|
||||
self.contentPath = url.path()
|
||||
@ -94,8 +105,7 @@ struct SettingsView: View {
|
||||
|
||||
private func selectOutputFolder() {
|
||||
isSelectingContentFolder = false
|
||||
//showFileImporter = true
|
||||
guard let url = savePanelUsingOpenPanel(key: "outputPathBookmark") else {
|
||||
guard let url = savePanelUsingOpenPanel(key: Storage.outputPathBookmarkKey) else {
|
||||
return
|
||||
}
|
||||
self.outputPath = url.path()
|
||||
@ -115,10 +125,10 @@ struct SettingsView: View {
|
||||
.replacingOccurrences(of: "file://", with: "")
|
||||
if isSelectingContentFolder {
|
||||
self.contentPath = path
|
||||
saveSecurityScopedBookmark(folder, key: "contentPathBookmark")
|
||||
saveSecurityScopedBookmark(folder, key: Storage.contentPathBookmarkKey)
|
||||
} else {
|
||||
self.outputPath = path
|
||||
saveSecurityScopedBookmark(folder, key: "outputPathBookmark")
|
||||
saveSecurityScopedBookmark(folder, key: Storage.outputPathBookmarkKey)
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,8 +145,23 @@ struct SettingsView: View {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user