diff --git a/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (iOS).xcscheme b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (iOS).xcscheme new file mode 100644 index 000000000..dcf3aaaa4 --- /dev/null +++ b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (iOS).xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS) 1.xcscheme b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS) 1.xcscheme new file mode 100644 index 000000000..7d134d118 --- /dev/null +++ b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS) 1.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS).xcscheme b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS).xcscheme new file mode 100644 index 000000000..e67aac480 --- /dev/null +++ b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/SafariExtension (macOS).xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension-Mac.xcscheme b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension-Mac.xcscheme new file mode 100644 index 000000000..fa3255a83 --- /dev/null +++ b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension-Mac.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme new file mode 100644 index 000000000..81c98b04c --- /dev/null +++ b/apple/Omnivore.xcodeproj/xcshareddata/xcschemes/ShareExtension.xcscheme @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/App/Views/OperationToast.swift b/apple/OmnivoreKit/Sources/App/Views/OperationToast.swift new file mode 100644 index 000000000..c0d8845ea --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/OperationToast.swift @@ -0,0 +1,46 @@ + +import SwiftUI + +enum OperationStatus { + case none + case isPerforming + case success + case failure +} + +struct OperationToast: View { + @Binding var operationMessage: String? + @Binding var showOperationToast: Bool + @Binding var operationStatus: OperationStatus + + var body: some View { + VStack { + HStack { + if operationStatus == .isPerforming { + Text(operationMessage ?? "Performing...") + Spacer() + ProgressView() + } else if operationStatus == .success { + Text(operationMessage ?? "Success") + Spacer() + } else if operationStatus == .failure { + Text(operationMessage ?? "Failure") + Spacer() + Button(action: { showOperationToast = false }, label: { + Text("Done").bold() + }) + } + } + .padding(10) + .frame(minHeight: 50) + .frame(maxWidth: 380) + .background(Color(hex: "2A2A2A")) + .foregroundColor(Color(hex: "EBEBEB")) + .cornerRadius(4.0) + .tint(Color.green) + } + .padding(.bottom, 60) + .padding(.horizontal, 10) + .ignoresSafeArea(.all, edges: .bottom) + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ClubsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ClubsView.swift new file mode 100644 index 000000000..a36567585 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ClubsView.swift @@ -0,0 +1,183 @@ +#if os(iOS) + import Models + import Services + import SwiftUI + import Views + + @MainActor final class RecommendationsGroupsViewModel: ObservableObject { + @Published var isLoading = false + @Published var isCreating = false + @Published var networkError = false + @Published var recommendationGroups = [InternalRecommendationGroup]() + + @Published var showCreateSheet = false + + @Published var showCreateError = false + @Published var createGroupError: String? + + @Published var onlyAdminCanPost = false + @Published var onlyAdminCanSeeMembers = false + + func loadGroups(dataService: DataService) async { + isLoading = true + + do { + recommendationGroups = try await dataService.recommendationGroups() + } catch { + networkError = true + } + + isLoading = false + } + + func createGroup(dataService: DataService, name: String) async { + isCreating = true + + let group = try? await dataService.createRecommendationGroup(name: name, + onlyAdminCanPost: onlyAdminCanPost, + onlyAdminCanSeeMembers: onlyAdminCanSeeMembers) + + if group != nil { + await loadGroups(dataService: dataService) + showCreateSheet = false + } else { + createGroupError = "Error creating club" + showCreateError = true + } + + isCreating = false + } + } + + struct ClubsView: View { + @State var name = "" + @EnvironmentObject var dataService: DataService + @Environment(\.dismiss) private var dismiss + + @StateObject var viewModel = RecommendationsGroupsViewModel() + + var nextButton: some View { + if viewModel.isCreating { + return AnyView(ProgressView()) + } else { + return AnyView(Button(action: { + Task { + await viewModel.createGroup(dataService: dataService, name: self.name) + } + }, label: { + Text(LocalText.genericNext) + }) + .disabled(name.isEmpty) + ) + } + } + + var body: some View { + NavigationView { + Form { + TextField(LocalText.genericName, text: $name, prompt: Text(LocalText.clubsName)) + + Section("Club Rules") { + Toggle("Only admins can post", isOn: $viewModel.onlyAdminCanPost) + Toggle("Only admins can see members", isOn: $viewModel.onlyAdminCanSeeMembers) + } + + Section { + Section { + Text(""" + [Learn more about clubs](https://blog.omnivore.app/p/dca38ba4-8a74-42cc-90ca-d5ffa5d075cc) + """) + .accentColor(.blue) + } + } + } + .alert(isPresented: $viewModel.showCreateError) { + Alert( + title: Text(viewModel.createGroupError ?? "Error creating group"), + dismissButton: .cancel(Text(LocalText.genericOk)) { + viewModel.createGroupError = nil + viewModel.showCreateError = false + } + ) + } + } + .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in + dismiss() + } + #if os(iOS) + .navigationViewStyle(.stack) + .navigationBarTitleDisplayMode(.inline) + .navigationBarItems(leading: + Button(action: { + viewModel.showCreateSheet = false + }, label: { Text(LocalText.cancelGeneric) }), + trailing: nextButton) + #endif + .navigationTitle("Create Club") + } + } + + struct GroupsView: View { + @EnvironmentObject var dataService: DataService + @StateObject var viewModel = RecommendationsGroupsViewModel() + + var body: some View { + Group { + #if os(iOS) + Form { + innerBody + } + #elseif os(macOS) + List { + innerBody + } + .listStyle(InsetListStyle()) + #endif + } + .sheet(isPresented: $viewModel.showCreateSheet) { + NavigationView { + ClubsView(viewModel: self.viewModel) + } + } + .task { await viewModel.loadGroups(dataService: dataService) } + .navigationTitle(LocalText.clubsGeneric) + } + + private var innerBody: some View { + Group { + Section { + Button( + action: { viewModel.showCreateSheet = true }, + label: { + HStack { + Image(systemName: "plus.circle.fill").foregroundColor(.green) + Text(LocalText.clubsCreate) + Spacer() + } + } + ) + } + + if !viewModel.isLoading { + if viewModel.recommendationGroups.count > 0 { + Section(header: Text(LocalText.clubsYours)) { + ForEach(viewModel.recommendationGroups) { recommendationGroup in + let viewModel = RecommendationsGroupViewModel(recommendationGroup: recommendationGroup) + NavigationLink( + destination: RecommendationGroupView(viewModel: viewModel) + ) { + Text(recommendationGroup.name) + } + } + } + } else { + Section { + Text(LocalText.clubsNotAMemberMessage) + .accentColor(.blue) + } + } + } + } + } + } +#endif diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/FetchContent.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/FetchContent.swift new file mode 100644 index 000000000..9291304a0 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/FetchContent.swift @@ -0,0 +1,47 @@ +import CoreData +import Foundation +import Models +import SwiftGraphQL + +public extension DataService { + func fetchContent(itemID: String) async throws { + enum MutationResult { + case result(success: Bool) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + fetchContentError: .init { .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") }, + fetchContentSuccess: .init { .result(success: try $0.success()) } + ) + } + + let mutation = Selection.Mutation { + try $0.fetchContent(id: itemID, selection: selection) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + + return try await withCheckedThrowingContinuation { continuation in + send(mutation, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + continuation.resume(throwing: BasicError.message(messageText: "network error")) + return + } + + switch payload.data { + case let .result(success: success): + if success { + continuation.resume() + } else { + continuation.resume(throwing: BasicError.message(messageText: "Operation failed")) + } + case let .error(errorMessage: errorMessage): + continuation.resume(throwing: BasicError.message(messageText: errorMessage)) + } + } + } + } +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateNewsletterEmailMutation.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateNewsletterEmailMutation.swift new file mode 100644 index 000000000..506377459 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateNewsletterEmailMutation.swift @@ -0,0 +1,53 @@ +import CoreData +import Foundation +import Models +import SwiftGraphQL + +public extension DataService { + func updateNewsletterEmail( + emailID: String, folder: String? = nil, description: String? = nil + ) async throws -> InternalNewsletterEmail { + enum MutationResult { + case result(email: InternalNewsletterEmail) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + updateNewsletterEmailError: .init { .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") }, + updateNewsletterEmailSuccess: .init { .result(email: try $0.newsletterEmail(selection: newsletterEmailSelection)) } + ) + } + + let mutation = Selection.Mutation { + try $0.updateNewsletterEmail( + input: InputObjects.UpdateNewsletterEmailInput( + description: OptionalArgument(description), + folder: OptionalArgument(folder), + + id: emailID + ), + selection: selection + ) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + + return try await withCheckedThrowingContinuation { continuation in + send(mutation, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + continuation.resume(throwing: BasicError.message(messageText: "network error")) + return + } + + switch payload.data { + case let .result(email: email): + continuation.resume(returning: email) + case let .error(errorMessage: errorMessage): + continuation.resume(throwing: BasicError.message(messageText: errorMessage)) + } + } + } + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Italic.ttf b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Italic.ttf new file mode 100644 index 000000000..7884a1ecf Binary files /dev/null and b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Italic.ttf differ diff --git a/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Variable.ttf b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Variable.ttf new file mode 100644 index 000000000..c3f165aed Binary files /dev/null and b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Fraunces-Variable.ttf differ diff --git a/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Italic.ttf b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Italic.ttf new file mode 100644 index 000000000..29743a318 Binary files /dev/null and b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Italic.ttf differ diff --git a/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Variable.ttf b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Variable.ttf new file mode 100644 index 000000000..527a458a9 Binary files /dev/null and b/apple/OmnivoreKit/Sources/Views/Resources/Fonts/Literata-Variable.ttf differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/102.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/102.png new file mode 100644 index 000000000..f5fe221f6 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/102.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/16.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 000000000..90f8121d8 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/32.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 000000000..663b7704a Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/512.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 000000000..2255a4b59 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/64.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 000000000..447fafc66 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/66.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/66.png new file mode 100644 index 000000000..6fa49f249 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/66.png differ diff --git a/apple/Resources/Assets.xcassets/AppIcon.appiconset/92.png b/apple/Resources/Assets.xcassets/AppIcon.appiconset/92.png new file mode 100644 index 000000000..3e7dc5833 Binary files /dev/null and b/apple/Resources/Assets.xcassets/AppIcon.appiconset/92.png differ diff --git a/apple/Sources/SafariExtension/Resources/scripts/api.js b/apple/Sources/SafariExtension/Resources/scripts/api.js new file mode 100644 index 000000000..ca8ac9911 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/scripts/api.js @@ -0,0 +1 @@ +function gqlRequest(e,n){return getStorageItem("apiKey").then((t=>fetch(e,{method:"POST",redirect:"follow",credentials:"include",mode:"cors",headers:{Accept:"application/json","Content-Type":"application/json",...t?{Authorization:t}:{}},body:n}))).then((e=>e.json())).then((e=>{if(!e.data)throw new Error("No response data");return e.data}))}async function updateLabelsCache(e,n){const t=JSON.stringify({query:"query GetLabels {\n labels {\n ... on LabelsSuccess {\n labels {\n ...LabelFields\n }\n }\n ... on LabelsError {\n errorCodes\n }\n }\n }\n fragment LabelFields on Label {\n id\n name\n color\n description\n createdAt\n }\n "}),r=await gqlRequest(e,t);return r.labels&&!r.labels.errorCodes&&r.labels.labels?(await setStorage({labels:r.labels.labels,labelsLastUpdated:(new Date).toISOString()}),r.labels.labels):(console.log("GQL Error updating label cache response:",r,r),console.log(!r.labels,r.labels.errorCodes,!r.labels.labels),[])}async function updatePageTitle(e,n,t){const r=JSON.stringify({query:"mutation UpdatePage($input: UpdatePageInput!) {\n updatePage(input: $input) {\n ... on UpdatePageSuccess {\n updatedPage {\n id\n }\n }\n ... on UpdatePageError {\n errorCodes\n }\n }\n }\n ",variables:{input:{pageId:n,title:t}}}),i=await gqlRequest(e,r);if(!i.updatePage||i.updatePage.errorCodes||!i.updatePage.updatedPage)throw console.log("GQL Error updating page:",i),new Error("Error updating title.");return i.updatePage.updatePage}async function setLabels(e,n,t){const r=JSON.stringify({query:"mutation SetLabels($input: SetLabelsInput!) {\n setLabels(input: $input) {\n ... on SetLabelsSuccess {\n labels {\n id\n name\n color\n }\n }\n ... on SetLabelsError {\n errorCodes\n }\n }\n }\n ",variables:{input:{pageId:n,labels:t}}}),i=await gqlRequest(e,r);if(!i.setLabels||i.setLabels.errorCodes||!i.setLabels.labels)throw console.log("GQL Error setting labels:",i),new Error("Error setting labels.");return await appendLabelsToCache(i.setLabels.labels),i.setLabels.labels}async function appendLabelsToCache(e){const n=await getStorageItem("labels");n?(e.forEach((e=>{n.find((n=>n.name===e.name))||n.unshift(e)})),await setStorage({labels:n,labelsLastUpdated:(new Date).toISOString()})):await setStorage({labels:e,labelsLastUpdated:(new Date).toISOString()})}async function addNote(e,n,t,r,i){const a=JSON.stringify({query:"query GetArticle(\n $username: String!\n $slug: String!\n $includeFriendsHighlights: Boolean\n ) {\n article(username: $username, slug: $slug) {\n ... on ArticleSuccess {\n article {\n highlights(input: { includeFriends: $includeFriendsHighlights }) {\n ...HighlightFields\n }\n }\n }\n ... on ArticleError {\n errorCodes\n }\n }\n }\n fragment HighlightFields on Highlight {\n id\n type\n annotation\n }\n ",variables:{username:"me",slug:n,includeFriendsHighlights:!1}}),o=await gqlRequest(e,a);if(!o.article||o.article.errorCodes||!o.article.article)return void console.log("GQL Error getting existing highlights:",o);const l=o.article.article.highlights.find((e=>"NOTE"==e.type));if(l){const n=JSON.stringify({query:"\n mutation UpdateHighlight($input: UpdateHighlightInput!) {\n updateHighlight(input: $input) {\n ... on UpdateHighlightSuccess {\n highlight {\n id\n }\n }\n ... on UpdateHighlightError {\n errorCodes\n }\n }\n }\n ",variables:{input:{highlightId:l.id,annotation:l.annotation?l.annotation+"\n\n"+i:i}}}),t=await gqlRequest(e,n);return t.updateHighlight&&!t.updateHighlight.errorCodes&&t.updateHighlight.highlight?t.updateHighlight.highlight.id:void console.log("GQL Error updating note:",t)}{const a=JSON.stringify({query:"\n mutation CreateHighlight($input: CreateHighlightInput!) {\n createHighlight(input: $input) {\n ... on CreateHighlightSuccess {\n highlight {\n id\n }\n }\n ... on CreateHighlightError {\n errorCodes\n }\n }\n }\n ",variables:{input:{id:t,shortId:r,type:"NOTE",articleId:n,annotation:i}}}),o=await gqlRequest(e,a);return o.createHighlight&&!o.createHighlight.errorCodes&&o.createHighlight.highlight?o.createHighlight.highlight.id:void console.log("GQL Error setting note:",o)}}async function archive(e,n){const t=JSON.stringify({query:"mutation SetLinkArchived($input: ArchiveLinkInput!) {\n setLinkArchived(input: $input) {\n ... on ArchiveLinkSuccess {\n linkId\n message\n }\n ... on ArchiveLinkError {\n message\n errorCodes\n }\n }\n }\n ",variables:{input:{linkId:n,archived:!0}}}),r=await gqlRequest(e,t);if(!r.setLinkArchived||r.setLinkArchived.errorCodes||!r.setLinkArchived.linkId)throw console.log("GQL Error archiving:",r),new Error("Error archiving.");return r.setLinkArchived.linkId}async function deleteItem(e,n){const t=JSON.stringify({query:"mutation SetBookmarkArticle($input: SetBookmarkArticleInput!) {\n setBookmarkArticle(input: $input) {\n ... on SetBookmarkArticleSuccess {\n bookmarkedArticle {\n id\n }\n }\n ... on SetBookmarkArticleError {\n errorCodes\n }\n }\n }\n ",variables:{input:{articleID:n,bookmark:!1}}}),r=await gqlRequest(e,t);if(!r.setBookmarkArticle||r.setBookmarkArticle.errorCodes||!r.setBookmarkArticle.bookmarkedArticle)throw console.log("GQL Error deleting:",r),new Error("Error deleting.");return r.setBookmarkArticle.bookmarkedArticle} \ No newline at end of file diff --git a/apple/Sources/SafariExtension/Resources/scripts/common.js b/apple/Sources/SafariExtension/Resources/scripts/common.js new file mode 100644 index 000000000..b1823bb74 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/scripts/common.js @@ -0,0 +1 @@ +"use strict";function handleBackendUrl(e){try{return[/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+)(?:\/.*)?/,/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu\.be|piped\.video))(\/(?:[\w-]+\?v=|embed\/|v\/)?)([\w-]+)(\S+)?$/].some((n=>n.test(e)))}catch(n){console.log("error checking url",e)}return!1}function getStorage(e){return new Promise((n=>{browserApi.storage.local.get(e||null,(e=>{n(e||{})}))}))}function getStorageItem(e){return new Promise((n=>{browserApi.storage.local.get(e,(r=>{const o=r&&r[e]||null;n(o)}))}))}function setStorage(e){return new Promise((n=>{browserApi.storage.local.set(e,n)}))}function removeStorage(e){return new Promise((n=>{browserApi.storage.local.remove(e,n)}))}window.browserApi="object"==typeof chrome&&chrome&&chrome.runtime&&chrome||"object"==typeof browser&&browser||{},window.browserActionApi=browserApi.action||browserApi.browserAction||browserApi.pageAction,window.browserScriptingApi=browserApi.scripting||browserApi.tabs,window.ENV_EXTENSION_ORIGIN=browserApi.runtime.getURL("PATH/").replace("/PATH/",""),window.ENV_IS_FIREFOX=ENV_EXTENSION_ORIGIN.startsWith("moz-extension://"),window.ENV_IS_EDGE=navigator.userAgent.toLowerCase().indexOf("edg")>-1,window.ENV_DOES_NOT_SUPPORT_BLOB_URL_ACCESS=/^((?!chrome|android).)*safari/i.test(navigator.userAgent),window.SELECTORS={CANONICAL_URL:["head > link[rel='canonical']"],TITLE:["head > meta[property='og:title']"]},window.ACTIONS={Ping:"PING",ShowMessage:"SHOW_MESSAGE",GetContent:"GET_CONTENT",AddIframeContent:"ADD_IFRAME_CONTENT",RefreshDarkMode:"REFRESH_DARK_MODE",GetAuthToken:"GET_AUTH_TOKEN",LabelCacheUpdated:"LABEL_CACHE_UPDATED",ShowToolbar:"SHOW_TOOLBAR",UpdateStatus:"UPDATE_STATUS",AddNote:"ADD_NOTE",EditTitle:"EDIT_TITLE",SetLabels:"SET_LABELS",Archive:"ARCHIVE",Delete:"DELETE"},window.SAVE_URL_QUERY="mutation SaveUrl ($input: SaveUrlInput!) {\n saveUrl(input:$input){\n ... on SaveSuccess {\n url\n clientRequestId\n }\n ... on SaveError {\n errorCodes\n }\n }\n}",window.SAVE_FILE_QUERY="mutation SaveFile ($input: SaveFileInput!) {\n saveFile(input:$input){\n ... on SaveSuccess {\n url\n clientRequestId\n }\n ... on SaveError {\n errorCodes\n }\n }\n}",window.SAVE_PAGE_QUERY="mutation SavePage ($input: SavePageInput!) {\n savePage(input:$input){\n ... on SaveSuccess {\n url\n clientRequestId\n }\n ... on SaveError {\n errorCodes\n }\n }\n}"; \ No newline at end of file diff --git a/apple/Sources/SafariExtension/Resources/scripts/options.js b/apple/Sources/SafariExtension/Resources/scripts/options.js new file mode 100644 index 000000000..c57be8e06 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/scripts/options.js @@ -0,0 +1 @@ +function saveAPIKey(){var e=document.getElementById("api-key").value;e?setStorage({apiKey:e}).then((()=>{alert("API key saved!")})):alert("No api-key specified, please create an API key at https://omnivore.app/settings/api")}function loadAPIKey(){getStorageItem("apiKey").then((e=>{e?document.getElementById("api-key").value=e:alert("No API key found in storage.")}))}function clearAPIKey(){document.getElementById("api-key").value="",setStorage({apiKey:void 0}).then((()=>{alert("API key cleared!")}))}function autoDismissChanged(e){const t=document.getElementById("disable-auto-dismiss").checked;console.log(" value: ",t,document.getElementById("disable-auto-dismiss")),setStorage({disableAutoDismiss:t?"true":null}).then((()=>{console.log("disableAutoDismiss updated",t)}))}function saveAutoDismissTime(){const e=document.getElementById("auto-dismiss-time").value;e.length<1||Number.isNaN(Number(e))?alert("Invalid value"):setStorage({autoDismissTime:e}).then((()=>{console.log("autoDismissTime updated",e)}))}document.getElementById("save-api-key-btn").addEventListener("click",saveAPIKey),document.getElementById("load-api-key-btn").addEventListener("click",loadAPIKey),document.getElementById("clear-api-key-btn").addEventListener("click",clearAPIKey),getStorageItem("disableAutoDismiss").then((e=>{document.getElementById("disable-auto-dismiss").checked=!!e})),document.getElementById("disable-auto-dismiss").addEventListener("change",autoDismissChanged),getStorageItem("autoDismissTime").then((e=>{document.getElementById("auto-dismiss-time").value=e??"2500"})),document.getElementById("auto-dismiss-time-btn").addEventListener("click",saveAutoDismissTime); \ No newline at end of file diff --git a/apple/Sources/SafariExtension/Resources/views/installed.html b/apple/Sources/SafariExtension/Resources/views/installed.html new file mode 100644 index 000000000..a886ae955 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/views/installed.html @@ -0,0 +1,118 @@ + + + + + + Data Collection Consent + + + + + + + + + + + diff --git a/apple/Sources/SafariExtension/Resources/views/options.html b/apple/Sources/SafariExtension/Resources/views/options.html new file mode 100644 index 000000000..6d94b26c6 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/views/options.html @@ -0,0 +1,52 @@ + + + + Omnivore Extension Settings + + + + +
+

API Key

+ +

Normally Omnivore uses an authentication cookie that is set when + you log in at omnivore.app. If + your security settings do not allow the extension to access this + cookie (for example if you are using containers), you can set an + API token in the extension's local storage instead. +

+ +

To do this, create an API key at + omnivore.app/settings/api, + paste it into the textboix below, and choose 'Save API Key'. +

+ +

+ + + +

+ + + + + +

 

+ +

Settings

+ +

+ + +

+ +

+ + + +

+ +
+ + + \ No newline at end of file diff --git a/apple/Sources/SafariExtension/Resources/views/toast.html b/apple/Sources/SafariExtension/Resources/views/toast.html new file mode 100644 index 000000000..c09979d78 --- /dev/null +++ b/apple/Sources/SafariExtension/Resources/views/toast.html @@ -0,0 +1,556 @@ + + +
+
+ + +
+
+ + + + + + + + + + + + + + + + +
+ +
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+
    +
  • + +
  • +
+
+ +
+
+
+ +
+ + + + + + +
+ + +
\ No newline at end of file