Rework storage structs, link preview
This commit is contained in:
parent
b99c064d10
commit
a7197b9628
@ -16,21 +16,18 @@
|
||||
E218502B2CF790B30090B18B /* PostContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502A2CF790AC0090B18B /* PostContentView.swift */; };
|
||||
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218502C2CF791440090B18B /* PostImagesView.swift */; };
|
||||
E21850332CFAFA2F0090B18B /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850322CFAFA200090B18B /* Settings.swift */; };
|
||||
E21850352CFAFA5A0090B18B /* SettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850342CFAFA570090B18B /* SettingsFile.swift */; };
|
||||
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */; };
|
||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */; };
|
||||
E218503D2CFCFD910090B18B /* LocalizedPostFeedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */; };
|
||||
E22990152D0E2B7F009F8D77 /* ItemSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */; };
|
||||
E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */; };
|
||||
E22990192D0E3546009F8D77 /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990182D0E3546009F8D77 /* ItemType.swift */; };
|
||||
E22990192D0E3546009F8D77 /* ItemReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990182D0E3546009F8D77 /* ItemReference.swift */; };
|
||||
E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901D2D0E4362009F8D77 /* LocalizedItem.swift */; };
|
||||
E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */; };
|
||||
E22990222D0ED12E009F8D77 /* TagOverviewFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990212D0ED129009F8D77 /* TagOverviewFile.swift */; };
|
||||
E22990242D0EDBD0009F8D77 /* HeaderElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990232D0EDBD0009F8D77 /* HeaderElement.swift */; };
|
||||
E22990262D0F582B009F8D77 /* FilePropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990252D0F5822009F8D77 /* FilePropertyView.swift */; };
|
||||
E22990282D0F596C009F8D77 /* IntegerPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990272D0F5967009F8D77 /* IntegerPropertyView.swift */; };
|
||||
E229902A2D0F5A14009F8D77 /* DetailTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990292D0F5A10009F8D77 /* DetailTitle.swift */; };
|
||||
E229902C2D0F6FC6009F8D77 /* ItemId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229902B2D0F6FC0009F8D77 /* ItemId.swift */; };
|
||||
E229902C2D0F6FC6009F8D77 /* LocalizedItemId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229902B2D0F6FC0009F8D77 /* LocalizedItemId.swift */; };
|
||||
E229902E2D0F7280009F8D77 /* IdPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229902D2D0F7278009F8D77 /* IdPropertyView.swift */; };
|
||||
E22990302D0F75DE009F8D77 /* BoolPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */; };
|
||||
E22990322D0F767B009F8D77 /* DatePropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E22990312D0F7678009F8D77 /* DatePropertyView.swift */; };
|
||||
@ -53,7 +50,6 @@
|
||||
E25DA5092CFD964E00AEF16D /* TagContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5082CFD964E00AEF16D /* TagContentView.swift */; };
|
||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50A2CFD988100AEF16D /* PageTagAssignmentView.swift */; };
|
||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA50C2CFD9B9C00AEF16D /* PostTagAssignmentView.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 */; };
|
||||
@ -64,8 +60,6 @@
|
||||
E25DA5272CFF745700AEF16D /* URL+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5262CFF745200AEF16D /* URL+Extensions.swift */; };
|
||||
E25DA52C2CFFC3EC00AEF16D /* SDWebImageAVIFCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52B2CFFC3EC00AEF16D /* SDWebImageAVIFCoder */; };
|
||||
E25DA52F2CFFC91B00AEF16D /* SDWebImageWebPCoder in Frameworks */ = {isa = PBXBuildFile; productRef = E25DA52E2CFFC91B00AEF16D /* SDWebImageWebPCoder */; };
|
||||
E25DA5362D0041EB00AEF16D /* PostSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */; };
|
||||
E25DA5382D00420E00AEF16D /* LocalizedPostSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */; };
|
||||
E25DA5412D00446C00AEF16D /* PostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5402D00446700AEF16D /* PostSettings.swift */; };
|
||||
E25DA5452D00952E00AEF16D /* SettingsSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5442D00952D00AEF16D /* SettingsSection.swift */; };
|
||||
E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */; };
|
||||
@ -82,7 +76,6 @@
|
||||
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA58A2D020C9200AEF16D /* PageImage.swift */; };
|
||||
E25DA58F2D02368D00AEF16D /* PageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA58E2D02368A00AEF16D /* PageSettings.swift */; };
|
||||
E25DA5912D023A8400AEF16D /* IntegerField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5902D023A7E00AEF16D /* IntegerField.swift */; };
|
||||
E25DA5932D023B3C00AEF16D /* PageSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5922D023B3600AEF16D /* PageSettingsFile.swift */; };
|
||||
E25DA5952D023BD100AEF16D /* PageSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */; };
|
||||
E25DA5992D02401E00AEF16D /* PageGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA5982D02401A00AEF16D /* PageGenerator.swift */; };
|
||||
E25DA59B2D024A2B00AEF16D /* DateItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25DA59A2D024A2900AEF16D /* DateItem.swift */; };
|
||||
@ -91,7 +84,6 @@
|
||||
E29D31242D0366860051B7F4 /* TagList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31232D0366820051B7F4 /* TagList.swift */; };
|
||||
E29D31262D0370A80051B7F4 /* VideoCommand+Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31252D0370A50051B7F4 /* VideoCommand+Option.swift */; };
|
||||
E29D31282D0371930051B7F4 /* ContentPageVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31272D0371870051B7F4 /* ContentPageVideo.swift */; };
|
||||
E29D312A2D039B090051B7F4 /* FileDescriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31292D039B050051B7F4 /* FileDescriptions.swift */; };
|
||||
E29D312C2D039DB80051B7F4 /* PageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312B2D039DB30051B7F4 /* PageDetailView.swift */; };
|
||||
E29D312E2D03A0D70051B7F4 /* LocalizedPageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312D2D03A0CF0051B7F4 /* LocalizedPageDetailView.swift */; };
|
||||
E29D31302D03A2C50051B7F4 /* DescriptionField.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */; };
|
||||
@ -129,7 +121,6 @@
|
||||
E29D31902D0B34870051B7F4 /* GenerationAnomaly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D318F2D0B34870051B7F4 /* GenerationAnomaly.swift */; };
|
||||
E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31932D0B7D250051B7F4 /* SimpleImage.swift */; };
|
||||
E29D31962D0C186E0051B7F4 /* PathSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31952D0C18690051B7F4 /* PathSettings.swift */; };
|
||||
E29D31982D0C19340051B7F4 /* PathSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D31972D0C19300051B7F4 /* PathSettingsFile.swift */; };
|
||||
E29D319B2D0C452B0051B7F4 /* PageIssue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319A2D0C452B0051B7F4 /* PageIssue.swift */; };
|
||||
E29D319D2D0C45B90051B7F4 /* PageIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319C2D0C45B60051B7F4 /* PageIssueView.swift */; };
|
||||
E29D319F2D0C46310051B7F4 /* PageIssueChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E29D319E2D0C46290051B7F4 /* PageIssueChecker.swift */; };
|
||||
@ -163,9 +154,6 @@
|
||||
E2A21C512CBBD53F0060935B /* FileResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A21C502CBBD53C0060935B /* FileResource.swift */; };
|
||||
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25A0B882CE4021400F33674 /* LocalizedPage.swift */; };
|
||||
E2A37D0E2CE527070000979F /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D0D2CE527040000979F /* Storage.swift */; };
|
||||
E2A37D112CE537800000979F /* PageFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D102CE537670000979F /* PageFile.swift */; };
|
||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D142CE68BEA0000979F /* PostFile.swift */; };
|
||||
E2A37D172CE73F1A0000979F /* TagFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D162CE73F170000979F /* TagFile.swift */; };
|
||||
E2A37D192CEA36A90000979F /* LocalizedTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D182CEA36A40000979F /* LocalizedTag.swift */; };
|
||||
E2A37D1B2CEA45560000979F /* Tag+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D1A2CEA45530000979F /* Tag+Mock.swift */; };
|
||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A37D1C2CEA922A0000979F /* LocalizedPost.swift */; };
|
||||
@ -187,6 +175,14 @@
|
||||
E2DD047E2C276F32003BFF1F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2DD047D2C276F32003BFF1F /* Preview Assets.xcassets */; };
|
||||
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E06DFA2CA4A6570019C2AF /* Content.swift */; };
|
||||
E2E06E002CA4A8F00019C2AF /* Page+Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E06DFF2CA4A8EB0019C2AF /* Page+Mock.swift */; };
|
||||
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */; };
|
||||
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */; };
|
||||
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */; };
|
||||
E2FD1D1D2D2DE31800B48627 /* ItemType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1C2D2DE31600B48627 /* ItemType.swift */; };
|
||||
E2FD1D1F2D2E9CC200B48627 /* ItemId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D1E2D2E9CBE00B48627 /* ItemId.swift */; };
|
||||
E2FD1D212D2EB22900B48627 /* ModelLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D202D2EB22700B48627 /* ModelLoader.swift */; };
|
||||
E2FD1D232D2EB27000B48627 /* LoadingResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D222D2EB26C00B48627 /* LoadingResult.swift */; };
|
||||
E2FD1D252D2EBA8000B48627 /* TagOverview.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FD1D242D2EBA7C00B48627 /* TagOverview.swift */; };
|
||||
E2FE0EE62D15A0B5002963B7 /* GenerationResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */; };
|
||||
E2FE0EE82D16D4A3002963B7 /* ConvertThrowing.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */; };
|
||||
E2FE0EEC2D1C1253002963B7 /* MultiFileSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */; };
|
||||
@ -196,8 +192,6 @@
|
||||
E2FE0EF82D1D8110002963B7 /* IconCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF72D1D810C002963B7 /* IconCommand.swift */; };
|
||||
E2FE0EFA2D25AFBA002963B7 /* PageHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EF92D25AFB5002963B7 /* PageHeader.swift */; };
|
||||
E2FE0EFC2D266D22002963B7 /* NavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */; };
|
||||
E2FE0EFE2D266DA5002963B7 /* NavigationSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */; };
|
||||
E2FE0F002D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */; };
|
||||
E2FE0F022D266FCB002963B7 /* LocalizedNavigationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */; };
|
||||
E2FE0F042D267206002963B7 /* LocalizedNavigationBarSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */; };
|
||||
E2FE0F062D267350002963B7 /* TextFieldPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F052D26734E002963B7 /* TextFieldPropertyView.swift */; };
|
||||
@ -209,7 +203,6 @@
|
||||
E2FE0F152D26918F002963B7 /* HtmlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F142D269188002963B7 /* HtmlCommand.swift */; };
|
||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */; };
|
||||
E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F182D2723E3002963B7 /* ImageSet.swift */; };
|
||||
E2FE0F1B2D274FDF002963B7 /* LinkPreviewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1A2D274FDA002963B7 /* LinkPreviewItem.swift */; };
|
||||
E2FE0F1E2D281AE1002963B7 /* TagOverviewGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */; };
|
||||
E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */; };
|
||||
E2FE0F222D2A84A0002963B7 /* VideoCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F212D2A849B002963B7 /* VideoCommand.swift */; };
|
||||
@ -223,7 +216,6 @@
|
||||
E2FE0F362D2B27F9002963B7 /* BlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */; };
|
||||
E2FE0F382D2B32F4002963B7 /* SingleFilePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */; };
|
||||
E2FE0F3A2D2B3E4F002963B7 /* AudioPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */; };
|
||||
E2FE0F3C2D2B3F45002963B7 /* AudioPlayerSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */; };
|
||||
E2FE0F3E2D2B4225002963B7 /* AudioSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */; };
|
||||
E2FE0F402D2B45D3002963B7 /* SwiftBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3F2D2B45CD002963B7 /* SwiftBlock.swift */; };
|
||||
E2FE0F422D2B4821002963B7 /* OtherCodeBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F412D2B480B002963B7 /* OtherCodeBlock.swift */; };
|
||||
@ -238,13 +230,11 @@
|
||||
E2FE0F572D2BCFD4002963B7 /* BlockLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F562D2BCFD4002963B7 /* BlockLineProcessor.swift */; };
|
||||
E2FE0F592D2BCFE4002963B7 /* OrderedKeyBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F582D2BCFE4002963B7 /* OrderedKeyBlockProcessor.swift */; };
|
||||
E2FE0F5B2D2BCFF2002963B7 /* KeyedBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F5A2D2BCFF2002963B7 /* KeyedBlockProcessor.swift */; };
|
||||
E2FE0F5E2D2BE190002963B7 /* FileResourceFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F5D2D2BE18B002963B7 /* FileResourceFile.swift */; };
|
||||
E2FE0F602D2C0422002963B7 /* VideoBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F5F2D2C041E002963B7 /* VideoBlock.swift */; };
|
||||
E2FE0F622D2C0D8D002963B7 /* VersionedVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F612D2C0D8D002963B7 /* VersionedVideo.swift */; };
|
||||
E2FE0F642D2C2F4D002963B7 /* ButtonBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F632D2C2F46002963B7 /* ButtonBlock.swift */; };
|
||||
E2FE0F662D2C3B3A002963B7 /* LabelsBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F652D2C3B33002963B7 /* LabelsBlock.swift */; };
|
||||
E2FE0F682D2D2CF6002963B7 /* LocalizedPageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */; };
|
||||
E2FE0F6A2D2D2D55002963B7 /* LocalizedPageSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F692D2D2D4F002963B7 /* LocalizedPageSettingsFile.swift */; };
|
||||
E2FE0F6C2D2D335E002963B7 /* LocalizedPageSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */; };
|
||||
E2FE0F6E2D2D3689002963B7 /* LocalizedAudioPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */; };
|
||||
E2FE0F702D2D5235002963B7 /* DraftIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */; };
|
||||
@ -260,21 +250,18 @@
|
||||
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>"; };
|
||||
E21850322CFAFA200090B18B /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
E21850342CFAFA570090B18B /* SettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsFile.swift; sourceTree = "<group>"; };
|
||||
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettings.swift; sourceTree = "<group>"; };
|
||||
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebsiteData+Mock.swift"; sourceTree = "<group>"; };
|
||||
E218503C2CFCFD8C0090B18B /* LocalizedPostFeedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostFeedSettingsView.swift; sourceTree = "<group>"; };
|
||||
E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemSelectionView.swift; sourceTree = "<group>"; };
|
||||
E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewPage.swift; sourceTree = "<group>"; };
|
||||
E22990182D0E3546009F8D77 /* ItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemType.swift; sourceTree = "<group>"; };
|
||||
E22990182D0E3546009F8D77 /* ItemReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemReference.swift; sourceTree = "<group>"; };
|
||||
E229901D2D0E4362009F8D77 /* LocalizedItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedItem.swift; sourceTree = "<group>"; };
|
||||
E229901F2D0ECBD4009F8D77 /* TagOverviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewDetailView.swift; sourceTree = "<group>"; };
|
||||
E22990212D0ED129009F8D77 /* TagOverviewFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewFile.swift; sourceTree = "<group>"; };
|
||||
E22990232D0EDBD0009F8D77 /* HeaderElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeaderElement.swift; sourceTree = "<group>"; };
|
||||
E22990252D0F5822009F8D77 /* FilePropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePropertyView.swift; sourceTree = "<group>"; };
|
||||
E22990272D0F5967009F8D77 /* IntegerPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerPropertyView.swift; sourceTree = "<group>"; };
|
||||
E22990292D0F5A10009F8D77 /* DetailTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailTitle.swift; sourceTree = "<group>"; };
|
||||
E229902B2D0F6FC0009F8D77 /* ItemId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemId.swift; sourceTree = "<group>"; };
|
||||
E229902B2D0F6FC0009F8D77 /* LocalizedItemId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedItemId.swift; sourceTree = "<group>"; };
|
||||
E229902D2D0F7278009F8D77 /* IdPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdPropertyView.swift; sourceTree = "<group>"; };
|
||||
E229902F2D0F75CF009F8D77 /* BoolPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoolPropertyView.swift; sourceTree = "<group>"; };
|
||||
E22990312D0F7678009F8D77 /* DatePropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePropertyView.swift; sourceTree = "<group>"; };
|
||||
@ -297,7 +284,6 @@
|
||||
E25DA5082CFD964E00AEF16D /* TagContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagContentView.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>"; };
|
||||
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>"; };
|
||||
@ -306,8 +292,6 @@
|
||||
E25DA5222CFF6C2600AEF16D /* ImageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGenerator.swift; sourceTree = "<group>"; };
|
||||
E25DA5242CFF73A600AEF16D /* NSSize+Scaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSSize+Scaling.swift"; sourceTree = "<group>"; };
|
||||
E25DA5262CFF745200AEF16D /* URL+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Extensions.swift"; sourceTree = "<group>"; };
|
||||
E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPostSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E25DA5402D00446700AEF16D /* PostSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostSettings.swift; sourceTree = "<group>"; };
|
||||
E25DA5442D00952D00AEF16D /* SettingsSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSection.swift; sourceTree = "<group>"; };
|
||||
E25DA56C2D00EBC900AEF16D /* NavigationBarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarSettingsView.swift; sourceTree = "<group>"; };
|
||||
@ -322,7 +306,6 @@
|
||||
E25DA58A2D020C9200AEF16D /* PageImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageImage.swift; sourceTree = "<group>"; };
|
||||
E25DA58E2D02368A00AEF16D /* PageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettings.swift; sourceTree = "<group>"; };
|
||||
E25DA5902D023A7E00AEF16D /* IntegerField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerField.swift; sourceTree = "<group>"; };
|
||||
E25DA5922D023B3600AEF16D /* PageSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E25DA5942D023BCC00AEF16D /* PageSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageSettingsDetailView.swift; sourceTree = "<group>"; };
|
||||
E25DA5982D02401A00AEF16D /* PageGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageGenerator.swift; sourceTree = "<group>"; };
|
||||
E25DA59A2D024A2900AEF16D /* DateItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateItem.swift; sourceTree = "<group>"; };
|
||||
@ -331,7 +314,6 @@
|
||||
E29D31232D0366820051B7F4 /* TagList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagList.swift; sourceTree = "<group>"; };
|
||||
E29D31252D0370A50051B7F4 /* VideoCommand+Option.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VideoCommand+Option.swift"; sourceTree = "<group>"; };
|
||||
E29D31272D0371870051B7F4 /* ContentPageVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPageVideo.swift; sourceTree = "<group>"; };
|
||||
E29D31292D039B050051B7F4 /* FileDescriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDescriptions.swift; sourceTree = "<group>"; };
|
||||
E29D312B2D039DB30051B7F4 /* PageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailView.swift; sourceTree = "<group>"; };
|
||||
E29D312D2D03A0CF0051B7F4 /* LocalizedPageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageDetailView.swift; sourceTree = "<group>"; };
|
||||
E29D312F2D03A2BD0051B7F4 /* DescriptionField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptionField.swift; sourceTree = "<group>"; };
|
||||
@ -369,7 +351,6 @@
|
||||
E29D318F2D0B34870051B7F4 /* GenerationAnomaly.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationAnomaly.swift; sourceTree = "<group>"; };
|
||||
E29D31932D0B7D250051B7F4 /* SimpleImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleImage.swift; sourceTree = "<group>"; };
|
||||
E29D31952D0C18690051B7F4 /* PathSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathSettings.swift; sourceTree = "<group>"; };
|
||||
E29D31972D0C19300051B7F4 /* PathSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E29D319A2D0C452B0051B7F4 /* PageIssue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssue.swift; sourceTree = "<group>"; };
|
||||
E29D319C2D0C45B60051B7F4 /* PageIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssueView.swift; sourceTree = "<group>"; };
|
||||
E29D319E2D0C46290051B7F4 /* PageIssueChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageIssueChecker.swift; sourceTree = "<group>"; };
|
||||
@ -401,9 +382,6 @@
|
||||
E2A21C472CBAF8830060935B /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
|
||||
E2A21C502CBBD53C0060935B /* FileResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileResource.swift; sourceTree = "<group>"; };
|
||||
E2A37D0D2CE527040000979F /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
E2A37D102CE537670000979F /* PageFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageFile.swift; sourceTree = "<group>"; };
|
||||
E2A37D142CE68BEA0000979F /* PostFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostFile.swift; sourceTree = "<group>"; };
|
||||
E2A37D162CE73F170000979F /* TagFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagFile.swift; sourceTree = "<group>"; };
|
||||
E2A37D182CEA36A40000979F /* LocalizedTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedTag.swift; sourceTree = "<group>"; };
|
||||
E2A37D1A2CEA45530000979F /* Tag+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tag+Mock.swift"; sourceTree = "<group>"; };
|
||||
E2A37D1C2CEA922A0000979F /* LocalizedPost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPost.swift; sourceTree = "<group>"; };
|
||||
@ -426,6 +404,14 @@
|
||||
E2DD047D2C276F32003BFF1F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Content.swift; sourceTree = "<group>"; };
|
||||
E2E06DFF2CA4A8EB0019C2AF /* Page+Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Page+Mock.swift"; sourceTree = "<group>"; };
|
||||
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreview.swift; sourceTree = "<group>"; };
|
||||
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingContext.swift; sourceTree = "<group>"; };
|
||||
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewDetailView.swift; sourceTree = "<group>"; };
|
||||
E2FD1D1C2D2DE31600B48627 /* ItemType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemType.swift; sourceTree = "<group>"; };
|
||||
E2FD1D1E2D2E9CBE00B48627 /* ItemId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemId.swift; sourceTree = "<group>"; };
|
||||
E2FD1D202D2EB22700B48627 /* ModelLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelLoader.swift; sourceTree = "<group>"; };
|
||||
E2FD1D222D2EB26C00B48627 /* LoadingResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingResult.swift; sourceTree = "<group>"; };
|
||||
E2FD1D242D2EBA7C00B48627 /* TagOverview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverview.swift; sourceTree = "<group>"; };
|
||||
E2FE0EE52D15A0B1002963B7 /* GenerationResults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerationResults.swift; sourceTree = "<group>"; };
|
||||
E2FE0EE72D16D4A3002963B7 /* ConvertThrowing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertThrowing.swift; sourceTree = "<group>"; };
|
||||
E2FE0EEB2D1C124E002963B7 /* MultiFileSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiFileSelectionView.swift; sourceTree = "<group>"; };
|
||||
@ -435,8 +421,6 @@
|
||||
E2FE0EF72D1D810C002963B7 /* IconCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconCommand.swift; sourceTree = "<group>"; };
|
||||
E2FE0EF92D25AFB5002963B7 /* PageHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHeader.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F032D2671FC002963B7 /* LocalizedNavigationBarSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedNavigationBarSettingsView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F052D26734E002963B7 /* TextFieldPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldPropertyView.swift; sourceTree = "<group>"; };
|
||||
@ -448,7 +432,6 @@
|
||||
E2FE0F142D269188002963B7 /* HtmlCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HtmlCommand.swift; sourceTree = "<group>"; };
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageId.swift; sourceTree = "<group>"; };
|
||||
E2FE0F182D2723E3002963B7 /* ImageSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSet.swift; sourceTree = "<group>"; };
|
||||
E2FE0F1A2D274FDA002963B7 /* LinkPreviewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewItem.swift; sourceTree = "<group>"; };
|
||||
E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewGenerator.swift; sourceTree = "<group>"; };
|
||||
E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Remove.swift"; sourceTree = "<group>"; };
|
||||
E2FE0F212D2A849B002963B7 /* VideoCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCommand.swift; sourceTree = "<group>"; };
|
||||
@ -462,7 +445,6 @@
|
||||
E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFilePlayer.swift; sourceTree = "<group>"; };
|
||||
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSettingsDetailView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3F2D2B45CD002963B7 /* SwiftBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftBlock.swift; sourceTree = "<group>"; };
|
||||
E2FE0F412D2B480B002963B7 /* OtherCodeBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherCodeBlock.swift; sourceTree = "<group>"; };
|
||||
@ -477,13 +459,11 @@
|
||||
E2FE0F562D2BCFD4002963B7 /* BlockLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockLineProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F582D2BCFE4002963B7 /* OrderedKeyBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedKeyBlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F5A2D2BCFF2002963B7 /* KeyedBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyedBlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F5D2D2BE18B002963B7 /* FileResourceFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileResourceFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F5F2D2C041E002963B7 /* VideoBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoBlock.swift; sourceTree = "<group>"; };
|
||||
E2FE0F612D2C0D8D002963B7 /* VersionedVideo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionedVideo.swift; sourceTree = "<group>"; };
|
||||
E2FE0F632D2C2F46002963B7 /* ButtonBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonBlock.swift; sourceTree = "<group>"; };
|
||||
E2FE0F652D2C3B33002963B7 /* LabelsBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelsBlock.swift; sourceTree = "<group>"; };
|
||||
E2FE0F672D2D2CF0002963B7 /* LocalizedPageSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F692D2D2D4F002963B7 /* LocalizedPageSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6B2D2D3358002963B7 /* LocalizedPageSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageSettingsView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6D2D2D3685002963B7 /* LocalizedAudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedAudioPlayerSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F6F2D2D5231002963B7 /* DraftIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftIndicator.swift; sourceTree = "<group>"; };
|
||||
@ -510,47 +490,18 @@
|
||||
E229901A2D0E3F09009F8D77 /* Item */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F1A2D274FDA002963B7 /* LinkPreviewItem.swift */,
|
||||
E2FD1D1E2D2E9CBE00B48627 /* ItemId.swift */,
|
||||
E2FD1D1C2D2DE31600B48627 /* ItemType.swift */,
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */,
|
||||
E229902B2D0F6FC0009F8D77 /* ItemId.swift */,
|
||||
E229902B2D0F6FC0009F8D77 /* LocalizedItemId.swift */,
|
||||
E229901D2D0E4362009F8D77 /* LocalizedItem.swift */,
|
||||
E29D31A22D0CC98B0051B7F4 /* Item.swift */,
|
||||
E22990182D0E3546009F8D77 /* ItemType.swift */,
|
||||
E22990182D0E3546009F8D77 /* ItemReference.swift */,
|
||||
E22990162D0E32F5009F8D77 /* TagOverviewPage.swift */,
|
||||
);
|
||||
path = Item;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E25DA5112CFF001900AEF16D /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D31292D039B050051B7F4 /* FileDescriptions.swift */,
|
||||
E25DA5322D0041C400AEF16D /* Settings */,
|
||||
E2A37D102CE537670000979F /* PageFile.swift */,
|
||||
E2A37D142CE68BEA0000979F /* PostFile.swift */,
|
||||
E2A37D162CE73F170000979F /* TagFile.swift */,
|
||||
E2FE0F5D2D2BE18B002963B7 /* FileResourceFile.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E25DA5322D0041C400AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */,
|
||||
E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */,
|
||||
E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */,
|
||||
E29D31972D0C19300051B7F4 /* PathSettingsFile.swift */,
|
||||
E25DA5372D00420D00AEF16D /* LocalizedPostSettingsFile.swift */,
|
||||
E25DA5352D0041E200AEF16D /* PostSettingsFile.swift */,
|
||||
E25DA5922D023B3600AEF16D /* PageSettingsFile.swift */,
|
||||
E2FE0F692D2D2D4F002963B7 /* LocalizedPageSettingsFile.swift */,
|
||||
E21850342CFAFA570090B18B /* SettingsFile.swift */,
|
||||
E22990212D0ED129009F8D77 /* TagOverviewFile.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -752,7 +703,6 @@
|
||||
E22990492D10BB90009F8D77 /* SecurityScopeBookmark.swift */,
|
||||
E22990472D10B7B7009F8D77 /* StorageAccessError.swift */,
|
||||
E22990452D10B7A6009F8D77 /* SecurityScopeStatus.swift */,
|
||||
E25DA5112CFF001900AEF16D /* Model */,
|
||||
E2A37D0D2CE527040000979F /* Storage.swift */,
|
||||
);
|
||||
path = Storage;
|
||||
@ -775,13 +725,14 @@
|
||||
E2B85F392C428F020047CD0C /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FD1D262D2EBBA300B48627 /* Loading */,
|
||||
E2FD1D0C2D2DBBA100B48627 /* LinkPreview.swift */,
|
||||
E229901A2D0E3F09009F8D77 /* Item */,
|
||||
E25DA53B2D0042EA00AEF16D /* Settings */,
|
||||
E2E06DFA2CA4A6570019C2AF /* Content.swift */,
|
||||
E29D31A02D0C75C50051B7F4 /* Content+Validation.swift */,
|
||||
E25DA5882D01CBCE00AEF16D /* Content+Generation.swift */,
|
||||
E25DA5162CFF00F200AEF16D /* Content+Save.swift */,
|
||||
E25DA5142CFF00B900AEF16D /* Content+Load.swift */,
|
||||
E24252092C52C9260029FF16 /* ContentLanguage.swift */,
|
||||
E25DA59A2D024A2900AEF16D /* DateItem.swift */,
|
||||
E21850162CEE55FB0090B18B /* FileType.swift */,
|
||||
@ -793,6 +744,7 @@
|
||||
E2A9CB7D2C7BCF2A005C89CC /* Page.swift */,
|
||||
E25A0B882CE4021400F33674 /* LocalizedPage.swift */,
|
||||
E29D31C22D0DBEF00051B7F4 /* Song.swift */,
|
||||
E2FD1D242D2EBA7C00B48627 /* TagOverview.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -816,6 +768,7 @@
|
||||
E2B85F462C42C7CA0047CD0C /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FD1D1A2D2DC62C00B48627 /* LinkPreviewDetailView.swift */,
|
||||
E22990142D0E2B74009F8D77 /* ItemSelectionView.swift */,
|
||||
E2A21C372CB9A4F10060935B /* Generic */,
|
||||
E2B85F4B2C4B8B7F0047CD0C /* Posts */,
|
||||
@ -899,7 +852,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E25DA5762D018B9500AEF16D /* File+Mock.swift */,
|
||||
E21850382CFCA6BA0090B18B /* WebsiteData+Mock.swift */,
|
||||
E218500A2CEE02FA0090B18B /* Content+Mock.swift */,
|
||||
E2A37D1A2CEA45530000979F /* Tag+Mock.swift */,
|
||||
E2A21C1F2CB28ED20060935B /* MockImage.swift */,
|
||||
@ -910,6 +862,16 @@
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2FD1D262D2EBBA300B48627 /* Loading */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FD1D202D2EB22700B48627 /* ModelLoader.swift */,
|
||||
E2FD1D222D2EB26C00B48627 /* LoadingResult.swift */,
|
||||
E2FD1D182D2DC4F500B48627 /* LoadingContext.swift */,
|
||||
);
|
||||
path = Loading;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2FE0F072D2689DC002963B7 /* Post Lists */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1103,9 +1065,10 @@
|
||||
files = (
|
||||
E29D31242D0366860051B7F4 /* TagList.swift in Sources */,
|
||||
E22990282D0F596C009F8D77 /* IntegerPropertyView.swift in Sources */,
|
||||
E2FD1D1D2D2DE31800B48627 /* ItemType.swift in Sources */,
|
||||
E2FE0F482D2BC7D1002963B7 /* MarkdownProcessor.swift in Sources */,
|
||||
E2FE0EFE2D266DA5002963B7 /* NavigationSettingsFile.swift in Sources */,
|
||||
E2FE0EE62D15A0B5002963B7 /* GenerationResults.swift in Sources */,
|
||||
E2FD1D252D2EBA8000B48627 /* TagOverview.swift in Sources */,
|
||||
E2FE0F152D26918F002963B7 /* HtmlCommand.swift in Sources */,
|
||||
E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */,
|
||||
E25DA5772D018B9900AEF16D /* File+Mock.swift in Sources */,
|
||||
@ -1127,7 +1090,6 @@
|
||||
E2FE0EF42D1D6D2E002963B7 /* GeneralIcons.swift in Sources */,
|
||||
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */,
|
||||
E229902E2D0F7280009F8D77 /* IdPropertyView.swift in Sources */,
|
||||
E2FE0F3C2D2B3F45002963B7 /* AudioPlayerSettingsFile.swift in Sources */,
|
||||
E2FE0F462D2BC777002963B7 /* MarkdownImageProcessor.swift in Sources */,
|
||||
E29D31AD2D0DA5360051B7F4 /* AudioPlayerIcons.swift in Sources */,
|
||||
E25DA5452D00952E00AEF16D /* SettingsSection.swift in Sources */,
|
||||
@ -1138,11 +1100,9 @@
|
||||
E2FE0F042D267206002963B7 /* LocalizedNavigationBarSettingsView.swift in Sources */,
|
||||
E2FE0F382D2B32F4002963B7 /* SingleFilePlayer.swift in Sources */,
|
||||
E29D31B12D0DA5510051B7F4 /* ContentIcon.swift in Sources */,
|
||||
E2A37D112CE537800000979F /* PageFile.swift in Sources */,
|
||||
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
||||
E2B85F452C429ED60047CD0C /* ImageGallery.swift in Sources */,
|
||||
E2B85F3B2C428F0E0047CD0C /* Post.swift in Sources */,
|
||||
E2FE0F6A2D2D2D55002963B7 /* LocalizedPageSettingsFile.swift in Sources */,
|
||||
E29D31852D0AE8EE0051B7F4 /* KnownHeaderElement.swift in Sources */,
|
||||
E25DA51B2CFF08BB00AEF16D /* PostFeedPageNavigation.swift in Sources */,
|
||||
E22990422D107A95009F8D77 /* ImageVersion.swift in Sources */,
|
||||
@ -1160,6 +1120,7 @@
|
||||
E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */,
|
||||
E29D31632D06E95D0051B7F4 /* NavigationIcon.swift in Sources */,
|
||||
E2FE0F362D2B27F9002963B7 /* BlockProcessor.swift in Sources */,
|
||||
E2FD1D1B2D2DC63800B48627 /* LinkPreviewDetailView.swift in Sources */,
|
||||
E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */,
|
||||
E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */,
|
||||
E2FE0F4F2D2BCD80002963B7 /* TagLinkCommand.swift in Sources */,
|
||||
@ -1167,9 +1128,8 @@
|
||||
E2A37D0C2CE4036B0000979F /* LocalizedPage.swift in Sources */,
|
||||
E2A21C102CB18B3A0060935B /* FlowHStack.swift in Sources */,
|
||||
E25DA5992D02401E00AEF16D /* PageGenerator.swift in Sources */,
|
||||
E25DA5382D00420E00AEF16D /* LocalizedPostSettingsFile.swift in Sources */,
|
||||
E2B85F3D2C4293F80047CD0C /* FeedPageGenerator.swift in Sources */,
|
||||
E229902C2D0F6FC6009F8D77 /* ItemId.swift in Sources */,
|
||||
E229902C2D0F6FC6009F8D77 /* LocalizedItemId.swift in Sources */,
|
||||
E25DA5952D023BD100AEF16D /* PageSettingsDetailView.swift in Sources */,
|
||||
E21850092CEE01C30090B18B /* PagePickerView.swift in Sources */,
|
||||
E29D31492D0489BB0051B7F4 /* AddFileView.swift in Sources */,
|
||||
@ -1179,18 +1139,14 @@
|
||||
E29D31302D03A2C50051B7F4 /* DescriptionField.swift in Sources */,
|
||||
E29D31BE2D0DB85A0051B7F4 /* AudioPlayerCommand.swift in Sources */,
|
||||
E29D31552D06D2CE0051B7F4 /* TagListView.swift in Sources */,
|
||||
E29D31982D0C19340051B7F4 /* PathSettingsFile.swift in Sources */,
|
||||
E2FE0F3A2D2B3E4F002963B7 /* AudioPlayerSettings.swift in Sources */,
|
||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
||||
E2FE0F092D2689F0002963B7 /* TagPageGeneratorSource.swift in Sources */,
|
||||
E22990302D0F75DE009F8D77 /* BoolPropertyView.swift in Sources */,
|
||||
E21850392CFCA6C00090B18B /* WebsiteData+Mock.swift in Sources */,
|
||||
E229901E2D0E4364009F8D77 /* LocalizedItem.swift in Sources */,
|
||||
E29D31262D0370A80051B7F4 /* VideoCommand+Option.swift in Sources */,
|
||||
E2FE0EF82D1D8110002963B7 /* IconCommand.swift in Sources */,
|
||||
E21850272CF3B42D0090B18B /* PostDetailView.swift in Sources */,
|
||||
E2A37D172CE73F1A0000979F /* TagFile.swift in Sources */,
|
||||
E29D318E2D0B2E680051B7F4 /* PageSettingsContentView.swift in Sources */,
|
||||
E22990242D0EDBD0009F8D77 /* HeaderElement.swift in Sources */,
|
||||
E29D31BC2D0DB5120051B7F4 /* CommandProcessor.swift in Sources */,
|
||||
@ -1221,18 +1177,16 @@
|
||||
E29D313F2D04822C0051B7F4 /* AddPostView.swift in Sources */,
|
||||
E25DA5752D018B6100AEF16D /* FileDetailView.swift in Sources */,
|
||||
E25DA50B2CFD988100AEF16D /* PageTagAssignmentView.swift in Sources */,
|
||||
E21850352CFAFA5A0090B18B /* SettingsFile.swift in Sources */,
|
||||
E29D31A12D0C75CA0051B7F4 /* Content+Validation.swift in Sources */,
|
||||
E22990172D0E330F009F8D77 /* TagOverviewPage.swift in Sources */,
|
||||
E29D316D2D07A5050051B7F4 /* PageGenerationResults.swift in Sources */,
|
||||
E229903E2D0F8F02009F8D77 /* StringPropertyView.swift in Sources */,
|
||||
E25DA5192CFF035900AEF16D /* Array+Split.swift in Sources */,
|
||||
E2FE0F1B2D274FDF002963B7 /* LinkPreviewItem.swift in Sources */,
|
||||
E2FE0F062D267350002963B7 /* TextFieldPropertyView.swift in Sources */,
|
||||
E2FD1D232D2EB27000B48627 /* LoadingResult.swift in Sources */,
|
||||
E29D31B82D0DAC250051B7F4 /* ButtonCommand.swift in Sources */,
|
||||
E29D31962D0C186E0051B7F4 /* PathSettings.swift in Sources */,
|
||||
E2B85F412C4294790047CD0C /* PageHead.swift in Sources */,
|
||||
E2FE0F5E2D2BE190002963B7 /* FileResourceFile.swift in Sources */,
|
||||
E2FE0F2A2D2AFBE6002963B7 /* ImageCompareIcons.swift in Sources */,
|
||||
E29D316B2D07488B0051B7F4 /* PostListPageGenerator.swift in Sources */,
|
||||
E218501D2CEE6CB60090B18B /* VerticalCenter.swift in Sources */,
|
||||
@ -1241,11 +1195,10 @@
|
||||
E2FE0F592D2BCFE4002963B7 /* OrderedKeyBlockProcessor.swift in Sources */,
|
||||
E2FE0EEC2D1C1253002963B7 /* MultiFileSelectionView.swift in Sources */,
|
||||
E29D31B32D0DA6E80051B7F4 /* ButtonIcons.swift in Sources */,
|
||||
E2FD1D212D2EB22900B48627 /* ModelLoader.swift in Sources */,
|
||||
E29D319D2D0C45B90051B7F4 /* PageIssueView.swift in Sources */,
|
||||
E25DA5732D018AA100AEF16D /* FileContentView.swift in Sources */,
|
||||
E22990222D0ED12E009F8D77 /* TagOverviewFile.swift in Sources */,
|
||||
E25DA5232CFF6C3700AEF16D /* ImageGenerator.swift in Sources */,
|
||||
E2FE0F002D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift in Sources */,
|
||||
E2A9CB7E2C7BCF2A005C89CC /* Page.swift in Sources */,
|
||||
E29D31202D0320E70051B7F4 /* ContentLabels.swift in Sources */,
|
||||
E2FE0F572D2BCFD4002963B7 /* BlockLineProcessor.swift in Sources */,
|
||||
@ -1263,6 +1216,7 @@
|
||||
E29D31A52D0CD03F0051B7F4 /* FileSelectionView.swift in Sources */,
|
||||
E22990322D0F767B009F8D77 /* DatePropertyView.swift in Sources */,
|
||||
E2A21C302CB490F90060935B /* HorizontalCenter.swift in Sources */,
|
||||
E2FD1D1F2D2E9CC200B48627 /* ItemId.swift in Sources */,
|
||||
E2FE0EFA2D25AFBA002963B7 /* PageHeader.swift in Sources */,
|
||||
E25DA5252CFF73AB00AEF16D /* NSSize+Scaling.swift in Sources */,
|
||||
E21850372CFCA55F0090B18B /* LocalizedPostSettings.swift in Sources */,
|
||||
@ -1283,7 +1237,7 @@
|
||||
E2FE0F112D268E7E002963B7 /* MarkdownCodeProcessor.swift in Sources */,
|
||||
E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */,
|
||||
E29D31C02D0DB9F20051B7F4 /* AudioPlayerContent.swift in Sources */,
|
||||
E22990192D0E3546009F8D77 /* ItemType.swift in Sources */,
|
||||
E22990192D0E3546009F8D77 /* ItemReference.swift in Sources */,
|
||||
E25DA51F2CFF15C400AEF16D /* NavigationBar.swift in Sources */,
|
||||
E2FE0F0F2D268D4F002963B7 /* BoxCommand.swift in Sources */,
|
||||
E22990482D10B7B7009F8D77 /* StorageAccessError.swift in Sources */,
|
||||
@ -1295,7 +1249,6 @@
|
||||
E29D314D2D04FCBF0051B7F4 /* FileToAddView.swift in Sources */,
|
||||
E2FE0F6C2D2D335E002963B7 /* LocalizedPageSettingsView.swift in Sources */,
|
||||
E218502D2CF791440090B18B /* PostImagesView.swift in Sources */,
|
||||
E29D312A2D039B090051B7F4 /* FileDescriptions.swift in Sources */,
|
||||
E2B85F572C4BD0BB0047CD0C /* Binding+Extension.swift in Sources */,
|
||||
E2B85F432C4294F60047CD0C /* FeedEntry.swift in Sources */,
|
||||
E25DA56D2D00EBCF00AEF16D /* NavigationBarSettingsView.swift in Sources */,
|
||||
@ -1311,13 +1264,12 @@
|
||||
E29D31AA2D0CEE3F0051B7F4 /* AudioPlayer.swift in Sources */,
|
||||
E2FE0F4B2D2BCCAA002963B7 /* MarkdownHeadlineProcessor.swift in Sources */,
|
||||
E2FE0F532D2BCE17002963B7 /* SvgCommand.swift in Sources */,
|
||||
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
||||
E2FE0F3E2D2B4225002963B7 /* AudioSettingsDetailView.swift in Sources */,
|
||||
E25DA58F2D02368D00AEF16D /* PageSettings.swift in Sources */,
|
||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
||||
E2FD1D0D2D2DBBA600B48627 /* LinkPreview.swift in Sources */,
|
||||
E22990362D0F79D2009F8D77 /* OptionalStringPropertyView.swift in Sources */,
|
||||
E229903C2D0F8A7B009F8D77 /* OptionalTextFieldPropertyView.swift in Sources */,
|
||||
E25DA5932D023B3C00AEF16D /* PageSettingsFile.swift in Sources */,
|
||||
E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */,
|
||||
E2FE0F222D2A84A0002963B7 /* VideoCommand.swift in Sources */,
|
||||
E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */,
|
||||
@ -1328,9 +1280,9 @@
|
||||
E2FE0F262D2AF9B0002963B7 /* ImageCompareCommand.swift in Sources */,
|
||||
E2FE0F1E2D281AE1002963B7 /* TagOverviewGenerator.swift in Sources */,
|
||||
E29D31572D06D38B0051B7F4 /* AddTagView.swift in Sources */,
|
||||
E25DA5362D0041EB00AEF16D /* PostSettingsFile.swift in Sources */,
|
||||
E29D31792D083DE50051B7F4 /* PageContentResultsView.swift in Sources */,
|
||||
E25DA5912D023A8400AEF16D /* IntegerField.swift in Sources */,
|
||||
E2FD1D192D2DC4F500B48627 /* LoadingContext.swift in Sources */,
|
||||
E29D318B2D0B07EE0051B7F4 /* ContentBox.swift in Sources */,
|
||||
E2FE0F4D2D2BCD30002963B7 /* PageLinkCommand.swift in Sources */,
|
||||
);
|
||||
|
@ -120,6 +120,14 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
func removingPrefix(_ prefix: String) -> String? {
|
||||
guard self.hasPrefix(prefix) else { return nil }
|
||||
return String(self.dropFirst(prefix.count))
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
var fileNameWithoutExtension: String {
|
||||
|
@ -38,9 +38,14 @@ struct PageLinkCommand: CommandProcessor {
|
||||
|
||||
let localized = page.localized(in: language)
|
||||
let url = page.absoluteUrl(in: language)
|
||||
let title = localized.linkPreviewTitle ?? localized.title
|
||||
let description = localized.linkPreviewDescription ?? ""
|
||||
let image = makePageImage(item: localized)
|
||||
let title = localized.linkPreview.title ?? localized.title
|
||||
let description = localized.linkPreview.description ?? ""
|
||||
let image = localized.linkPreview.image.map {
|
||||
let size = content.settings.pages.pageLinkImageSize
|
||||
let imageSet = $0.imageSet(width: size, height: size, language: language)
|
||||
results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
|
||||
return RelatedPageLink(
|
||||
title: title,
|
||||
@ -49,13 +54,4 @@ struct PageLinkCommand: CommandProcessor {
|
||||
image: image)
|
||||
.content
|
||||
}
|
||||
|
||||
private func makePageImage(item: LinkPreviewItem) -> ImageSet? {
|
||||
item.linkPreviewImage.map { image in
|
||||
let size = content.settings.pages.pageLinkImageSize
|
||||
let imageSet = image.imageSet(width: size, height: size, language: language)
|
||||
results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,13 @@ struct TagLinkCommand: CommandProcessor {
|
||||
let localized = tag.localized(in: language)
|
||||
let url = tag.absoluteUrl(in: language)
|
||||
let title = localized.name
|
||||
let description = localized.linkPreviewDescription ?? ""
|
||||
let image = makePageImage(item: localized)
|
||||
let description = localized.linkPreview.description ?? ""
|
||||
let image = localized.linkPreview.image.map {
|
||||
let size = content.settings.pages.pageLinkImageSize
|
||||
let imageSet = $0.imageSet(width: size, height: size, language: language)
|
||||
results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
|
||||
return RelatedPageLink(
|
||||
title: title,
|
||||
@ -43,13 +48,4 @@ struct TagLinkCommand: CommandProcessor {
|
||||
image: image)
|
||||
.content
|
||||
}
|
||||
|
||||
private func makePageImage(item: LinkPreviewItem) -> ImageSet? {
|
||||
item.linkPreviewImage.map { image in
|
||||
let size = content.settings.pages.pageLinkImageSize
|
||||
let imageSet = image.imageSet(width: size, height: size, language: language)
|
||||
results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ struct ImageSet: HtmlProducer {
|
||||
|
||||
let quality: CGFloat
|
||||
|
||||
let description: String
|
||||
let description: String?
|
||||
|
||||
let extraAttributes: String
|
||||
|
||||
init(image: FileResource, maxWidth: Int, maxHeight: Int, description: String, quality: CGFloat = 0.7, extraAttributes: String? = nil) {
|
||||
init(image: FileResource, maxWidth: Int, maxHeight: Int, description: String?, quality: CGFloat = 0.7, extraAttributes: String? = nil) {
|
||||
self.image = image
|
||||
self.maxWidth = maxWidth
|
||||
self.maxHeight = maxHeight
|
||||
@ -39,6 +39,13 @@ struct ImageSet: HtmlProducer {
|
||||
]
|
||||
}
|
||||
|
||||
private var imageAltText: String {
|
||||
guard let description else {
|
||||
return ""
|
||||
}
|
||||
return " alt='\(description.htmlEscaped())'"
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
let fileExtension = image.type.fileExtension.map { "." + $0 } ?? ""
|
||||
|
||||
@ -48,7 +55,7 @@ struct ImageSet: HtmlProducer {
|
||||
result += "<picture>"
|
||||
result += "<source type='image/avif' srcset='\(prefix1x).avif 1x, \(prefix2x).avif 2x'/>"
|
||||
result += "<source type='image/webp' srcset='\(prefix1x).webp 1x, \(prefix1x).webp 2x'/>"
|
||||
result += "<img srcset='\(prefix2x)\(fileExtension) 2x' src='\(prefix1x)\(fileExtension)' loading='lazy' alt='\(description.htmlEscaped())'\(extraAttributes)/>"
|
||||
result += "<img srcset='\(prefix2x)\(fileExtension) 2x' src='\(prefix1x)\(fileExtension)' loading='lazy'\(imageAltText)\(extraAttributes)/>"
|
||||
result += "</picture>"
|
||||
}
|
||||
}
|
||||
|
@ -58,8 +58,8 @@ final class PageGenerator {
|
||||
|
||||
let pageHeader = PageHeader(
|
||||
language: language,
|
||||
title: localized.linkPreviewTitle ?? localized.title,
|
||||
description: localized.linkPreviewDescription,
|
||||
title: localized.linkPreview.title ?? localized.title,
|
||||
description: localized.linkPreview.description,
|
||||
iconUrl: iconUrl,
|
||||
languageButton: languageButton,
|
||||
links: content.navigationBar(in: language),
|
||||
|
@ -10,7 +10,7 @@ private struct TagData {
|
||||
init(tag: Tag, language: ContentLanguage) {
|
||||
let localized = tag.localized(in: language)
|
||||
self.url = tag.absoluteUrl(in: language)
|
||||
self.title = localized.linkPreviewTitle ?? localized.name
|
||||
self.title = localized.linkPreview.title ?? localized.name
|
||||
self.localized = localized
|
||||
}
|
||||
}
|
||||
@ -81,12 +81,12 @@ final class TagOverviewGenerator {
|
||||
self.results = results
|
||||
}
|
||||
|
||||
func generatePages(tags: [Tag], overview: TagOverviewPage) {
|
||||
func generatePages(tags: [Tag], overview: Tag) {
|
||||
let localized = overview.localized(in: language)
|
||||
let header = TagHeaderContent(
|
||||
language: language,
|
||||
title: localized.linkPreviewTitle ?? localized.title,
|
||||
description: localized.linkPreviewDescription,
|
||||
title: localized.linkPreview.title ?? localized.title,
|
||||
description: localized.linkPreview.description,
|
||||
iconUrl: content.settings.navigation.localized(in: language).rootUrl,
|
||||
links: content.navigationBar(in: language),
|
||||
headers: content.postPageHeaders,
|
||||
@ -123,8 +123,13 @@ final class TagOverviewGenerator {
|
||||
additionalFooter: "") { content in
|
||||
content += "<h1 class='separated-headline'>\(header.title)</h1>"
|
||||
for tag in tags {
|
||||
let description = tag.localized.linkPreviewDescription ?? ""
|
||||
let image = self.makePageImage(item: tag.localized)
|
||||
let description = tag.localized.linkPreview.description ?? ""
|
||||
let image = tag.localized.linkPreview.image.map {
|
||||
let size = self.content.settings.pages.pageLinkImageSize
|
||||
let imageSet = $0.imageSet(width: size, height: size, language: self.language)
|
||||
self.results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
|
||||
content += RelatedPageLink(
|
||||
title: tag.title,
|
||||
@ -148,13 +153,4 @@ final class TagOverviewGenerator {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func makePageImage(item: LinkPreviewItem) -> ImageSet? {
|
||||
item.linkPreviewImage.map { image in
|
||||
let size = content.settings.pages.pageLinkImageSize
|
||||
let imageSet = image.imageSet(width: size, height: size, language: language)
|
||||
results.require(imageSet: imageSet)
|
||||
return imageSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ struct TagPageGeneratorSource: PostListPageGeneratorSource {
|
||||
}
|
||||
|
||||
var pageDescription: String {
|
||||
tag.localized(in: language).linkPreviewDescription ?? ""
|
||||
tag.localized(in: language).linkPreview.description ?? ""
|
||||
}
|
||||
|
||||
func pageUrlPrefix(for language: ContentLanguage) -> String {
|
||||
|
@ -56,7 +56,7 @@ final class GenerationResults: ObservableObject {
|
||||
var redirects: [String : String] = [:]
|
||||
|
||||
/// The cache of previously used GenerationResults
|
||||
private var cache: [ItemId : PageGenerationResults] = [:]
|
||||
private var cache: [LocalizedItemId : PageGenerationResults] = [:]
|
||||
|
||||
private(set) var general: PageGenerationResults!
|
||||
|
||||
@ -66,14 +66,14 @@ final class GenerationResults: ObservableObject {
|
||||
// MARK: Life cycle
|
||||
|
||||
init() {
|
||||
let id = ItemId(language: .english, itemType: .general)
|
||||
let id = LocalizedItemId(language: .english, itemType: .general)
|
||||
let general = PageGenerationResults(itemId: id, delegate: self)
|
||||
self.general = general
|
||||
cache[id] = general
|
||||
self.resultCount = 1
|
||||
}
|
||||
|
||||
func makeResults(_ itemId: ItemId) -> PageGenerationResults {
|
||||
func makeResults(_ itemId: LocalizedItemId) -> PageGenerationResults {
|
||||
guard let result = cache[itemId] else {
|
||||
let result = PageGenerationResults(itemId: itemId, delegate: self)
|
||||
cache[itemId] = result
|
||||
@ -83,18 +83,18 @@ final class GenerationResults: ObservableObject {
|
||||
return result
|
||||
}
|
||||
|
||||
func makeResults(for type: ItemType, in language: ContentLanguage) -> PageGenerationResults {
|
||||
let itemId = ItemId(language: language, itemType: type)
|
||||
func makeResults(for type: ItemReference, in language: ContentLanguage) -> PageGenerationResults {
|
||||
let itemId = LocalizedItemId(language: language, itemType: type)
|
||||
return makeResults(itemId)
|
||||
}
|
||||
|
||||
func makeResults(for page: Page, in language: ContentLanguage) -> PageGenerationResults {
|
||||
let itemId = ItemId(language: language, itemType: .page(page))
|
||||
let itemId = LocalizedItemId(language: language, itemType: .page(page))
|
||||
return makeResults(itemId)
|
||||
}
|
||||
|
||||
func makeResults(for tag: Tag, in language: ContentLanguage) -> PageGenerationResults {
|
||||
let itemId = ItemId(language: language, itemType: .tagPage(tag))
|
||||
let itemId = LocalizedItemId(language: language, itemType: .tagPage(tag))
|
||||
return makeResults(itemId)
|
||||
}
|
||||
|
||||
@ -209,9 +209,9 @@ final class GenerationResults: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private extension Dictionary where Value == Set<ItemId> {
|
||||
private extension Dictionary where Value == Set<LocalizedItemId> {
|
||||
|
||||
mutating func remove<S>(keys: S, of item: ItemId) where S: Sequence, S.Element == Key {
|
||||
mutating func remove<S>(keys: S, of item: LocalizedItemId) where S: Sequence, S.Element == Key {
|
||||
for key in keys {
|
||||
guard var value = self[key] else { continue }
|
||||
value.remove(item)
|
||||
|
@ -17,7 +17,7 @@ extension ImageToGenerate: Hashable {
|
||||
|
||||
final class PageGenerationResults: ObservableObject {
|
||||
|
||||
let itemId: ItemId
|
||||
let itemId: LocalizedItemId
|
||||
|
||||
private unowned let delegate: GenerationResults
|
||||
|
||||
@ -73,13 +73,13 @@ final class PageGenerationResults: ObservableObject {
|
||||
private(set) var warnings: Set<String>
|
||||
|
||||
/// The files that could not be saved to the output folder
|
||||
private(set) var unsavedOutputFiles: [String: Set<ItemType>] = [:]
|
||||
private(set) var unsavedOutputFiles: [String: Set<ItemReference>] = [:]
|
||||
|
||||
private(set) var pageIsEmpty: Bool
|
||||
|
||||
private(set) var redirect: (originalUrl: String, newUrl: String)?
|
||||
|
||||
init(itemId: ItemId, delegate: GenerationResults) {
|
||||
init(itemId: LocalizedItemId, delegate: GenerationResults) {
|
||||
self.itemId = itemId
|
||||
self.delegate = delegate
|
||||
inaccessibleFiles = []
|
||||
@ -245,7 +245,7 @@ final class PageGenerationResults: ObservableObject {
|
||||
delegate.warning(warning)
|
||||
}
|
||||
|
||||
func unsavedOutput(_ path: String, source: ItemType) {
|
||||
func unsavedOutput(_ path: String, source: ItemReference) {
|
||||
unsavedOutputFiles[path, default: []].insert(source)
|
||||
delegate.unsaved(path)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ struct InitialSetupView: View {
|
||||
if let message {
|
||||
Text(message)
|
||||
.padding(.bottom)
|
||||
.lineLimit(10)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@ -52,14 +53,25 @@ struct InitialSetupView: View {
|
||||
set(message: "Failed to set content path")
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
do {
|
||||
try content.loadFromDisk()
|
||||
} catch {
|
||||
set(message: "Failed to load database: \(error)")
|
||||
|
||||
DispatchQueue.global().async {
|
||||
let loader = ModelLoader(content: content, storage: content.storage)
|
||||
let result = loader.load()
|
||||
guard result.errors.isEmpty else {
|
||||
let message = "Failed to load database\n" + result.errors.sorted().joined(separator: "\n")
|
||||
set(message: message)
|
||||
return
|
||||
}
|
||||
dismiss()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
content.files = result.files
|
||||
content.posts = result.posts
|
||||
content.pages = result.pages
|
||||
content.tags = result.tags
|
||||
content.settings = result.settings
|
||||
content.tagOverview = result.tagOverview
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,12 @@ struct MainView: App {
|
||||
@State
|
||||
private var showInitialSetupSheet = false
|
||||
|
||||
@State
|
||||
private var showLoadErrorSheet = false
|
||||
|
||||
@State
|
||||
private var loadErrors: [String] = []
|
||||
|
||||
@ViewBuilder
|
||||
var sidebar: some View {
|
||||
switch selectedTab {
|
||||
@ -202,14 +208,30 @@ struct MainView: App {
|
||||
.environment(\.language, language)
|
||||
.environmentObject(content)
|
||||
}
|
||||
.sheet(isPresented: $showLoadErrorSheet) {
|
||||
VStack {
|
||||
Text("Failed to load database")
|
||||
.font(.headline)
|
||||
List(loadErrors, id: \.self) { error in
|
||||
HStack {
|
||||
Text(error)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 200)
|
||||
Button("Dismiss", action: { showLoadErrorSheet = false })
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func save() {
|
||||
do {
|
||||
try content.saveToDisk()
|
||||
} catch {
|
||||
print("Failed to save content: \(error.localizedDescription)")
|
||||
guard content.saveToDisk() else {
|
||||
print("Failed to save content")
|
||||
#warning("Show error message")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,10 +240,12 @@ struct MainView: App {
|
||||
showInitialSheet()
|
||||
return
|
||||
}
|
||||
do {
|
||||
try content.loadFromDisk()
|
||||
} catch {
|
||||
print("Failed to load content: \(error.localizedDescription)")
|
||||
content.loadFromDisk { errors in
|
||||
guard !errors.isEmpty else {
|
||||
return
|
||||
}
|
||||
self.loadErrors = errors
|
||||
self.showLoadErrorSheet = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,158 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension Content {
|
||||
|
||||
private func convert(_ tag: LocalizedTagFile, images: [String : FileResource]) -> LocalizedTag {
|
||||
LocalizedTag(
|
||||
content: self,
|
||||
urlComponent: tag.urlComponent,
|
||||
name: tag.name,
|
||||
linkPreviewTitle: tag.linkPreviewTitle,
|
||||
linkPreviewDescription: tag.linkPreviewDescription,
|
||||
linkPreviewImage: tag.linkPreviewImage.map { images[$0] },
|
||||
originalUrl: tag.originalURL)
|
||||
}
|
||||
|
||||
private func convert(_ page: LocalizedPageFile, images: [String : FileResource]) -> LocalizedPage {
|
||||
LocalizedPage(
|
||||
content: self,
|
||||
urlString: page.url,
|
||||
title: page.title,
|
||||
lastModified: page.lastModifiedDate,
|
||||
originalUrl: page.originalURL,
|
||||
linkPreviewImage: page.linkPreviewImage.map { images[$0] },
|
||||
linkPreviewTitle: page.linkPreviewTitle,
|
||||
linkPreviewDescription: page.linkPreviewDescription,
|
||||
hideTitle: page.hideTitle ?? false)
|
||||
}
|
||||
|
||||
func loadFromDisk() throws {
|
||||
guard storage.contentScope != nil else {
|
||||
print("Storage not initialized, not loading content")
|
||||
throw StorageAccessError.noBookmarkData
|
||||
}
|
||||
|
||||
let settings = storage.loadSettings() ?? .default
|
||||
|
||||
guard let tagData = storage.loadAllTags() else {
|
||||
print("Failed to load file tags")
|
||||
return
|
||||
}
|
||||
if tagData.isEmpty { print("No tags loaded") }
|
||||
|
||||
guard let pagesData = storage.loadAllPages() else {
|
||||
print("Failed to load file pages")
|
||||
return
|
||||
}
|
||||
if pagesData.isEmpty { print("No pages loaded") }
|
||||
|
||||
guard let postsData = storage.loadAllPosts() else {
|
||||
print("Failed to load file posts")
|
||||
return
|
||||
}
|
||||
if postsData.isEmpty { print("No posts loaded") }
|
||||
|
||||
guard let fileList = storage.loadAllFiles() else {
|
||||
print("Failed to load file list")
|
||||
return
|
||||
}
|
||||
if fileList.isEmpty { print("No files loaded") }
|
||||
|
||||
print("Loaded data from disk, processing...")
|
||||
// All data loaded from storage, start constructing the data model
|
||||
|
||||
let files: [String : FileResource] = fileList.reduce(into: [:]) { (files, data) in
|
||||
let fileId = data.key
|
||||
let fileData = data.value.data
|
||||
let isExternal = data.value.isExternal
|
||||
files[fileId] = FileResource(content: self, id: fileId, file: fileData, isExternalFile: isExternal)
|
||||
}
|
||||
|
||||
let images = files.filter { $0.value.type.isImage }
|
||||
|
||||
let tags = tagData.reduce(into: [:]) { (tags, data) in
|
||||
tags[data.key] = Tag(
|
||||
content: self,
|
||||
id: data.value.id,
|
||||
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, files: files)
|
||||
|
||||
let posts: [String : Post] = postsData.reduce(into: [:]) { dict, data in
|
||||
let (postId, post) = data
|
||||
let linkedPage = post.linkedPageId.map { pages[$0] }
|
||||
let german = LocalizedPost(content: self, file: post.german, images: images)
|
||||
let english = LocalizedPost(content: self, file: post.english, images: images)
|
||||
|
||||
dict[postId] = Post(
|
||||
content: self,
|
||||
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)
|
||||
}
|
||||
|
||||
let tagOverview = settings.tagOverview.map { file in
|
||||
TagOverviewPage(
|
||||
content: self,
|
||||
german: .init(content: self, file: file.german, image: file.german.linkPreviewImage.map { files[$0] }),
|
||||
english: .init(content: self, file: file.english, image: file.english.linkPreviewImage.map { files[$0] }))
|
||||
}
|
||||
|
||||
self.tags = tags.values.sorted()
|
||||
self.pages = pages.values.sorted(ascending: false) { $0.startDate }
|
||||
self.files = files.values.sorted { $0.id }
|
||||
self.posts = posts.values.sorted(ascending: false) { $0.startDate }
|
||||
self.tagOverview = tagOverview
|
||||
self.settings = .init(file: settings, files: files) { raw in
|
||||
#warning("Notify about missing links")
|
||||
guard let type = ItemType(rawValue: raw, posts: posts, pages: pages, tags: tags) else {
|
||||
return nil
|
||||
}
|
||||
switch type {
|
||||
case .general:
|
||||
return nil
|
||||
case .post(let post):
|
||||
return post
|
||||
case .feed:
|
||||
return nil // TODO: Provide feed object
|
||||
case .page(let page):
|
||||
return page
|
||||
case .tagPage(let tag):
|
||||
return tag
|
||||
case .tagOverview:
|
||||
return tagOverview
|
||||
}
|
||||
}
|
||||
|
||||
print("Content loaded")
|
||||
}
|
||||
|
||||
private func loadPages(_ pagesData: [String : PageFile], tags: [String : Tag], files: [String : FileResource]) -> [String : Page] {
|
||||
pagesData.reduce(into: [:]) { pages, data in
|
||||
let (pageId, page) = data
|
||||
pages[pageId] = Page(
|
||||
content: self,
|
||||
id: pageId,
|
||||
externalLink: page.externalLink,
|
||||
isDraft: page.isDraft,
|
||||
createdDate: page.createdDate,
|
||||
hideDate: page.hideDate ?? false,
|
||||
startDate: page.startDate,
|
||||
endDate: page.endDate,
|
||||
german: convert(page.german, images: files),
|
||||
english: convert(page.english, images: files),
|
||||
tags: page.tags.compactMap { tags[$0] },
|
||||
requiredFiles: page.requiredFiles?.compactMap { files[$0] } ?? [])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,22 +2,25 @@ import Foundation
|
||||
|
||||
extension Content {
|
||||
|
||||
func saveToDisk() throws {
|
||||
func saveToDisk() -> Bool {
|
||||
guard didLoadContent else { return false }
|
||||
guard storage.contentScope != nil else {
|
||||
print("Storage not initialized, not saving content")
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
var failedSaves = 0
|
||||
failedSaves += pages.count { !storage.save(pageMetadata: $0.pageFile, for: $0.id) }
|
||||
failedSaves += posts.count { !storage.save(post: $0.postFile, for: $0.id) }
|
||||
failedSaves += tags.count { !storage.save(tagMetadata: $0.file, for: $0.id) }
|
||||
failedSaves.increment(!storage.save(settings: settings.file(tagOverview: tagOverview)))
|
||||
failedSaves += files.count { !storage.save(fileInfo: $0.fileInfo, for: $0.id) }
|
||||
failedSaves += pages.count { !storage.save(pageMetadata: $0.data, for: $0.id) }
|
||||
failedSaves += posts.count { !storage.save(post: $0.data, for: $0.id) }
|
||||
failedSaves += tags.count { !storage.save(tagMetadata: $0.data, for: $0.id) }
|
||||
failedSaves.increment(!storage.save(settings: settings.data(tagOverview: tagOverview)))
|
||||
failedSaves += files.count { !storage.save(fileInfo: $0.data, for: $0.id) }
|
||||
|
||||
if failedSaves > 0 {
|
||||
print("Save partially failed with \(failedSaves) errors")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func removeUnlinkedFiles() -> Bool {
|
||||
@ -37,49 +40,3 @@ extension Content {
|
||||
return success
|
||||
}
|
||||
}
|
||||
|
||||
private extension Page {
|
||||
|
||||
var pageFile: PageFile {
|
||||
.init(isDraft: isDraft,
|
||||
externalLink: externalLink,
|
||||
tags: tags.map { $0.id },
|
||||
hideDate: hideDate ? true : nil,
|
||||
createdDate: createdDate,
|
||||
startDate: startDate,
|
||||
endDate: hasEndDate ? endDate : nil,
|
||||
german: german.pageFile,
|
||||
english: english.pageFile,
|
||||
requiredFiles: requiredFiles.nonEmpty?.map { $0.id }.sorted())
|
||||
}
|
||||
}
|
||||
|
||||
private extension LocalizedPage {
|
||||
|
||||
var pageFile: LocalizedPageFile {
|
||||
.init(url: urlString,
|
||||
title: title,
|
||||
linkPreviewImage: linkPreviewImage?.id,
|
||||
linkPreviewTitle: linkPreviewTitle,
|
||||
linkPreviewDescription: linkPreviewDescription,
|
||||
lastModifiedDate: lastModified,
|
||||
originalURL: originalUrl,
|
||||
hideTitle: hideTitle ? true : nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ import Combine
|
||||
|
||||
final class Content: ObservableObject {
|
||||
|
||||
@Published
|
||||
var didLoadContent = false
|
||||
|
||||
@ObservedObject
|
||||
var storage: Storage
|
||||
|
||||
@ -23,7 +26,7 @@ final class Content: ObservableObject {
|
||||
var files: [FileResource]
|
||||
|
||||
@Published
|
||||
var tagOverview: TagOverviewPage?
|
||||
var tagOverview: Tag?
|
||||
|
||||
@Published
|
||||
var results: GenerationResults
|
||||
@ -47,7 +50,7 @@ final class Content: ObservableObject {
|
||||
pages: [Page],
|
||||
tags: [Tag],
|
||||
files: [FileResource],
|
||||
tagOverview: TagOverviewPage?) {
|
||||
tagOverview: Tag?) {
|
||||
self.settings = settings
|
||||
self.posts = posts
|
||||
self.pages = pages
|
||||
@ -112,16 +115,11 @@ final class Content: ObservableObject {
|
||||
pages.insert(page, at: 0)
|
||||
}
|
||||
|
||||
func update(contentPath: URL) {
|
||||
func update(contentPath: URL, callback: @escaping ([String]) -> ()) {
|
||||
guard storage.save(contentPath: contentPath) else {
|
||||
return
|
||||
}
|
||||
clear()
|
||||
do {
|
||||
try loadFromDisk()
|
||||
} catch {
|
||||
print("Failed to reload content: \(error)")
|
||||
}
|
||||
loadFromDisk(callback: callback)
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
@ -146,4 +144,29 @@ final class Content: ObservableObject {
|
||||
func file(withOutputPath: String) -> FileResource? {
|
||||
files.first { $0.absoluteUrl == withOutputPath }
|
||||
}
|
||||
|
||||
func loadFromDisk(callback: @escaping (_ errors: [String]) -> ()) {
|
||||
DispatchQueue.global().async {
|
||||
let loader = ModelLoader(content: self, storage: self.storage)
|
||||
let result = loader.load()
|
||||
guard result.errors.isEmpty else {
|
||||
DispatchQueue.main.async {
|
||||
self.didLoadContent = false
|
||||
callback(result.errors.sorted())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.files = result.files
|
||||
self.posts = result.posts
|
||||
self.pages = result.pages
|
||||
self.tags = result.tags
|
||||
self.settings = result.settings
|
||||
self.tagOverview = result.tagOverview
|
||||
self.didLoadContent = true
|
||||
callback([])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,34 +1,43 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class FileResource: Item {
|
||||
final class FileResource: Item, LocalizedItem {
|
||||
|
||||
let type: FileType
|
||||
|
||||
/// Indicate if the file content is stored by the app
|
||||
@Published
|
||||
var isExternallyStored: Bool
|
||||
|
||||
/// The file/image description in German
|
||||
@Published
|
||||
var german: String
|
||||
var german: String?
|
||||
|
||||
/// The file/image description in English
|
||||
@Published
|
||||
var english: String
|
||||
var english: String?
|
||||
|
||||
/// A version string of this resource, mostly for assets
|
||||
@Published
|
||||
var version: String?
|
||||
|
||||
/// A URL where the resource was copied/downloaded from
|
||||
@Published
|
||||
var sourceUrl: String?
|
||||
|
||||
/// The list of generated image versions for this image
|
||||
@Published
|
||||
var generatedImageVersions: Set<String>
|
||||
|
||||
/// A custom file path in the output folder where this file is located
|
||||
@Published
|
||||
var customOutputPath: String?
|
||||
|
||||
/// The date when the file was added
|
||||
@Published
|
||||
var addedDate: Date
|
||||
|
||||
/// The date when the file was last modified
|
||||
@Published
|
||||
var modifiedDate: Date
|
||||
|
||||
@ -53,8 +62,8 @@ final class FileResource: Item {
|
||||
modifiedDate: Date = .now) {
|
||||
self.type = FileType(fileExtension: id.fileExtension)
|
||||
self.isExternallyStored = isExternallyStored
|
||||
self.german = german ?? ""
|
||||
self.english = english ?? ""
|
||||
self.german = german
|
||||
self.english = english
|
||||
self.version = version
|
||||
self.sourceUrl = sourceUrl
|
||||
self.generatedImageVersions = generatedImageVersions
|
||||
@ -64,20 +73,6 @@ final class FileResource: Item {
|
||||
super.init(content: content, id: id)
|
||||
}
|
||||
|
||||
init(content: Content, id: String, file: FileResourceFile, isExternalFile: Bool) {
|
||||
self.type = FileType(fileExtension: id.fileExtension)
|
||||
self.isExternallyStored = isExternalFile
|
||||
self.german = file.germanDescription ?? ""
|
||||
self.english = file.englishDescription ?? ""
|
||||
self.version = file.version
|
||||
self.sourceUrl = file.sourceUrl
|
||||
self.generatedImageVersions = Set(file.generatedImages ?? [])
|
||||
self.customOutputPath = file.customOutputPath
|
||||
self.addedDate = file.addedDate
|
||||
self.modifiedDate = file.modifiedDate
|
||||
super.init(content: content, id: id)
|
||||
}
|
||||
|
||||
/**
|
||||
Only for bundle images
|
||||
*/
|
||||
@ -101,7 +96,7 @@ final class FileResource: Item {
|
||||
content.storage.fileContent(for: id) ?? ""
|
||||
}
|
||||
|
||||
func dataContent() -> Data? {
|
||||
func dataContent() -> Foundation.Data? {
|
||||
content.storage.fileData(for: id)
|
||||
}
|
||||
|
||||
@ -131,7 +126,6 @@ final class FileResource: Item {
|
||||
// Image must have changed, so force regeneration
|
||||
DispatchQueue.main.async {
|
||||
self.imageDimensions = size
|
||||
self.didChange()
|
||||
self.removeGeneratedImages()
|
||||
}
|
||||
}
|
||||
@ -299,12 +293,34 @@ final class FileResource: Item {
|
||||
}
|
||||
}
|
||||
|
||||
extension FileResource: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
extension FileResource {
|
||||
|
||||
var fileInfo: FileResourceFile {
|
||||
convenience init(content: Content, id: String, data: FileResource.Data, isExternalFile: Bool) {
|
||||
self.init(
|
||||
content: content,
|
||||
id: id,
|
||||
isExternallyStored: isExternalFile,
|
||||
english: data.englishDescription,
|
||||
german: data.germanDescription,
|
||||
version: data.version,
|
||||
sourceUrl: data.sourceUrl,
|
||||
generatedImageVersions: Set(data.generatedImages ?? []),
|
||||
customOutputPath: data.customOutputPath,
|
||||
addedDate: data.addedDate,
|
||||
modifiedDate: data.modifiedDate)
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
englishDescription: english.nonEmpty,
|
||||
germanDescription: german.nonEmpty,
|
||||
englishDescription: english,
|
||||
germanDescription: german,
|
||||
generatedImages: generatedImageVersions.sorted().nonEmpty,
|
||||
customOutputPath: customOutputPath,
|
||||
version: version,
|
||||
@ -312,15 +328,16 @@ extension FileResource {
|
||||
addedDate: addedDate,
|
||||
modifiedDate: modifiedDate)
|
||||
}
|
||||
}
|
||||
|
||||
extension FileResource: LocalizedItem {
|
||||
|
||||
}
|
||||
|
||||
extension FileResource: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
id
|
||||
/// This struct holds metadata about a file resource that is stored in the content folder.
|
||||
struct Data: Codable {
|
||||
let englishDescription: String?
|
||||
let germanDescription: String?
|
||||
let generatedImages: [String]?
|
||||
let customOutputPath: String?
|
||||
let version: String?
|
||||
let sourceUrl: String?
|
||||
let addedDate: Date
|
||||
let modifiedDate: Date
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +39,20 @@ class Item: ObservableObject, Identifiable {
|
||||
var itemType: ItemType {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var itemReference: ItemReference {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var itemId: ItemId {
|
||||
.init(type: itemType, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
extension Item: Equatable {
|
||||
|
||||
static func == (lhs: Item, rhs: Item) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
lhs.id == rhs.id && lhs.itemType == rhs.itemType
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,12 +60,13 @@ extension Item: Hashable {
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
hasher.combine(itemType)
|
||||
}
|
||||
}
|
||||
|
||||
extension Item: Comparable {
|
||||
|
||||
static func < (lhs: Item, rhs: Item) -> Bool {
|
||||
lhs.id < rhs.id
|
||||
lhs.id < rhs.id && lhs.itemType < rhs.itemType
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +1,11 @@
|
||||
|
||||
struct ItemId {
|
||||
|
||||
let language: ContentLanguage
|
||||
let type: ItemType
|
||||
|
||||
let itemType: ItemType
|
||||
let id: String?
|
||||
}
|
||||
|
||||
extension ItemId: Equatable {
|
||||
|
||||
static func == (lhs: ItemId, rhs: ItemId) -> Bool {
|
||||
lhs.language == rhs.language &&
|
||||
lhs.itemType == rhs.itemType
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemId: Hashable {
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(language)
|
||||
hasher.combine(itemType.id)
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemId: Comparable {
|
||||
extension ItemId: Codable {
|
||||
|
||||
static func < (lhs: ItemId, rhs: ItemId) -> Bool {
|
||||
guard lhs.itemType == rhs.itemType else {
|
||||
return lhs.itemType < rhs.itemType
|
||||
}
|
||||
return lhs.language < rhs.language
|
||||
}
|
||||
}
|
||||
|
68
CHDataManagement/Model/Item/ItemReference.swift
Normal file
68
CHDataManagement/Model/Item/ItemReference.swift
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
enum ItemReference {
|
||||
|
||||
case general
|
||||
|
||||
case post(Post)
|
||||
|
||||
case feed
|
||||
|
||||
case page(Page)
|
||||
|
||||
case tagPage(Tag)
|
||||
|
||||
case tagOverview
|
||||
}
|
||||
|
||||
extension ItemReference: Equatable {
|
||||
|
||||
}
|
||||
|
||||
extension ItemReference: Hashable {
|
||||
|
||||
}
|
||||
|
||||
extension ItemReference: Identifiable {
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .general:
|
||||
return "0-general"
|
||||
case .feed:
|
||||
return "1-feed"
|
||||
case .post(let post):
|
||||
return "2-post-\(post.id)"
|
||||
case .page(let page):
|
||||
return "3-page-\(page.id)"
|
||||
case .tagPage(let tag):
|
||||
return "5-tag-\(tag.id)"
|
||||
case .tagOverview:
|
||||
return "4-tag-overview"
|
||||
}
|
||||
}
|
||||
|
||||
init?(context: LoadingContext, rawValue: String) {
|
||||
if rawValue == "0-general" {
|
||||
self = .general
|
||||
} else if rawValue == "1-feed" {
|
||||
self = .feed
|
||||
} else if rawValue == "4-tag-overview" {
|
||||
self = .tagOverview
|
||||
} else if let id = rawValue.removingPrefix("3-page-"), let page = context.page(id) {
|
||||
self = .page(page)
|
||||
} else if let id = rawValue.removingPrefix("2-post-"), let post = context.post(id) {
|
||||
self = .post(post)
|
||||
} else if let id = rawValue.removingPrefix("5-tag-"), let tag = context.tag(id) {
|
||||
self = .tagPage(tag)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ItemReference: Comparable {
|
||||
|
||||
static func < (lhs: ItemReference, rhs: ItemReference) -> Bool {
|
||||
lhs.id < rhs.id
|
||||
}
|
||||
}
|
@ -1,76 +1,21 @@
|
||||
|
||||
enum ItemType {
|
||||
enum ItemType: String, Equatable, Hashable {
|
||||
|
||||
case general
|
||||
case post = "post"
|
||||
|
||||
case post(Post)
|
||||
case page = "page"
|
||||
|
||||
case feed
|
||||
case tag = "tag"
|
||||
|
||||
case page(Page)
|
||||
|
||||
case tagPage(Tag)
|
||||
|
||||
case tagOverview
|
||||
}
|
||||
|
||||
extension ItemType: Equatable {
|
||||
|
||||
}
|
||||
|
||||
extension ItemType: Hashable {
|
||||
|
||||
}
|
||||
|
||||
extension ItemType: Identifiable {
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .general:
|
||||
return "0-general"
|
||||
case .feed:
|
||||
return "1-feed"
|
||||
case .post(let post):
|
||||
return "2-post-\(post.id)"
|
||||
case .page(let page):
|
||||
return "3-page-\(page.id)"
|
||||
case .tagPage(let tag):
|
||||
return "5-tag-\(tag.id)"
|
||||
case .tagOverview:
|
||||
return "4-tag-overview"
|
||||
}
|
||||
}
|
||||
|
||||
init?(rawValue: String, posts: [String : Post], pages: [String : Page], tags: [String : Tag]) {
|
||||
if rawValue == "0-general" {
|
||||
self = .general
|
||||
} else if rawValue == "1-feed" {
|
||||
self = .feed
|
||||
} else if rawValue == "4-tag-overview" {
|
||||
self = .tagOverview
|
||||
} else if let id = rawValue.removingPrefix("3-page-"), let page = pages[id] {
|
||||
self = .page(page)
|
||||
} else if let id = rawValue.removingPrefix("2-post-"), let post = posts[id] {
|
||||
self = .post(post)
|
||||
} else if let id = rawValue.removingPrefix("5-tag-"), let tag = tags[id] {
|
||||
self = .tagPage(tag)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case tagOverview = "tag-overview"
|
||||
}
|
||||
|
||||
extension ItemType: Comparable {
|
||||
|
||||
static func < (lhs: ItemType, rhs: ItemType) -> Bool {
|
||||
lhs.id < rhs.id
|
||||
public static func < (lhs: ItemType, rhs: ItemType) -> Bool {
|
||||
lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
func removingPrefix(_ prefix: String) -> String? {
|
||||
guard self.hasPrefix(prefix) else { return nil }
|
||||
return String(self.dropFirst(prefix.count))
|
||||
}
|
||||
extension ItemType: Codable {
|
||||
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
|
||||
protocol LinkPreviewItem: AnyObject {
|
||||
|
||||
var linkPreviewImage: FileResource? { get set }
|
||||
|
||||
var linkPreviewTitle: String? { get }
|
||||
|
||||
var linkPreviewDescription: String? { get }
|
||||
}
|
||||
|
||||
extension LinkPreviewItem {
|
||||
|
||||
func remove(linkPreviewImage file: FileResource) {
|
||||
if linkPreviewImage == file {
|
||||
linkPreviewImage = nil
|
||||
}
|
||||
}
|
||||
}
|
33
CHDataManagement/Model/Item/LocalizedItemId.swift
Normal file
33
CHDataManagement/Model/Item/LocalizedItemId.swift
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
struct LocalizedItemId {
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
let itemType: ItemReference
|
||||
}
|
||||
|
||||
extension LocalizedItemId: Equatable {
|
||||
|
||||
static func == (lhs: LocalizedItemId, rhs: LocalizedItemId) -> Bool {
|
||||
lhs.language == rhs.language &&
|
||||
lhs.itemType == rhs.itemType
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedItemId: Hashable {
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(language)
|
||||
hasher.combine(itemType.id)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedItemId: Comparable {
|
||||
|
||||
static func < (lhs: LocalizedItemId, rhs: LocalizedItemId) -> Bool {
|
||||
guard lhs.itemType == rhs.itemType else {
|
||||
return lhs.itemType < rhs.itemType
|
||||
}
|
||||
return lhs.language < rhs.language
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
/*
|
||||
final class TagOverviewPage: Item {
|
||||
|
||||
static let id = "all-tags"
|
||||
@ -105,3 +105,4 @@ final class LocalizedTagOverviewPage: ObservableObject {
|
||||
!content.containsTag(withUrlComponent: urlComponent)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
58
CHDataManagement/Model/LinkPreview.swift
Normal file
58
CHDataManagement/Model/LinkPreview.swift
Normal file
@ -0,0 +1,58 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
The information to use when constructing the link preview of a page.
|
||||
|
||||
The information will be placed in the `<head>` of the page as `<meta>` tags.
|
||||
*/
|
||||
final class LinkPreview: ObservableObject {
|
||||
|
||||
/// The description to show when linking to a page (contained in the `<head>` of the page)
|
||||
@Published
|
||||
var title: String?
|
||||
|
||||
/// The image id of the thumbnail to attach to the link preview (contained in the `<head>` of the page)
|
||||
@Published
|
||||
var description: String?
|
||||
|
||||
/// The title to show for a link preview (contained in the `<head>` of the page)
|
||||
@Published
|
||||
var image: FileResource?
|
||||
|
||||
init(title: String? = nil, description: String? = nil, image: FileResource? = nil) {
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.image = image
|
||||
}
|
||||
|
||||
/**
|
||||
Remove a file if it is used in the link preview.
|
||||
*/
|
||||
func remove(_ file: FileResource) {
|
||||
if image == file {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
var data: Data {
|
||||
.init(title: title, description: description, image: image?.id)
|
||||
}
|
||||
|
||||
init(context: LoadingContext, data: Data) {
|
||||
self.title = data.title
|
||||
self.description = data.description
|
||||
self.image = data.image.map(context.image)
|
||||
}
|
||||
}
|
||||
|
||||
extension LinkPreview {
|
||||
|
||||
/// The object to serialize a link preview for storage
|
||||
struct Data: Codable {
|
||||
let title: String?
|
||||
let description: String?
|
||||
let image: String?
|
||||
}
|
||||
}
|
110
CHDataManagement/Model/Loading/LoadingContext.swift
Normal file
110
CHDataManagement/Model/Loading/LoadingContext.swift
Normal file
@ -0,0 +1,110 @@
|
||||
|
||||
final class LoadingContext {
|
||||
|
||||
let content: Content
|
||||
|
||||
var files: [String: FileResource] = [:]
|
||||
|
||||
var pages: [String : Page] = [:]
|
||||
|
||||
var tags: [String : Tag] = [:]
|
||||
|
||||
var posts: [String : Post] = [:]
|
||||
|
||||
var errors: Set<String> = []
|
||||
|
||||
var tagOverview: TagOverview?
|
||||
|
||||
var settings: Settings?
|
||||
|
||||
init(content: Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
func results() -> LoadingResult {
|
||||
.init(
|
||||
settings: settings ?? .default,
|
||||
posts: posts.values.sorted(ascending: false) { $0.startDate },
|
||||
pages: pages.values.sorted(ascending: false) { $0.startDate },
|
||||
tags: tags.values.sorted(),
|
||||
files: files.values.sorted { $0.id },
|
||||
tagOverview: tagOverview,
|
||||
errors: errors.sorted())
|
||||
}
|
||||
|
||||
func error(_ message: String) {
|
||||
errors.insert(message)
|
||||
}
|
||||
|
||||
func post(_ postId: String) -> Post? {
|
||||
if let post = posts[postId] {
|
||||
return post
|
||||
}
|
||||
error("Missing post \(postId)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func tag(_ tagId: String) -> Tag? {
|
||||
if let tag = tags[tagId] {
|
||||
return tag
|
||||
}
|
||||
error("Missing tag \(tagId)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func page(_ pageId: String) -> Page? {
|
||||
if let page = pages[pageId] {
|
||||
return page
|
||||
}
|
||||
error("Missing page \(pageId)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func file(_ fileId: String) -> FileResource? {
|
||||
if let file = files[fileId] {
|
||||
return file
|
||||
}
|
||||
error("Missing file \(fileId)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func image(_ imageId: String) -> FileResource? {
|
||||
guard let image = file(imageId) else {
|
||||
return nil
|
||||
}
|
||||
if image.type.isImage {
|
||||
return image
|
||||
}
|
||||
error("Image \(imageId) is not an image")
|
||||
return nil
|
||||
}
|
||||
|
||||
func item(itemId: ItemId) -> Item? {
|
||||
switch itemId.type {
|
||||
case .post:
|
||||
guard let id = itemId.id else {
|
||||
error("Missing post id in itemId")
|
||||
return nil
|
||||
}
|
||||
return post(id)
|
||||
case .page:
|
||||
guard let id = itemId.id else {
|
||||
error("Missing page id in itemId")
|
||||
return nil
|
||||
}
|
||||
return page(id)
|
||||
case .tag:
|
||||
guard let id = itemId.id else {
|
||||
error("Missing tag id in itemId")
|
||||
return nil
|
||||
}
|
||||
return tag(id)
|
||||
case .tagOverview:
|
||||
guard let tagOverview else {
|
||||
error("Missing tag overview")
|
||||
return nil
|
||||
}
|
||||
return tagOverview
|
||||
}
|
||||
}
|
||||
}
|
17
CHDataManagement/Model/Loading/LoadingResult.swift
Normal file
17
CHDataManagement/Model/Loading/LoadingResult.swift
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
struct LoadingResult {
|
||||
|
||||
let settings: Settings
|
||||
|
||||
let posts: [Post]
|
||||
|
||||
let pages: [Page]
|
||||
|
||||
let tags: [Tag]
|
||||
|
||||
let files: [FileResource]
|
||||
|
||||
let tagOverview: Tag?
|
||||
|
||||
let errors: [String]
|
||||
}
|
96
CHDataManagement/Model/Loading/ModelLoader.swift
Normal file
96
CHDataManagement/Model/Loading/ModelLoader.swift
Normal file
@ -0,0 +1,96 @@
|
||||
|
||||
final class ModelLoader {
|
||||
|
||||
let content: Content
|
||||
|
||||
let storage: Storage
|
||||
|
||||
let context: LoadingContext
|
||||
|
||||
init(content: Content, storage: Storage) {
|
||||
self.content = content
|
||||
self.storage = storage
|
||||
self.context = .init(content: content)
|
||||
}
|
||||
|
||||
func load() -> LoadingResult {
|
||||
loadInternal()
|
||||
return context.results()
|
||||
}
|
||||
|
||||
private func loadInternal() {
|
||||
guard storage.contentScope != nil else {
|
||||
context.error("Storage not initialized, not loading content")
|
||||
return
|
||||
}
|
||||
|
||||
loadFiles()
|
||||
loadTags()
|
||||
loadPages()
|
||||
loadPosts()
|
||||
loadSettings()
|
||||
}
|
||||
|
||||
private func loadFiles() {
|
||||
guard let files = storage.loadAllFiles() else {
|
||||
context.error("Failed to load file list")
|
||||
return
|
||||
}
|
||||
if files.isEmpty { print("No files loaded") }
|
||||
|
||||
files.forEach { (fileId, data) in
|
||||
let fileData = data.data
|
||||
let isExternal = data.isExternal
|
||||
context.files[fileId] = FileResource(content: content, id: fileId, data: fileData, isExternalFile: isExternal)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadTags() {
|
||||
guard let tags = storage.loadAllTags() else {
|
||||
context.error("Failed to load file tags")
|
||||
return
|
||||
}
|
||||
if tags.isEmpty { print("No tags loaded") }
|
||||
|
||||
tags.forEach { (tagId, data) in
|
||||
context.tags[tagId] = Tag(context: context, id: tagId, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadPages() {
|
||||
guard let pages = storage.loadAllPages() else {
|
||||
context.error("Failed to load file pages")
|
||||
return
|
||||
}
|
||||
if pages.isEmpty { print("No pages loaded") }
|
||||
|
||||
pages.forEach { pageId, data in
|
||||
context.pages[pageId] = Page(context: context, id: pageId, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadPosts() {
|
||||
guard let posts = storage.loadAllPosts() else {
|
||||
context.error("Failed to load file posts")
|
||||
return
|
||||
}
|
||||
if posts.isEmpty { print("No posts loaded") }
|
||||
|
||||
posts.forEach { postId, data in
|
||||
context.posts[postId] = Post(context: context, id: postId, data: data)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadSettings() {
|
||||
guard let settings = storage.loadSettings() else {
|
||||
context.error("Failed to load settings")
|
||||
return
|
||||
}
|
||||
|
||||
context.tagOverview = settings.tagOverview.map { data in
|
||||
TagOverview(context: context, id: "all-tags", data: data)
|
||||
}
|
||||
|
||||
context.settings = Settings(context: context, data: settings)
|
||||
}
|
||||
}
|
@ -35,13 +35,7 @@ final class LocalizedPage: ObservableObject {
|
||||
let originalUrl: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewImage: FileResource?
|
||||
|
||||
@Published
|
||||
var linkPreviewTitle: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewDescription: String?
|
||||
var linkPreview: LinkPreview
|
||||
|
||||
@Published
|
||||
var hideTitle: Bool
|
||||
@ -51,18 +45,14 @@ final class LocalizedPage: ObservableObject {
|
||||
title: String,
|
||||
lastModified: Date? = nil,
|
||||
originalUrl: String? = nil,
|
||||
linkPreviewImage: FileResource? = nil,
|
||||
linkPreviewTitle: String? = nil,
|
||||
linkPreviewDescription: String? = nil,
|
||||
linkPreview: LinkPreview = .init(),
|
||||
hideTitle: Bool = false) {
|
||||
self.content = content
|
||||
self.urlString = urlString
|
||||
self.title = title
|
||||
self.lastModified = lastModified
|
||||
self.originalUrl = originalUrl
|
||||
self.linkPreviewImage = linkPreviewImage
|
||||
self.linkPreviewTitle = linkPreviewTitle
|
||||
self.linkPreviewDescription = linkPreviewDescription
|
||||
self.linkPreview = linkPreview
|
||||
self.hideTitle = hideTitle
|
||||
}
|
||||
|
||||
@ -72,6 +62,37 @@ final class LocalizedPage: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPage: LinkPreviewItem {
|
||||
|
||||
extension LocalizedPage {
|
||||
|
||||
convenience init(context: LoadingContext, data: LocalizedPage.Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
urlString: data.url,
|
||||
title: data.title,
|
||||
lastModified: data.lastModifiedDate,
|
||||
originalUrl: data.originalURL,
|
||||
linkPreview: .init(context: context, data: data.linkPreview),
|
||||
hideTitle: data.hideTitle ?? false)
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a localized page
|
||||
struct Data: Codable {
|
||||
let url: String
|
||||
let title: String
|
||||
let linkPreview: LinkPreview.Data
|
||||
let lastModifiedDate: Date?
|
||||
let originalURL: String?
|
||||
let hideTitle: Bool?
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
url: urlString,
|
||||
title: title,
|
||||
linkPreview: linkPreview.data,
|
||||
lastModifiedDate: lastModified,
|
||||
originalURL: originalUrl,
|
||||
hideTitle: hideTitle ? true : nil)
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,7 @@ final class LocalizedPost: ObservableObject {
|
||||
var pageLinkText: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewImage: FileResource?
|
||||
|
||||
@Published
|
||||
var linkPreviewTitle: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewDescription: String?
|
||||
var linkPreview: LinkPreview
|
||||
|
||||
init(content: Content,
|
||||
title: String? = nil,
|
||||
@ -36,41 +30,14 @@ final class LocalizedPost: ObservableObject {
|
||||
lastModified: Date? = nil,
|
||||
images: [FileResource] = [],
|
||||
pageLinkText: String? = nil,
|
||||
linkPreviewImage: FileResource? = nil,
|
||||
linkPreviewTitle: String? = nil,
|
||||
linkPreviewDescription: String? = nil) {
|
||||
linkPreview: LinkPreview = .init()) {
|
||||
self.content = content
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.lastModified = lastModified
|
||||
self.images = images
|
||||
self.pageLinkText = pageLinkText
|
||||
self.linkPreviewImage = linkPreviewImage
|
||||
self.linkPreviewTitle = linkPreviewTitle
|
||||
self.linkPreviewDescription = linkPreviewDescription
|
||||
}
|
||||
|
||||
init(content: Content, file: LocalizedPostFile, images: [String : FileResource]) {
|
||||
self.content = content
|
||||
self.title = file.title
|
||||
self.text = file.content
|
||||
self.lastModified = file.lastModifiedDate
|
||||
self.images = file.images.compactMap { images[$0] }
|
||||
self.pageLinkText = file.pageLinkText
|
||||
self.linkPreviewImage = file.linkPreviewImage.map { images[$0] }
|
||||
self.linkPreviewTitle = file.linkPreviewTitle
|
||||
self.linkPreviewDescription = file.linkPreviewDescription
|
||||
}
|
||||
|
||||
var postFile: LocalizedPostFile {
|
||||
.init(images: images.map { $0.id },
|
||||
title: title,
|
||||
content: text,
|
||||
lastModifiedDate: lastModified,
|
||||
pageLinkText: pageLinkText,
|
||||
linkPreviewImage: linkPreviewImage?.id,
|
||||
linkPreviewTitle: linkPreviewTitle,
|
||||
linkPreviewDescription: linkPreviewDescription)
|
||||
self.linkPreview = linkPreview
|
||||
}
|
||||
|
||||
func contains(_ string: String) -> Bool {
|
||||
@ -84,10 +51,41 @@ final class LocalizedPost: ObservableObject {
|
||||
if images.contains(file) {
|
||||
images.remove(file)
|
||||
}
|
||||
remove(linkPreviewImage: file)
|
||||
linkPreview.remove(file)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPost: LinkPreviewItem {
|
||||
// MARK: Storage
|
||||
|
||||
extension LocalizedPost {
|
||||
|
||||
convenience init(context: LoadingContext, data: Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
title: data.title,
|
||||
text: data.text,
|
||||
lastModified: data.lastModifiedDate,
|
||||
images: data.images.compactMap(context.image),
|
||||
pageLinkText: data.pageLinkText,
|
||||
linkPreview: .init(context: context, data: data.linkPreview))
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(images: images.map { $0.id },
|
||||
title: title,
|
||||
text: text,
|
||||
lastModifiedDate: lastModified,
|
||||
pageLinkText: pageLinkText,
|
||||
linkPreview: linkPreview.data)
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a localized post
|
||||
struct Data: Codable {
|
||||
let images: [String]
|
||||
let title: String?
|
||||
let text: String
|
||||
let lastModifiedDate: Date?
|
||||
let pageLinkText: String?
|
||||
let linkPreview: LinkPreview.Data
|
||||
}
|
||||
}
|
||||
|
@ -12,14 +12,7 @@ final class LocalizedTag: ObservableObject {
|
||||
var name: String
|
||||
|
||||
@Published
|
||||
var linkPreviewTitle: String?
|
||||
|
||||
@Published
|
||||
var linkPreviewDescription: String?
|
||||
|
||||
/// The image id of the thumbnail
|
||||
@Published
|
||||
var linkPreviewImage: FileResource?
|
||||
var linkPreview: LinkPreview
|
||||
|
||||
/// The original url in the previous site layout
|
||||
let originalUrl: String?
|
||||
@ -27,42 +20,51 @@ final class LocalizedTag: ObservableObject {
|
||||
init(content: Content,
|
||||
urlComponent: String,
|
||||
name: String,
|
||||
linkPreviewTitle: String? = nil,
|
||||
linkPreviewDescription: String? = nil,
|
||||
linkPreviewImage: FileResource? = nil,
|
||||
linkPreview: LinkPreview = .init(),
|
||||
originalUrl: String? = nil) {
|
||||
self.content = content
|
||||
self.urlComponent = urlComponent
|
||||
self.name = name
|
||||
self.linkPreviewTitle = linkPreviewTitle
|
||||
self.linkPreviewDescription = linkPreviewDescription
|
||||
self.linkPreviewImage = linkPreviewImage
|
||||
self.linkPreview = linkPreview
|
||||
self.originalUrl = originalUrl
|
||||
}
|
||||
|
||||
func isValid(urlComponent: String) -> Bool {
|
||||
!urlComponent.isEmpty &&
|
||||
content.isValidIdForTagOrPageOrPost(urlComponent) &&
|
||||
!content.containsTag(withUrlComponent: urlComponent)
|
||||
}
|
||||
|
||||
/// The title to display when considering multiple items of this tag
|
||||
var title: String {
|
||||
linkPreviewTitle ?? name
|
||||
linkPreview.title ?? name
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedTag: LinkPreviewItem {
|
||||
|
||||
}
|
||||
// MARK: Storage
|
||||
|
||||
extension LocalizedTag {
|
||||
|
||||
var tagFile: LocalizedTagFile {
|
||||
convenience init(context: LoadingContext, data: Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
urlComponent: data.urlComponent,
|
||||
name: data.name,
|
||||
linkPreview: .init(context: context, data: data.linkPreview),
|
||||
originalUrl: data.originalUrl)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let urlComponent: String
|
||||
let name: String
|
||||
let linkPreview: LinkPreview.Data
|
||||
let originalUrl: String?
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(urlComponent: urlComponent,
|
||||
name: name,
|
||||
linkPreviewTitle: linkPreviewTitle,
|
||||
linkPreviewDescription: linkPreviewDescription,
|
||||
linkPreviewImage: linkPreviewImage?.id,
|
||||
originalURL: originalUrl)
|
||||
linkPreview: linkPreview.data,
|
||||
originalUrl: originalUrl)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
final class Page: Item {
|
||||
final class Page: Item, DateItem, LocalizedItem {
|
||||
|
||||
override var itemType: ItemType { .page }
|
||||
|
||||
/**
|
||||
The external link this page points to.
|
||||
@ -38,9 +40,7 @@ final class Page: Item {
|
||||
@Published
|
||||
var tags: [Tag]
|
||||
|
||||
/**
|
||||
Additional files to copy, because the page content references them
|
||||
*/
|
||||
/// Additional files to copy, because the page content references them
|
||||
@Published
|
||||
var requiredFiles: [FileResource]
|
||||
|
||||
@ -141,7 +141,7 @@ final class Page: Item {
|
||||
content.settings.paths.pagesOutputFolderPath + "/" + localized(in: language).urlString
|
||||
}
|
||||
|
||||
override var itemType: ItemType {
|
||||
override var itemReference: ItemReference {
|
||||
.page(self)
|
||||
}
|
||||
|
||||
@ -161,15 +161,57 @@ final class Page: Item {
|
||||
if requiredFiles.contains(file) {
|
||||
requiredFiles.remove(file)
|
||||
}
|
||||
english.remove(linkPreviewImage: file)
|
||||
german.remove(linkPreviewImage: file)
|
||||
english.linkPreview.remove(file)
|
||||
german.linkPreview.remove(file)
|
||||
}
|
||||
}
|
||||
|
||||
extension Page: DateItem {
|
||||
// MARK: Storage
|
||||
|
||||
}
|
||||
extension Page {
|
||||
|
||||
extension Page: LocalizedItem {
|
||||
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
id: id,
|
||||
externalLink: data.externalLink,
|
||||
isDraft: data.isDraft,
|
||||
createdDate: data.createdDate,
|
||||
hideDate: data.hideDate ?? false,
|
||||
startDate: data.startDate,
|
||||
endDate: data.endDate,
|
||||
german: .init(context: context, data: data.german),
|
||||
english: .init(context: context, data: data.english),
|
||||
tags: data.tags.compactMap(context.tag),
|
||||
requiredFiles: data.requiredFiles?.compactMap(context.file) ?? [])
|
||||
}
|
||||
|
||||
/// The structure to store the metadata of a page on disk
|
||||
struct Data: Codable {
|
||||
let isDraft: Bool
|
||||
let externalLink: String?
|
||||
let tags: [String]
|
||||
let hideDate: Bool?
|
||||
let createdDate: Date
|
||||
let startDate: Date
|
||||
let endDate: Date?
|
||||
let german: LocalizedPage.Data
|
||||
let english: LocalizedPage.Data
|
||||
let requiredFiles: [String]?
|
||||
}
|
||||
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
isDraft: isDraft,
|
||||
externalLink: externalLink,
|
||||
tags: tags.map { $0.id },
|
||||
hideDate: hideDate ? true : nil,
|
||||
createdDate: createdDate,
|
||||
startDate: startDate,
|
||||
endDate: hasEndDate ? endDate : nil,
|
||||
german: german.data,
|
||||
english: english.data,
|
||||
requiredFiles: requiredFiles.nonEmpty?.map { $0.id }.sorted())
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
final class Post: Item {
|
||||
final class Post: Item, DateItem, LocalizedItem {
|
||||
|
||||
override var itemType: ItemType { .post }
|
||||
|
||||
@Published
|
||||
var isDraft: Bool
|
||||
@ -142,10 +144,42 @@ final class Post: Item {
|
||||
}
|
||||
}
|
||||
|
||||
extension Post: DateItem {
|
||||
|
||||
}
|
||||
|
||||
extension Post: LocalizedItem {
|
||||
extension Post {
|
||||
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
id: id,
|
||||
isDraft: data.isDraft,
|
||||
createdDate: data.createdDate,
|
||||
startDate: data.startDate,
|
||||
endDate: data.endDate,
|
||||
tags: data.tags.compactMap(context.tag),
|
||||
german: .init(context: context, data: data.german),
|
||||
english: .init(context: context, data: data.english),
|
||||
linkedPage: data.linkedPageId.map(context.page))
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let isDraft: Bool
|
||||
let createdDate: Date
|
||||
let startDate: Date
|
||||
let endDate: Date?
|
||||
let tags: [String]
|
||||
let german: LocalizedPost.Data
|
||||
let english: LocalizedPost.Data
|
||||
let linkedPageId: String?
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
isDraft: isDraft,
|
||||
createdDate: createdDate,
|
||||
startDate: startDate,
|
||||
endDate: hasEndDate ? endDate : nil,
|
||||
tags: tags.map { $0.id },
|
||||
german: german.data,
|
||||
english: english.data,
|
||||
linkedPageId: linkedPage?.id)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
final class AudioPlayerSettings: ObservableObject {
|
||||
final class AudioPlayerSettings: ObservableObject, LocalizedItem {
|
||||
|
||||
@Published
|
||||
var playlistCoverImageSize: Int
|
||||
@ -34,24 +34,6 @@ final class AudioPlayerSettings: ObservableObject {
|
||||
self.english = english
|
||||
}
|
||||
|
||||
init(file: AudioPlayerSettingsFile, files: [String : FileResource]) {
|
||||
self.playlistCoverImageSize = file.playlistCoverImageSize
|
||||
self.smallCoverImageSize = file.smallCoverImageSize
|
||||
self.audioPlayerJsFile = file.audioPlayerJsFile.map { files[$0] }
|
||||
self.audioPlayerCssFile = file.audioPlayerCssFile.map { files[$0] }
|
||||
self.german = .init(file: file.german)
|
||||
self.english = .init(file: file.english)
|
||||
}
|
||||
|
||||
var file: AudioPlayerSettingsFile {
|
||||
.init(playlistCoverImageSize: playlistCoverImageSize,
|
||||
smallCoverImageSize: smallCoverImageSize,
|
||||
audioPlayerJsFile: audioPlayerJsFile?.id,
|
||||
audioPlayerCssFile: audioPlayerCssFile?.id,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
if audioPlayerJsFile == file {
|
||||
audioPlayerJsFile = nil
|
||||
@ -62,17 +44,37 @@ final class AudioPlayerSettings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension AudioPlayerSettings {
|
||||
|
||||
static let `default`: AudioPlayerSettings = .init(
|
||||
playlistCoverImageSize: 280,
|
||||
smallCoverImageSize: 78,
|
||||
audioPlayerJsFile: nil,
|
||||
audioPlayerCssFile: nil,
|
||||
german: .init(playlistText: "Wiedergabeliste"),
|
||||
english: .init(playlistText: "Playlist"))
|
||||
}
|
||||
convenience init(context: LoadingContext, data: Data) {
|
||||
self.init(
|
||||
playlistCoverImageSize: data.playlistCoverImageSize,
|
||||
smallCoverImageSize: data.smallCoverImageSize,
|
||||
audioPlayerJsFile: data.audioPlayerJsFile.map(context.file),
|
||||
audioPlayerCssFile: data.audioPlayerCssFile.map(context.file),
|
||||
german: .init(data: data.german),
|
||||
english: .init(data: data.english))
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(playlistCoverImageSize: playlistCoverImageSize,
|
||||
smallCoverImageSize: smallCoverImageSize,
|
||||
audioPlayerJsFile: audioPlayerJsFile?.id,
|
||||
audioPlayerCssFile: audioPlayerCssFile?.id,
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let playlistCoverImageSize: Int
|
||||
let smallCoverImageSize: Int
|
||||
let audioPlayerJsFile: String?
|
||||
let audioPlayerCssFile: String?
|
||||
let german: LocalizedAudioPlayerSettings.Data
|
||||
let english: LocalizedAudioPlayerSettings.Data
|
||||
}
|
||||
|
||||
extension AudioPlayerSettings: LocalizedItem {
|
||||
|
||||
}
|
||||
|
@ -8,12 +8,21 @@ final class LocalizedAudioPlayerSettings: ObservableObject {
|
||||
init(playlistText: String) {
|
||||
self.playlistText = playlistText
|
||||
}
|
||||
}
|
||||
|
||||
init(file: LocalizedAudioPlayerSettingsFile) {
|
||||
self.playlistText = file.playlistText
|
||||
// MARK: Storage
|
||||
|
||||
extension LocalizedAudioPlayerSettings {
|
||||
|
||||
convenience init(data: Data) {
|
||||
self.init(playlistText: data.playlistText)
|
||||
}
|
||||
|
||||
var file: LocalizedAudioPlayerSettingsFile {
|
||||
var data: Data {
|
||||
.init(playlistText: playlistText)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let playlistText: String
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,21 @@ final class LocalizedNavigationSettings: ObservableObject {
|
||||
init(rootUrl: String) {
|
||||
self.rootUrl = rootUrl
|
||||
}
|
||||
}
|
||||
|
||||
init(file: LocalizedNavigationSettingsFile) {
|
||||
self.rootUrl = file.rootUrl
|
||||
// MARK: Storage
|
||||
|
||||
extension LocalizedNavigationSettings {
|
||||
|
||||
convenience init(data: Data) {
|
||||
self.init(rootUrl: data.rootUrl)
|
||||
}
|
||||
|
||||
var file: LocalizedNavigationSettingsFile {
|
||||
struct Data: Codable {
|
||||
let rootUrl: String
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(rootUrl: rootUrl)
|
||||
}
|
||||
}
|
||||
|
@ -14,14 +14,25 @@ final class LocalizedPageSettings: ObservableObject {
|
||||
self.emptyPageTitle = emptyPageTitle
|
||||
self.emptyPageText = emptyPageText
|
||||
}
|
||||
}
|
||||
|
||||
init(file: LocalizedPageSettingsFile) {
|
||||
self.emptyPageTitle = file.emptyPageTitle
|
||||
self.emptyPageText = file.emptyPageText
|
||||
// MARK: Storage
|
||||
|
||||
extension LocalizedPageSettings {
|
||||
|
||||
convenience init(data: Data) {
|
||||
self.init(
|
||||
emptyPageTitle: data.emptyPageTitle,
|
||||
emptyPageText: data.emptyPageText)
|
||||
}
|
||||
|
||||
var file: LocalizedPageSettingsFile {
|
||||
var data: Data {
|
||||
.init(emptyPageTitle: emptyPageTitle,
|
||||
emptyPageText: emptyPageText)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let emptyPageTitle: String
|
||||
let emptyPageText: String
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,23 @@ import Foundation
|
||||
|
||||
final class LocalizedPostSettings: ObservableObject {
|
||||
|
||||
/// The page title for the post feed
|
||||
@Published
|
||||
var title: String
|
||||
|
||||
/// The page description for the post feed
|
||||
@Published
|
||||
var description: String
|
||||
|
||||
/// The path to the feed in the final website, appended with the page number
|
||||
@Published
|
||||
var feedUrlPrefix: String
|
||||
|
||||
/**
|
||||
The text to display when linking to a page
|
||||
|
||||
Each post may define a custom text.
|
||||
*/
|
||||
@Published
|
||||
var defaultPageLinkText: String
|
||||
|
||||
@ -20,21 +28,32 @@ final class LocalizedPostSettings: ObservableObject {
|
||||
self.feedUrlPrefix = feedUrlPrefix
|
||||
self.defaultPageLinkText = defaultPageLinkText
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
// MARK: Storage
|
||||
|
||||
init(file: LocalizedPostSettingsFile) {
|
||||
self.title = file.feedTitle
|
||||
self.description = file.feedDescription
|
||||
self.feedUrlPrefix = file.feedUrlPrefix
|
||||
self.defaultPageLinkText = file.defaultPageLinkText ?? "View"
|
||||
extension LocalizedPostSettings {
|
||||
|
||||
convenience init(data: Data) {
|
||||
self.init(
|
||||
title: data.feedTitle,
|
||||
description: data.feedDescription,
|
||||
feedUrlPrefix: data.feedUrlPrefix,
|
||||
defaultPageLinkText: data.defaultPageLinkText)
|
||||
}
|
||||
|
||||
var file: LocalizedPostSettingsFile {
|
||||
var data: Data {
|
||||
.init(
|
||||
feedTitle: title,
|
||||
feedDescription: description,
|
||||
feedUrlPrefix: feedUrlPrefix,
|
||||
defaultPageLinkText: defaultPageLinkText)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let feedTitle: String
|
||||
let feedDescription: String
|
||||
let feedUrlPrefix: String
|
||||
let defaultPageLinkText: String
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
final class NavigationSettings: ObservableObject {
|
||||
final class NavigationSettings: ObservableObject, LocalizedItem {
|
||||
|
||||
/// The items to show in the navigation bar
|
||||
@Published
|
||||
@ -19,23 +19,31 @@ final class NavigationSettings: ObservableObject {
|
||||
self.german = german
|
||||
self.english = english
|
||||
}
|
||||
|
||||
init(file: NavigationSettingsFile, map: (String) -> Item?) {
|
||||
self.navigationItems = file.navigationItems.compactMap(map)
|
||||
self.german = LocalizedNavigationSettings(file: file.german)
|
||||
self.english = LocalizedNavigationSettings(file: file.english)
|
||||
}
|
||||
|
||||
var file: NavigationSettingsFile {
|
||||
.init(
|
||||
navigationItems: navigationItems.map { $0.itemType.id },
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigationSettings: LocalizedItem {
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension NavigationSettings {
|
||||
|
||||
convenience init(context: LoadingContext, data: NavigationSettings.Data) {
|
||||
self.init(
|
||||
navigationItems: data.navigationItems.compactMap(context.item),
|
||||
german: LocalizedNavigationSettings(data: data.german),
|
||||
english: LocalizedNavigationSettings(data: data.english))
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let navigationItems: [ItemId]
|
||||
let german: LocalizedNavigationSettings.Data
|
||||
let english: LocalizedNavigationSettings.Data
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
navigationItems: navigationItems.map { $0.itemId },
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
}
|
||||
|
||||
extension NavigationSettings {
|
||||
|
@ -32,30 +32,26 @@ final class PageSettings: ObservableObject {
|
||||
@Published
|
||||
var english: LocalizedPageSettings
|
||||
|
||||
init(file: PageSettingsFile, files: [String : FileResource]) {
|
||||
self.contentWidth = file.contentWidth
|
||||
self.largeImageWidth = file.largeImageWidth
|
||||
self.pageLinkImageSize = file.pageLinkImageSize
|
||||
self.defaultCssFile = file.defaultCssFile.map { files[$0] }
|
||||
self.codeHighlightingJsFile = file.codeHighlightingJsFile.map { files[$0] }
|
||||
self.modelViewerJsFile = file.modelViewerJsFile.map { files[$0] }
|
||||
self.imageCompareCssFile = file.imageCompareCssFile.map { files[$0] }
|
||||
self.imageCompareJsFile = file.imageCompareJsFile.map { files[$0] }
|
||||
self.german = .init(file: file.german)
|
||||
self.english = .init(file: file.english)
|
||||
}
|
||||
|
||||
var file: PageSettingsFile {
|
||||
.init(contentWidth: contentWidth,
|
||||
largeImageWidth: largeImageWidth,
|
||||
pageLinkImageSize: pageLinkImageSize,
|
||||
defaultCssFile: defaultCssFile?.id,
|
||||
codeHighlightingJsFile: codeHighlightingJsFile?.id,
|
||||
modelViewerJsFile: modelViewerJsFile?.id,
|
||||
imageCompareJsFile: imageCompareJsFile?.id,
|
||||
imageCompareCssFile: imageCompareCssFile?.id,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
init(contentWidth: Int,
|
||||
largeImageWidth: Int,
|
||||
pageLinkImageSize: Int,
|
||||
defaultCssFile: FileResource? = nil,
|
||||
codeHighlightingJsFile: FileResource? = nil,
|
||||
modelViewerJsFile: FileResource? = nil,
|
||||
imageCompareJsFile: FileResource? = nil,
|
||||
imageCompareCssFile: FileResource? = nil,
|
||||
german: LocalizedPageSettings,
|
||||
english: LocalizedPageSettings) {
|
||||
self.contentWidth = contentWidth
|
||||
self.largeImageWidth = largeImageWidth
|
||||
self.pageLinkImageSize = pageLinkImageSize
|
||||
self.defaultCssFile = defaultCssFile
|
||||
self.codeHighlightingJsFile = codeHighlightingJsFile
|
||||
self.modelViewerJsFile = modelViewerJsFile
|
||||
self.imageCompareJsFile = imageCompareJsFile
|
||||
self.imageCompareCssFile = imageCompareCssFile
|
||||
self.german = german
|
||||
self.english = english
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
@ -77,6 +73,52 @@ final class PageSettings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension PageSettings {
|
||||
|
||||
convenience init(context: LoadingContext, data: Data) {
|
||||
self.init(
|
||||
contentWidth: data.contentWidth,
|
||||
largeImageWidth: data.largeImageWidth,
|
||||
pageLinkImageSize: data.pageLinkImageSize,
|
||||
defaultCssFile: data.defaultCssFile.map(context.file),
|
||||
codeHighlightingJsFile: data.codeHighlightingJsFile.map(context.file),
|
||||
modelViewerJsFile: data.modelViewerJsFile.map(context.file),
|
||||
imageCompareJsFile: data.imageCompareJsFile.map(context.file),
|
||||
imageCompareCssFile: data.imageCompareCssFile.map(context.file),
|
||||
german: .init(data: data.german),
|
||||
english: .init(data: data.english))
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(contentWidth: contentWidth,
|
||||
largeImageWidth: largeImageWidth,
|
||||
pageLinkImageSize: pageLinkImageSize,
|
||||
defaultCssFile: defaultCssFile?.id,
|
||||
codeHighlightingJsFile: codeHighlightingJsFile?.id,
|
||||
modelViewerJsFile: modelViewerJsFile?.id,
|
||||
imageCompareJsFile: imageCompareJsFile?.id,
|
||||
imageCompareCssFile: imageCompareCssFile?.id,
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let contentWidth: Int
|
||||
let largeImageWidth: Int
|
||||
let pageLinkImageSize: Int
|
||||
let defaultCssFile: String?
|
||||
let codeHighlightingJsFile: String?
|
||||
let modelViewerJsFile: String?
|
||||
let imageCompareJsFile: String?
|
||||
let imageCompareCssFile: String?
|
||||
let german: LocalizedPageSettings.Data
|
||||
let english: LocalizedPageSettings.Data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PageSettings: LocalizedItem {
|
||||
|
||||
}
|
||||
|
@ -23,23 +23,54 @@ final class PathSettings: ObservableObject {
|
||||
@Published
|
||||
var tagsOutputFolderPath: String
|
||||
|
||||
init(file: PathSettingsFile) {
|
||||
self.assetsOutputFolderPath = file.assetsOutputFolderPath
|
||||
self.pagesOutputFolderPath = file.pagesOutputFolderPath
|
||||
self.imagesOutputFolderPath = file.imagesOutputFolderPath
|
||||
self.filesOutputFolderPath = file.filesOutputFolderPath
|
||||
self.videosOutputFolderPath = file.videosOutputFolderPath
|
||||
self.tagsOutputFolderPath = file.tagsOutputFolderPath
|
||||
self.audioOutputFolderPath = file.audioOutputFolderPath
|
||||
}
|
||||
|
||||
var file: PathSettingsFile {
|
||||
.init(assetsOutputFolderPath: assetsOutputFolderPath,
|
||||
pagesOutputFolderPath: pagesOutputFolderPath,
|
||||
imagesOutputFolderPath: imagesOutputFolderPath,
|
||||
filesOutputFolderPath: filesOutputFolderPath,
|
||||
videosOutputFolderPath: videosOutputFolderPath,
|
||||
tagsOutputFolderPath: tagsOutputFolderPath,
|
||||
audioOutputFolderPath: audioOutputFolderPath)
|
||||
init(assetsOutputFolderPath: String,
|
||||
pagesOutputFolderPath: String,
|
||||
imagesOutputFolderPath: String,
|
||||
filesOutputFolderPath: String,
|
||||
videosOutputFolderPath: String,
|
||||
audioOutputFolderPath: String,
|
||||
tagsOutputFolderPath: String) {
|
||||
self.assetsOutputFolderPath = assetsOutputFolderPath
|
||||
self.pagesOutputFolderPath = pagesOutputFolderPath
|
||||
self.imagesOutputFolderPath = imagesOutputFolderPath
|
||||
self.filesOutputFolderPath = filesOutputFolderPath
|
||||
self.videosOutputFolderPath = videosOutputFolderPath
|
||||
self.audioOutputFolderPath = audioOutputFolderPath
|
||||
self.tagsOutputFolderPath = tagsOutputFolderPath
|
||||
}
|
||||
}
|
||||
|
||||
extension PathSettings {
|
||||
|
||||
convenience init(data: Data) {
|
||||
self.init(
|
||||
assetsOutputFolderPath: data.assetsOutputFolderPath,
|
||||
pagesOutputFolderPath: data.pagesOutputFolderPath,
|
||||
imagesOutputFolderPath: data.imagesOutputFolderPath,
|
||||
filesOutputFolderPath: data.filesOutputFolderPath,
|
||||
videosOutputFolderPath: data.videosOutputFolderPath,
|
||||
audioOutputFolderPath: data.audioOutputFolderPath,
|
||||
tagsOutputFolderPath: data.tagsOutputFolderPath)
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
assetsOutputFolderPath: assetsOutputFolderPath,
|
||||
pagesOutputFolderPath: pagesOutputFolderPath,
|
||||
imagesOutputFolderPath: imagesOutputFolderPath,
|
||||
filesOutputFolderPath: filesOutputFolderPath,
|
||||
videosOutputFolderPath: videosOutputFolderPath,
|
||||
audioOutputFolderPath: audioOutputFolderPath,
|
||||
tagsOutputFolderPath: tagsOutputFolderPath)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let assetsOutputFolderPath: String
|
||||
let pagesOutputFolderPath: String
|
||||
let imagesOutputFolderPath: String
|
||||
let filesOutputFolderPath: String
|
||||
let videosOutputFolderPath: String
|
||||
let audioOutputFolderPath: String
|
||||
let tagsOutputFolderPath: String
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
final class PostSettings: ObservableObject {
|
||||
final class PostSettings: ObservableObject, LocalizedItem {
|
||||
|
||||
/// The number of posts to show in a single page of the news feed
|
||||
@Published
|
||||
@ -41,28 +41,6 @@ final class PostSettings: ObservableObject {
|
||||
self.english = english
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
init(file: PostSettingsFile, files: [String : FileResource]) {
|
||||
self.postsPerPage = file.postsPerPage
|
||||
self.contentWidth = file.contentWidth
|
||||
self.swiperCssFile = file.swiperCssFile.map { files[$0] }
|
||||
self.swiperJsFile = file.swiperJsFile.map { files[$0] }
|
||||
self.defaultCssFile = file.defaultCssFile.map { files[$0] }
|
||||
self.german = .init(file: file.german)
|
||||
self.english = .init(file: file.english)
|
||||
}
|
||||
|
||||
var file: PostSettingsFile {
|
||||
.init(postsPerPage: postsPerPage,
|
||||
contentWidth: contentWidth,
|
||||
swiperCssFile: swiperCssFile?.id,
|
||||
swiperJsFile: swiperJsFile?.id,
|
||||
defaultCssFile: defaultCssFile?.id,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
if swiperJsFile == file {
|
||||
swiperJsFile = nil
|
||||
@ -76,13 +54,38 @@ final class PostSettings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension PostSettings {
|
||||
|
||||
static var `default`: PostSettings {
|
||||
.init(file: .default, files: [:])
|
||||
convenience init(context: LoadingContext, data: Data) {
|
||||
self.init(
|
||||
postsPerPage: data.postsPerPage,
|
||||
contentWidth: data.contentWidth,
|
||||
swiperCssFile: data.swiperCssFile.map(context.file),
|
||||
swiperJsFile: data.swiperJsFile.map(context.file),
|
||||
defaultCssFile: data.defaultCssFile.map(context.file),
|
||||
german: .init(data: data.german),
|
||||
english: .init(data: data.english))
|
||||
}
|
||||
|
||||
var data: PostSettings.Data {
|
||||
.init(postsPerPage: postsPerPage,
|
||||
contentWidth: contentWidth,
|
||||
swiperCssFile: swiperCssFile?.id,
|
||||
swiperJsFile: swiperJsFile?.id,
|
||||
defaultCssFile: defaultCssFile?.id,
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let postsPerPage: Int
|
||||
let contentWidth: Int
|
||||
let swiperCssFile: String?
|
||||
let swiperJsFile: String?
|
||||
let defaultCssFile: String?
|
||||
let german: LocalizedPostSettings.Data
|
||||
let english: LocalizedPostSettings.Data
|
||||
}
|
||||
}
|
||||
|
||||
extension PostSettings: LocalizedItem {
|
||||
|
||||
}
|
||||
|
@ -30,25 +30,6 @@ final class Settings: ObservableObject {
|
||||
self.audioPlayer = audioPlayer
|
||||
}
|
||||
|
||||
init(file: SettingsFile, files: [String : FileResource], map: (String) -> Item?) {
|
||||
self.navigation = NavigationSettings(file: file.navigation, map: map)
|
||||
|
||||
self.posts = PostSettings(file: file.posts, files: files)
|
||||
self.pages = PageSettings(file: file.pages, files: files)
|
||||
self.paths = PathSettings(file: file.paths)
|
||||
self.audioPlayer = .init(file: file.audioPlayer, files: files)
|
||||
}
|
||||
|
||||
func file(tagOverview: TagOverviewPage?) -> SettingsFile {
|
||||
.init(
|
||||
paths: paths.file,
|
||||
navigation: navigation.file,
|
||||
posts: posts.file,
|
||||
pages: pages.file,
|
||||
audioPlayer: audioPlayer.file,
|
||||
tagOverview: tagOverview?.file)
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
pages.remove(file)
|
||||
posts.remove(file)
|
||||
@ -56,6 +37,39 @@ final class Settings: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Storage
|
||||
|
||||
extension Settings {
|
||||
|
||||
convenience init(context: LoadingContext, data: Settings.Data) {
|
||||
self.init(
|
||||
paths: .init(data: data.paths),
|
||||
navigation: .init(context: context, data: data.navigation),
|
||||
posts: .init(context: context, data: data.posts),
|
||||
pages: .init(context: context, data: data.pages),
|
||||
audioPlayer: .init(context: context, data: data.audioPlayer))
|
||||
}
|
||||
|
||||
func data(tagOverview: Tag?) -> Data {
|
||||
.init(
|
||||
paths: paths.data,
|
||||
navigation: navigation.data,
|
||||
posts: posts.data,
|
||||
pages: pages.data,
|
||||
audioPlayer: audioPlayer.data,
|
||||
tagOverview: tagOverview?.data)
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
let paths: PathSettings.Data
|
||||
let navigation: NavigationSettings.Data
|
||||
let posts: PostSettings.Data
|
||||
let pages: PageSettings.Data
|
||||
let audioPlayer: AudioPlayerSettings.Data
|
||||
let tagOverview: Tag.Data?
|
||||
}
|
||||
}
|
||||
|
||||
extension Settings {
|
||||
|
||||
static let `default`: Settings = .init(
|
||||
@ -65,3 +79,70 @@ extension Settings {
|
||||
pages: .default,
|
||||
audioPlayer: .default)
|
||||
}
|
||||
|
||||
extension AudioPlayerSettings {
|
||||
|
||||
static let `default`: AudioPlayerSettings = .init(
|
||||
playlistCoverImageSize: 280,
|
||||
smallCoverImageSize: 78,
|
||||
audioPlayerJsFile: nil,
|
||||
audioPlayerCssFile: nil,
|
||||
german: .init(playlistText: "Wiedergabeliste"),
|
||||
english: .init(playlistText: "Playlist"))
|
||||
}
|
||||
|
||||
extension PostSettings {
|
||||
|
||||
static var `default`: PostSettings {
|
||||
.init(postsPerPage: 25,
|
||||
contentWidth: 600,
|
||||
swiperCssFile: nil,
|
||||
swiperJsFile: nil,
|
||||
defaultCssFile: nil,
|
||||
german: .init(
|
||||
title: "Beiträge",
|
||||
description: "Alle Beiträge",
|
||||
feedUrlPrefix: "blog",
|
||||
defaultPageLinkText: "Anzeigen"),
|
||||
english: .init(
|
||||
title: "Blog posts",
|
||||
description: "All blog posts",
|
||||
feedUrlPrefix: "blog",
|
||||
defaultPageLinkText: "View"))
|
||||
}
|
||||
}
|
||||
|
||||
extension PathSettings {
|
||||
|
||||
static var `default`: PathSettings {
|
||||
.init(
|
||||
assetsOutputFolderPath: "asset",
|
||||
pagesOutputFolderPath: "page",
|
||||
imagesOutputFolderPath: "image",
|
||||
filesOutputFolderPath: "file",
|
||||
videosOutputFolderPath: "video",
|
||||
audioOutputFolderPath: "audio",
|
||||
tagsOutputFolderPath: "tag")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PageSettings {
|
||||
|
||||
static var `default`: PageSettings {
|
||||
.init(contentWidth: 600,
|
||||
largeImageWidth: 1200,
|
||||
pageLinkImageSize: 180,
|
||||
defaultCssFile: nil,
|
||||
codeHighlightingJsFile: nil,
|
||||
modelViewerJsFile: nil,
|
||||
imageCompareJsFile: nil,
|
||||
imageCompareCssFile: nil,
|
||||
german: .init(
|
||||
emptyPageTitle: "Leere Seite",
|
||||
emptyPageText: "Diese Seite ist leer"),
|
||||
english: .init(
|
||||
emptyPageTitle: "Empty page",
|
||||
emptyPageText: "This page is empty"))
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
final class Tag: Item {
|
||||
class Tag: Item, LocalizedItem {
|
||||
|
||||
override var itemType: ItemType { .tag }
|
||||
|
||||
@Published
|
||||
var isVisible: Bool
|
||||
@ -59,7 +61,7 @@ final class Tag: Item {
|
||||
localized(in: language).title
|
||||
}
|
||||
|
||||
override var itemType: ItemType {
|
||||
override var itemReference: ItemReference {
|
||||
.tagPage(self)
|
||||
}
|
||||
|
||||
@ -68,21 +70,35 @@ final class Tag: Item {
|
||||
}
|
||||
|
||||
func remove(_ file: FileResource) {
|
||||
english.remove(linkPreviewImage: file)
|
||||
german.remove(linkPreviewImage: file)
|
||||
english.linkPreview.remove(file)
|
||||
german.linkPreview.remove(file)
|
||||
}
|
||||
}
|
||||
|
||||
extension Tag: LocalizedItem {
|
||||
|
||||
}
|
||||
// MARK: Storage
|
||||
|
||||
extension Tag {
|
||||
|
||||
var file: TagFile {
|
||||
.init(id: id,
|
||||
isVisible: isVisible,
|
||||
german: german.tagFile,
|
||||
english: english.tagFile)
|
||||
convenience init(context: LoadingContext, id: String, data: Data) {
|
||||
self.init(
|
||||
content: context.content,
|
||||
id: id,
|
||||
isVisible: data.isVisible ?? true,
|
||||
german: .init(context: context, data: data.german),
|
||||
english: .init(context: context, data: data.english))
|
||||
}
|
||||
|
||||
struct Data: Codable {
|
||||
// Defaults to true if unset
|
||||
let isVisible: Bool?
|
||||
let german: LocalizedTag.Data
|
||||
let english: LocalizedTag.Data
|
||||
}
|
||||
|
||||
var data: Data {
|
||||
.init(
|
||||
isVisible: isVisible ? nil : false,
|
||||
german: german.data,
|
||||
english: english.data)
|
||||
}
|
||||
}
|
||||
|
7
CHDataManagement/Model/TagOverview.swift
Normal file
7
CHDataManagement/Model/TagOverview.swift
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
final class TagOverview: Tag {
|
||||
|
||||
override var itemId: ItemId {
|
||||
.init(type: .tagOverview, id: id)
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ struct PartialSvgImage: HtmlProducer {
|
||||
|
||||
let imagePath: String
|
||||
|
||||
let altText: String
|
||||
let altText: String?
|
||||
|
||||
let x: Int
|
||||
|
||||
@ -20,9 +20,16 @@ struct PartialSvgImage: HtmlProducer {
|
||||
return Double(width) / Double(height)
|
||||
}
|
||||
|
||||
private var imageAltText: String {
|
||||
guard let altText else {
|
||||
return ""
|
||||
}
|
||||
return " alt='\(altText.htmlEscaped())'"
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
result += "<span class='content-image svg-image'>"
|
||||
result += "<img src='\(imagePath)#svgView(viewBox(\(x), \(y), \(width), \(height)))' loading='lazy' style='aspect-ratio:\(aspectRatio)' alt='\(altText)'/>"
|
||||
result += "<img src='\(imagePath)#svgView(viewBox(\(x), \(y), \(width), \(height)))' loading='lazy' style='aspect-ratio:\(aspectRatio)'\(imageAltText)/>"
|
||||
result += "</span>"
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,18 @@ struct SimpleImage: HtmlProducer {
|
||||
|
||||
let imagePath: String
|
||||
|
||||
let altText: String
|
||||
let altText: String?
|
||||
|
||||
private var imageAltText: String {
|
||||
guard let altText else {
|
||||
return ""
|
||||
}
|
||||
return " alt='\(altText.htmlEscaped())'"
|
||||
}
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
result += "<div class='content-image svg-image'>"
|
||||
result += "<img src='\(imagePath)' loading='lazy' alt='\(altText)'/>"
|
||||
result += "<img src='\(imagePath)' loading='lazy'\(imageAltText)/>"
|
||||
result += "</div>"
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,16 @@ struct ModelViewer {
|
||||
|
||||
let file: String
|
||||
|
||||
let description: String
|
||||
let description: String?
|
||||
|
||||
private var imageAltText: String {
|
||||
guard let description else {
|
||||
return ""
|
||||
}
|
||||
return " alt='\(description.htmlEscaped())'"
|
||||
}
|
||||
|
||||
var content: String {
|
||||
"<model-viewer alt='\(description)' src='\(file)' ar shadow-intensity='1' camera-controls touch-action='pan-y'></model-viewer>"
|
||||
"<model-viewer\(imageAltText) src='\(file)' ar shadow-intensity='1' camera-controls touch-action='pan-y'></model-viewer>"
|
||||
}
|
||||
}
|
||||
|
@ -43,15 +43,15 @@ extension LocalizedTag {
|
||||
content: .mock,
|
||||
urlComponent: "electronics",
|
||||
name: "Electronics",
|
||||
linkPreviewDescription: "Some description of the tag",
|
||||
linkPreviewImage: FileResource(resourceImage: "image1", type: .jpg),
|
||||
linkPreview: .init(description: "Some description of the tag",
|
||||
image: FileResource(resourceImage: "image1", type: .jpg)),
|
||||
originalUrl: "projects/electronics")
|
||||
|
||||
static let german = LocalizedTag(
|
||||
content: .mock,
|
||||
urlComponent: "elektronik",
|
||||
name: "Elektronik",
|
||||
linkPreviewDescription: "Eine Beschreibung des Tags",
|
||||
linkPreviewImage: FileResource(resourceImage: "image2", type: .jpg),
|
||||
linkPreview: .init(description: "Eine Beschreibung des Tags",
|
||||
image: FileResource(resourceImage: "image2", type: .jpg)),
|
||||
originalUrl: "projects/electronics")
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
extension PathSettings {
|
||||
|
||||
static var `default`: PathSettings {
|
||||
.init(file: .default)
|
||||
}
|
||||
}
|
||||
|
||||
extension PageSettings {
|
||||
|
||||
static var `default`: PageSettings {
|
||||
.init(file: .default, files: [:])
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPostSettings {
|
||||
|
||||
static var german: LocalizedPostSettings {
|
||||
.init(
|
||||
title: "Titel",
|
||||
description: "Beschreibung",
|
||||
feedUrlPrefix: "blog",
|
||||
defaultPageLinkText: "Anzeigen")
|
||||
}
|
||||
|
||||
static var english: LocalizedPostSettings {
|
||||
.init(
|
||||
title: "A Title",
|
||||
description: "Description",
|
||||
feedUrlPrefix: "feed",
|
||||
defaultPageLinkText: "View")
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
|
||||
struct FileDescriptions {
|
||||
|
||||
let fileId: String
|
||||
|
||||
let german: String?
|
||||
|
||||
let english: String?
|
||||
}
|
||||
|
||||
extension FileDescriptions: Codable {
|
||||
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
This struct holds metadata about a file resource that is stored in the content folder.
|
||||
*/
|
||||
struct FileResourceFile {
|
||||
|
||||
/// The file/image description in German
|
||||
let englishDescription: String?
|
||||
|
||||
/// The file/image description in English
|
||||
let germanDescription: String?
|
||||
|
||||
/// The list of generated image versions for this image
|
||||
let generatedImages: [String]?
|
||||
|
||||
/// A custom file path in the output folder where this file is located
|
||||
let customOutputPath: String?
|
||||
|
||||
/// A version string of this resource, mostly for assets
|
||||
let version: String?
|
||||
|
||||
/// A URL where the resource was copied/downloaded from
|
||||
let sourceUrl: String?
|
||||
|
||||
/// The date when the file was added
|
||||
let addedDate: Date
|
||||
|
||||
/// The date when the file was last modified
|
||||
let modifiedDate: Date
|
||||
}
|
||||
|
||||
|
||||
extension FileResourceFile: Codable {
|
||||
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct PageFile {
|
||||
|
||||
let isDraft: Bool
|
||||
|
||||
let externalLink: String?
|
||||
|
||||
let tags: [String]
|
||||
|
||||
let hideDate: Bool?
|
||||
|
||||
let createdDate: Date
|
||||
|
||||
let startDate: Date
|
||||
|
||||
let endDate: Date?
|
||||
|
||||
let german: LocalizedPageFile
|
||||
|
||||
let english: LocalizedPageFile
|
||||
|
||||
/**
|
||||
Specifies additional files which should be copied to the destination when generating the content.
|
||||
- Note: This property defaults to an empty set.
|
||||
*/
|
||||
let requiredFiles: [String]?
|
||||
}
|
||||
|
||||
extension PageFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The structure to store the metadata of a localized page
|
||||
*/
|
||||
struct LocalizedPageFile {
|
||||
|
||||
let url: String
|
||||
|
||||
let title: String
|
||||
|
||||
let linkPreviewImage: String?
|
||||
|
||||
let linkPreviewTitle: String?
|
||||
|
||||
let linkPreviewDescription: String?
|
||||
|
||||
let lastModifiedDate: Date?
|
||||
|
||||
let originalURL: String?
|
||||
|
||||
let hideTitle: Bool?
|
||||
}
|
||||
|
||||
extension LocalizedPageFile: Codable {
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct PostFile {
|
||||
|
||||
let isDraft: Bool
|
||||
|
||||
let createdDate: Date
|
||||
|
||||
let startDate: Date
|
||||
|
||||
let endDate: Date?
|
||||
|
||||
let tags: [String]
|
||||
|
||||
let german: LocalizedPostFile
|
||||
|
||||
let english: LocalizedPostFile
|
||||
|
||||
let linkedPageId: String?
|
||||
}
|
||||
|
||||
extension PostFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The structure to store the metadata of a localized post
|
||||
*/
|
||||
struct LocalizedPostFile {
|
||||
|
||||
let images: [String]
|
||||
|
||||
let title: String?
|
||||
|
||||
let content: String
|
||||
|
||||
let lastModifiedDate: Date?
|
||||
|
||||
let pageLinkText: String?
|
||||
|
||||
let linkPreviewImage: String?
|
||||
|
||||
let linkPreviewTitle: String?
|
||||
|
||||
let linkPreviewDescription: String?
|
||||
}
|
||||
|
||||
extension LocalizedPostFile: Codable {
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
|
||||
struct AudioPlayerSettingsFile: Codable {
|
||||
|
||||
let playlistCoverImageSize: Int
|
||||
|
||||
let smallCoverImageSize: Int
|
||||
|
||||
let audioPlayerJsFile: String?
|
||||
|
||||
let audioPlayerCssFile: String?
|
||||
|
||||
let german: LocalizedAudioPlayerSettingsFile
|
||||
|
||||
let english: LocalizedAudioPlayerSettingsFile
|
||||
}
|
||||
|
||||
struct LocalizedAudioPlayerSettingsFile: Codable {
|
||||
|
||||
let playlistText: String
|
||||
}
|
||||
|
||||
extension AudioPlayerSettingsFile: LocalizedItem {
|
||||
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct LocalizedNavigationSettingsFile {
|
||||
|
||||
let rootUrl: String
|
||||
}
|
||||
|
||||
extension LocalizedNavigationSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension LocalizedNavigationSettingsFile {
|
||||
|
||||
static var `default`: LocalizedNavigationSettingsFile {
|
||||
.init(rootUrl: "/")
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
|
||||
struct LocalizedPageSettingsFile {
|
||||
|
||||
let emptyPageTitle: String
|
||||
|
||||
let emptyPageText: String
|
||||
|
||||
init(emptyPageTitle: String, emptyPageText: String) {
|
||||
self.emptyPageTitle = emptyPageTitle
|
||||
self.emptyPageText = emptyPageText
|
||||
}
|
||||
}
|
||||
|
||||
extension LocalizedPageSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension LocalizedPageSettingsFile {
|
||||
|
||||
static var `default`: LocalizedPageSettingsFile {
|
||||
.init(emptyPageTitle: "Empty Page", emptyPageText: "This page is empty.")
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
|
||||
struct LocalizedPostSettingsFile {
|
||||
|
||||
/// The page title for the post feed
|
||||
let feedTitle: String
|
||||
|
||||
/// The page description for the post feed
|
||||
let feedDescription: String
|
||||
|
||||
/// The path to the feed in the final website, appended with the page number
|
||||
let feedUrlPrefix: String
|
||||
|
||||
/**
|
||||
The text to display when linking to a page
|
||||
|
||||
Each post may define a custom text.
|
||||
*/
|
||||
let defaultPageLinkText: String?
|
||||
}
|
||||
|
||||
extension LocalizedPostSettingsFile: Codable { }
|
||||
|
||||
extension LocalizedPostSettingsFile {
|
||||
|
||||
static var `default`: LocalizedPostSettingsFile {
|
||||
.init(feedTitle: "A title",
|
||||
feedDescription: "A description",
|
||||
feedUrlPrefix: "blog",
|
||||
defaultPageLinkText: "View")
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct NavigationSettingsFile {
|
||||
|
||||
/// The tags to show in the navigation bar
|
||||
let navigationItems: [String]
|
||||
|
||||
let german: LocalizedNavigationSettingsFile
|
||||
|
||||
let english: LocalizedNavigationSettingsFile
|
||||
}
|
||||
|
||||
extension NavigationSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension NavigationSettingsFile {
|
||||
|
||||
static var `default`: NavigationSettingsFile {
|
||||
.init(
|
||||
navigationItems: [],
|
||||
german: .default,
|
||||
english: .default)
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
|
||||
struct PageSettingsFile {
|
||||
|
||||
let contentWidth: Int
|
||||
|
||||
let largeImageWidth: Int
|
||||
|
||||
let pageLinkImageSize: Int
|
||||
|
||||
let defaultCssFile: String?
|
||||
|
||||
let codeHighlightingJsFile: String?
|
||||
|
||||
let modelViewerJsFile: String?
|
||||
|
||||
let imageCompareJsFile: String?
|
||||
|
||||
let imageCompareCssFile: String?
|
||||
|
||||
let german: LocalizedPageSettingsFile
|
||||
|
||||
let english: LocalizedPageSettingsFile
|
||||
}
|
||||
|
||||
extension PageSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension PageSettingsFile {
|
||||
|
||||
static var `default`: PageSettingsFile {
|
||||
.init(contentWidth: 600,
|
||||
largeImageWidth: 1200,
|
||||
pageLinkImageSize: 180,
|
||||
defaultCssFile: nil,
|
||||
codeHighlightingJsFile: nil,
|
||||
modelViewerJsFile: nil,
|
||||
imageCompareJsFile: nil,
|
||||
imageCompareCssFile: nil,
|
||||
german: .default,
|
||||
english: .default)
|
||||
}
|
||||
}
|
||||
|
||||
extension PageSettingsFile: LocalizedItem {
|
||||
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
|
||||
struct PathSettingsFile {
|
||||
|
||||
let assetsOutputFolderPath: String
|
||||
|
||||
let pagesOutputFolderPath: String
|
||||
|
||||
let imagesOutputFolderPath: String
|
||||
|
||||
let filesOutputFolderPath: String
|
||||
|
||||
let videosOutputFolderPath: String
|
||||
|
||||
let tagsOutputFolderPath: String
|
||||
|
||||
let audioOutputFolderPath: String
|
||||
|
||||
init(assetsOutputFolderPath: String,
|
||||
pagesOutputFolderPath: String,
|
||||
imagesOutputFolderPath: String,
|
||||
filesOutputFolderPath: String,
|
||||
videosOutputFolderPath: String,
|
||||
tagsOutputFolderPath: String,
|
||||
audioOutputFolderPath: String) {
|
||||
self.assetsOutputFolderPath = assetsOutputFolderPath
|
||||
self.pagesOutputFolderPath = pagesOutputFolderPath
|
||||
self.imagesOutputFolderPath = imagesOutputFolderPath
|
||||
self.filesOutputFolderPath = filesOutputFolderPath
|
||||
self.videosOutputFolderPath = videosOutputFolderPath
|
||||
self.tagsOutputFolderPath = tagsOutputFolderPath
|
||||
self.audioOutputFolderPath = audioOutputFolderPath
|
||||
}
|
||||
}
|
||||
|
||||
extension PathSettingsFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
extension PathSettingsFile {
|
||||
|
||||
static var `default`: PathSettingsFile {
|
||||
PathSettingsFile(
|
||||
assetsOutputFolderPath: "asset",
|
||||
pagesOutputFolderPath: "page",
|
||||
imagesOutputFolderPath: "image",
|
||||
filesOutputFolderPath: "file",
|
||||
videosOutputFolderPath: "video",
|
||||
tagsOutputFolderPath: "tag",
|
||||
audioOutputFolderPath: "audio")
|
||||
}
|
||||
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct PostSettingsFile {
|
||||
|
||||
/// The number of posts to show in a single page of the news feed
|
||||
let postsPerPage: Int
|
||||
|
||||
/// The maximum width of the main content
|
||||
let contentWidth: Int
|
||||
|
||||
let swiperCssFile: String?
|
||||
|
||||
let swiperJsFile: String?
|
||||
|
||||
let defaultCssFile: String?
|
||||
|
||||
let german: LocalizedPostSettingsFile
|
||||
|
||||
let english: LocalizedPostSettingsFile
|
||||
}
|
||||
|
||||
extension PostSettingsFile: Codable { }
|
||||
|
||||
extension PostSettingsFile {
|
||||
|
||||
static var `default`: PostSettingsFile {
|
||||
.init(postsPerPage: 25,
|
||||
contentWidth: 600,
|
||||
swiperCssFile: nil,
|
||||
swiperJsFile: nil,
|
||||
defaultCssFile: nil,
|
||||
german: .default,
|
||||
english: .default)
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct SettingsFile {
|
||||
|
||||
let paths: PathSettingsFile
|
||||
|
||||
/// The tags to show in the navigation bar
|
||||
let navigation: NavigationSettingsFile
|
||||
|
||||
let posts: PostSettingsFile
|
||||
|
||||
let pages: PageSettingsFile
|
||||
|
||||
let audioPlayer: AudioPlayerSettingsFile
|
||||
|
||||
let tagOverview: TagOverviewFile?
|
||||
}
|
||||
|
||||
extension SettingsFile: Codable { }
|
||||
|
||||
extension SettingsFile {
|
||||
|
||||
static var `default`: SettingsFile {
|
||||
.init(
|
||||
paths: .default,
|
||||
navigation: .default,
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
audioPlayer: AudioPlayerSettings.default.file,
|
||||
tagOverview: nil
|
||||
)
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
|
||||
struct TagOverviewFile {
|
||||
|
||||
let german: LocalizedTagOverviewFile
|
||||
|
||||
let english: LocalizedTagOverviewFile
|
||||
}
|
||||
|
||||
extension TagOverviewFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
The structure to store the metadata of a localized page
|
||||
*/
|
||||
struct LocalizedTagOverviewFile {
|
||||
|
||||
let url: String
|
||||
|
||||
let title: String
|
||||
|
||||
let linkPreviewImage: String?
|
||||
|
||||
let linkPreviewTitle: String?
|
||||
|
||||
let linkPreviewDescription: String?
|
||||
}
|
||||
|
||||
extension LocalizedTagOverviewFile: Codable {
|
||||
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct TagFile {
|
||||
|
||||
let id: String
|
||||
|
||||
let isVisible: Bool
|
||||
|
||||
let german: LocalizedTagFile
|
||||
|
||||
let english: LocalizedTagFile
|
||||
|
||||
}
|
||||
|
||||
extension TagFile: Codable {
|
||||
|
||||
}
|
||||
|
||||
struct LocalizedTagFile {
|
||||
|
||||
/// The id of the tag, used also as a url component
|
||||
let urlComponent: String
|
||||
|
||||
/// A custom name, different from the tag id
|
||||
let name: String
|
||||
|
||||
let linkPreviewTitle: String?
|
||||
|
||||
let linkPreviewDescription: String?
|
||||
|
||||
/// The image id of the thumbnail
|
||||
let linkPreviewImage: String?
|
||||
|
||||
/// The original url in the previous site layout
|
||||
let originalURL: String?
|
||||
|
||||
}
|
||||
|
||||
extension LocalizedTagFile: Codable {
|
||||
|
||||
}
|
@ -312,6 +312,7 @@ struct SecurityBookmark {
|
||||
do {
|
||||
data = try Data(contentsOf: url)
|
||||
} catch {
|
||||
#warning("Get these errors")
|
||||
print("Storage: Failed to read file \(url.path()): \(error)")
|
||||
return
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ final class Storage: ObservableObject {
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
#warning("Rework to make this non-optional by creating a wrapper class")
|
||||
@Published
|
||||
var contentScope: SecurityBookmark?
|
||||
|
||||
@ -72,13 +73,13 @@ final class Storage: ObservableObject {
|
||||
return contentScope.write(pageContent, to: path)
|
||||
}
|
||||
|
||||
func save(pageMetadata: PageFile, for pageId: String) -> Bool {
|
||||
func save(pageMetadata: Page.Data, for pageId: String) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
let path = pageMetadataPath(page: pageId)
|
||||
return contentScope.encode(pageMetadata, to: path)
|
||||
}
|
||||
|
||||
func loadAllPages() -> [String : PageFile]? {
|
||||
func loadAllPages() -> [String : Page.Data]? {
|
||||
contentScope?.decodeJsonFiles(in: pagesFolderName)
|
||||
}
|
||||
|
||||
@ -144,13 +145,13 @@ final class Storage: ObservableObject {
|
||||
postsFolderName + "/" + postFileName(postId)
|
||||
}
|
||||
|
||||
func save(post: PostFile, for postId: String) -> Bool {
|
||||
func save(post: Post.Data, for postId: String) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
let path = postFilePath(post: postId)
|
||||
return contentScope.encode(post, to: path)
|
||||
}
|
||||
|
||||
func loadAllPosts() -> [String : PostFile]? {
|
||||
func loadAllPosts() -> [String : Post.Data]? {
|
||||
contentScope?.decodeJsonFiles(in: postsFolderName)
|
||||
}
|
||||
|
||||
@ -183,13 +184,13 @@ final class Storage: ObservableObject {
|
||||
tagsFolderName + "/" + tagFileName(tagId: tagId)
|
||||
}
|
||||
|
||||
func save(tagMetadata: TagFile, for tagId: String) -> Bool {
|
||||
func save(tagMetadata: Tag.Data, for tagId: String) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
let path = tagFilePath(tag: tagId)
|
||||
return contentScope.encode(tagMetadata, to: path)
|
||||
}
|
||||
|
||||
func loadAllTags() -> [String : TagFile]? {
|
||||
func loadAllTags() -> [String : Tag.Data]? {
|
||||
contentScope?.decodeJsonFiles(in: tagsFolderName)
|
||||
}
|
||||
|
||||
@ -311,9 +312,9 @@ final class Storage: ObservableObject {
|
||||
|
||||
- Returns: A dictionary with the file ids as keys and the metadata file as a value.
|
||||
*/
|
||||
func loadAllFiles() -> [String : (data: FileResourceFile, isExternal: Bool)]? {
|
||||
func loadAllFiles() -> [String : (data: FileResource.Data, isExternal: Bool)]? {
|
||||
guard let contentScope else { return nil }
|
||||
guard let list: [String : FileResourceFile] = contentScope.decodeJsonFiles(in: fileInfoFolderName) else {
|
||||
guard let list: [String : FileResource.Data] = contentScope.decodeJsonFiles(in: fileInfoFolderName) else {
|
||||
return nil
|
||||
}
|
||||
guard let existingFiles = contentScope.fileNames(inRelativeFolder: filesFolderName).map(Set.init) else {
|
||||
@ -326,7 +327,7 @@ final class Storage: ObservableObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func save(fileInfo: FileResourceFile, for fileId: String) -> Bool {
|
||||
func save(fileInfo: FileResource.Data, for fileId: String) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
let path = fileInfoPath(file: fileId)
|
||||
return contentScope.encode(fileInfo, to: path)
|
||||
@ -359,12 +360,12 @@ final class Storage: ObservableObject {
|
||||
|
||||
// MARK: Settings
|
||||
|
||||
func loadSettings() -> SettingsFile? {
|
||||
func loadSettings() -> Settings.Data? {
|
||||
guard let contentScope else { return nil }
|
||||
return contentScope.decode(at: settingsDataFileName)
|
||||
}
|
||||
|
||||
func save(settings: SettingsFile) -> Bool {
|
||||
func save(settings: Settings.Data) -> Bool {
|
||||
guard let contentScope else { return false }
|
||||
return contentScope.encode(settings, to: settingsDataFileName)
|
||||
}
|
||||
|
@ -73,12 +73,12 @@ struct FileDetailView: View {
|
||||
|
||||
switch language {
|
||||
case .english:
|
||||
StringPropertyView(
|
||||
OptionalStringPropertyView(
|
||||
title: "Description",
|
||||
text: $file.english,
|
||||
footer: "The description for the file. Descriptions are used for images and to explain the content of a file.")
|
||||
case .german:
|
||||
StringPropertyView(
|
||||
OptionalStringPropertyView(
|
||||
title: "Description",
|
||||
text: $file.german,
|
||||
footer: "The description for the file. Descriptions are used for images and to explain the content of a file.")
|
||||
@ -151,8 +151,7 @@ struct FileDetailView: View {
|
||||
file.determineFileSize()
|
||||
// Force regeneration of images and/or file copying
|
||||
file.removeFileFromOutputFolder()
|
||||
// Trigger content view update to reload image
|
||||
file.didChange()
|
||||
file.modifiedDate = .now
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ struct ItemSelectionView: View {
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
if !selectedItems.contains(where: { $0 is TagOverviewPage }) {
|
||||
if !selectedItems.contains(where: { $0.itemReference == tagOverview.itemReference }) {
|
||||
selectedItems.append(tagOverview)
|
||||
}
|
||||
}
|
||||
|
29
CHDataManagement/Views/LinkPreviewDetailView.swift
Normal file
29
CHDataManagement/Views/LinkPreviewDetailView.swift
Normal file
@ -0,0 +1,29 @@
|
||||
import SwiftUI
|
||||
|
||||
struct LinkPreviewDetailView: View {
|
||||
|
||||
@ObservedObject
|
||||
var linkPreview: LinkPreview
|
||||
|
||||
let fallbackTitle: String?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
OptionalStringPropertyView(
|
||||
title: "Preview Title",
|
||||
text: $linkPreview.title,
|
||||
prompt: fallbackTitle,
|
||||
footer: "The title to use in a link preview")
|
||||
|
||||
OptionalImagePropertyView(
|
||||
title: "Preview Image",
|
||||
selectedImage: $linkPreview.image,
|
||||
footer: "The image to show in a link preview")
|
||||
|
||||
OptionalTextFieldPropertyView(
|
||||
title: "Preview Description",
|
||||
text: $linkPreview.description,
|
||||
footer: "The description to show in a link preview")
|
||||
}
|
||||
}
|
||||
}
|
@ -30,21 +30,7 @@ struct LocalizedPageDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Preview Title",
|
||||
text: $page.linkPreviewTitle,
|
||||
prompt: page.title,
|
||||
footer: "The title to use for the page when linking to it")
|
||||
|
||||
OptionalImagePropertyView(
|
||||
title: "Preview Image",
|
||||
selectedImage: $page.linkPreviewImage,
|
||||
footer: "The image to show for previews of this page")
|
||||
|
||||
OptionalTextFieldPropertyView(
|
||||
title: "Preview Description",
|
||||
text: $page.linkPreviewDescription,
|
||||
footer: "The description to show in previews of the page")
|
||||
LinkPreviewDetailView(linkPreview: page.linkPreview, fallbackTitle: page.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,21 +12,7 @@ struct LocalizedPostDetailView: View {
|
||||
text: $post.pageLinkText,
|
||||
footer: "The custom text to show for the link to the linked page")
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Preview Title",
|
||||
text: $post.linkPreviewTitle,
|
||||
prompt: post.title,
|
||||
footer: "The title to use for the post when linking to it")
|
||||
|
||||
OptionalImagePropertyView(
|
||||
title: "Preview Image",
|
||||
selectedImage: $post.linkPreviewImage,
|
||||
footer: "The image to show for previews of this post")
|
||||
|
||||
OptionalTextFieldPropertyView(
|
||||
title: "Preview Description",
|
||||
text: $post.linkPreviewDescription,
|
||||
footer: "The description to show in previews of the post")
|
||||
LinkPreviewDetailView(linkPreview: post.linkPreview, fallbackTitle: post.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ struct LocalizedPostFeedSettingsView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
LocalizedPostFeedSettingsView(settings: .english)
|
||||
LocalizedPostFeedSettingsView(settings: PostSettings.default.english)
|
||||
.padding()
|
||||
}
|
||||
|
@ -8,6 +8,12 @@ struct PathSettingsView: View {
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@State
|
||||
private var showLoadErrorSheet: Bool = false
|
||||
|
||||
@State
|
||||
private var loadErrors: [String] = []
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
@ -19,7 +25,7 @@ struct PathSettingsView: View {
|
||||
title: "Content Folder",
|
||||
folder: $content.storage.contentScope,
|
||||
footer: "The folder where the raw content of the website is stored") { url in
|
||||
content.update(contentPath: url)
|
||||
content.update(contentPath: url, callback: showLoadErrors)
|
||||
}
|
||||
|
||||
FolderOnDiskPropertyView(
|
||||
@ -65,8 +71,32 @@ struct PathSettingsView: View {
|
||||
footer: "The path in the output folder where assets are stored")
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $showLoadErrorSheet) {
|
||||
VStack {
|
||||
Text("Failed to load database")
|
||||
.font(.headline)
|
||||
List(loadErrors, id: \.self) { error in
|
||||
HStack {
|
||||
Text(error)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 200)
|
||||
Button("Dismiss", action: { showLoadErrorSheet = false })
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func showLoadErrors(errors: [String]) {
|
||||
guard !errors.isEmpty else {
|
||||
return
|
||||
}
|
||||
loadErrors = errors
|
||||
showLoadErrorSheet = true
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
@ -15,8 +15,8 @@ struct TagOverviewDetailView: View {
|
||||
title: "Tag Overview",
|
||||
text: "Configure the page showing all tags")
|
||||
|
||||
if let page = content.tagOverview?.localized(in: language) {
|
||||
TagOverviewDetails(page: page)
|
||||
if let tag = content.tagOverview?.localized(in: language) {
|
||||
LocalizedTagDetailView(tag: tag)
|
||||
.id(language)
|
||||
} else {
|
||||
Button("Create", action: createTagOverviewPage)
|
||||
@ -27,50 +27,10 @@ struct TagOverviewDetailView: View {
|
||||
}
|
||||
|
||||
private func createTagOverviewPage() {
|
||||
content.tagOverview = TagOverviewPage(
|
||||
content.tagOverview = Tag(
|
||||
content: content,
|
||||
german: .init(content: content, title: "Alle Tags", urlString: "alle"),
|
||||
english: .init(content: content, title: "All tags", urlString: "all"))
|
||||
}
|
||||
}
|
||||
|
||||
private struct TagOverviewDetails: View {
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
@ObservedObject
|
||||
var page: LocalizedTagOverviewPage
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
StringPropertyView(
|
||||
title: "Title",
|
||||
text: $page.title,
|
||||
footer: "The title of the overview page")
|
||||
|
||||
IdPropertyView(
|
||||
id: $page.urlComponent,
|
||||
title: "Page URL String",
|
||||
footer: "The url component to use for the link to the page",
|
||||
validation: page.isValid,
|
||||
update: { page.urlComponent = $0 })
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Preview Title",
|
||||
text: $page.linkPreviewTitle,
|
||||
prompt: page.title,
|
||||
footer: "The title to use for the page when linking to it")
|
||||
|
||||
OptionalImagePropertyView(
|
||||
title: "Preview Image",
|
||||
selectedImage: $page.linkPreviewImage,
|
||||
footer: "The image to show for previews of this page")
|
||||
|
||||
OptionalTextFieldPropertyView(
|
||||
title: "Preview Description",
|
||||
text: $page.linkPreviewDescription,
|
||||
footer: "The description to show in previews of the page")
|
||||
}
|
||||
id: "all-tags",
|
||||
german: .init(content: content, urlComponent: "alle", name: "Alle Tags"),
|
||||
english: .init(content: content, urlComponent: "all", name: "All tags"))
|
||||
}
|
||||
}
|
||||
|
@ -31,21 +31,7 @@ struct LocalizedTagDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
OptionalStringPropertyView(
|
||||
title: "Preview Title",
|
||||
text: $tag.linkPreviewTitle,
|
||||
prompt: tag.name,
|
||||
footer: "The title to use for the tag in previews and on tag pages")
|
||||
|
||||
OptionalImagePropertyView(
|
||||
title: "Preview Image",
|
||||
selectedImage: $tag.linkPreviewImage,
|
||||
footer: "The image to show for previews of this page")
|
||||
|
||||
OptionalTextFieldPropertyView(
|
||||
title: "Preview Description",
|
||||
text: $tag.linkPreviewDescription,
|
||||
footer: "The description to show in previews of the page")
|
||||
LinkPreviewDetailView(linkPreview: tag.linkPreview, fallbackTitle: tag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user